packages feed

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_ #-}