Safe Haskell | None |
---|---|
Language | Haskell2010 |
Ecstasy is a library architected around the
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.
- data ComponentType
- type family Component (s :: StorageType) (c :: ComponentType) (a :: Type) :: Type where ...
- defStorage :: HasWorld world m => world (WorldOf m)
- data StorageType
- data SystemT w m a
- runSystemT :: Monad m => world (WorldOf m) -> SystemT world m a -> m a
- yieldSystemT :: Monad m => SystemState world m -> SystemT world m a -> m (SystemState world m, a)
- type System w = SystemT w Identity
- runSystem :: world (WorldOf Identity) -> System world a -> a
- type SystemState w m = (Int, w (WorldOf m))
- createEntity :: (HasWorld world m, Monad m) => world FieldOf -> SystemT world m Ent
- newEntity :: HasWorld' world => world FieldOf
- getEntity :: (HasWorld world m, Monad m) => Ent -> SystemT world m (world FieldOf)
- setEntity :: HasWorld world m => Ent -> world SetterOf -> SystemT world m ()
- deleteEntity :: (HasWorld world m, Monad m) => Ent -> SystemT world m ()
- emap :: (HasWorld world m, Monad m) => EntTarget world m -> QueryT world m (world SetterOf) -> SystemT world m ()
- efor :: (HasWorld world m, Monad m) => EntTarget world m -> QueryT world m a -> SystemT world m [a]
- eover :: (HasWorld world m, Monad m) => EntTarget world m -> QueryT world m (a, world SetterOf) -> SystemT world m [a]
- unchanged :: HasWorld' world => world SetterOf
- delEntity :: HasWorld' world => world SetterOf
- type EntTarget world m = SystemT world m [Ent]
- allEnts :: Monad m => EntTarget world m
- someEnts :: Monad m => [Ent] -> EntTarget world m
- anEnt :: Monad m => Ent -> EntTarget world m
- data QueryT w m a
- runQueryT :: (HasWorld world m, Monad m) => Ent -> QueryT world m a -> SystemT world m (Maybe a)
- query :: Monad m => (world FieldOf -> Maybe a) -> QueryT world m a
- with :: Monad m => (world FieldOf -> Maybe a) -> QueryT world m ()
- without :: Monad m => (world FieldOf -> Maybe a) -> QueryT world m ()
- queryEnt :: Monad m => QueryT world m Ent
- queryMaybe :: Monad m => (world FieldOf -> Maybe a) -> QueryT world m (Maybe a)
- queryFlag :: Monad m => (world FieldOf -> Maybe ()) -> QueryT world m Bool
- queryDef :: Monad m => z -> (world FieldOf -> Maybe z) -> QueryT world m z
- data Update a
- maybeToUpdate :: Maybe a -> Update a
- surgery :: (Monad (t m), Monad m, StorageSurgeon t m world) => (forall x. t m x -> m (x, b)) -> SystemT world (t m) a -> SystemT world m (b, a)
- data Ent
- data VTable m a = VTable {}
- class Generic a
Defining 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 aMaybe
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 monadicvget
andvset
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 defStorage.
data ComponentType Source #
Data kind used to parameterize the fields of the ECS record.
type family Component (s :: StorageType) (c :: ComponentType) (a :: Type) :: Type where ... Source #
A type family to be used in your ECS recrod.
Storage
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
newEntity
{ 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'
defStorage :: HasWorld world m => world (WorldOf m) Source #
The default world, which contains only empty containers.
data StorageType Source #
Data kind used to parameterize the ECS record.
The SystemT monad
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.
A monad transformer over an ECS given a world w
.
MonadReader r m => MonadReader r (SystemT w m) Source # | |
MonadState s m => MonadState s (SystemT w m) Source # | |
MonadWriter ww m => MonadWriter ww (SystemT w m) Source # | |
MonadTrans (SystemT w) Source # | |
Monad m => Monad (SystemT w m) Source # | |
Functor m => Functor (SystemT w m) Source # | |
Monad m => Applicative (SystemT w m) Source # | |
MonadIO m => MonadIO (SystemT w m) Source # | |
yieldSystemT :: Monad m => SystemState world m -> SystemT world m a -> m (SystemState world m, a) Source #
Provides a resumable SystemT
. This is a pretty big hack until I come up
with a better formalization for everything.
Working with SystemT
createEntity :: (HasWorld world m, Monad m) => world FieldOf -> SystemT world m Ent Source #
Create a new entity.
getEntity :: (HasWorld world m, Monad m) => Ent -> SystemT world m (world FieldOf) Source #
Fetches an entity from the world given its Ent
.
setEntity :: HasWorld world m => Ent -> world SetterOf -> SystemT world m () Source #
Updates an Ent
in the world given its setter.
SystemT traversals
SystemT
provides functionality for traversing over entities
that match a EntTarget
and a 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
. For example, given a world:Update
a
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.
emap :: (HasWorld world m, Monad m) => EntTarget world m -> QueryT world m (world SetterOf) -> SystemT world m () Source #
Map a QueryT
transformation over all entites that match it.
efor :: (HasWorld world m, Monad m) => EntTarget world m -> QueryT world m a -> SystemT world m [a] Source #
Collect the results of a monadic computation over every entity matching
a QueryT
.
eover :: (HasWorld world m, Monad m) => EntTarget world m -> QueryT world m (a, world SetterOf) -> SystemT world m [a] Source #
unchanged :: HasWorld' world => world SetterOf Source #
The default setter, which keeps all components with their previous value.
delEntity :: HasWorld' world => world SetterOf Source #
A setter which will delete the entity if its QueryT
matches.
Entity targets
The QueryT monad
The QueryT
transformer provides an environment for querying
components of an entity. Due to its MonadPlus
instance,
failing queries will prevent further computations in the monad from running.
A computation to run over a particular entity.
MonadReader r m => MonadReader r (QueryT w m) Source # | |
MonadState s m => MonadState s (QueryT w m) Source # | |
MonadWriter ww m => MonadWriter ww (QueryT w m) Source # | |
MonadTrans (QueryT w) Source # | |
Monad m => Monad (QueryT w m) Source # | |
Functor m => Functor (QueryT w m) Source # | |
Monad m => Applicative (QueryT w m) Source # | |
MonadIO m => MonadIO (QueryT w m) Source # | |
Monad m => Alternative (QueryT w m) Source # | |
Monad m => MonadPlus (QueryT w m) Source # | |
runQueryT :: (HasWorld world m, Monad m) => Ent -> QueryT world m a -> SystemT world m (Maybe a) Source #
Queries
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 = doemap
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.
query :: Monad m => (world FieldOf -> Maybe a) -> QueryT world m a Source #
Get the value of a component, failing the QueryT
if it isn't present.
with :: Monad m => (world FieldOf -> Maybe a) -> QueryT world m () Source #
Only evaluate this QueryT
for entities which have the given component.
without :: Monad m => (world FieldOf -> Maybe a) -> QueryT world m () Source #
Only evaluate this QueryT
for entities which do not have the given
component.
queryMaybe :: Monad m => (world FieldOf -> Maybe a) -> QueryT world m (Maybe a) Source #
Attempt to get the value of a component.
queryFlag :: Monad m => (world FieldOf -> Maybe ()) -> QueryT world m Bool Source #
Query a flag as a Bool
.
queryDef :: Monad m => z -> (world FieldOf -> Maybe z) -> QueryT world m z Source #
Perform a query with a default.
Updates
Describes how we can change an a
.
Functor Update Source # | |
Foldable Update Source # | |
Traversable Update Source # | |
GDefault False (K1 * i (Update c)) Source # | |
GDefault True (K1 * i (Update c)) Source # | |
Applicative m => GSetEntity m (K1 * i (Update a)) (K1 * i' (IntMap a)) Source # | |
Monad m => GSetEntity m (K1 * i (Update a)) (K1 * i' (VTable m a)) Source # | |
Applicative m => GSetEntity m (K1 * i (Update a)) (K1 * i' (Maybe (Int, a))) Source # | |
Eq a => Eq (Update a) Source # | |
Ord a => Ord (Update a) Source # | |
Read a => Read (Update a) Source # | |
Show a => Show (Update a) Source # | |
GConvertSetter (K1 * i (Maybe a)) (K1 * i' (Update a)) Source # | |
GConvertSetter (K1 * i a) (K1 * i' (Update a)) Source # | |
Introducing effects
surgery :: (Monad (t m), Monad m, StorageSurgeon t m world) => (forall x. t m x -> m (x, b)) -> SystemT world (t m) a -> SystemT world m (b, a) Source #
Run a monad transformer underneath a SystemT
.
Due to the recursive interactions between SystemT
and QueryT
, we're
often unable to put a temporary monad transformer on the top of the stack.
As a result, often surgery
is our ony means of introducting ephemeral
effects.
draw ::SystemT
World IO [Graphics] draw = fmap fst .surgery
runWriterT $ for_ thingsToRender $ \thingy -> tell [thingy]
Miscellany
A collection of methods necessary to dispatch reads and writes to
a Virtual
component.
(MonadTrans t, Functor (t m), Monad m) => GHoistWorld t m (K1 * i (VTable m a)) (K1 * i' (VTable (t m) a)) Source # | |
Monad m => GSetEntity m (K1 * i (Update a)) (K1 * i' (VTable m a)) Source # | |
Monad m => GGetEntity m (K1 * i (VTable m a)) (K1 * i' (Maybe a)) Source # | |
(Applicative m, KnownSymbol sym) => GDefault keep (M1 * S (MetaSel (Just Symbol sym) x y z) (K1 * i (VTable m a))) Source # | |
GGraft (K1 * i (VTable m a)) (K1 * i' (VTable (t m) a)) Source # | |
Re-exports
Representable types of kind *. This class is derivable in GHC with the DeriveGeneric flag on.
Generic Bool | |
Generic Ordering | |
Generic () | |
Generic Fixity | |
Generic Associativity | |
Generic SourceUnpackedness | |
Generic SourceStrictness | |
Generic DecidedStrictness | |
Generic [a] | |
Generic (Maybe a) | |
Generic (Par1 p) | |
Generic (ZipList a) | |
Generic (Identity a) | |
Generic (Either a b) | |
Generic (V1 k p) | |
Generic (U1 k p) | |
Generic (a, b) | |
Generic (WrappedMonad m a) | |
Generic (Proxy k t) | |
Generic (Rec1 k f p) | |
Generic (URec k (Ptr ()) p) | |
Generic (URec k Char p) | |
Generic (URec k Double p) | |
Generic (URec k Float p) | |
Generic (URec k Int p) | |
Generic (URec k Word p) | |
Generic (a, b, c) | |
Generic (WrappedArrow a b c) | |
Generic (K1 k i c p) | |
Generic ((:+:) k f g p) | |
Generic ((:*:) k f g p) | |
Generic (a, b, c, d) | |
Generic (M1 k i c f p) | |
Generic ((:.:) k2 k1 f g p) | |
Generic (a, b, c, d, e) | |
Generic (a, b, c, d, e, f) | |
Generic (a, b, c, d, e, f, g) | |