skypjack / entt

Gaming meets modern C++ - a fast and reliable entity component system (ECS) and much more
https://github.com/skypjack/entt/wiki
MIT License
9.96k stars 876 forks source link

Call for comments: signals on component creation/destruction #62

Closed skypjack closed 6 years ago

skypjack commented 6 years ago

Ok, people is asking this and I tried to figure out if it's possible. Probably, I found a nice way to add signalling stuff to the registry in a way that is in policy with the whole framework: pay only for what you use.. It means that there will be no performance hits for those components that aren't (let me say) observed. On the other side, it will be possible to be notified about creation and destruction of specific components if required.

I've still to review the whole idea and try to implement it. However, I'd like to have feedbacks about it.

Is it a feature in which you could be interested? Any suggestion, comment or request?

As a side note, it would unlock other features like implicit blueprints (when component A is attached, attach also B and C silently) and so on.

Let me know, feedbacks are appreciated!! I'll close the issue probably in a couple of days.

Thank you.

skypjack commented 6 years ago

By reviewing the codebase I found that a change in this direction can improve things also for persistent views. In fact, they affect to some extent creation and deletion of components even if persistent views aren't used. It can be (probably) avoided.
I put a note in the TODO file for future changes that improve things in general and let users enable or disable signalling for specific components.

I'm closing the issue for it's no longer an open discussion.
I'll work on it for sure sooner or later. ;-)

skypjack commented 6 years ago

Reopened this to discuss API and use cases.
I'll put all the details in a comment later.

skypjack commented 6 years ago

At a first glance, this is a possible approach:

An alternative approach could be this one probably:

More details will come. What about so far? Questions? Comments?

gamagan commented 6 years ago

If this feature is free(ish), this is probably something that people can make use of.

Will this also include notification for entities being created/destroyed? That is, registry.create()/destroy() ?

skypjack commented 6 years ago

@gamagan What do you mean with if this feature is free? The whole library is under the MIT license, right? I don't think it will include notifications for creation and destruction of entities, for it's a great performance hit that I don't think it's worth it. I mean, what are the use cases for that? Can you elaborate a bit? Thank you.

gamagan commented 6 years ago

hehe. Free as related to performance.

If getting signals on create()/destroy() will tank performance, then it aint worth it.

Also, its not a must have feature. I was just curious.

Use case: System(s) listen to create()/destroy() signals. On create(), they inspect if the entity has components they care about. If so, keep reference to entity. on destroy() signal, remove entity from list. You would also subscribe to component add/remove signals, to keep list updated. Then, on the update method, you dont have to iterate over views: u have the exact list u need.

skypjack commented 6 years ago

Oh, ok. Then no, unfortunately it's not for free, quite obvious.
Actually, the performance hit is pretty high if compared to the performance of EnTT.

Your use case makes sense, but you can obtain the same by listening at components, right?
If you are going to inspect an entity to see if it has some specific components, then you can just listen at them and do the same after all.
Moreover, consider your statement:

You would also subscribe to component add/remove signals, to keep list updated. Then, on the update method, you dont have to iterate over views: u have the exact list u need.

This way you're replicating more or less the behavior of a persistent view, that is extremely fast and keeps tightly packed its entities. You can also share a view between different systems, thus reducing memory usage. So... :-)

The fact is that a kind of signals on construction/destruction for components are somehow required to keep updated persistent views. They have a cost, of course, but EnTT cannot work without them, so it's a matter of defining an API to let users use them too.
On the other side, events on construction/destruction of entities aren't required and thus introducing them would mean add a performance hit for no benefits for the framework. Note also that users can still add an extra layer for that if they want, so I prefer to keep them out of the registry if possible.

That's all.

gamagan commented 6 years ago

Sounds good.

vblanco20-1 commented 6 years ago

