Jump to content
  • Advertisement


  • Content Count

  • Joined

  • Last visited

Community Reputation

2408 Excellent


About Zipster

  • Rank

Personal Information

  • Role
    Technical Director
  • Interests

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

  1. You could start with a basic implementation where component factories are registered by name in a map. When creating your entities, iterate over the "components" object and use the key to look up the factory in the map. Then pass the corresponding value to that factory so it can instantiate the component with the proper data. As there's nothing with this approach specific to components or entities, it can be reused and extended to support just about any structured, nested data model. This technique is commonly used in serialization libraries, where you frequently need to reconstruct runtime data from information received over the network, or some other external source.
  2. Zipster

    Symbol lookup error on Linux

    CMake is more than capable of handling the build configuration you describe, provided that the target dependencies have been correctly declared. The two most likely causes of your issue are 1) libLinearMath.a isn't being linked in (however this would probably result in a lot of other unresolved symbols), or 2) libLinearMath.a is being linked in before whichever object uses the symbol. Either way, it points to a fixable configuration problem. Is the code that calls CProfileManager::Reset() inside a translation unit that's linked directly into the shared library, or is it in another static library (or perhaps shared library)? How have you configured your target dependencies (i.e. [TARGET_]LINK_LIBRARIES)? Also next time you build, use "make VERBOSE=1" for it to echo all the commands it executes so you can observe the link line it generates.
  3. Zipster

    c++ use map with struct

    The best approach really depends on who you expect to be consuming this data. You have to be really careful when using standard library types such as std::string across binary boundaries, or anything that has a layout that can change between platforms and/or build environments. Even if you take all the necessary steps to ensure these are identical, only C++ code knows about std::string. Perhaps this isn't an issue if all your software components are written in C++, which is why so much of this depends on your intent
  4. The handles can go directly into any standard library container without the need for a custom allocator: extern IObject* createObject(); std::vector<ObjectHandle> handles { ObjectHandle(createObject()), ObjectHandle(createObject()), ObjectHandle(createObject()) }; The behavior of your "defragmenting" heap is different from how the language and standard library expects storage to behave, so while I'm sure that you could find a way to wrap object allocation in an allocator, it wouldn't do you much good if it can't be used anywhere. At least with this approach, you retain full control over your heap while still being able to use standard containers for the handles.
  5. When an object is moved, any references to it must be updated with the new location. The "handles" register themselves with the object so that for the duration of their lifetime, they can be informed of any such relocation. But aside from this registration logic, they're just simple wrappers around naked pointers that implement a few operators so they behave "pointer-like" to a user.
  6. Imagine that you were designing this as a pen and paper game. No type systems, instances, or other computer language constructs. How would your "rule" system work then? How would you describe to a human player whether or not their character is allowed to use some weapon (or object in general)? Choosing a specific example, let's say you tell them that swords can only be used by warriors. What questions might they have about this rule, and how would you answer them?
  7. This isn't a problem to be solved by the allocator. With the appropriate handle semantics, I don't see any reason why this shouldn't be possible: class IObject { public: virtual void Bind(IObjectHandle* handle) = 0; virtual void Unbind(IObjectHandle* handle) = 0; }; class IObjectHandle { public: virtual void Rebind(IObject* object) = 0; }; class Object : public IObject { std::set<IObjectHandle*> m_handles; public: Object(const Object&) { } Object(Object&& other) { m_handles = std::move(other.m_handles); for (auto&& handle : m_handles) { handle->Rebind(this); } } ~Object() { assert(m_handles.empty()); for (auto&& handle : m_handles) { handle->Rebind(nullptr); } } Object& operator=(const Object&) { return *this; } Object& operator=(Object&& other) { m_handles = std::move(other.m_handles); for (auto&& handle : m_handles) { handle->Rebind(this); } return *this; } void BindHandle(IObjectHandle* handle) override { m_handles.insert(handle); } void UnbindHandle(IObjectHandle* handle) override { m_handles.erase(handle); } }; class ObjectHandle : public IObjectHandle { IObject* m_object = nullptr; public: ObjectHandle(const ObjectHandle& other) { m_object = other.m_object; if (m_object) { m_object->BindHandle(this); } } ObjectHandle(ObjectHandle&& other) { if (other.m_object) { other.m_object->UnbindHandle(&other); other.m_object = nullptr; } m_object = other.m_object; if (m_object) { m_object->BindHandle(this); } } ObjectHandle(IObject* object) { m_object = object; if (m_object) { m_object->BindHandle(this); } } ~ObjectHandle() { if (m_object) { m_object->UnbindHandle(this); } } ObjectHandle& operator=(ObjectHandle handle) { handle.swap(*this); return *this; } void RebindObject(IObject* object) override { m_object = object; } operator bool() const { return m_object != nullptr; } IObject* operator->() const { return m_object; } IObject& operator*() { return *m_object; } const IObject& operator*() const { return *m_object; } void swap(ObjectHandle& other) { if (&other != this) { if (m_object) m_object->UnbindHandle(this); if (other.m_object) other.m_object->UnbindHandle(&other); using std::swap; swap(m_object, other.m_object); if (m_object) m_object->BindHandle(this); if (other.m_object) other.m_object->BindHandle(&other); } } }; Note that most of the heavy lifting happens as part of the move and swap semantics. The copy semantics of the object are also stubbed out, since handles are uniquely bound to a single object. However I also have to question why this is needed in the first place. If fragmentation is a real concern, it's usually a symptom of another problem like sub-optimal allocation strategies or memory usage patterns.
  8. Most C# developers would probably expect an exception to be thrown. Not only is this an exceptional case, but it's also how the standard library handles it. So when in Rome... stick with established best practices Also, consider the fact that games usually load files that are necessary for proper functionality and expected to exist (assets, etc.), based on the assumption that the game was properly installed. There isn't much the software could do to recover and continue from missing assets, and as a developer I wouldn't want my game to gimp along with missing data anyway!
  9. Could you elaborate or provide an example of what you're looking for? It isn't clear if you're using the term "combination" literally, or colloquially to mean some other type of arrangement.
  10. LEB128 and related encodings will let you send any size integer, but for now you should probably just stick with a fixed size like 4 bytes until everything is in a working state.
  11. I would use a light-weight wrapper object that implements operator bool() and operator->() at the very least, so you get pointer-like semantics and usage with the option to add behavior as necessary. For instance, you could make the copy constructor explicit so users can still copy the handle, while also allowing one to easily locate such copies through static analysis. Or if you're feeling clever, you could even add tracing/logging to copies, access, etc., so if/when something goes wrong, there's a "chain of custody" so to speak that let's you identify errant usage. Anything like this can also be compiled out in production builds so there's almost no overhead.
  12. Zipster

    Allocator design issues

    This wouldn't change the interface. The purpose of "Deallocate" is to inform the allocator that the memory is no longer in use, at which point it can do with it what it pleases (including nothing at all). If the user is expected to know more about the underlying implementation details, you have a leaky abstraction. Take a look at the standard allocator traits. The essential elements of the interface are allocate/deallocate, and construct/destroy (notice how they're considered separate responsibilities). You really don't need much beyond that, aside perhaps from some templated helper functions that combine allocate+construct and destroy+deallocate. Then, different implementations of this interface can be combined using the adapter pattern to achieve the layered functionality you're looking for.
  13. You mentioned several times that you're familiar with writing multi-threaded software, so you already know what there is to gain by taking advantage of additional processing power that would otherwise remain idle. That's the answer to your question, but to be perfectly honest, it seems to me you already knew that. I feel the real issue is that you're having trouble accepting the amount of time and effort involved in implementing proper thread synchronization. At the end of the OP you ask, "What is the best overall architecture that is versatile, safe, performant, and future-proof?". But considering how much emphasis you place on wanting to minimize any additional time or effort (even going so far as to quantify the manhours), the question reads in my mind as "What is the best overall architecture that is versatile, safe, performant, future-proof... and least amount of work to implement?". Other engines/studios handle this by acknowledging the complexity and scope of the problem and designing for it from the beginning. They don't treat it as just another feature that can be implemented at some late stage in the project, and then look for ways to shoehorn it into their existing architecture with minimal impact on everything else. That's not to say it can't be done, but there isn't going to be a quick fix or magic bullet to get you to where you want to be. You either have to accept that there's no way around the work that has to be done to achieve the versatile, safe, performant, and future-proof implementation you're looking for, or throw it all out and accept the issues and limitations that come with a single-threaded runtime environment. Once you commit to doing the work (or not) the way forward will become clear.
  14. Intrinsic to any "identifier" are two pieces of information, what you're identifying (animal, plant) and how you identify it (id, name). Any solution that prevents you from mixing the "whats" but not the "hows" (or vice versa) is certainly better than nothing, but only focusing on one and not the other doesn't give you a "strongly typed" ID, and it's an obvious code smell for that reason. And perhaps this doesn't affect the OP, but in my experience it's extremely common to have objects that belong to multiple ID spaces (client, server, world, etc.) that are also similarly typed, and can just as easily be mixed accidentally, so for posterity why wouldn't we want to discuss the actual nature of the problem so real solutions can be explored?
  15. I'm not a huge fan of this "identifier" pattern. Consider: using AnimalID = IDType<Animal, unsigned int>; // Animal identifier using id using AnimalName = IDType<Animal, std::string>; // Animal identifier using name using AnimalLabel = IDType<Animal, std::string>; // Animal identifier using label It's suggestive of semantics that don't exist, which to me a big code smell.
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!