entt: Components references are invalidated upon call to registry.assign().
The file: entt/src/entt/entity/sparse_set.hpp
has a member std::vector<object_type> instances, which is where component instances are stored.
The call to registry.assign<Component>(...) will ultimately store the components in the ‘instances’ member mentioned above. registry.assign() returns a reference to the component created.
The problem is that this returned reference is brittle. As soon as another call to registry.assign() is made, previously held component references are invalidated. That is because the underlying vector holding the component instances will be resized, which will move the contents to a different memory. This is bound to create extremely subtle bugs.
It should be possible to keep the reference to components around as long as the instance is alive and the component hasn’t been removed.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Comments: 18 (9 by maintainers)
The actual error is that you shouldn’t retain references to components, the same way as you shouldn’t retain references to elements in a vector.
Moreover, if a vector offers you a position (index) as a safe way to access elements, the registry offers you the entity identifier.
The parallel can continue, but you got the idea.
Note that ie
std::vector<T>::emplace_backreturns a reference to the inserted element. I wouldn’t define not clear a behavior just copied over from the standard library.Indeed, it shouldn’t. Put aside the fact that you shouldn’t do that in general in C++, references are not meant for that. Use entity identifiers instead. Moreover, pools won’t be able to rearrange elements when needed and offer you the best performance in all cases if we decided to give such a strong guarantee.
Let me explain why these are all the ways wrong.
Non contiguous memory, it would defeat one of the purposes of using an ECS and ruin performance. Moreover, the parameter should be of the registry, then pushed down to sparse sets and so on. More code to maintain (for free, and I’m not willing to maintain code I dislike for free) for no benefits.
Just use the tool the way it’s meant.
This would defeat completely the effort spent to keep components tightly packed. You would jump in memory each and every time you access a component, with an indecent loss of performance.
No longer contiguous memory, the API would change (ie no more
datamember functions), you would miss some features (you cannot memcpy components at once) and performance would degrade, just to mention some drawbacks.Moreover, a fully allocator-aware library is in my plans and the goal is quite different than this, therefore it would also force me to redesign it to match another allocator introduced for no benefits.
As a side note, none of the above proposals would solve the problem. A sparse set rearranges elements on destroy and references would still be invalidated in this case. So, they are just half a solution and cannot be applied this way.
Ironically, entity identifiers already manage to abstract everything and they offer even much more than what you would obtain with the approaches above mentioned (stable index, versioning, null value, and so on). So, we would also miss some other useful features.
EnTTalready offers a way to keep track of components with stable identifiers, it sticks to the behavior and the API of the standard library and so far I haven’t read in this issue of an alternative approach that would represent an improvement.For me, an invalid request. I’m sorry.
I’ll close this in a couple of days if no good reasons pop out in the meantime to convince me that this is a must have (extra) feature.
EnTT offers pointer stability upon addition by default nowadays and also a stable pointer mode for all operations on requests. It doesn’t use the handle mechanism that you described though, mostly for performance reasons.
get-ting the component all the times would be a waste of cpu cycles in like 99% of cases. On the other hand, it’s easy to implement such a handle on top of EnTT if you want it.Oh, ok, yeah, in this case the reference can get invalidated. Good point.
@gamagan You should shouldn’t be holding onto component references. This is not a bug. You should be using entity IDs. Entity IDs are stable. You can hold onto them for as long as you like and they won’t become invalidated.
All of your suggestions to “fix” this will slow down this very fast library. The following example is safe.
foowill not be handed invalid references.