Component dependencies would be a very useful feature. Ive already implemented a version of that on my end as i needed it (as im using wrappers to create the components, those wrappers also check for component dependencies). For what you are saying, it seems interesting. At the moment im using a "Delete" component that i add when i want to delete something, and then my DeleteSystem checks for all the dependencies. But that is a lot more problematic than just listening for component deletion of a specific type and act on it. Just making a system listen for the destruction/creation of the component they handle would be good enough.

Its important that it only costs performance if you are actively using it. Would it degrade performance if i have 30 components with destroy listener, but i destroy an entity that has none of them and no listeners?

On the listener itself, you say that it would give the entity ID. Is there any possibility it could also give a pointer/reference to the Registry? There are cases where a user might have multiple registries and would want events on both and a way to easily separate them. Or just to use the registry to check for other components on the entity ID from the event. This would allow one to very easily just point the listener into a global function.

skypjack commented 6 years ago

@vblanco20-1

Ive already implemented a version of that on my end as i needed it (as im using wrappers to create the components, those wrappers also check for component dependencies).

Can I ask you more details on this? Maybe I'm missing something to which you thought and I could steal your ideas. :-)

Its important that it only costs performance if you are actively using it. Would it degrade performance if i have 30 components with destroy listener, but i destroy an entity that has none of them and no listeners?

In general and trying to sum up, the performance hit in this case is the one of a comparison between iterators for each component.
Imagine you have an array of listeners for each component. If you delete a component, the registry tries to iterate listeners to notify them, so you have at least a sort of:

while(begin != end) { /*... */ }

In case no listeners are registered, begin is equal to end and thus the loop terminates immediately. However, at least a comparison is required, unfortunately.

To be honest, the current version of EnTT already contains something similar to be able to notify persistent views if any.
Creation and destruction of entities and components aren't usually performed frequently and thus a small performance hit on that part is acceptable. This is the thought that drove the choice when I was implementing persistent views.

On the listener itself, you say that it would give the entity ID. Is there any possibility it could also give a pointer/reference to the Registry?

It makes sense actually, good point. It shouldn't be a problem. I'll try to do it as soon as I have time to work on this feature. Stay tuned, I'll give you a feedback on this sooner or later.

vblanco20-1 commented 6 years ago

Can I ask you more details on this? Maybe I'm missing something to which you thought and I could steal your ideas. :-)

Sadly its not useful to you. As you saw from my other posts, im integrating enTT with unreal engine. To do this i use "wrappers" that are unreal engine ActorComponents. This ones act as a container, and when a normal unreal object gets spawned in the world, the ActorComponents get notified and register their internal EnTT component with the global registry. For dependencies, i do a "has" check on this wrappers. For example my HomingBullet entt component has a HomingBulletComponentWrapper. when i spawn a unreal engine actor that has a HomingBulletComponentWrapper (and other wrappers), they register with the ECS, and the bullet starts moving. HomingBullet adds Velocity and Position if they arent already there.

If thats the performance cost of the listeners, thats absolutely no issue. Sounds good.

skypjack commented 6 years ago

@vblanco20-1 Good, not a problem.
The idea is to let users attach only member functions. No lambdas, to avoid extra allocations due to the use of std::functions.
What about? Your experience with EnTT is invaluable. Thank you very much for your help, really appreciated.

vblanco20-1 commented 6 years ago

You already have signals and similar. Easiest would be to just use one of those, no?

skypjack commented 6 years ago

@vblanco20-1 Yep and they don't accept lambdas. :-)

vblanco20-1 commented 6 years ago

If you are sending the registry + the entity ID, there is a good chance it can work on free functions, with a completely basic function pointer. If you need to access "persistent" data, you could get it from a tag in the registry.

skypjack commented 6 years ago

@vblanco20-1 Actually SigH already works with both free functions and member functions, so why not? I wouldn't limit the whole thing to the sole free functions. It will be easier also to attach directly systems to the registry and you can avoid defining specific tags for your purposes.

skypjack commented 6 years ago

Just a thought (feedbacks are appreciated on this).

To completely avoid allocations, we could limit the number of listeners to one on construction and one on destruction for each component.

Benefits:

