aztecs-0.3.0.0: src/Data/Aztecs/Scheduler.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Data.Aztecs.Scheduler where
import Data.Aztecs.Access (Access, runAccess)
import Data.Aztecs.System (System (..), Task (runTask))
import Data.Aztecs.World (World (..))
import qualified Data.Aztecs.World as W
import Data.Aztecs.World.Components (ComponentID)
import Data.Data (Proxy (..), TypeRep, Typeable, typeOf)
import Data.Foldable (foldrM)
import Data.List (sortBy)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
data Node m = Node
{ nodeTask :: Task m () (),
nodeBefore :: Set TypeRep,
nodeAfter :: Set TypeRep
}
instance Show (Node m) where
show n =
"Node "
++ "{ nodeBefore = "
++ show (nodeBefore n)
++ ", nodeAfter = "
++ show (nodeAfter n)
++ " }"
data Constraint = Constraint {constraintKind :: ConstraintKind, constraintId :: TypeRep}
deriving (Eq, Show)
data ConstraintKind = Before | After deriving (Eq, Show)
before :: forall m a. (System m a) => Constraint
before = Constraint Before (typeOf (Proxy @a))
after :: forall m a. (System m a) => Constraint
after = Constraint After (typeOf (Proxy @a))
data Startup
data Update
newtype Scheduler m = Scheduler {unScheduler :: Map TypeRep (Stage m)}
deriving (Show, Monoid)
instance Semigroup (Scheduler m) where
s1 <> s2 = Scheduler (Map.unionWith (<>) (unScheduler s1) (unScheduler s2))
schedule :: forall m l a. (Typeable l, System m a) => [Constraint] -> Scheduler m
schedule cs =
let (bs, as) =
foldr
( \c (bAcc, aAcc) -> case constraintKind c of
Before -> (constraintId c : bAcc, aAcc)
After -> (bAcc, constraintId c : aAcc)
)
([], [])
cs
in Scheduler
( Map.singleton
(typeOf (Proxy @l))
( Stage
( Map.singleton
(typeOf (Proxy @a))
( Node
{ nodeTask = task @m @a,
nodeBefore = Set.fromList bs,
nodeAfter = Set.fromList as
}
)
)
)
)
newtype Stage m = Stage {unStage :: Map TypeRep (Node m)}
deriving (Show, Semigroup, Monoid)
data ScheduleBuilderNode m = ScheduleBuilderNode
{ scheduleBuilderNodeTask :: Task m () (),
scheduleBuilderNodeBefore :: Set TypeRep
}
instance Show (ScheduleBuilderNode m) where
show n = "Node " ++ "{ scheduleBuilderNodeBefore = " ++ show (scheduleBuilderNodeBefore n) ++ " }"
newtype ScheduleBuilderStage m = ScheduleBuilderStage {unScheduleBuilderStage :: Map TypeRep (ScheduleBuilderNode m)}
deriving (Show)
data Error = MissingError TypeRep | AmbiguousError TypeRep
deriving (Show)
stageBuilder :: Stage m -> (ScheduleBuilderStage m, [Error])
stageBuilder s =
let go' nodeId (acc, errorAcc) = case Map.lookup nodeId acc of
Just n ->
let acc' = Map.insert nodeId n {nodeBefore = Set.insert nodeId (nodeBefore n)} acc
errorAcc' =
if Set.member nodeId (nodeBefore n)
then AmbiguousError nodeId : errorAcc
else errorAcc
in (acc', errorAcc')
Nothing -> (acc, MissingError nodeId : errorAcc)
go node (acc, errorAcc) = foldr go' (acc, errorAcc) (nodeAfter node)
(nodes, errors) = foldr go (unStage s, []) (unStage s)
in ( ScheduleBuilderStage $
fmap
( \n ->
ScheduleBuilderNode
{ scheduleBuilderNodeTask = nodeTask n,
scheduleBuilderNodeBefore = nodeBefore n
}
)
nodes,
errors
)
data ScheduleGraphNode m = ScheduleGraphNode
{ scheduleGraphNodeIds :: [Set ComponentID],
scheduleGraphNodeBefore :: Set TypeRep,
runScheduleGraphNode :: World -> m (World -> World, Access m ())
}
instance Show (ScheduleGraphNode m) where
show n =
"Node "
++ "{ scheduleGraphNodeIds = "
++ show (scheduleGraphNodeIds n)
++ ", scheduleGraphNodeBefore = "
++ show (scheduleGraphNodeBefore n)
++ " }"
newtype ScheduleGraphStage m = ScheduleGraphStage {unScheduleGraphStage :: Map TypeRep (ScheduleGraphNode m)}
deriving (Show)
buildGraphStage :: (Functor m) => ScheduleBuilderStage m -> World -> (ScheduleGraphStage m, World)
buildGraphStage s w =
let go (nodeId, node) (acc, wAcc) =
let (cs, cIds, f) = runTask (scheduleBuilderNodeTask node) (components wAcc)
in ( Map.insert
nodeId
ScheduleGraphNode
{ scheduleGraphNodeIds = cIds,
scheduleGraphNodeBefore = scheduleBuilderNodeBefore node,
runScheduleGraphNode = fmap (\(_, f', a) -> (f', a)) . f ()
}
acc,
wAcc {components = cs}
)
(nodes, w') = foldr go (Map.empty, w) (Map.toList $ unScheduleBuilderStage s)
in (initGraphStage $ ScheduleGraphStage nodes, w')
initGraphStage :: ScheduleGraphStage m -> ScheduleGraphStage m
initGraphStage s =
let go (nodeId, node) nodeAcc =
let go' nodeId' (beforeAcc, nodeAcc') = case Map.lookup nodeId' nodeAcc of
Just node' -> (scheduleGraphNodeBefore node' <> beforeAcc, nodeAcc')
Nothing -> foldr go' (beforeAcc, nodeAcc') (Set.toList $ scheduleGraphNodeBefore (unScheduleGraphStage s Map.! nodeId))
(befores, nodeAcc'') = foldr go' (Set.empty, nodeAcc) (Set.toList $ scheduleGraphNodeBefore node)
in Map.insert nodeId node {scheduleGraphNodeBefore = befores} nodeAcc''
in ScheduleGraphStage $ foldr go Map.empty (Map.toList $ unScheduleGraphStage s)
buildStage :: ScheduleGraphStage m -> [ScheduleNode m]
buildStage s =
map (\n -> ScheduleNode {runScheduleNode = runScheduleGraphNode n})
. sortBy (\a b -> compare (length $ scheduleGraphNodeIds a) (length $ scheduleGraphNodeIds b))
$ Map.elems (unScheduleGraphStage s)
newtype ScheduleBuilder m = ScheduleBuilder {unScheduleBuilder :: Map TypeRep (ScheduleBuilderStage m)}
deriving (Show)
builder :: Scheduler m -> (ScheduleBuilder m, [Error])
builder (Scheduler s) =
let (stages, errors) = unzip $ fmap stageBuilder (Map.elems s)
in (ScheduleBuilder $ Map.fromList $ zip (Map.keys s) stages, concat errors)
newtype ScheduleGraph m = ScheduleGraph {unScheduleGraph :: Map TypeRep (ScheduleGraphStage m)}
deriving (Show)
buildGraph :: (Functor m) => Scheduler m -> World -> (ScheduleGraph m, World, [Error])
buildGraph s w =
let (sb, errors) = builder s
(s', w') = buildGraph' sb w
in (s', w', errors)
buildGraph' :: (Functor m) => ScheduleBuilder m -> World -> (ScheduleGraph m, World)
buildGraph' (ScheduleBuilder s) w =
let go (stageId, stage) (acc, wAcc) =
let (stage', wAcc') = buildGraphStage stage wAcc
in (Map.insert stageId stage' acc, wAcc')
(stages, w') = foldr go (Map.empty, w) (Map.toList s)
in (ScheduleGraph stages, w')
newtype ScheduleNode m = ScheduleNode {runScheduleNode :: World -> m (World -> World, Access m ())}
newtype Schedule m = Schedule {unSchedule :: Map TypeRep [ScheduleNode m]}
build :: (Functor m) => Scheduler m -> World -> (Schedule m, World, [Error])
build s w = let (s', w', errors) = buildGraph s w in (build' s', w', errors)
build' :: (Functor m) => ScheduleGraph m -> Schedule m
build' g = Schedule $ fmap buildStage (unScheduleGraph g)
runStage :: forall l m. (Typeable l, Monad m) => Schedule m -> World -> m World
runStage s w = case Map.lookup (typeOf (Proxy @l)) (unSchedule s) of
Just stage ->
let go node wAcc = do
(f, access) <- runScheduleNode node wAcc
(_, wAcc') <- runAccess access (f wAcc)
return wAcc'
in foldrM go w stage
Nothing -> return w
runWorld :: (Monad m) => Scheduler m -> World -> m ()
runWorld s w = do
let (s', w', _) = build s w
w'' <- runStage @Startup s' w'
let go wAcc = do
wAcc' <- runStage @Update s' wAcc
go wAcc'
go w''
run :: (Monad m) => Scheduler m -> m ()
run s = runWorld s W.empty