aztecs 0.1.0.0 → 0.1.0.1
raw patch · 13 files changed
+1085/−16 lines, 13 filesdep +asyncdep +containersdep +mtl
Dependencies added: async, containers, mtl
Files
- CHANGELOG.md +0/−5
- aztecs.cabal +12/−11
- src/Data/Aztecs.hs +33/−0
- src/Data/Aztecs/Command.hs +43/−0
- src/Data/Aztecs/Core.hs +5/−0
- src/Data/Aztecs/Query.hs +134/−0
- src/Data/Aztecs/Schedule.hs +207/−0
- src/Data/Aztecs/Storage.hs +65/−0
- src/Data/Aztecs/System.hs +218/−0
- src/Data/Aztecs/Task.hs +29/−0
- src/Data/Aztecs/World.hs +71/−0
- src/Data/Aztecs/World/Archetypes.hs +181/−0
- src/Data/Aztecs/World/Components.hs +87/−0
− CHANGELOG.md
@@ -1,5 +0,0 @@-# Revision history for aztecs--## 0.1.0.0 -- YYYY-mm-dd--* First version. Released on an unsuspecting world.
aztecs.cabal view
@@ -1,14 +1,15 @@-cabal-version: 3.0-name: aztecs-version: 0.1.0.0-license: BSD-3-Clause-license-file: LICENSE-maintainer: matt@hunzinger.me-author: Matt Hunzinger-build-type: Simple-extra-doc-files: CHANGELOG.md-synopsis: A type-safe and friendly ECS for Haskell -description: A type-safe and friendly ECS for Haskell +cabal-version: 3.0+name: aztecs+version: 0.1.0.1+license: BSD-3-Clause+license-file: LICENSE+maintainer: matt@hunzinger.me+author: Matt Hunzinger+synopsis: A type-safe and friendly ECS for Haskell +description: A type-safe and friendly ECS for Haskell +category: Game Engine++library exposed-modules: Data.Aztecs Data.Aztecs.Core
+ src/Data/Aztecs.hs view
@@ -0,0 +1,33 @@+module Data.Aztecs+ ( Entity,+ EntityComponent (..),+ Component (..),+ World,+ Query,+ Access (..),+ Task,+ System (..),+ Constraint (..),+ before,+ after,+ Schedule (..),+ Startup,+ Update,+ Scheduler (..),+ schedule,+ runScheduler,+ )+where++import Data.Aztecs.Query+ ( Query (..),+ )+import Data.Aztecs.Schedule+import Data.Aztecs.System+import Data.Aztecs.Task (Task (..))+import Data.Aztecs.World+ ( Component (..),+ Entity,+ EntityComponent (..),+ World,+ )
+ src/Data/Aztecs/Command.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Data.Aztecs.Command+ ( Command (..),+ spawn,+ insert,+ Edit (..),+ )+where++import Control.Monad.IO.Class+import Control.Monad.State (StateT (..))+import qualified Control.Monad.State as S+import Data.Aztecs.World (Component, Entity, World)+import qualified Data.Aztecs.World as W+import Data.Dynamic (Typeable)+import Data.Proxy+import Prelude hiding (all)++data Edit where+ Spawn :: (Component c) => Entity -> (Proxy c) -> Edit+ Insert :: (Component c) => Entity -> (Proxy c) -> Edit++-- | Command to update the `World`.+newtype Command m a = Command (StateT World m a)+ deriving (Functor, Applicative, Monad, MonadIO)++-- | Spawn a `Component` and return its `Entity`.+spawn :: (Component a, Typeable a) => a -> Command IO Entity+spawn a = Command $ do+ w <- S.get+ (e, w') <- liftIO $ W.spawn a w+ S.put $ w'+ return e++-- | Insert a `Component` into an `Entity`.+insert :: (Component a, Typeable a) => Entity -> a -> Command IO ()+insert e a = Command $ do+ w <- S.get+ w' <- liftIO $ W.insert e a w+ S.put w'
+ src/Data/Aztecs/Core.hs view
@@ -0,0 +1,5 @@+module Data.Aztecs.Core (Entity (..), EntityComponent (..)) where++newtype Entity = Entity Int deriving (Eq, Ord, Show)++data EntityComponent a = EntityComponent Entity a deriving (Show)
+ src/Data/Aztecs/Query.hs view
@@ -0,0 +1,134 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Data.Aztecs.Query+ ( ReadWrites (..),+ Query (..),+ entity,+ read,+ buildQuery,+ all,+ all',+ get,+ get',+ write,+ )+where++import Control.Monad.IO.Class (MonadIO (..))+import Data.Aztecs.World (Component, Entity, World (..))+import Data.Aztecs.World.Archetypes (Archetype, ArchetypeId, ArchetypeState (..), archetype)+import qualified Data.Aztecs.World.Archetypes as A+import Data.Foldable (foldrM)+import qualified Data.Map as Map+import Data.Set (Set)+import Data.Typeable+import Prelude hiding (all, read)++-- | Component IDs to read and write.+data ReadWrites = ReadWrites (Set TypeRep) (Set TypeRep)++instance Semigroup ReadWrites where+ ReadWrites rs ws <> ReadWrites rs' ws' = ReadWrites (rs <> rs') (ws <> ws')++instance Monoid ReadWrites where+ mempty = ReadWrites mempty mempty++-- | Builder for a `Query`.+data Query m a where+ PureQ :: a -> Query m a+ MapQ :: (a -> b) -> Query m a -> Query m b+ AppQ :: Query m (a -> b) -> Query m a -> Query m b+ BindQ :: Query m a -> (a -> Query m b) -> Query m b+ EntityQ :: Query m Entity+ ReadQ :: (Component c) => Archetype -> Query m c+ WriteQ :: (Component c) => (c -> c) -> Archetype -> Query m c+ LiftQ :: m a -> Query m a++instance Functor (Query m) where+ fmap = MapQ++instance Applicative (Query m) where+ pure = PureQ+ (<*>) = AppQ++instance Monad (Query m) where+ (>>=) = BindQ++entity :: Query m Entity+entity = EntityQ++-- | Read a `Component`.+read :: forall m c. (Component c) => Query m c+read = ReadQ (archetype @c)++-- | Alter a `Component`.+write :: forall m c. (Component c) => (c -> c) -> Query m c+write c = WriteQ c (archetype @c)++buildQuery :: Query m a -> Archetype+buildQuery (PureQ _) = mempty+buildQuery (MapQ _ qb) = buildQuery qb+buildQuery (AppQ f a) = buildQuery f <> buildQuery a+buildQuery EntityQ = mempty+buildQuery (ReadQ a) = a+buildQuery (WriteQ _ a) = a+buildQuery (BindQ a _) = buildQuery a+buildQuery (LiftQ _) = mempty++all :: (MonadIO m) => ArchetypeId -> Query m a -> World -> m [a]+all a qb w@(World _ as) = case A.getArchetype a as of+ Just s -> all' s qb w+ Nothing -> return []++all' :: (MonadIO m) => ArchetypeState -> Query m a -> World -> m [a]+all' es@(ArchetypeState _ m _) q w =+ foldrM+ ( \e acc -> do+ a <- get' e es q w+ return $ case a of+ Just a' -> (a' : acc)+ Nothing -> acc+ )+ []+ (Map.keys m)++get :: (MonadIO m) => ArchetypeId -> Query m a -> Entity -> World -> m (Maybe a)+get a qb e w@(World _ as) = case A.getArchetype a as of+ Just s -> get' e s qb w+ Nothing -> return Nothing++get' :: (MonadIO m) => Entity -> ArchetypeState -> Query m a -> World -> m (Maybe a)+get' _ _ (PureQ a) _ = return $ Just a+get' e es (MapQ f qb) w = do+ a <- get' e es qb w+ return $ fmap f a+get' e es (AppQ fqb aqb) w = do+ f <- get' e es fqb w+ a <- get' e es aqb w+ return $ f <*> a+get' e _ EntityQ _ = return $ Just e+get' e (ArchetypeState _ m _) (ReadQ _) _ = return $ do+ cs <- Map.lookup e m+ (c, _) <- A.getArchetypeComponent cs+ return c+get' e (ArchetypeState _ m _) (WriteQ f _) _ = do+ let res = do+ cs <- Map.lookup e m+ A.getArchetypeComponent cs+ case res of+ Just (c, g) -> do+ let c' = f c+ liftIO $ g c'+ return $ Just c'+ Nothing -> return Nothing+get' e es (BindQ qb f) w = do+ a <- get' e es qb w+ case a of+ Just a' -> get' e es (f a') w+ Nothing -> return Nothing+get' _ _ (LiftQ a) _ = fmap Just a
+ src/Data/Aztecs/Schedule.hs view
@@ -0,0 +1,207 @@+{-# 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
+ src/Data/Aztecs/Storage.hs view
@@ -0,0 +1,65 @@+module Data.Aztecs.Storage (Storage (..), table, table') where++import Control.Monad (filterM)+import Data.Aztecs.Core+import Data.Functor ((<&>))+import Data.IORef (IORef, newIORef, readIORef, writeIORef)+import Data.List (find)++data Storage a = Storage+ { empty :: IO (Storage a),+ spawn :: Entity -> a -> IO (Storage a),+ get :: Entity -> IO (Maybe (a, a -> Storage a -> IO (Storage a))),+ toList :: IO [EntityComponent a],+ toList' :: IO [(EntityComponent a, a -> IO ())],+ remove :: Entity -> IO (Storage a)+ }++table' :: [IORef (EntityComponent a)] -> Storage a+table' cs =+ Storage+ { empty = pure $ table' [],+ spawn = \e a -> do+ r <- newIORef (EntityComponent e a)+ return $ table' (r : cs),+ get = \e -> do+ cs' <-+ mapM+ ( \r -> do+ a <- readIORef r+ return (a, r)+ )+ cs+ return+ ( find (\(EntityComponent e' _, _) -> e == e') cs'+ <&> \(EntityComponent _ a, r) ->+ ( a,+ \a' t -> do+ writeIORef r (EntityComponent e a')+ return t+ )+ ),+ toList = mapM readIORef cs,+ toList' =+ mapM+ ( \r -> do+ (EntityComponent e a) <- readIORef r+ return+ ( (EntityComponent e a),+ \a' -> writeIORef r (EntityComponent e a')+ )+ )+ cs,+ remove = \e -> do+ cs' <-+ filterM+ ( \r -> do+ (EntityComponent e' _) <- readIORef r+ return $ e /= e'+ )+ cs+ return $ table' cs'+ }++table :: Storage a+table = table' []
+ src/Data/Aztecs/System.hs view
@@ -0,0 +1,218 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Data.Aztecs.System+ ( Access (..),+ runAccess,+ runAccess',+ all,+ get,+ command,+ System (..),+ runSystem,+ runSystem',+ Cache (..),+ )+where++import Control.Monad.IO.Class+import Data.Aztecs.Command+import Data.Aztecs.Query (Query (..))+import qualified Data.Aztecs.Query as Q+import Data.Aztecs.World (Entity, World (..))+import Data.Aztecs.World.Archetypes (Archetype, ArchetypeId)+import qualified Data.Aztecs.World.Archetypes as A+import Data.Foldable (foldrM)+import Data.Kind+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Typeable+import Prelude hiding (all, read)++newtype Cache = Cache (Map Archetype ArchetypeId)+ deriving (Semigroup, Monoid)++data Access (m :: Type -> Type) a where+ PureA :: a -> Access m a+ MapA :: (a -> b) -> Access m a -> Access m b+ AppA :: Access m (a -> b) -> Access m a -> Access m b+ BindA :: Access m a -> (a -> Access m b) -> Access m b+ AllA :: Archetype -> Query m a -> Access m [a]+ GetA :: Archetype -> Query m a -> Entity -> Access m (Maybe a)+ CommandA :: Command m () -> Access m ()+ LiftA :: m a -> Access m a++instance Functor (Access m) where+ fmap = MapA++instance (Monad m) => Applicative (Access m) where+ pure = PureA+ (<*>) = AppA++instance (Monad m) => Monad (Access m) where+ (>>=) = BindA++instance (MonadIO m) => MonadIO (Access m) where+ liftIO io = LiftA (liftIO io)++runAccess :: Access IO a -> World -> Cache -> IO (Either (Access IO a) a, World, Cache, [Command IO ()])+runAccess (PureA a) w c = return (Right a, w, c, [])+runAccess (MapA f a) w cache = do+ (a', w', cache', cmds) <- runAccess a w cache+ return+ ( case a' of+ Left a'' -> Left (MapA f a'')+ Right a'' -> Right (f a''),+ w',+ cache',+ cmds+ )+runAccess (AppA f a) w cache = do+ (f', w', cache', cmds) <- runAccess f w cache+ (a', w'', cache'', cmds') <- runAccess a w' cache'+ return+ ( case (f', a') of+ (Right f'', Right a'') -> Right (f'' a'')+ (Left f'', _) -> Left (AppA f'' a)+ (_, Left a'') -> Left (AppA f a''),+ w'',+ cache'',+ cmds ++ cmds'+ )+runAccess (BindA a f) w cache = do+ (a', w', cache', cmds) <- runAccess a w cache+ case a' of+ Left a'' -> return (Left (BindA a'' f), w', cache', cmds)+ Right a'' -> runAccess (f a'') w' cache'+runAccess (AllA a qb) w (Cache cache) = case Map.lookup (Q.buildQuery qb) cache of+ Just aId -> do+ es <- Q.all aId qb w+ return (Right es, w, Cache cache, [])+ Nothing -> return (Left (AllA a qb), w, Cache cache, [])+runAccess (GetA arch q e) w (Cache cache) = do+ case Map.lookup arch cache of+ Just aId -> do+ a <- Q.get aId q e w+ return (Right a, w, Cache cache, [])+ Nothing -> return (Left (GetA arch q e), w, Cache cache, [])+runAccess (CommandA cmd) w cache = return (Right (), w, cache, [cmd])+runAccess (LiftA io) w cache = do+ a <- liftIO io+ return (Right a, w, cache, [])++runAccess' :: Access IO a -> World -> Cache -> IO (a, World, Cache, [Command IO ()])+runAccess' (PureA a) w c = return (a, w, c, [])+runAccess' (MapA f a) w cache = do+ (a', w', cache', cmds) <- runAccess' a w cache+ return (f a', w', cache', cmds)+runAccess' (AppA f a) w cache = do+ (f', w', cache', cmds) <- runAccess' f w cache+ (a', w'', cache'', cmds') <- runAccess' a w' cache'+ return (f' a', w'', cache'', cmds ++ cmds')+runAccess' (BindA a f) w cache = do+ (a', w', cache', cmds) <- runAccess' a w cache+ (b, w'', cache'', cmds') <- runAccess' (f a') w' cache'+ return (b, w'', cache'', cmds ++ cmds')+runAccess' (AllA _ qb) (World cs as) (Cache cache) = do+ (aId, w) <- case Map.lookup (Q.buildQuery qb) cache of+ Just q' -> return (q', World cs as)+ Nothing -> do+ (x, as') <- A.insertArchetype (Q.buildQuery qb) cs as+ return (x, World cs as')+ es <- Q.all aId qb w+ return (es, w, Cache cache, [])+runAccess' (GetA arch q e) (World cs as) (Cache cache) = do+ (aId, w) <- case Map.lookup arch cache of+ Just q' -> return (q', World cs as)+ Nothing -> do+ (x, as') <- A.insertArchetype arch cs as+ return (x, World cs as')+ a <- Q.get aId q e w+ return (a, w, Cache cache, [])+runAccess' (CommandA cmd) w cache = return ((), w, cache, [cmd])+runAccess' (LiftA io) w cache = do+ a <- liftIO io+ return (a, w, cache, [])++-- | Query all matches.+all :: (Monad m) => Query m a -> Access m [a]+all q = fst <$> all' mempty q++all' :: (Monad m) => Archetype -> Query m a -> Access m ([a], Archetype)+all' arch (PureQ a) = pure ([a], arch)+all' arch (MapQ f a) = all' arch (f <$> a)+all' arch (AppQ f a) = all' arch (f <*> a)+all' arch (BindQ a f) = do+ (a', arch') <- all' arch a+ foldrM+ ( \q (acc, archAcc) -> do+ (as, archAcc') <- all' archAcc (f q)+ return (as ++ acc, archAcc')+ )+ ([], arch')+ a'+all' arch (LiftQ m) = do+ a <- LiftA m+ return ([a], arch)+all' arch (ReadQ arch') = do+ let arch'' = (arch <> arch')+ as <- AllA arch'' (ReadQ arch')+ return (as, arch'')+all' arch (WriteQ f arch') = do+ let arch'' = (arch <> arch')+ as <- AllA arch'' (WriteQ f arch')+ return (as, arch'')+all' arch EntityQ = do+ es <- AllA arch EntityQ+ return (es, arch)++get :: (Monad m) => Entity -> Query m a -> Access m (Maybe a)+get e q = fst <$> get' mempty e q++get' :: (Monad m) => Archetype -> Entity -> Query m a -> Access m (Maybe a, Archetype)+get' arch _ (PureQ a) = pure (Just a, arch)+get' arch e (MapQ f qb) = get' arch e (f <$> qb)+get' arch e (AppQ f a) = get' arch e (f <*> a)+get' arch e (BindQ a f) = do+ (a', arch') <- get' arch e a+ case fmap f a' of+ Just a'' -> get' arch' e a''+ Nothing -> return (Nothing, arch')+get' arch _ (LiftQ m) = do+ a <- LiftA m+ return (Just a, arch)+get' arch e (ReadQ arch') = do+ let arch'' = (arch <> arch')+ a <- GetA arch'' (ReadQ arch') e+ return (a, arch'')+get' arch e (WriteQ f arch') = do+ let arch'' = (arch <> arch')+ a <- GetA arch'' (WriteQ f arch') e+ return (a, arch'')+get' arch e EntityQ = return (Just e, arch)++command :: Command m () -> Access m ()+command = CommandA++class (Typeable a) => System m a where+ access :: Access m ()++runSystem :: forall a. (System IO a) => World -> IO World+runSystem w = do+ (_, _, _, w') <- runSystem' @a (Cache mempty) w+ return w'++runSystem' :: forall a. (System IO a) => Cache -> World -> IO (Maybe (Access IO ()), Cache, [Command IO ()], World)+runSystem' cache w = do+ (result, w', cache', cmds) <- runAccess (access @IO @a) w cache+ case result of+ Left a -> return (Just a, cache', cmds, w')+ Right _ -> return (Nothing, cache', cmds, w')
+ src/Data/Aztecs/Task.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Data.Aztecs.Task+ ( Task (..),+ command,+ runTask,+ )+where++import Control.Monad.IO.Class+import Control.Monad.State (StateT (..))+import qualified Control.Monad.State as S+import Data.Aztecs.Command+import Data.Aztecs.World (World)+import Prelude hiding (all)++-- | System task.+newtype Task m s a = Task (StateT (s, [Command m ()], World) m a)+ deriving (Functor, Applicative, Monad, MonadIO)++runTask :: (Functor m) => Task m s a -> s -> World -> m (a, s, [Command m ()], World)+runTask (Task t) s w = fmap (\(a, (s', cmds, w')) -> (a, s', cmds, w')) (S.runStateT t (s, [], w))++-- | Queue a `Command` to run after this system is complete.+command :: (Monad m) => Command m () -> Task m a ()+command cmd = Task $ do+ (s, cmds, w) <- S.get+ S.put $ (s, cmds <> [cmd], w)
+ src/Data/Aztecs/World.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Data.Aztecs.World+ ( Entity,+ EntityComponent (..),+ Component (..),+ World (..),+ union,+ spawn,+ insert,+ get,+ getRow,+ newWorld,+ setRow,+ remove,+ )+where++import Data.Aztecs.Core+import Data.Aztecs.Storage (Storage)+import Data.Aztecs.World.Archetypes (Archetypes, newArchetypes)+import qualified Data.Aztecs.World.Archetypes as A+import Data.Aztecs.World.Components (Component, Components, newComponents)+import qualified Data.Aztecs.World.Components as C+import Data.Typeable+import Prelude hiding (read)++data World = World Components Archetypes deriving (Show)++newWorld :: World+newWorld = World newComponents newArchetypes++union :: World -> World -> World+union (World cs as) (World cs' _) = World (C.union cs cs') as++spawn :: forall c. (Component c) => c -> World -> IO (Entity, World)+spawn c (World cs as) = do+ (e, cs') <- C.spawn c cs + return (e, World cs' as)++insert :: forall c. (Component c) => Entity -> c -> World -> IO World+insert e c (World cs as) = do+ cs' <- C.insert e c cs+ return $ World cs' (A.insert @c e cs' as)++getRow :: (Component c) => Proxy c -> World -> Maybe (Storage c)+getRow p (World cs _) = C.getRow p cs++get :: forall c. (Component c) => Entity -> World -> IO (Maybe (c, c -> World -> IO World))+get e (World cs _) = do+ res <- C.get e cs+ case res of+ Just (c, f) ->+ return $+ Just+ ( c,+ \c' (World cs' as) -> do+ cs'' <- f c' cs'+ return $ World cs'' as+ )+ Nothing -> return Nothing++setRow :: forall c. (Component c) => Storage c -> World -> World+setRow row (World cs as) = World (C.setRow row cs) as++remove :: forall c. (Component c) => Entity -> World -> World+remove e (World cs as) = World (C.remove @c e cs) as
+ src/Data/Aztecs/World/Archetypes.hs view
@@ -0,0 +1,181 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Data.Aztecs.World.Archetypes+ ( Archetype (..),+ ArchetypeComponent (..),+ ArchetypeComponents (..),+ getArchetypeComponent,+ ArchetypeState (..),+ ArchetypeId (..),+ Archetypes (..),+ newArchetypes,+ archetype,+ insertArchetype,+ getArchetype,+ insert,+ )+where++import Data.Aztecs.Core+import qualified Data.Aztecs.Storage as S+import Data.Aztecs.World.Components (Component, Components, getRow)+import qualified Data.Aztecs.World.Components as C+import Data.Dynamic (Dynamic, fromDynamic, toDyn)+import Data.Foldable (foldrM)+import Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Maybe (fromMaybe, isJust)+import Data.Set (Set)+import qualified Data.Set as Set+import Data.Typeable+import Prelude hiding (read)++data ArchetypeComponent where+ ArchetypeComponent :: (Component c) => Proxy c -> ArchetypeComponent++instance Eq ArchetypeComponent where+ ArchetypeComponent a == ArchetypeComponent b = typeOf a == typeOf b++instance Ord ArchetypeComponent where+ ArchetypeComponent a `compare` ArchetypeComponent b = typeOf a `compare` typeOf b++instance Show ArchetypeComponent where+ show (ArchetypeComponent p) = show (typeOf p)++newtype Archetype = Archetype (Set ArchetypeComponent)+ deriving (Eq, Ord, Show, Monoid, Semigroup)++archetype :: forall c. (Component c) => Archetype+archetype = Archetype . Set.singleton $ ArchetypeComponent (Proxy @c)++newtype ArchetypeId = ArchetypeId Int deriving (Eq, Ord, Show)++newtype ArchetypeComponents = ArchetypeComponents (Map TypeRep Dynamic)+ deriving (Show)++getArchetypeComponent :: forall c. (Component c) => ArchetypeComponents -> Maybe (c, c -> IO ())+getArchetypeComponent (ArchetypeComponents m) = do+ d <- Map.lookup (typeOf (Proxy @c)) m+ fromDynamic d++insertArchetypeComponent :: forall c. (Component c) => c -> (c -> IO ()) -> ArchetypeComponents -> ArchetypeComponents+insertArchetypeComponent c f (ArchetypeComponents m) = ArchetypeComponents $ Map.insert (typeOf (Proxy @c)) (toDyn (c, f)) m++data ArchetypeState = ArchetypeState Archetype (Map Entity ArchetypeComponents) [ArchetypeId]+ deriving (Show)++data Archetypes+ = Archetypes+ (IntMap ArchetypeState)+ (Map TypeRep [ArchetypeId])+ (Map Archetype ArchetypeId)+ Int+ deriving (Show)++newArchetypes :: Archetypes+newArchetypes = Archetypes IntMap.empty Map.empty Map.empty 0++insertArchetype :: Archetype -> Components -> Archetypes -> IO (ArchetypeId, Archetypes)+insertArchetype (Archetype a) w (Archetypes es ids as i) = case Map.lookup (Archetype a) as of+ Just (ArchetypeId i') -> return (ArchetypeId i, Archetypes es ids as i')+ Nothing -> do+ (es', ids') <-+ foldrM+ ( \(ArchetypeComponent p) (eAcc, acc) -> do+ cs <- fromMaybe (pure []) $ fmap (\s -> S.toList' s) (getRow p w)+ let eAcc' = map (\(EntityComponent e c, f) -> (e, insertArchetypeComponent c f (ArchetypeComponents mempty))) cs+ return (eAcc' ++ eAcc, Map.unionWith (<>) (Map.singleton (typeOf p) [ArchetypeId i]) acc)+ )+ ([], ids)+ (Set.toList a)+ return (ArchetypeId i, Archetypes (IntMap.insert i (ArchetypeState (Archetype a) (Map.fromList es') []) es) ids' as (i + 1))++getArchetype :: ArchetypeId -> Archetypes -> Maybe ArchetypeState+getArchetype (ArchetypeId i) (Archetypes es _ _ _) = IntMap.lookup i es++insert :: forall c. (Component c) => Entity -> Components -> Archetypes -> Archetypes+insert e cs (Archetypes es ids as j) = case Map.lookup (typeOf (Proxy @c)) ids of+ Just (ids') ->+ let insertInArchetype :: Int -> IntMap ArchetypeState -> IntMap ArchetypeState+ insertInArchetype archetypeId acc =+ IntMap.alter (updateArchetypeState archetypeId) archetypeId acc++ updateArchetypeState :: Int -> Maybe ArchetypeState -> Maybe ArchetypeState+ updateArchetypeState _ state = case state of+ Just (ArchetypeState arch esAcc deps) ->+ let isMatch =+ all+ (\(ArchetypeComponent p) -> isJust $ C.getRow p cs)+ (Set.toList $ unwrapArchetype arch)+ in if isMatch+ then+ Just $ ArchetypeState arch (Map.singleton e (ArchetypeComponents mempty) <> esAcc) deps+ else state+ Nothing -> state++ updateDependencies :: Int -> IntMap ArchetypeState -> IntMap ArchetypeState+ updateDependencies archetypeId acc = case IntMap.lookup archetypeId acc of+ Just (ArchetypeState _ _ deps) -> foldr updateDependencies (insertInArchetype archetypeId acc) (map getArchetypeId deps)+ Nothing -> acc+ es' = foldr updateDependencies es (map getArchetypeId ids')+ in merge $ Archetypes es' ids as j+ Nothing -> Archetypes es ids as j++merge :: Archetypes -> Archetypes+merge archetypes@(Archetypes es _ _ _) =+ foldl processArchetype archetypes (IntMap.toList es)+ where+ processArchetype :: Archetypes -> (Int, ArchetypeState) -> Archetypes+ processArchetype acc (parentId, ArchetypeState parentArch _ _) =+ foldl (updateDependency parentId parentArch) acc (IntMap.toList es)++ updateDependency :: Int -> Archetype -> Archetypes -> (Int, ArchetypeState) -> Archetypes+ updateDependency parentId parentArch acc (childId, ArchetypeState childArch _ _) =+ let parentComponents = unwrapArchetype parentArch+ childComponents = unwrapArchetype childArch+ in if childId /= parentId && Set.isSubsetOf childComponents parentComponents+ then mergeWithDeps (ArchetypeId parentId) (ArchetypeId childId) acc+ else acc++mergeWithDeps :: ArchetypeId -> ArchetypeId -> Archetypes -> Archetypes+mergeWithDeps parentId childId (Archetypes es ids as nextId) =+ case (IntMap.lookup (getArchetypeId parentId) es, IntMap.lookup (getArchetypeId childId) es) of+ (Just (ArchetypeState parentArch parentEntities parentDeps), Just (ArchetypeState childArch childEntities childDeps)) ->+ let parentComponents = unwrapArchetype parentArch+ childComponents = unwrapArchetype childArch++ adjustedChildComponents = Set.intersection parentComponents childComponents+ adjustedChildArch = Archetype adjustedChildComponents++ (childId', updatedEs, updatedAs, newNextId) =+ if adjustedChildComponents == childComponents+ then (childId, es, as, nextId)+ else case Map.lookup adjustedChildArch as of+ Just existingId -> (existingId, es, as, nextId)+ Nothing ->+ let newChildId = ArchetypeId nextId+ newState = ArchetypeState adjustedChildArch childEntities childDeps+ in ( newChildId,+ IntMap.insert nextId newState es,+ Map.insert adjustedChildArch newChildId as,+ nextId + 1+ )++ updatedParentDeps = childId' : parentDeps+ updatedParentState = ArchetypeState parentArch parentEntities updatedParentDeps+ finalEs = IntMap.insert (getArchetypeId parentId) updatedParentState updatedEs+ in Archetypes finalEs ids updatedAs newNextId+ _ -> Archetypes es ids as nextId++getArchetypeId :: ArchetypeId -> Int+getArchetypeId (ArchetypeId x) = x++unwrapArchetype :: Archetype -> Set ArchetypeComponent+unwrapArchetype (Archetype set) = set
+ src/Data/Aztecs/World/Components.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Data.Aztecs.World.Components+ ( Component (..),+ Components,+ union,+ spawn,+ insert,+ adjust,+ get,+ getRow,+ newComponents,+ setRow,+ remove,+ )+where++import Data.Aztecs.Core+import Data.Aztecs.Storage (Storage, table)+import qualified Data.Aztecs.Storage as S+import Data.Dynamic (Dynamic, fromDynamic, toDyn)+import Data.Map (Map, alter, empty, lookup)+import qualified Data.Map as Map+import Data.Maybe (fromMaybe)+import Data.Typeable+import Prelude hiding (read)++class (Typeable a) => Component a where+ storage :: Storage a+ storage = table++data Components = Components (Map TypeRep Dynamic) Entity deriving (Show)++newComponents :: Components+newComponents = Components empty (Entity 0)++union :: Components -> Components -> Components+union (Components a e) (Components b _) = Components (Map.union a b) e++spawn :: forall c. (Component c) => c -> Components -> IO (Entity, Components)+spawn c (Components w (Entity e)) = do+ w' <- insert (Entity e) c (Components w (Entity $ e + 1))+ return (Entity e, w')++insert :: forall c. (Component c) => Entity -> c -> Components -> IO Components+insert e c (Components w e') = do+ w' <-+ Map.alterF+ ( \maybeRow -> do+ s <- S.spawn (fromMaybe storage (maybeRow >>= fromDynamic)) e c+ return . Just $ toDyn s+ )+ (typeOf (Proxy :: Proxy c))+ w+ return $ Components w' e'++adjust :: (Component c) => c -> (c -> c) -> Entity -> Components -> IO Components+adjust a f w = insert w (f a)++getRow :: (Component c) => Proxy c -> Components -> Maybe (Storage c)+getRow p (Components w _) = Data.Map.lookup (typeOf p) w >>= fromDynamic++get :: forall c. (Component c) => Entity -> Components -> IO (Maybe (c, c -> Components -> IO Components))+get e (Components w _) = case Data.Map.lookup (typeOf @(Proxy c) Proxy) w >>= fromDynamic of+ Just s -> do+ res <- S.get s e+ case res of+ Just (c, f) ->+ return $+ Just+ ( c,+ \c' (Components w' e') ->+ return $ Components (alter (\row -> Just . toDyn $ f c' (fromMaybe storage (row >>= fromDynamic))) (typeOf @(Proxy c) Proxy) w') e'+ )+ Nothing -> return Nothing+ Nothing -> return Nothing++setRow :: forall c. (Component c) => Storage c -> Components -> Components+setRow cs (Components w e') = Components (Map.insert (typeOf @(Proxy c) Proxy) (toDyn cs) w) e'++remove :: forall c. (Component c) => Entity -> Components -> Components+remove e (Components w e') = Components (alter (\row -> row >>= f) (typeOf @(Proxy c) Proxy) w) e'+ where+ f row = fmap (\row' -> toDyn $ S.remove @c row' e) (fromDynamic row)