apecs-effectful-0.1.0.0: src/Apecs/Effectful.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
-----------------------------------------------------------------------------
-- |
-- Module : Apecs.Effectful
-- Copyright : (c) Michael Szvetits, 2023
-- License : BSD-3-Clause (see the file LICENSE)
-- Maintainer : typedbyte@qualified.name
-- Stability : stable
-- Portability : portable
--
-- Adaptation of the apecs library for the effectful ecosystem.
-----------------------------------------------------------------------------
module Apecs.Effectful
( -- * Effectful Adaptation
ECS
, runECS
, runGC
, Get
, Set
, Destroy
, Members
, newEntity
, newEntity_
, get
, tryGet
, set
, ($=)
, destroy
, exists
, modify
, ($~)
, cmap
, cmapM
, cmapM_
, cfold
, cfoldM
, cfoldM_
-- * Re-exports
, Entity(..)
, EntityCounter
, Not(..)
, Component(..)
, Has(..)
, Cache
, Global
, Map
, Unique
, SystemT(SystemT)
, makeWorld
, makeWorldAndComponents
, global
, explInit
, asks
) where
-- apecs
import Apecs qualified as Apecs
import Apecs.Core qualified as Apecs
import Apecs hiding
( Destroy
, Get
, Members
, Set
, cfold
, cfoldM
, cfoldM_
, cmap
, cmapM
, cmapM_
, exists
, destroy
, get
, modify
, newEntity
, newEntity_
, runGC
, set
, ($=)
, ($~)
)
-- base
import Data.Kind (Type)
-- effectful-core
import Effectful (Eff, Dispatch(Static), DispatchOf, Effect, IOE, (:>))
import Effectful.Dispatch.Static (SideEffects(..), StaticRep, evalStaticRep, getStaticRep, unsafeEff_)
-- vector
import Data.Vector.Unboxed qualified as U
-- | Provide the ability to query and manipulate worlds of type @w@.
data ECS (w :: Type) :: Effect
type instance DispatchOf (ECS w) = Static WithSideEffects
newtype instance StaticRep (ECS w) = ECS w
-- | Indicates that world @w@ has writeable components of type @c@.
type Set w c = Apecs.Set w IO c
-- | Indicates that world @w@ has readable components of type @c@.
type Get w c = Apecs.Get w IO c
-- | Indicates that world @w@ has deletable components of type @c@.
type Destroy w c = Apecs.Destroy w IO c
-- | Indicates that world @w@ contains components of type @c@.
type Members w c = Apecs.Members w IO c
-- | Run the t'ECS' effect using the world initialization function provided by
-- 'makeWorld' or 'makeWorldAndComponents'.
runECS :: IOE :> es => IO w -> Eff (ECS w : es) a -> Eff es a
runECS worldInit m = do
w <- unsafeEff_ worldInit
evalStaticRep (ECS w) m
toEff :: ECS w :> es => System w a -> Eff es a
toEff system = do
ECS w <- getStaticRep
unsafeEff_ $ runSystem system w
{-# INLINE toEff #-}
-- | Explicitly invoke the garbage collector.
runGC :: forall w es. ECS w :> es => Eff es ()
runGC = toEff @w Apecs.runGC
{-# INLINE runGC #-}
-- | Writes the given components to a new entity.
newEntity :: forall w c es. (ECS w :> es, Set w c, Get w EntityCounter) => c -> Eff es Entity
newEntity = toEff @w . Apecs.newEntity
{-# INLINE newEntity #-}
-- | Writes the given components to a new entity.
newEntity_ :: forall w c es. (ECS w :> es, Set w c, Get w EntityCounter) => c -> Eff es ()
newEntity_ = toEff @w . Apecs.newEntity_
{-# INLINE newEntity_ #-}
-- | Read a component from an entity.
get :: forall w c es. (ECS w :> es, Get w c) => Entity -> Eff es c
get = toEff @w . Apecs.get
{-# INLINE get #-}
-- | Read a component from an entity, if available.
tryGet :: forall w c es. (ECS w :> es, Get w c) => Entity -> Eff es (Maybe c)
tryGet entity = do
existing <- exists @c @w entity
if existing then do
c <- get @w entity
pure (Just c)
else
pure Nothing
{-# INLINE tryGet #-}
-- | Writes a component to a given entity.
set :: forall w c es. (ECS w :> es, Set w c) => Entity -> c -> Eff es ()
set entity = toEff @w . Apecs.set entity
{-# INLINE set #-}
-- | Writes a component to a given entity.
infixr 2 $=
($=) :: forall w c es. (ECS w :> es, Set w c) => Entity -> c -> Eff es ()
($=) entity = toEff @w . (Apecs.$=) entity
{-# INLINE ($=) #-}
-- | Destroys component @c@ for the given entity.
destroy :: forall c w es. (ECS w :> es, Destroy w c) => Entity -> Eff es ()
destroy entity = toEff @w $ Apecs.destroy entity (Proxy :: Proxy c)
{-# INLINE destroy #-}
-- | Returns whether the given entity has component @c@.
exists :: forall c w es. (ECS w :> es, Get w c) => Entity -> Eff es Bool
exists entity = toEff @w $ Apecs.exists entity (Proxy :: Proxy c)
{-# INLINE exists #-}
-- | Read a component and writes a new component of an entity.
modify :: forall w cx cy es. (ECS w :> es, Get w cx, Set w cy) => Entity -> (cx -> cy) -> Eff es ()
modify entity = toEff @w . Apecs.modify entity
{-# INLINE modify #-}
-- | Read the component @cx@ and writes the component @cy@ of an entity.
infixr 2 $~
($~) :: forall w cx cy es. (ECS w :> es, Get w cx, Set w cy) => Entity -> (cx -> cy) -> Eff es ()
($~) entity = toEff @w . (Apecs.$~) entity
{-# INLINE ($~) #-}
-- | Read the component @cx@ and writes the component @cy@ of all entities.
cmap :: forall w cx cy es. (ECS w :> es, Get w cx, Members w cx, Set w cy) => (cx -> cy) -> Eff es ()
cmap = toEff @w . Apecs.cmap
{-# INLINE cmap #-}
-- | Monadic variant of 'cmap'.
cmapM :: forall w cx cy es. (ECS w :> es, Get w cx, Set w cy, Members w cx) => (cx -> Eff es cy) -> Eff es ()
cmapM f = do
sx <- toEff @w (Apecs.getStore @w @IO @cx)
sy <- toEff @w (Apecs.getStore @w @IO @cy)
sl <- unsafeEff_ $ Apecs.explMembers sx
U.forM_ sl $ \e -> do
x <- unsafeEff_ $ Apecs.explGet sx e
y <- f x
unsafeEff_ $ Apecs.explSet sy e y
{-# INLINE cmapM #-}
-- | Monadic variant of 'cmap', ignoring the result of the applied function.
cmapM_ :: forall w c es. (ECS w :> es, Get w c, Members w c) => (c -> Eff es ()) -> Eff es ()
cmapM_ f = do
s <- toEff @w (Apecs.getStore @w @IO @c)
l <- unsafeEff_ $ Apecs.explMembers s
U.forM_ l $ \e ->
unsafeEff_ (Apecs.explGet s e) >>= f
{-# INLINE cmapM_ #-}
-- | Fold over the components @c@ of the game world.
cfold :: forall w c a es. (ECS w :> es, Members w c, Get w c) => (a -> c -> a) -> a -> Eff es a
cfold f = toEff @w . Apecs.cfold f
{-# INLINE cfold #-}
-- | Monadic variant of 'cfold'.
cfoldM :: forall w c a es. (ECS w :> es, Members w c, Get w c) => (a -> c -> Eff es a) -> a -> Eff es a
cfoldM f start = do
s <- toEff @w (Apecs.getStore @w @IO @c)
l <- unsafeEff_ $ Apecs.explMembers s
U.foldM' (\a e -> unsafeEff_ (Apecs.explGet s e) >>= f a) start l
{-# INLINE cfoldM #-}
-- | Monadic variant of 'cfold', ignoring the result.
cfoldM_ :: forall w c a es. (ECS w :> es, Members w c, Get w c) => (a -> c -> Eff es a) -> a -> Eff es ()
cfoldM_ f start = do
s <- toEff @w (Apecs.getStore @w @IO @c)
l <- unsafeEff_ $ Apecs.explMembers s
U.foldM'_ (\a e -> unsafeEff_ (Apecs.explGet s e) >>= f a) start l
{-# INLINE cfoldM_ #-}