mischief-ecs-0.1.0.0: src/Mischief/ECS/Tutorial/Queries.hs
{-# OPTIONS_GHC -Wno-unused-imports #-}
-- |
-- Module: Queries Tutorial
-- Description: Tutorial on using @Queries@
--
-- This module contains a more in-depth tutorial on @Mischief Queries@.
--
-- [Previous Chapter: Relationships]("Mischief.ECS.Tutorial.Relationships")
--
-- [Next Chapter: Systems]("Mischief.ECS.Tutorial.Systems")
--
-- [Main Page]("Mischief.ECS")
module Mischief.ECS.Tutorial.Queries
( -- * Learn You an ECS for Great Mischief! - 6. Queries
-- $intro
-- * Outputs
-- $outputs
-- * Val
-- $val
-- * Filters
-- $filters
-- * The Check Filter
-- $check
-- * Quasi-Queries
-- $quasi
-- * [Next Chapter: Systems]("Mischief.ECS.Tutorial.Systems")
)
where
import Mischief.ECS
-- $intro
-- 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:
--
-- @
-- ('Result' A, 'Maybe' ('Result' B))
-- @
--
-- With an actual return type of:
--
-- @
-- x :: ['Result' A, 'Maybe' ('Result' B)]
-- @
--
-- 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' ('Result' A, 'Maybe' ('Result' B))
-- @
--
-- @get@ queries the components of a specific entity:
--
-- @
-- x <- 'get' ('C' \@A) e
-- @
--
-- @
-- x :: 'Maybe' ('Result' A)
-- @
--
-- @get@ returns @'Nothing'@ if the entity doesn't exist or if it doesn't contain any of the components.
-- $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:
--
-- @
-- ['Result' Foo, (Bar, 'Maybe' Baz)]
-- @
--
-- @Val@ maps anything that looks like a @Result c@ to just @c@, while leaving most other things be as they are.
-- $outputs
-- Here's a list of the the various markers you can use in queries and each of their output types:
--
-- Entity:
--
-- * @'E'@ -> @'Entity'@
--
-- Components:
--
-- * @'C' c@ -> @'Result' c@
-- * @'M' c@ -> @'Maybe' ('Result' c)@
-- * @'Has' c@ -> @'Bool'@
--
-- Relationships:
--
-- * @'R' c e@ -> @'Result' ('Rel' c)@
-- * @'R' c 'Any'@ -> @['Result' ('Rel' c)]@
-- * @'MR' c e@ -> @'Maybe' ('Result' ('Rel' c))@
-- * @'MR' c 'Any'@ -> @'Maybe' ['Result' ('Rel' c)@
-- * @'HasR' c e@ -> @'Bool'@
-- * @'HasR' c 'Any'@ -> @'Bool'@
--
-- Transitive:
--
-- * @'R' c ('Q' q)@ -> @[Result of q]@
-- * @'MR' c ('Q' q)@ -> @'Maybe' [Result of q]@
-- * @'HasR' c ('Q' q)@ -> @'Bool'@
--
-- 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.
-- $filters
-- Filters can be passed to @'@ variants of query functions, such as @'query''@ and @'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 @'C'@ and @'R'@ types. These are all valid filters:
--
-- * @'With' ('C' \@Foo)@
-- * @'With' ('R' \@Foo e, 'C' \@Bar)@
-- * @'With' ('R' \@Foo 'Any', 'C' \@Bar, 'C' \@Baz)@
--
-- These filters are:
--
-- * @'With'@
-- * @'Without'@
-- * @'Changed'@
-- * @'Added'@
-- $check
-- @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 @'CheckR'@ variant which also expects an entity or @Any@.
--
-- Selecting all entities which Like alice more than 5:
--
-- @
-- 'query'' 'E' ('CheckR' alice (> Likes 5))
-- @
--
-- Selecting all entities which like any other entity more than 10:
--
-- @
-- 'query'' 'E' ('CheckR' 'Any' (> Like 10))
-- @
-- $quasi
-- @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 @'C' \@c@ becomes @c@.
--
-- === Relationships
--
-- What about relationships?
--
-- @
-- 'query' ('R' \@Foo e, 'R' \@Bar 'Any')
-- @
--
-- @
-- ['q'|Foo -> e, Bar -> *|]
-- @
--
-- @'R' \@c a@ is translated to @c -> 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' \@Bar 'Any')
-- @
--
-- @
-- ['q'|Maybe Foo, Has Bar -> *|]
-- @
--
-- Since Quasi-Queries are parsed internally by Mischief, alternative symbols are allowed:
--
-- * @Maybe@ | @maybe@ | @M@ | @m@
-- * @Has@ | @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
--
-- @'E'@ can be written as: @Entity@, @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' ('C' @\Foo, R @\Bar 'Any'), '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|]
-- @