packages feed

aztecs-0.6.0: src/Aztecs/ECS/Schedule.hs

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}

module Aztecs.ECS.Schedule
  ( -- * Schedules
    Schedule (..),
    reader,
    system,
    forever,
    forever_,
    access,
    task,
    runSchedule,
    runSchedule_,
  )
where

import Aztecs.ECS.Access (AccessT (..), runAccessT)
import Aztecs.ECS.System (System (..))
import Aztecs.ECS.System.Dynamic (DynamicSystem (..))
import Aztecs.ECS.System.Dynamic.Reader (DynamicReaderSystem (..))
import Aztecs.ECS.System.Reader (ReaderSystem (..))
import qualified Aztecs.ECS.View as V
import Aztecs.ECS.World (World (..))
import qualified Aztecs.ECS.World as W
import Aztecs.ECS.World.Components (Components)
import Control.Arrow (Arrow (..))
import Control.Category (Category (..))
import Control.DeepSeq
import Control.Exception (evaluate)
import Control.Monad ((>=>))
import Control.Monad.Identity (Identity (runIdentity))
import Control.Monad.State (MonadState (..))
import Control.Monad.Trans (MonadTrans (..))
import Data.Functor (void)

newtype Schedule m i o = Schedule {runSchedule' :: Components -> (i -> AccessT m o, Components)}
  deriving (Functor)

instance (Monad m) => Category (Schedule m) where
  id = Schedule $ \cs -> (return, cs)
  Schedule f . Schedule g = Schedule $ \cs ->
    let (g', cs') = g cs
        (f', cs'') = f cs'
     in (g' >=> f', cs'')

instance Arrow (Schedule IO) where
  arr f = Schedule $ \cs -> (return Prelude.. f, cs)
  first (Schedule f) = Schedule $ \cs ->
    let (g, cs') = f cs in (\(b, d) -> (,) <$> g b <*> return d, cs')

runSchedule :: (Monad m) => Schedule m i o -> World -> i -> m (o, World)
runSchedule s w i = do
  let (f, cs) = runSchedule' s (components w)
  (o, w') <- runAccessT (f i) w {components = cs}
  return (o, w')

runSchedule_ :: (Monad m) => Schedule m () () -> m ()
runSchedule_ s = void (runSchedule s W.empty ())

reader :: (Monad m) => ReaderSystem i o -> Schedule m i o
reader t = Schedule $ \cs ->
  let (dynT, _, cs') = runReaderSystem t cs
      go i = AccessT $ do
        w <- get
        return $ runReaderSystemDyn dynT w i
   in (go, cs')

system :: (Monad m) => System i o -> Schedule m i o
system t = Schedule $ \cs ->
  let (dynT, _, cs') = runSystem t cs
      go i = AccessT $ do
        w <- get
        let (o, v, a) = runSystemDyn dynT w i
            ((), w') = runIdentity $ runAccessT a $ V.unview v w
        put w'
        return o
   in (go, cs')

access :: (Monad m) => (i -> AccessT m o) -> Schedule m i o
access f = Schedule $ \cs -> (f, cs)

task :: (Monad m) => (i -> m o) -> Schedule m i o
task f = Schedule $ \cs -> (AccessT Prelude.. lift Prelude.. f, cs)

forever :: Schedule IO i o -> (o -> IO ()) -> Schedule IO i ()
forever s f = Schedule $ \cs ->
  let (g, cs') = runSchedule' s cs
      go i = AccessT $ do
        w <- get
        let loop wAcc = do
              (o, wAcc') <- lift $ runAccessT (g i) wAcc
              lift $ evaluate $ rnf wAcc'
              lift $ f o
              loop wAcc'
        loop w
   in (go, cs')

forever_ :: Schedule IO i o -> Schedule IO i ()
forever_ s = forever s (const $ pure ())