| Safe Haskell | None |
|---|---|
| Language | GHC2024 |
Mischief.ECS.Tutorial.Relationships
Description
This module contains a more in-depth tutorial on Mischief Relationships.
Synopsis
Learn You an ECS for Great Mischief! - 5. Relationships
Introduction
Mischief implement Relationships in a similar way to Flecs.
When a component is added to an entity, it is actually indexed by a ComponentId:
dataComponentId= ComponentId {id ::Entity, entity ::MaybeEntity}
The first field, id, is the entity corresponding to the component, while the second field, entity, is an optional reference to another entity.
This means that each ComponentId can either be a simple component, or a pair between a component and an entity (technically even between
two components or two entities but that's not directly allowed by the API).
So a relationship in Mischief is a pair between a component and an entity. It can be inserted on entities using Rel:
dataRelc = Rel {comp :: c, target ::Entity}
For instance, this is how we spawn an entity b that's a child of a:
a <-spawn() b <-spawn(RelChildOfa)
Insertion
Let's consider the following component, which will symbolize that an entity likes another, and by how much:
data Likes = LikesIntderiving (Component)
And three spawned entities: alice, bob, charlie.
As mentioned before, we can insert a relationship using the Rel type.
insert(Rel(Likes 3) alice,Rel(Likes 5) charlie) bobinsert(Rel(Likes 2) bob) alice
If we insert a second relationship with the same component and the same target, its value will overwrite the other. For instance, the following code
will make alice like bob by 3 instead of 2:
insert(Rel(Likes 3) bob) alice
Removal
Removing relationships can be done through the remove function, same as normal components. But instead of using the marker, we will use the C marker.R
Making bob stop liking alice.
remove(R@Likes alice) bob
The marker takes a type hint of the relationship's type (R@Likes), and a target Entity (alice). But it can also be given the Any wildcard instead:
remove(R@LikesAny) bob
This will remove all Likes relationships from bob, making him not like anyone.
Querying
Relationships can be queried using the R marker.
x <-query(R@Likes alice)
x :: [Result(RelLikes)]
In quasi-queries, this becomes:
x <- [q|Likes -> alice|]
Result (Rel c) is the return type of R @c e. It can be used in most Result-based operation discussed in the previous chapter, such as and set.delete
Querying can also be done using the Any wildcard:
x <-query(R@Likes Any)
x :: [[Result(RelLikes)]]
In quasi-queries, Any is symbolized by *:
x <- [q|Likes -> *|]
As you can see, the return type of is R @c Any[Result (Rel c)]. It's returning a list of relationships, rather than a single relationship (unless the
relationship is exclusive, but more on that in a bit).
Remember that the fields of the inner type of a Result are inherited by the Result itself. So we can just use .comp and .target to get the component and target of a Result (Rel c).
Justx <-get(R@Likes alice) bob
x.target :: Entity x.comp :: Likes
In the case of querying for R c Any, the query will only match entities that have at least one such relationship. The resulting [Result (Rel c)] should never be empty.
If you wish to also include entities that do not have those relationships, you can use (short for MRMaybe Relationships), the relational equivalent of .M
x <-query(MR@Likes alice)
x :: [Maybe(Result(Relc))]
Which is this in quasi form:
x ['q'|Maybe Likes - alice|]
Transitive Querying
Transitive queries are a powerful primitive which allow us to easily query components based on relational connections.
For instance, this is how we can get the name of each entity, along with the names of all entities they like:
x <-query(C@Name,R@Likes (Q(C@Name)))
x :: [(Result Name, [Result Name])]
They tend to look much better when written as quasi-queries (don't forget the ()!):
x <- [q|Name, Likes -> (Name)]
Note that transitive queries can be nested as much as you want:
x <- [q|Name, Likes -> (Name, Likes -> (Name))|]
x :: [(Result Name, [(Result Name, [Result Name])])]
Hooks
There are a number of predefined hooks that are useful when working with relationships, which can be found in Mischief.ECS.Hooks.
For instance, relComplementary can be used to automate adding a complementary relationship on the target of a relationship.
As a quick example of why this is useful, let's create a Before/After relationship between entities:
data Before = Before data After = After instanceComponentBefore wherehooks=relComplementary(constAfter) instanceComponentAfter wherehooks=relComplementary(constBefore)
Now, when we do:
insert(RelBefore a) b
A Rel After b will be inserted automatically on a.
And when we do:
remove(R@Before) b
Rel After b will be removed from a.
And vice versa.
There are also and relCleanupRemove for automatically removing a relationship (or ,respectively, despawning its entity) when its target has been despawned. And a
more generic relCleanupDespawn which allows adding custom cleanup behavior.relCleanup
Examples
Example 1
An example showing different operations on relationships, and the relCleanupRemove hook.
import Mischief.ECS.Prelude import Mischief.ECS.Hooks qualified as Hooks import Mischief.ECS.Systems qualified as Systems data Likes = LikesIntderiving (Show) instanceComponentLikes wherehooks= Hooks.relCleanupRemovemain ::IO() main = do app <-newAppMainPluginrunAppapp data MainPlugin = MainPlugin deriving (Eq) instancePluginMainPlugin where init _ = Systems.addStartuptest test ::System() test = do alice <-spawn(Name"Alice") bob <-spawn(Name"Bob") charlie <-spawn(Name"Charlie")insert(Rel(Likes 5) alice,Rel(Likes 10) bob) charlieinsert(Rel(Likes 7) alice) bobinsert(Rel(Likes 9) bob,Rel(Likes 5) charlie) alice (info.text) =<<query(C@Name,R@Likes alice) (info.text) =<<query(C@Name,R@Likes bob) (info.text) =<<query(C@Name,R@Likes charlie)remove(R@LikesAny) alicedespawnbob (info.text) =<<query(C@Name,R@Likes alice) (info.text) =<<query(C@Name,R@Likes bob) (info.text) =<<query(C@Name,R@Likes charlie)
> [INFO] [("Bob",Rel {comp = Likes 7, target = 28v1}),("Charlie",Rel {comp = Likes 5, target = 28v1})]
> [INFO] [("Alice",Rel {comp = Likes 9, target = 29v1}),("Charlie",Rel {comp = Likes 10, target = 29v1})]
> [INFO] [("Alice",Rel {comp = Likes 5, target = 30v1})]
> [INFO] [("Charlie",Rel {comp = Likes 5, target = 28v1})]
> [INFO] []
> [INFO] []
You can see in the second set of prints that nobody likes Bob anymore, since he "died", and the cleanup hook made it so any "Likes -> Bob" relationships were automatically removed.