Closed skypjack closed 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. ;-)
Reopened this to discuss API and use cases.
I'll put all the details in a comment later.
At a first glance, this is a possible approach:
For each type of component it will be possible to attach listeners and be notified about either creation or destruction
A listener will receive a single argument, that is the entity involved
In case of creation of a component, the signal is sent immediately after the operation
In case of destruction of a component, the signal is sent immediately before the operation
There will be no signals in case of creation and/or destruction of entities
Currently I'm still looking for the best way to send signals for tags
An alternative approach could be this one probably:
A single sink to which attach listeners that are notified about the creation of components, listeners will receive both the entity and the numeric identifier of the component involved
A single sink to which attach listeners that are notified about the destruction of components, listeners will receive both the entity and the numeric identifier of the component involved
More details will come. What about so far? Questions? Comments?
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() ?
@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.
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.
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.
Sounds good.
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.
@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.
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.
@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::function
s.
What about? Your experience with EnTT
is invaluable.
Thank you very much for your help, really appreciated.
You already have signals and similar. Easiest would be to just use one of those, no?
@vblanco20-1 Yep and they don't accept lambdas. :-)
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.
@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.
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?
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.
I'm experimenting with both the solutions. Some updates:
The single listener approach is trickier than what I thought initially, mainly because of the persistent views that break a bit the whole thing. It's quite hard to define an implicit list among them and I haven't find a good way to do that so far.
Using SigH
and the others seem to be slower than implementing a custom solution tailored to the requirements of the Registry
class. It's quite redundant actually, because the code required is very similar to the one of SigH
. Anyway, performance are definitely better.
Submitting the component to the listeners during creation seems to ruin a bit the performance for unknown reasons, even though it doesn't involve any get
or whatever. Submitting the component to the listeners during destruction ruins definitely the performance, mainly because it requires an extra get
onto the underlying set that could be avoided otherwise.
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.
@mhaemmerle I see memory usage as the main issue in this case. Sparse sets aren't the best approach to this problem unfortunately.
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?
@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?
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 :)
@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? ;-)
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>());
@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.
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.
@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.
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...
@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?
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.
@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
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.
Syntax looks good to me. I could easily get use from it. What is the signature of the signal function?
@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?
What about doing the component too? you would save a get(). void(Registry&,Entity,ComponentType)
@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:
void(Registry &, Component &, Entity)
during construction, void(Registry &, Entity)
during destruction).get
during destruction that could be avoided otherwise).void(Registry &, Entity)
, the one I proposed).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!! ;-)
That makes complete sense. I guess Registry&,Entity would be the best, to keep construction and destruction on the same signature.
@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);
@ArnCarveris
What is the expected behavior exactly? Your listener attached to all the pools of components or a listener for construction/destruction of entities?
@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.
@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.
@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.
@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.
@skypjack I see, that is bad idea, there are ways that can be achieved without it.
@mhaemmerle It's me or Entitas hasn't a replace event and it triggers a couple of destruction/construction events in place of it?
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.
@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.
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.
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.