ecstasy-0.2.0.1: src/Data/Ecstasy.hs
-- | Ecstasy is a library architected around the
-- <http://reasonablypolymorphic.com/blog/higher-kinded-data/ HKD pattern>, the
-- gist of which is to define a "template" type that can be reused for several
-- purposes. Users of ecstasy should define a record type of 'Component's
-- parameterized over a variable of kind 'StorageType':
--
-- @
-- data World s = Entity
-- { position :: 'Component' s ''Field' (V2 Double)
-- , graphics :: 'Component' s ''Field' Graphics
-- , isPlayer :: 'Component' s ''Unique' ()
-- }
-- deriving ('Generic')
-- @
--
-- Ensure that this type have an instance of 'Generic'.
--
-- For usability, it might be desirable to also define the following type
-- synonym:
--
-- @
-- type Entity = World ''FieldOf'
-- @
--
-- which is the only form of the @World@ that most users of ecstasy will
-- need to interact with.
--
-- Throughout this document there are references to the @HasWorld@ and
-- @HasWorld'@ classes, which are implementation details and provided
-- automatically by the library.
module Data.Ecstasy
(
-- * Defining components
-- $components
ComponentType (..)
, Component
-- * Storage
-- $world
, defStorage
, StorageType (..)
-- * The SystemT monad
-- $systemt
, SystemT ()
, runSystemT
, yieldSystemT
, System
, runSystem
, SystemState
-- * Working with SystemT
, createEntity
, newEntity
, getEntity
, setEntity
, deleteEntity
-- * SystemT traversals
-- $traversals
, emap
, efor
, eover
, unchanged
, delEntity
-- * Entity targets
, EntTarget
, allEnts
, someEnts
, anEnt
-- * The QueryT monad
-- $queryt
, QueryT ()
, runQueryT
-- * Queries
-- $querying
, query
, with
, without
, queryEnt
, queryMaybe
, queryFlag
, queryDef
-- * Updates
, Update (..)
, maybeToUpdate
-- * Miscellany
, Ent ()
, VTable (..)
-- * Re-exports
, Generic ()
) where
import Data.Ecstasy.Internal hiding (HasWorld, HasWorld')
import Data.Ecstasy.Types
import GHC.Generics
-- $components
-- Components are pieces of data that may or may not exist on a particular
-- entity. In fact, an 'Ent' is nothing more than an identifier, against which
-- components are linked.
--
-- Components classified by their 'ComponentType', which describes the
-- semantics behind a component.
--
-- [@Field@] A 'Field' is a "normal" component and corresponds exactly to
-- a 'Maybe' value.
--
-- [@Unique@] A 'Unique' component may only exist on a single entity at a given
-- time. They are often used to annotate "notable" entites, such as whom the
-- camera should be following.
--
-- [@Virtual@] A 'Virtual' component is defined in terms of monadic 'vget' and
-- 'vset' actions, rather than having dedicated storage in the ECS. Virtual
-- components are often used to connect to external systems, eg. to a 3rd party
-- physics engine which wants to own its own data. For more information on
-- using virtual components, see <#world defStorage>.
--
--
-- $world
-- #world# 'defStorage' provides a suitable container for storing entity data, to
-- be used with 'runSystemT' and friends. If you are not using any 'Virtual'
-- components, it can be used directly.
--
-- However, when using 'Virtual' components, the 'VTable' for each must be set
-- on 'defStorage' before being given as a parameter to 'runSystemT'. For
-- example, we can write a virtual 'String' component that writes its updates
-- to stdout:
--
-- @
-- data World s = Entity
-- { stdout :: 'Component' s ''Virtual' String
-- }
-- deriving ('Generic')
--
-- main :: IO ()
-- main = do
-- let storage = 'defStorage'
-- { stdout = 'VTable'
-- { 'vget' = \\_ -> pure Nothing
-- , 'vset' = \\_ m -> for_ m putStrLn
-- }
-- }
-- 'runSystemT' storage $ do
-- void $ 'createEntity' 'unchanged
-- { stdout = Just "hello world"
-- }
-- @
--
-- In this example, if you were to use 'defStorage' rather than @storage@ as the
-- argument to 'runSystemT', you would receive the following error:
--
-- @unset VTable for Virtual component \'stdout\'@
-- $systemt
-- The 'SystemT' transformer provides capabilities for creating, modifying,
-- reading and deleting entities, as well as performing <#traversals query
-- traversals> over them. It is the main monad of ecstasy.
-- $queryt
-- The 'QueryT' transformer provides an environment for <#traversals querying>
-- components of an entity. Due to its 'Control.Monad.MonadPlus' instance,
-- failing queries will prevent further computations in the monad from running.
-- $traversals
-- #traversals# 'SystemT' provides functionality for traversing over entities
-- that match a 'EntTarget' and a <#querying query>. The functions 'emap' and
-- 'eover' return a @world 'SetterOf@, corresponding to partial update of the
-- targeted entity.
--
-- A @world 'SetterOf@ is the world record where all of its selectors have the
-- type @'Update' a@. For example, given a world:
--
-- @
-- data World s = Entity
-- { position :: 'Component' s ''Field' (V2 Double)
-- , graphics :: 'Component' s ''Field' Graphics
-- , isPlayer :: 'Component' s ''Unique' ()
-- }
-- @
--
-- then @World 'SetterOf@ is equivalent to the following definition:
--
-- @
-- data World 'SetterOf = Entity
-- { position :: 'Update' (V2 Double)
-- , graphics :: 'Update' Graphics
-- , isPlayer :: 'Update' ()
-- }
-- @
--
-- 'unchanged' provides a @world 'SetterOf@ which will update no components,
-- and can have partial modifications added to it.
--
-- 'delEntity' provides a @world 'SetterOf@ which will delete all components
-- associated with the targeted entity.
-- $querying
-- #querying# The 'QueryT' monad provides functionality for performing
-- computations over an 'Ent''s components. The basic primitive is 'query',
-- which will pull the value of a component, and fail the query if it isn't
-- set.
--
-- For example, given the following world:
--
-- @
-- data World s = Entity
-- { position :: 'Component' s ''Field' (V2 Double)
-- , velocity :: 'Component' s ''Field' (V2 Double)
-- }
-- deriving ('Generic')
-- @
--
-- we could model a discrete time simulation via:
--
-- @
-- stepTime :: 'System' World ()
-- stepTime = do
-- 'emap' 'allEnts' $ do
-- pos <- 'query' position
-- vel <- 'query' velocity
-- pure $ 'unchanged'
-- { position = 'Set' $ pos + vel
-- }
-- @
--
-- which will add an entity's velocity to its position, so long as it has both
-- components to begin with.