Drawbacks:

In general, it looks like supporting events has a consequence: creation, destruction and iterations cannot be kept all at the minimum.
We must find the best compromise and I vote to keep iterations as lower as possible in time.

What about?

vblanco20-1 commented 6 years ago

In my opinion, one single listener is a good idea if it makes it faster. The more generic case would be to have one system listen for the destruction of the component they are handling with an external resource. If one wants to have multiple listeners, he can just then use another kind of external bus, wich allows one to still have multiple listeners if thats what he wants.

skypjack commented 6 years ago

I'm experimenting with both the solutions. Some updates:

mhaemmerle commented 6 years ago

What about using something like persistent views for the created/destroyed components? So one could prepare a persistent view for either component creation or destruction, retrieve it in one or more systems and clear it at the end of the frame?

Depending on how you look at it, it could either be a replacement for the events or a completely separate implementation.

skypjack commented 6 years ago

@mhaemmerle I see memory usage as the main issue in this case. Sparse sets aren't the best approach to this problem unfortunately.

mhaemmerle commented 6 years ago

Don't let yourself be misled by my use of "persistent view" - that was aiming more at the user's perspective of having to prepare the view before utilizing it :) and thus enabling the collection of added/removed components. The core of my suggestion is to store the added/removed components in a datastructure/view that can be iterated over by the user (potentially multiple times / frame). And just in case we're talking about different things: I'd expect exactly one component type per one of these iterable views/datastructures so that the user doesn't have to filter other types out in systems. Depending on the used datastructure there will obviously be an overhead per stored component - but if it's not outrageously large, it could very well be acceptable and the cost attached to this feature?

skypjack commented 6 years ago

@mhaemmerle I'm asking just to fully understand your point. The registry hasn't the concept of tick/frame/loop, so there is no guarantee that data are meaningful, no matter what is the data structure in use. It would be completely in charge to the user to clean them sooner or later. Moreover, it requires to copy entity identifiers and components, because the registry recycles all of them as soon as possible to keep at a minimum the memory usage (likely already within the same loop, if possible). How could it be better than sending a signal and let the users decide what to do?

mhaemmerle commented 6 years ago

I think it would be perfectly fine to communicate the behaviour of that collection in the documentation. I'am also going out on a leg here, but my assumption is that almost all users of ECS are using them in some kind of game/render loop? Also the reset function on the registry could clear out the collection if the ECS is used in a more on/off way.

The components could obviously not be reused while being in said collection. For the entities you could store identifier and version and return a nullptr when a user trys to retrieve an entity with a correct identifier but an old version.

The "problem" with the pure signal approach is that it's up to the user to collect the components and iterate them in a system, which forces outside knowledge of said system to register the signal handler. Further if the component is about to be reused, it can't be stored in a collection outside the registry, unless they're all copied. For me it doesn't seem to be the ECS approach anymore, where one gets a view into a stream of components that can be iterated upon very fast and where the framework holds all the data and manages it's lifecycle (even more important with custom allocators).

Maybe I should have been clearer about this: I don't think these two ways are mutually exclusive of each other. I just think EnTT managing the collection of added/destroyed components and subsequent iteration and (user-triggered-)destruction of that collection conforms to the managed view-iterate approach of EnTT/ECS. It also opens up the building of composable reactive systems as seen in Entitas-CSharp while being opt-in instead of ... well, there's no way to opt-out in Entitas-CSharp :)

skypjack commented 6 years ago

@mhaemmerle

[...] my assumption is that almost all users of ECS are using them in some kind of game/render loop?

I'm using EnTT at work and it's not part of a loop actually. :-)

The components could obviously not be reused while being in said collection.

This is the most tricky part. As it works now, destroying a component makes it immediately available to its pool. Instead, you are proposing a kind of two-step destroy process. I've no idea about how to implement it without refactoring a great part of the codebase.

The "problem" with the pure signal approach is that it's up to the user to collect the components and iterate them in a system, which forces outside knowledge of said system to register the signal handler.

