packages feed

aztecs-0.1.0.1: src/Data/Aztecs/Schedule.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Data.Aztecs.Schedule
  ( Node (..),
    Schedule (..),
    ScheduleNode (..),
    runSchedule,
    Startup,
    Update,
    Constraint (..),
    before,
    after,
    Scheduler (..),
    schedule,
    SchedulerGraph (..),
    buildScheduler,
    runSchedulerGraph,
    runScheduler,
  )
where

import Control.Concurrent.Async (mapConcurrently)
import Control.Monad.State (StateT (runStateT))
import Data.Aztecs.Command
import Data.Aztecs.System
import Data.Aztecs.World
  ( World,
    newWorld,
    union,
  )
import Data.Foldable (foldrM)
import Data.Functor ((<&>))
import Data.List (groupBy, sortBy)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Proxy
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Typeable
import Prelude hiding (all, read)

data Constraint = Before TypeRep | After TypeRep

before :: forall m a. (System m a) => Constraint
before = Before $ typeOf (Proxy :: Proxy a)

after :: forall m a. (System m a) => Constraint
after = After $ typeOf (Proxy :: Proxy a)

data Node m where
  Node :: (System m a) => Proxy a -> Cache -> Node m

data ScheduleNode m = ScheduleNode (Node m) [Constraint]

data Schedule m = Schedule (Map TypeRep (ScheduleNode m))

instance Semigroup (Schedule m) where
  Schedule a <> Schedule b = Schedule $ a <> b

instance Monoid (Schedule m) where
  mempty = Schedule mempty

data GraphNode m = GraphNode (Node m) (Set TypeRep) (Set TypeRep)

build :: (Monad m) => Schedule m -> [[GraphNode m]]
build (Schedule s) =
  let graph =
        fmap
          ( \(ScheduleNode node constraints) ->
              let (deps, befores) =
                    foldr
                      ( \c (depAcc, afterAcc) -> case c of
                          Before i -> (depAcc, [i])
                          After i -> (depAcc ++ [i], afterAcc)
                      )
                      ([], [])
                      constraints
               in GraphNode node (Set.fromList deps) (Set.fromList befores)
          )
          s
      graph' =
        foldr
          ( \(GraphNode _ _ befores) acc ->
              foldr
                ( \i acc' ->
                    Map.adjust
                      ( \(GraphNode n deps bs) ->
                          GraphNode n (Set.singleton i <> deps) bs
                      )
                      i
                      acc'
                )
                acc
                befores
          )
          graph
          graph
      nodes =
        sortBy
          ( \(GraphNode _ deps _) (GraphNode _ deps' _) ->
              compare (length deps') (length deps)
          )
          (Map.elems graph')
   in groupBy
        ( \(GraphNode a deps aBefores) (GraphNode b deps' bBefores) ->
            (length deps == length deps')
            -- TODO || hasConflict (GraphNode a deps aBefores) (GraphNode b deps' bBefores)
        )
        nodes

runNode :: Node IO -> World -> IO (Node IO, Maybe (Access IO ()), [Command IO ()], World)
runNode (Node p cache) w =
  runSystemProxy p cache w <&> (\(next, a', cmds, w') -> (Node p a', next, cmds, w'))

runSystemProxy :: forall a. (System IO a) => Proxy a -> Cache -> World -> IO (Maybe (Access IO ()), Cache, [Command IO ()], World)
runSystemProxy _ = runSystem' @a

-- | Run a `Command`, returning any temporary `Entity`s and the updated `World`.
runCommand :: Command IO () -> World -> IO (World)
runCommand (Command cmd) w = snd <$> runStateT cmd w

runSchedule :: [[GraphNode IO]] -> World -> IO ([[GraphNode IO]], World)
runSchedule nodes w =
  foldrM
    ( \nodeGroup (nodeAcc, w') -> do
        results <-
          mapConcurrently
            ( \(GraphNode n as bs) -> do
                (n', next, cmds, w'') <- runNode n w
                return ((next, (GraphNode n' as bs)), cmds, w'')
            )
            nodeGroup
        let (nexts, cmdLists, worlds) =
              foldr
                ( \(n, b, c) (ns, bs, cs) ->
                    (n : ns, b : bs, c : cs)
                )
                ([], [], [])
                results
            finalWorld = foldr union w' worlds
            (cmds, w'') = (concat cmdLists, finalWorld)

        (w''', nodes', cmds') <-
          foldrM
            ( \(a, (GraphNode (Node p cache) as bs)) (wAcc, nodeAcc', cmdAcc) -> case a of
                Just a' -> do
                  ((), wAcc', cache', cmdAcc') <- runAccess' a' wAcc cache
                  return (wAcc', (GraphNode (Node p cache') as bs) : nodeAcc', cmdAcc' ++ cmdAcc)
                Nothing -> return (w, (GraphNode (Node p cache) as bs) : nodeAcc', cmdAcc)
            )
            (w'', [], [])
            nexts

        w'''' <- foldrM (\cmd wAcc -> runCommand cmd wAcc) w''' (cmds ++ cmds')
        return (nodes' : nodeAcc, w'''')
    )
    ([], w)
    nodes

newtype Scheduler m = Scheduler (Map TypeRep (Schedule m))
  deriving (Monoid)

instance Semigroup (Scheduler m) where
  Scheduler a <> Scheduler b = Scheduler $ Map.unionWith (<>) a b

data Startup

data Update

schedule :: forall l m s. (Typeable l, System m s) => [Constraint] -> Scheduler m
schedule cs =
  Scheduler $
    Map.singleton
      (typeOf (Proxy :: Proxy l))
      ( Schedule $
          Map.singleton
            (typeOf (Proxy :: Proxy s))
            (ScheduleNode (Node (Proxy :: Proxy s) mempty) cs)
      )

newtype SchedulerGraph m = SchedulerGraph (Map TypeRep [[GraphNode m]])

buildScheduler :: (Monad m) => Scheduler m -> SchedulerGraph m
buildScheduler (Scheduler s) = SchedulerGraph $ fmap build s

runSchedulerGraph :: forall l. (Typeable l) => SchedulerGraph IO -> World -> IO (SchedulerGraph IO, World)
runSchedulerGraph (SchedulerGraph g) w = case Map.lookup (typeOf (Proxy :: Proxy l)) g of
  Just s -> do
    (nodes, w') <- runSchedule s w
    let g' = Map.insert (typeOf (Proxy :: Proxy l)) nodes g
    return (SchedulerGraph g', w')
  Nothing -> return (SchedulerGraph g, w)

runScheduler :: Scheduler IO -> IO ()
runScheduler s = do
  let g = buildScheduler s
  (g', w) <- runSchedulerGraph @Startup g newWorld
  let go gAcc wAcc = do
        (gAcc', wAcc') <- runSchedulerGraph @Update gAcc wAcc
        go gAcc' wAcc'
  go g' w