| Safe Haskell | None |
|---|---|
| Language | GHC2024 |
Mischief.ECS.Tutorial.Queries
Description
This module contains a more in-depth tutorial on Mischief Queries.
Synopsis
Learn You an ECS for Great Mischief! - 6. Queries
Introduction
Queries are the main way we read component data. They are fully type-checked and based on archetypes, making them quite fast.
Each query associates an output type to the types you put into it.
For instance,
x <-query(C@A,M@B)
Will have an output type of:
(ResultA,Maybe(ResultB))
With an actual return type of:
x :: [ResultA,Maybe(ResultB)]
Because query actually returns [Output].
single on the other hand, returns a Maybe Output, only outputting something if there is exactly one entity which matches the query.
x <-single(C@A,M@B)
x ::Maybe(ResultA,Maybe(ResultB))
get queries the components of a specific entity:
x <-get(C@A) e
x ::Maybe(ResultA)
get returns if the entity doesn't exist or if it doesn't contain any of the components.Nothing
Outputs
Here's a list of the the various markers you can use in queries and each of their output types:
Entity:
Components:
Relationships:
->Rc eResult(Relc)->RcAny[Result(Relc)]->MRc eMaybe(Result(Relc))->MRcAnyMaybe[Result(Relc)->HasRc eBool->HasRcAnyBool
Transitive:
Note that the relational queries using Any and the transitive ones will return a single element
instead of a list, in the case of that relation being exclusive.
Val
Working with Result c can be annoying, especially if you only want to read the value of a component and not mutate anything.
That's why all query types can be wrapped in a Val in order to extract their values out of the Result:
query(C@Foo,Val(C@Bar,M@Baz))
Will return:
[ResultFoo, (Bar,MaybeBaz)]
Val maps anything that looks like a Result c to just c, while leaving most other things be as they are.
Filters
Filters can be passed to ' variants of query functions, such as and query'.single'
There is an implicit and between filters. (A, B) means A and B. If you wish to express A or B, you can write it as A |. B. Not can be used to
negate filters.
(A |. Not (B, C)) means A or (not (B and C)).
Most filters expect a tuple of and C types. These are all valid filters:R
These filters are:
The Check Filter
Check is a special filter which takes a f :: c -> Bool function and only accepts entities for which f applied over the c component is True.
Naturally, all entities that don't contain the c component will fail.
For instance, here's how we can select all entities named "Bob":
query'E(Check(==Name"Bob"))
For relationships, you must use the dedicated variant which also expects an entity or CheckRAny.
Selecting all entities which Like alice more than 5:
query'E(CheckRalice (> Likes 5))
Selecting all entities which like any other entity more than 10:
query'E(CheckRAny(> Like 10))
Quasi-Queries
Quasi-Queries are queries written via a special quasi-quoter. Make sure to have the QuasiQuotes and TemplateHaskell langauge extensions enabled in order to use them.
Components
Here's how we can rewrite a simple componnet query in quasi form:
query(C@Foo,C@Bar)
[q|Foo, Bar|]
As you can see, a becomes C @cc.
Relationships
What about relationships?
query(R@Foo e,R@BarAny)
[q|Foo -> e, Bar -> *|]
is translated to R @c ac -> a, and Any becomes *.
Transitive
Transitive queries are written the same as relationship ones, but with () around their target:
query(R@Foo (Q(C@Bar)))
[q|Foo -> (Bar)]
Modifiers
Quasi-Queries also accept Maybe and Has modifiers:
query(M@Foo,HasR@BarAny)
[q|Maybe Foo, Has Bar -> *|]
Since Quasi-Queries are parsed internally by Mischief, alternative symbols are allowed:
Maybe|maybe|M|mHas|has|H|h
So the above query can also be written as:
[q|M Foo, H Bar -> *|]
You don't need to worry about the distinction between M and MR and so on, the parer will infer which to use.
Val
Val is accepted in quasi notation too:
query(Val(C@Foo,C@Bar))
[q|Val (Foo, Bar)|]
Val can be written as: Val, val, V, v, *.
So an equivalent way to write the above would be:
[q|*(Foo, Bar)|]
Entity
can be written as: EEntity, entity, E, e.
Filters
In order to add a filter to a quasi-query, we must separate it with a / from the rest of the query:
query'(C@Name) (With(CFoo, RBarAny),Without(C@Baz))
[q|Name / With (Foo, Bar -> *), Without Baz|]
Added and Changed also exists for quasi-queries. All filters can be written either starting with a lower or uppercase letter. For instance, both with and With are correct.
Not can also be written as ! and |. can be written as |., ||, or, Or.
Check
In quasi notation, Check is unified for both components and relationships. Simply put -> a after it if it's a relationship!
Getting all entities named "Bob" which like alice more than 5:
[q|Entity / Check (== Name "Bob"), Check (> Likes 5) -> alice|]
The argument for Check can be any arbitrary lambda function or a function defined outside the quasi-quote.
Generics
In order to use a quasi-query for a type with generic parameters, such as:
data A a b = A deriving (Component)
The entire type must be put in (). For instance:
[q|Maybe (A Int Float), Likes -> *|]
Other Quasies
There is also the g Quasi-Query for get, and s for single:
[g|Name|] alice
[s|Name / with Player|]