What I don't understand is why one would want to store components aside. I mean, as an example I can imagine a blueprint system that reacts to component creation and adds more components to the entity involved. In this case, a signal based approach is more than enough, right? Another example, component deletion: a cleanup function that listens and reacts to perform a more elaborate cleanup for some reasons. Can you elaborate a bit more instead about a case in which component creation or destruction could trigger an operation that requires to store aside the components? I don't see the use case, my fault.

Maybe I should have been clearer about this: I don't think these two ways are mutually exclusive of each other.

Yeah, don't worry. The discussion is interesting and that's why I'm making a lot of questions. Thank you for all of your answers!!

It also opens up the building of composable reactive systems as seen in Entitas-CSharp while being opt-in instead of ... well, there's no way to opt-out in Entitas-CSharp :)

Aren't reactive systems used to react to changes? (Un)Fortunately EnTT doesn't keep track of changes in components, you can freely modify their contents and never tell EnTT you did it. This way it's quite difficult to react to changes... :-) Anyway we can still react to creation and destruction, that is better than nothing, right? ;-)

mhaemmerle commented 6 years ago

Aren't reactive systems used to react to changes? (Un)Fortunately EnTT doesn't keep track of changes in components, you can freely modify their contents and never tell EnTT you did it. This way it's quite difficult to react to changes... :-) Anyway we can still react to creation and destruction, that is better than nothing, right? ;-)

Guess why I was asking for a dedicated method for replacing tags? :) When using add, replace or remove you're telling the framework about your intentions and it can do more than e.g. just swapping out a component for an entity; like dispatching an event, adding the component to a meta collection etc.

What I don't understand is why one would want to store components aside.

Actually you don't need to store the components at all, now that I had more time to think about it. It's just the entity that gets stored; I'am actually confused now why I suggested it in the first place. It would only make sense in very select use cases and even then you could just keep a delta component around.

In the MatchOneEntt conversion from the Entitas-CSharp example I had to define multiple additional components to model reactive behaviour. Specifically GameBoardUpdatedComponent, PositionUpdatedComponent, ScoreUpdatedComponent, UpdateScoreComponent, FallEventComponent and FillEventComponent. That's 6 additionally needed components on top of the existing 18 ones - which equates to 33% more components. While in the original e.g. the AnimatePositionSystem was working with a collection that observed adding of the PositionComponent to only run when the position changed (which in Entitas-CSharp means that the component gets replaced via e.g. entity.replacePosition(0, 0, 0)), for EnTT I had to add a meta component (PositionUpdatedComponent) to tell the AnimatePositionSystem that it has to run and update the actor position of said entity and then remove the PositionUpdatedComponent (see here). Similar behaviour for e.g. the FallSystem and the FillSystem which in the original are working with a collection that observes the removal of the GameBoardElementComponent and execute only if there's at least one entity (!!! :-) :-)) in the observed collection.

Below is a list of the systems used and executed in order in the MatchOneEntt example project. Even though the PositionComponent gets assigned in the GameBoardSystem, subsequently used (=read) in some of the following systems, it's only right at the end in the SetViewPositionSystem and AnimatePositionSystem that the comparatively expensive operation of updating the transform of an actor gets executed - if (!!!) the PositionComponent has changed (= added to the entity).

And that .. is why I would love to see change collections in EnTT :)

Add(std::make_shared<ProcessInputSystem>());

Add(std::make_shared<GameBoardSystem>());
Add(std::make_shared<FallSystem>());
Add(std::make_shared<FillSystem>());

Add(std::make_shared<ScoreSystem>());
Add(std::make_shared<ScoreListenerSystem>());

Add(std::make_shared<RemoveViewSystem>());
Add(std::make_shared<AddViewSystem>());
Add(std::make_shared<SetViewPositionSystem>());
Add(std::make_shared<AnimatePositionSystem>());

Add(std::make_shared<DestroySystem>());
skypjack commented 6 years ago

@mhaemmerle

