entt: range based `registry::insert_missing` and `registry::remove_if_exists`

I think I found a legitimate use-case for the two above methods.

When you have relationship components which contain std::vector<entity> it’s often not practical to generate a view which in the first case excludes the existing component, and in the second returns only the existing ones and matches all entities of the vector. Currently you have to iterate and add/remove the components individually.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 34 (19 by maintainers)

Commits related to this issue

Most upvoted comments

Well, the documentation came with the very first version of EnTT and i wasn’t sure that this behavior wouldn’t change at the time. Nowadays I suspect this won’t change any time soon though! 😄 So, yes, we can make it clear in the documentation, of course. Probably we should open a different issue for that btw.

I’ve added a version of remove_if_exists (renamed to remove actually) to experimental. An iterator based version of insert_missing is trickier imho and there is no way to make it faster than its counterpart defined in user space. So, I think we can safely close this issue. Thanks for all the comments!

I’ll merge the changes to master soon. Ping me if you try them and find any issue in the meantime.

EnTT doesn’t care much if you put a pack or not as the last argument of your lambda. This is an optimization made by the compiler, if any. It sees that you don’t use the parameters and prunes the code that reaches them. I’ve no idea if it can do the same with an iterable object to be honest. In theory, yes. In practice, it’s implementation defined, so dunno.

Also note that in both cases empty types aren’t returned. I usually use this kind of components as filters, so, in this case, there are no differences.

My suggestion is: if you care, try and measure. This is probably the best thing you can do. However, it may be that a compiler manages to make this optimization while another one does not. For example, clang does a great job at vectorizing views and groups while msvc has more difficulties because it isn’t (wasn’t?) that great at optimizing templated stuff.

Wouldn’t this completely solve this problem?

Indeed yes, C++20 will be a game changer for EnTT. However, it’s far from being usable at the moment, so we have to wait for a while…

Yeah, the documentation is scaring but it works exactly as @Kerndog73 reported. Iterators go back-front to allow insertions/deletions during iterations. It goes without saying that it won’t be possible anymore if you iterate things front-back.

is there any hard problems in exposing to the user of EnTT iterators/ranges that dereference to something like component reference tuples?

Unfortunately, the problem here is the C++ language itself and its standard library. I’d like to return iterators from views the value_type of which is std::tuple<entity, Comp1 &, ..., CompN &>. However, the standard says explicitly that the reference type of a forward iterator must be an actual reference and not a proxy object. In theory, this isn’t strictly required for multi-pass guarantee though. Let’s suppose I decided to use a tuple-like object as value_type. This would turn the iterator in an input iterator. However, some algorithms like the parallel std::for_each require their arguments to be forward iterators. Therefore:

This would allow for a wealth of interplay with the STL algorithms.

No, this would restrict the possibilities to use them with the algorithms provided with the standard library.

That said, single component views and groups offer the raw<T> member function to get raw pointers to a packed arrays of elements. We can get around the limitations above by using this stuff carefully.

@sunbubble Do I get right what you want? You want to make a call like entt::insert_missing< Comp1, Comp2 > (container.begin (), container.end ()) and have components assigned only to those entities from the container that don’t already have them?