Skip to content

There is no viable alternative to HierarchyEvent #19019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Zeenobit opened this issue May 1, 2025 · 8 comments
Open

There is no viable alternative to HierarchyEvent #19019

Zeenobit opened this issue May 1, 2025 · 8 comments
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible P-Regression Functionality that used to work but no longer does. Add a test for this! S-Needs-Design This issue requires design work to think about how it would best be accomplished

Comments

@Zeenobit
Copy link
Contributor

Zeenobit commented May 1, 2025

What problem does this solve or what need does it fill?

With 0.16, it looks like HierarchyEvent was removed. Prior to 0.16, HierarchyEvent was the only way to reliably detect specific changes to a hierarchy.

I realize we have RemovedComponents. The problem with it is that it only tells you from which entities it was removed; it doesn't give you the component data.

So if I listen to RemovedComponent<ChildOf>, I won't know which parent the entity was removed from. I could listen to Changed<Children>, but what if Children gets removed? Then I'd have to also listen to RemovedComponents<Children>... and I'd still have the same issue as RemovedComponent<ChildOf>.

What solution would you like?

Either a RelationEvent to detect when relations change, or a way to access removed component data.

What alternative(s) have you considered?

Alternatively, I could use component hooks, but I believe having the ability to know when a specific relationship has ended between which exact entities at a system level is critical.

Additional context

This has also been brought up before:
#13925 (comment)
#17398 (comment)

@Zeenobit Zeenobit added C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled labels May 1, 2025
@alice-i-cecile
Copy link
Member

Can you use observers here? You should have all of the same information available: the removed data from the components is available.

@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events P-Regression Functionality that used to work but no longer does. Add a test for this! S-Needs-Reproduction Needs an up-to-date or minimal reproduction and removed S-Needs-Triage This issue needs to be labelled labels May 1, 2025
@Zeenobit
Copy link
Contributor Author

Zeenobit commented May 2, 2025

I suppose I could. I'm assuming that's basically what Jondolf suggested in their comment.