Guess why I was asking for a dedicated method for replacing tags? :)

I see. :-)

Actually you don't need to store the components at all, now that I had more time to think about it. It's just the entity that gets stored; I'am actually confused now why I suggested it in the first place.

Good. You confused me too. I'm glad we agree on this point.

[...]

In general, as long as there exists a non-const version of get, I think that sending a signal during a replace could be misleading. I mean, it gives the idea that the framework keeps track of changes, but it isn't true. You can still work around the built-in system by means of a get and errors like this can be very subtle. Not also that views do not have a replace member function and keep track of changes from them could be even more tricky.

mhaemmerle commented 6 years ago

In general, as long as there exists a non-const version of get, I think that sending a signal during a replace could be misleading. I mean, it gives the idea that the framework keeps track of changes, but it isn't true. You can still work around the built-in system by means of a get and errors like this can be very subtle. Not also that views do not have a replace member function and keep track of changes from them could be even more tricky.

It seems like I was unable to explain the concept of reactive systems and their benefits in my previous comments. At this point I think I've said all there is about this topic.

skypjack commented 6 years ago

@mhaemmerle

It seems like I was unable to explain the concept of reactive systems and their benefits in my previous comments.

You succeeded in explaining the whole fact. However, the two libraries have a very different nature. I like the concept of reactive systems indeed and I see the benefits in your case (even though I would have done things in a slightly different manner, so as not to create 6 extra components), but they cannot be implemented exactly the same way they have been developed in Entitas. This is a fact.

As an example, the main difference with EnTT is that components in Entitas are (pseudo) immutable. It means that you must update them through a replaceSomething and thus the library can intercept changes and react accordingly. In EnTT, you can do this instead:

registry.get<Position>(entity) = { 0.f, 0.f };

That is, components are mutable. You can change them and the registry won't know any time soon about it. Because of this, a collector attached to a replace event can work if and only if you extensively use replace and never try to modify a component directly. Pretty error-prone with the current API, isn't it? On the other side, you cannot create or destroy components in any way but using assign and remove/destroy. Because of this, it's easy to create signals for those events and users cannot work around it and make mistakes.

mhaemmerle commented 6 years ago

I like the concept of reactive systems indeed and I see the benefits in your case (even though I would have done things in a slightly different manner, so as not to create 6 extra components), but they cannot be implemented exactly the same way they have been developed in Entitas. This is a fact.

I hereby invite you to fork the repository and refactor it to the proper EnTT way :)

Your're right in the regard that this will not work in Entitas:

var entities = context.GetEntities(Matcher<GameEntity>.AllOf(GameMatcher.Position));
foreach(var e in entities) {
    e.position = new Position(0, 0);
}

As an example, the main difference with EnTT is that components in Entitas are (pseudo) immutable.

Though EnTT and Entitas share the exact same behaviour when mutating the component directly. You can do the following in Entitas and it will work perfectly fine (apart from losing support for reactive systems for the mutated component type - in this case Position):

var entities = context.GetEntities(Matcher<GameEntity>.AllOf(GameMatcher.Position));
foreach(var e in entities) {
    var pos = e.position;
    pos .x += 1;
    pos .y += 1;
}

Using the replace method is simply a convention that apparently everybody in the Entitas community is able to follow. When searching the issues of the Entitas repository for replace I've got 89 hits and only one of them was asking a bit of an undecipherable (see here) question about adding or replacing components. That together with the sheer popularity of Entitas shows that in reality it's a non-issue to use the documented API methods for adding/replacing/removing components.

So for me it rather sounds like you've made up your mind and are not interested in reactive systems/collections because then we would be talking about ways to actually make them possible instead of assuming that the average user of EnTT has the intellect of a potatoe and is incapable of grasping the concept of a replace method ;)

Also an easy way for you to direct the conversation into a more productive direction would have been the suggestion to make get really return a const component and maybe even ask for a pull request for it...

dbacchet commented 6 years ago

@mhaemmerle, I read twice the entire thread and I believe you're overreacting a little bit :)

