| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Data.Ecstasy
Contents
Description
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 Components
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
Fieldis a "normal" component and corresponds exactly to aMaybevalue. Unique- A
Uniquecomponent 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
Virtualcomponent is defined in terms of monadicvgetandvsetactions, 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.
Instances
| 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.
Instances
| 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 ::SystemWorld () stepTime = doemapallEnts$ do pos <-queryposition vel <-queryvelocity 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.
Constructors
| Keep | Keep the current value. |
| Unset | Delete the current value if it exists. |
| Set !a | Set the current value. |
Instances
| 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 ::SystemTWorld IO [Graphics] draw = fmap fst .surgeryrunWriterT $ for_ thingsToRender $ \thingy -> tell [thingy]
Miscellany
A collection of methods necessary to dispatch reads and writes to
a Virtual component.
Constructors
| VTable | |
Instances
| (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.
Instances
| 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) | |