It feels a bit heavy though, since as I understand it (please correct me if I'm wrong), this would be a separate entity with the sole purpose of reacting to Trigger<OnRemove, ChildOf> and the trigger would be executed immediately (like a hook). And to support all possible hierarchy changes, I'd basically need two observers per relation (OnRemove + Changed).

Is there a particular reason the event API was removed? It's a much more natural interface when immediate execution is a not an issue. It'd be so much more useful as a RelationEvent<T> now that we have relations. Particularly because it handles Add/Remove/Change all together.

Edit: Maybe the middle-ground solution here is an abstraction layer that uses observers to generate relation events, so it could be opt-in by the user, but without requiring the user to re-implement the pattern for every relation.

@alice-i-cecile
Copy link
Member

It feels a bit heavy though, since as I understand it (please correct me if I'm wrong), this would be a separate entity with the sole purpose of reacting to Trigger<OnRemove, ChildOf> and the trigger would be executed immediately (like a hook). And to support all possible hierarchy changes, I'd basically need two observers per relation (OnRemove + Changed).

This would be two entities for any hierarachy event, regardless of which entity is getting changed. While observers can watch only a subset of entities, by default they watch for any entity.

Is there a particular reason the event API was removed?

From my recollection, this was "let's try and reduce duplicate tools and passive overhead".

Edit: Maybe the middle-ground solution here is an abstraction layer that uses observers to generate relation events, so it could be opt-in by the user, but without requiring the user to re-implement the pattern for every relation.

This is a reasonable idea, but I still suspect that there was a miscommunication about what's entailed with the observer pattern. Do you mean "for every new type of relation (analagous to ChildOf)" that the user might implement?

@alice-i-cecile alice-i-cecile added S-Needs-Design This issue requires design work to think about how it would best be accomplished and removed S-Needs-Reproduction Needs an up-to-date or minimal reproduction labels May 4, 2025
@Zeenobit
Copy link
Contributor Author

Zeenobit commented May 4, 2025

Do you mean "for every new type of relation (analagous to ChildOf)" that the user might implement?

Yes. For example, in my project, I have an inventory system. I need to know when an item is inserted or removed from the inventory in order to update some state.

Previously, I could do this by having an Inventory and InventoryItem component. I could then listen to HierarchyEvent to check if an inventory item's parent is an Inventory or not. If so, the item is contained; if not, it is not. I would then update the relation data and then dispatch an event from this, such as InventoryEvent, and extend the logic further.

But now, with relations, I would need to have two observers to monitor ChildOf relation changes in order to detect when an item is inserted into or removed from inventory and use that to update my Inventory/InventoryItem relation. I could then dispatch my InventoryEvent from the observers.

In my mind, it would be a lot more straightforward to just "map" the HierarchyEvent to an InventoryEvent.

Alternatively, I could have more observers to detect changes to InventoryItem relation and forego events entirely. Maybe you can convince me otherwise! 🙂 But to me, making the user implement this pattern of two observers per custom relation feels excessive and error prone.

With generic relations events, I could see mapping between any RelationEvent<T> to any RelationEvent<U> implemented via a single system, without needing extra entities "polluting" the world. It'd be a very scalable API.


Another possible solution is to add a new ChangedOrRemoved trigger (naming TBD) to cover Added/Changed/Removed all together. That would at least unify the observers which would simplify the API a lot. Then we could have mappings of relation triggers to relation triggers, similar to events. The "entity pollution" would still be a concern to me though.


Edit: Also, if I understand correctly, it wouldn't be just 2 observers per relation. It's 2 observer per use of relation. So for example, if I have three independent game features (e.g. plugins), all monitoring hierarchy changes, I would need 6 observers?

@alice-i-cecile
Copy link
Member

Edit: Also, if I understand correctly, it wouldn't be just 2 observers per relation. It's 2 observer per use of relation. So for example, if I have three independent game features (e.g. plugins), all monitoring hierarchy changes, I would need 6 observers?

Correct; that's what I'm saying.

Another possible solution is to add a new ChangedOrRemoved trigger (naming TBD) to cover Added/Changed/Removed all together. That would at least unify the observers which would simplify the API a lot. Then we could have mappings of relation triggers to relation triggers, similar to events.

Something like this is my preference: there's clearly an abstraction missing here in some form; you shouldn't have to write two distinct observers for the same logic.

The "entity pollution" would still be a concern to me though.

Can you say more about why? Are you worried about memory overhead? Inspector pollution? Visceral ick? All of those are valid, but I want to make sure I understand the objections clearly. The fact that observers are implemented via entities is largely a convenience and implementation detail.

@Zeenobit
Copy link
Contributor Author

Zeenobit commented May 5, 2025

Can you say more about why? Are you worried about memory overhead? Inspector pollution? Visceral ick? All of those are valid, but I want to make sure I understand the objections clearly. The fact that observers are implemented via entities is largely a convenience and implementation detail.

It's a bit all of the above.

  • We can only have a finite number of entities (it is a lot, but still finite in face of exponential growth). So it feels like wasting a finite resource for something that shouldn't really be an entity. As I understand it, the Observers would also create new archetypes (Edit: I guess it's just one archetype since there is only one Observer type)
  • It would pollute any inspector UI, requiring inspectors to treat them as "special entities".
  • If it's convenient for an Observer to be a component on an entity, why isn't it convenient for a Resource or an EventReader/Writer to also be components on entities? What makes Observers different to require an entity?
  • As I understand it, Observers also run immediately during world mutation, which makes them less efficient than using systems/events (similar to hooks)
  • Them being on entities also exposes them to accidentally being referenced and treated like entities. Would it ever make sense to manipulate the components on an Observer?

All this makes observers feel like second-class citizens to me. Which is fine, but I would argue detecting relation changes is such a critical part of systems development that it deserves a first-class API, like events.

@alice-i-cecile
Copy link
Member

Thanks for the explanation! I'm looking at an overhaul of observers this cycle, and this perspective was really helpful to me.

If it's convenient for an Observer to be a component on an entity, why isn't it convenient for a Resource or an EventReader/Writer to also be components on entities? What makes Observers different to require an entity?

FWIW, I'm intending to do this for resources too :)

@Zeenobit
Copy link
Contributor Author

Zeenobit commented May 5, 2025

Thanks for the explanation and follow up! :)

FWIW, I'm intending to do this for resources too :)

If we're going the route of putting everything on entities, it makes a lot more sense to me!

Then if we just have a ChangedOrRmoved trigger, it'd be a good enough alternative to HierarchyEvent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible P-Regression Functionality that used to work but no longer does. Add a test for this! S-Needs-Design This issue requires design work to think about how it would best be accomplished
Projects
None yet
Development

No branches or pull requests

2 participants