So for me it rather sounds like you've made up your mind and are not interested in reactive systems/collections because then we would be talking about ways to actually make them possible instead of assuming that the average user of EnTT has the intellect of a potatoe and is incapable of grasping the concept of a replace method.

The point that @skypjack was raising was not about brain-dead users that cannot understand how to use a method; was about a design decision and being consistent with that.

Also an easy way for you to direct the conversation into a more productive direction would have been the suggestion to make get really return a const component and maybe even ask for a pull request for it...

Or maybe to ask since the beginning "what's the best way of modeling reactive systems using EnTT"? I feel you want to implement exactly the same semantics of Entitas on top of EnTT, but the design of the library is quite different... Asking for a const get is essentially asking to redesign the user API. Direct access to the components has been there since the very beginning and I use it intensively in my code for example, because I assume that my developers are smart enough and I want them to be able to touch directly the memory without useless layers of indirection.

I feel like the enhancement you're proposing is interesting, and I'm sure that @skypjack is already thinking on how to make it possible, but probably with an API different than what you have in mind. Wouldn't that solve the problem too?

skypjack commented 6 years ago

Thank you @dbacchet and, of course, it goes without saying that I'm thinking about it. You know... :-) It's a matter of time.

@mhaemmerle

I'm sorry you reacted this way. Just to be clear, my goal isn't to make a clone of an already existent library. Even more if a requested feature can be a potential performance hit or a design flaw.

Fortunately users of EnTT are smarter enough to help me in designing and defining such a good library step by step. Most of the time they gave me a hint so as to implement something the right way within EnTT, instead of pretending to copy a feature as it is from some other libraries. This time isn't different, I'm getting a lot out of this issue because of you all!!

I would be happy if you decided to continue contributing to EnTT.

mhaemmerle commented 6 years ago

@dbacchet @skypjack Seems like I should've added an emoticon after the second to last paragraph, because it's meant tongue in cheek but sadly left room for a different interpretation. I've dropped the ball by not making that absolutely clear (like in the very beginning of the comment) and fixed that through an edit.

I'am going to wait for the implementation of the Signals on component creation/destruction feature to decide whether EnTT fits the bill for me or not.

Cheers

skypjack commented 6 years ago

It's pretty simple. The API will look probably like this:

// Free function
registry.construction<Component>().connect<&func>();
// Member function
registry.destruction<Component>().connect<Class, &Class::member>(& instance);

Something along this line also to disconnect listeners. As mentioned in the title, signals on component creation/destruction. I'm still trying to figure out if the same is possible for tags and how to implement it.

I've still to consider any other change to the topic so far.

vblanco20-1 commented 6 years ago

Syntax looks good to me. I could easily get use from it. What is the signature of the signal function?

skypjack commented 6 years ago

@vblanco20-1 Probably void(Registry &, Entity). The same both for construction and destruction. It's easy to reuse also in future in case we add signals on replace, create and destroy. Sounds good? Not sure if it's worth it sending events also on creation/destruction for tags actually. Any suggestion?

vblanco20-1 commented 6 years ago

What about doing the component too? you would save a get(). void(Registry&,Entity,ComponentType)

skypjack commented 6 years ago

@vblanco20-1

You save a get only in some cases actually. As an example, during a remove the registry doesn't get the component, so to get it for you introduce a performance hit otherwise avoidable in case you weren't interested in the component.

Here the design decision is:

Why I'm proposing and voting for the last one?

Well, one reason is obvious: I don't want to get anything during a remove and I like to have the same signature in each case.
Moreover, I considered a possible use case of a listener during construction: blueprint. In other terms, when component C is attached to an entity, attach also A and B. In this case, the listener isn't interested at all in the component itself, right?
Because of that, I started thinking that probably there are a lot of cases where the listener isn't interested in the component and it can still get it otherwise because of the reference to the registry. On the other side, a common and straightforward signature for all the cases is tempting me a lot.

