| Safe Haskell | None |
|---|---|
| Language | GHC2024 |
Mischief.ECS.Tutorial.Guides.Change
Synopsis
Learn You an ECS for Great Mischief! - 2.11. Change Detection
This chapter will show you ways of listening and reacting to changes in the World.
Events
There are various pre-defined events that can be used to listen to when a component is added, inserted, or removed:
onAddName ::OnAddName ->System() onAddName OnAdd {entity} = do Just name <-single[q|entity. Name|]info[i|#{entity} was just added the name #{name}|]
For insertion and removal you can use OnSet and OnRemove respectively.
There are also OnAddRel, OnSetRel, OnRemoveRel which act the same but can listen to relationship changes.
For instance:
onRemoveChildOf ::OnRemoveRelChildOf ->System() onRemoveChildOf OnRemoveRel {entity, target} =info[i|#{entiy} is no longer the child of #{target}|]
Query Filters
You can use the Changed filter in a query to select the entities that have had a component changed.
With the following query we can get the name of all entities that have had the Health component changed in the last frame:
[q|Name / Changed Health|] &query
Note however, that the changed marker is set per-archetype table. This means that you might get extra entities, not just those which have had that component changed. This is intended to be used for optimization purposes, and not for reacting to individual changes. Use events for that instead.
Avoiding False Changes
One essential detail to be aware of here is that listening to insertion (through OnSet or Changed) doesn't necessarily mean a component's value has been changed!
The following insert will trigger change detection:
p <-spawn(Health 100)insert(Health 100) p
To avoid this, you can derive Eq on your components and use , which will only perform insertion if the value of the component is different
from the current one.insertIfNeq
Component Hooks
Hooks are specialized events associated directly to a Component instance.
instanceComponentFoo where onAdd = [hook onAddFoo] onSet = [hook onSetFoo] onRemove = [hook onRemoveFoo] onAddFoo ::HookContext->System() onAddFoo = ... onSetFoo ::HookContext->System() onSetFoo = ... onRemoveFoo ::HookContext->System() onRemoveFoo = ...
Similar to change events, HookContext has an .entity field you can use to access the entity the hook was triggered on.
Relationship support dedicated hooks, named onAddRel, onSetRel, onRemoveRel, following the same rules as the normal hooks.
This is how we can log a message each time Likes is added between two entities:
instanceComponentLikes where onAddRel = [hookRelonLikesAdd] onLikesAdd ::HookContextRel->System() onLikesAdd HookContextRel {entity, target} =info[i|#{entity} now likes #{target}!|]
There are a number of predefined hooks that are useful when working with relationships, which can be found in Mischief.ECS.HooksRel.
For instance, addOther can be used to automate adding a complementary relationship on the target of a relationship.
As a quick example of why this is useful, this is how you'd create a Before/After relationship between entities:
data Before = Before data After = After instanceComponentBefore where onAddRel = [HooksRel.addOther(constAfter)] instanceComponentAfter wherehooks= [HooksRel.addOther(constBefore)]
Now, when you do:
insert(RelBefore a) b
A Rel After b will be inserted automatically on a.
And vice versa.
There are other interesting hooks, such as ones for automatically cleaning up a relationship when an entity is despawned. Check out Mischief.ECS.HooksRel for details!