I've still to decide about it (I'm refactoring the signal stuff in a sort of signal-plus-sink model, so as to be able to reuse them in the registry without breaking encapsulation), so feel free to give me a good enough reason to follow another approach and let's discuss it!! ;-)

vblanco20-1 commented 6 years ago

That makes complete sense. I guess Registry&,Entity would be the best, to keep construction and destruction on the same signature.

ArnCarveris commented 6 years ago

@skypjack Would be nice, if will be implemented following API.

// Entity construction
registry.construction().connect<&func>();
// Entity destruction
registry.destruction().connect<Class, &Class::member>(& instance);
skypjack commented 6 years ago

@ArnCarveris

What is the expected behavior exactly? Your listener attached to all the pools of components or a listener for construction/destruction of entities?

ArnCarveris commented 6 years ago

@skypjack

What is the expected behavior exactly? Your listener attached to all the pools of components or a listener for construction/destruction of entities?

Only when entity was created/destroyed.

skypjack commented 6 years ago

@ArnCarveris The sole drawback I see is the performance hit it introduces. Not along critical paths probably, not sure about it.
What are the use cases for that? I can't figure out why I should be interested in creation/destruction of entities but for debug purposes. Moreover, note that we can obtain almost the same result using a dedicated component and observing it.

ArnCarveris commented 6 years ago

@skypjack

The sole drawback I see is the performance hit it introduces.

What performance hit difference between entity & component creation/destruction?

What are the use cases for that? I can't figure out why I should be interested in creation/destruction of entities but for debug purposes.

For internal editor, capturing created entity range group.

Moreover, note that we can obtain almost the same result using a dedicated component and observing it.

No need create extra magic component for observing entity only lifetime.

skypjack commented 6 years ago

@ArnCarveris

What performance hit difference between entity & component creation/destruction?

They are two different problems.
Component creation/destruction is somehow required to provide users with persistent views. Moreover, it enables several features otherwise pretty difficult to develop.
Entity creation/destruction isn't required for the purposes of the registry. Adding a signal handler there would mean adding something like this (even if you have no listeners at all):

while(begin != end) { /* ... */ }

So, the performance hit is the one of a few instructions to retrieve and compare a couple of iterators in case you have no listeners.

For internal editor, capturing created entity range group.

Internal editor?

No need create extra magic component for observing entity only lifetime.

Yep. I see that introducing a signal handler means to get rid of extra components to do that. I was only mentioning the fact that it's still possible without a signal handler, that's all.

ArnCarveris commented 6 years ago

@skypjack I see, that is bad idea, there are ways that can be achieved without it.

skypjack commented 6 years ago

@mhaemmerle It's me or Entitas hasn't a replace event and it triggers a couple of destruction/construction events in place of it?

mhaemmerle commented 6 years ago

There's a delegate on the entity here and the site where it's called here. This is triggered after calling the respective replace method in the generated extension here (watch out, the last link is from the demo project). Though a user might never use said delegate directly as it's mostly used internally to update the watching collections for the reactive systems.

skypjack commented 6 years ago

@mhaemmerle

I was referring to something like this (first link I found, not sure it's still valid):

Whenever you replace either of those it will be removed from the group (intentionally), because the idea is to remove the old component (=> entity doesn't match anymore) and the replace it with the new component (=> entity matches again). As a result e.ReplaceXyz will always trigger OnEntityAdded AND OnEntityRemoved

Anyway it looks like it's strictly related to the groups. Your links seem to confirm that the common behavior isn't to trigger also added/removed events.

Thank you.

mhaemmerle commented 6 years ago

The groups have an event of type AddedOrRemoved seen here which confirms your initial assumption. Yes, the discussion in the link is a bit older, but the rough concept is still in place. If a user is interested in getting events for added/replaced/removed components, they would create a group for it and use the respective delegate. After adding the new event system to Entitas a couple of weeks ago, there's hardly any need for that anymore though. Also it would be an anti-pattern to attach to an entity's delegate directly as an end-user.