aztecs 0.2.0.0 → 0.3.0.0
raw patch · 19 files changed
+1352/−443 lines, 19 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Data.Aztecs.Archetype: AnyStorage :: Dynamic -> (Int -> Dynamic -> Dynamic -> Dynamic) -> (Int -> Dynamic -> (Maybe Dynamic, Dynamic)) -> (Int -> Dynamic -> (Maybe AnyStorage, Dynamic)) -> AnyStorage
- Data.Aztecs.Archetype: Archetype :: Map ComponentID AnyStorage -> Archetype
- Data.Aztecs.Archetype: [insertDyn] :: AnyStorage -> Int -> Dynamic -> Dynamic -> Dynamic
- Data.Aztecs.Archetype: [removeAny] :: AnyStorage -> Int -> Dynamic -> (Maybe AnyStorage, Dynamic)
- Data.Aztecs.Archetype: [removeDyn] :: AnyStorage -> Int -> Dynamic -> (Maybe Dynamic, Dynamic)
- Data.Aztecs.Archetype: [storageDyn] :: AnyStorage -> Dynamic
- Data.Aztecs.Archetype: [storages] :: Archetype -> Map ComponentID AnyStorage
- Data.Aztecs.Archetype: all :: Component a => ComponentID -> Archetype -> [(EntityID, a)]
- Data.Aztecs.Archetype: anyStorage :: forall s a. Storage s a => s a -> AnyStorage
- Data.Aztecs.Archetype: data AnyStorage
- Data.Aztecs.Archetype: empty :: Archetype
- Data.Aztecs.Archetype: insert :: forall a. Component a => EntityID -> ComponentID -> a -> Archetype -> Archetype
- Data.Aztecs.Archetype: insertAscList :: forall a. Component a => ComponentID -> [(EntityID, a)] -> Archetype -> Archetype
- Data.Aztecs.Archetype: instance GHC.Show.Show Data.Aztecs.Archetype.AnyStorage
- Data.Aztecs.Archetype: instance GHC.Show.Show Data.Aztecs.Archetype.Archetype
- Data.Aztecs.Archetype: lookup :: forall a. Component a => EntityID -> ComponentID -> Archetype -> Maybe a
- Data.Aztecs.Archetype: lookupStorage :: Component a => ComponentID -> Archetype -> Maybe (StorageT a a)
- Data.Aztecs.Archetype: newtype Archetype
- Data.Aztecs.Archetype: remove :: EntityID -> Archetype -> (Map ComponentID Dynamic, Archetype)
- Data.Aztecs.Archetype: removeStorages :: EntityID -> Archetype -> (Map ComponentID AnyStorage, Archetype)
- Data.Aztecs.Core: ComponentID :: Int -> ComponentID
- Data.Aztecs.Core: Components :: Map TypeRep ComponentID -> ComponentID -> Components
- Data.Aztecs.Core: EntityID :: Int -> EntityID
- Data.Aztecs.Core: [componentIds] :: Components -> Map TypeRep ComponentID
- Data.Aztecs.Core: [nextComponentId] :: Components -> ComponentID
- Data.Aztecs.Core: [unComponentId] :: ComponentID -> Int
- Data.Aztecs.Core: [unEntityId] :: EntityID -> Int
- Data.Aztecs.Core: class (Typeable a, Storage (StorageT a) a) => Component a where {
- Data.Aztecs.Core: data Components
- Data.Aztecs.Core: emptyComponents :: Components
- Data.Aztecs.Core: insertComponentId :: forall c. Component c => Components -> (ComponentID, Components)
- Data.Aztecs.Core: instance GHC.Classes.Eq Data.Aztecs.Core.ComponentID
- Data.Aztecs.Core: instance GHC.Classes.Eq Data.Aztecs.Core.EntityID
- Data.Aztecs.Core: instance GHC.Classes.Ord Data.Aztecs.Core.ComponentID
- Data.Aztecs.Core: instance GHC.Classes.Ord Data.Aztecs.Core.EntityID
- Data.Aztecs.Core: instance GHC.Show.Show Data.Aztecs.Core.ComponentID
- Data.Aztecs.Core: instance GHC.Show.Show Data.Aztecs.Core.Components
- Data.Aztecs.Core: instance GHC.Show.Show Data.Aztecs.Core.EntityID
- Data.Aztecs.Core: lookupComponentId :: forall a. Typeable a => Components -> Maybe ComponentID
- Data.Aztecs.Core: newtype ComponentID
- Data.Aztecs.Core: newtype EntityID
- Data.Aztecs.Core: type StorageT a = IntMap;
- Data.Aztecs.Core: }
- Data.Aztecs.Query: allWorld :: Query a -> World -> [Entity a]
- Data.Aztecs.Query: instance (Data.Aztecs.Core.Component a, Data.Typeable.Internal.Typeable (Data.Aztecs.Core.StorageT a)) => Data.Aztecs.Query.ToQuery '[a]
- Data.Aztecs.Query: instance (Data.Aztecs.Core.Component a, Data.Typeable.Internal.Typeable (Data.Aztecs.Core.StorageT a), Data.Aztecs.Query.ToQuery as) => Data.Aztecs.Query.ToQuery (a : as)
- Data.Aztecs.Query: instance Data.Aztecs.Query.ToQuery '[]
- Data.Aztecs.Query: mapWith :: (FromEntity i, ToEntity o, EntityT i ~ ConcatT a b, EntityT o ~ b) => Query a -> Query b -> (i -> o) -> World -> ([o], World)
- Data.Aztecs.Query: mapWorld :: forall i o. (FromEntity i, ToEntity o, Intersect (EntityT i) (EntityT o), ToQuery (IntersectT (EntityT i) (EntityT o)), Difference (EntityT i) (EntityT o), ToQuery (DifferenceT (EntityT i) (EntityT o)), ConcatT (DifferenceT (EntityT i) (EntityT o)) (IntersectT (EntityT i) (EntityT o)) ~ EntityT i, IntersectT (EntityT i) (EntityT o) ~ EntityT o) => (i -> o) -> World -> ([o], World)
- Data.Aztecs.World: [archetypeComponents] :: World -> Map ArchetypeID (Set ComponentID)
- Data.Aztecs.World: [archetypeIds] :: World -> Map (Set ComponentID) ArchetypeID
- Data.Aztecs.World: [nextArchetypeId] :: World -> ArchetypeID
- Data.Aztecs.World: insertArchetype :: Set ComponentID -> Archetype -> World -> (ArchetypeID, World)
- Data.Aztecs.World: instance GHC.Classes.Eq Data.Aztecs.World.ArchetypeID
- Data.Aztecs.World: instance GHC.Classes.Ord Data.Aztecs.World.ArchetypeID
- Data.Aztecs.World: instance GHC.Show.Show Data.Aztecs.World.ArchetypeID
+ Data.Aztecs: -- | <a>Storage</a> of this component.
+ Data.Aztecs: class (Typeable a) => System m a
+ Data.Aztecs: data Scheduler m
+ Data.Aztecs: data Startup
+ Data.Aztecs: data Update
+ Data.Aztecs: run :: Monad m => Scheduler m -> m ()
+ Data.Aztecs: schedule :: forall m l a. (Typeable l, System m a) => [Constraint] -> Scheduler m
+ Data.Aztecs: task :: System m a => Task m () ()
+ Data.Aztecs.Access: all :: forall m a. (Monad m, ToEntity a, FromEntity a, Queryable (EntityT a)) => Access m [a]
+ Data.Aztecs.Access: alter :: forall a m. (Monad m, FromEntity a, ToEntity a, Queryable (EntityT a)) => EntityID -> (a -> a) -> Access m ()
+ Data.Aztecs.Access: lookup :: forall a m. (Monad m, FromEntity a, Queryable (EntityT a)) => EntityID -> Access m (Maybe a)
+ Data.Aztecs.Access: lookupQuery :: Monad m => EntityID -> Query a -> Access m (Maybe (Entity a))
+ Data.Aztecs.Access: map :: forall m i o. (Monad m, Map (IsEq (Entity (EntityT i)) (Entity (EntityT o))) i o) => (i -> o) -> Access m [o]
+ Data.Aztecs.Access: spawn_ :: (Monad m, ComponentIds (EntityT a), ToEntity a, Insert (Entity (EntityT a))) => a -> Access m ()
+ Data.Aztecs.Component: -- | <a>Storage</a> of this component.
+ Data.Aztecs.Component: ComponentID :: Int -> ComponentID
+ Data.Aztecs.Component: [unComponentId] :: ComponentID -> Int
+ Data.Aztecs.Component: class (Typeable a, Storage (StorageT a) a) => Component a where {
+ Data.Aztecs.Component: instance GHC.Classes.Eq Data.Aztecs.Component.ComponentID
+ Data.Aztecs.Component: instance GHC.Classes.Ord Data.Aztecs.Component.ComponentID
+ Data.Aztecs.Component: instance GHC.Show.Show Data.Aztecs.Component.ComponentID
+ Data.Aztecs.Component: newtype ComponentID
+ Data.Aztecs.Component: type StorageT a = IntMap;
+ Data.Aztecs.Component: }
+ Data.Aztecs.Entity: EntityID :: Int -> EntityID
+ Data.Aztecs.Entity: [unEntityId] :: EntityID -> Int
+ Data.Aztecs.Entity: class ComponentIds (a :: [Type])
+ Data.Aztecs.Entity: class Has a e
+ Data.Aztecs.Entity: class Sort (a :: [Type]) (b :: [Type])
+ Data.Aztecs.Entity: component :: Has a e => e -> a
+ Data.Aztecs.Entity: componentIds :: ComponentIds a => Components -> (Set ComponentID, Components)
+ Data.Aztecs.Entity: instance (Data.Aztecs.Component.Component a, Data.Aztecs.Entity.ComponentIds as) => Data.Aztecs.Entity.ComponentIds (a : as)
+ Data.Aztecs.Entity: instance (Data.Aztecs.Entity.Has b (Data.Aztecs.Entity.Entity as), Data.Aztecs.Entity.Sort as bs) => Data.Aztecs.Entity.Sort as (b : bs)
+ Data.Aztecs.Entity: instance (GHC.Classes.Eq a, GHC.Classes.Eq (Data.Aztecs.Entity.Entity as)) => GHC.Classes.Eq (Data.Aztecs.Entity.Entity (a : as))
+ Data.Aztecs.Entity: instance Data.Aztecs.Entity.ComponentIds '[]
+ Data.Aztecs.Entity: instance Data.Aztecs.Entity.Has a (Data.Aztecs.Entity.Entity (a : ts))
+ Data.Aztecs.Entity: instance Data.Aztecs.Entity.Has a (Data.Aztecs.Entity.Entity ts) => Data.Aztecs.Entity.Has a (Data.Aztecs.Entity.Entity (b : ts))
+ Data.Aztecs.Entity: instance Data.Aztecs.Entity.Sort as '[]
+ Data.Aztecs.Entity: instance GHC.Classes.Eq (Data.Aztecs.Entity.Entity '[])
+ Data.Aztecs.Entity: instance GHC.Classes.Eq Data.Aztecs.Entity.EntityID
+ Data.Aztecs.Entity: instance GHC.Classes.Ord Data.Aztecs.Entity.EntityID
+ Data.Aztecs.Entity: instance GHC.Show.Show Data.Aztecs.Entity.EntityID
+ Data.Aztecs.Entity: newtype EntityID
+ Data.Aztecs.Entity: setComponent :: Has a e => a -> e -> e
+ Data.Aztecs.Entity: sort :: Sort a b => Entity a -> Entity b
+ Data.Aztecs.Query: QueryState :: Set ComponentID -> (Archetype -> ([Entity a], [Entity a] -> Archetype -> Archetype)) -> (EntityID -> Archetype -> Maybe (Entity a)) -> (EntityID -> Archetype -> Entity a -> Archetype) -> QueryState a
+ Data.Aztecs.Query: [queryStateAll] :: QueryState a -> Archetype -> ([Entity a], [Entity a] -> Archetype -> Archetype)
+ Data.Aztecs.Query: [queryStateComponentIds] :: QueryState a -> Set ComponentID
+ Data.Aztecs.Query: [queryStateInsert] :: QueryState a -> EntityID -> Archetype -> Entity a -> Archetype
+ Data.Aztecs.Query: [queryStateLookup] :: QueryState a -> EntityID -> Archetype -> Maybe (Entity a)
+ Data.Aztecs.Query: alter :: forall a. (FromEntity a, ToEntity a, Queryable (EntityT a)) => EntityID -> (a -> a) -> World -> World
+ Data.Aztecs.Query: class Map (flag :: Bool) i o
+ Data.Aztecs.Query: class Queryable a
+ Data.Aztecs.Query: data QueryState a
+ Data.Aztecs.Query: fetchId :: forall a. (Component a, Typeable (StorageT a)) => ComponentID -> Query '[a]
+ Data.Aztecs.Query: instance (Data.Aztecs.Component.Component a, Data.Typeable.Internal.Typeable (Data.Aztecs.Component.StorageT a)) => Data.Aztecs.Query.Queryable '[a]
+ Data.Aztecs.Query: instance (Data.Aztecs.Component.Component a, Data.Typeable.Internal.Typeable (Data.Aztecs.Component.StorageT a), Data.Aztecs.Query.Queryable as) => Data.Aztecs.Query.Queryable (a : as)
+ Data.Aztecs.Query: instance (Data.Aztecs.Entity.FromEntity i, Data.Aztecs.Entity.ToEntity o, Data.Aztecs.Entity.EntityT i GHC.Types.~ a, Data.Aztecs.Entity.EntityT o GHC.Types.~ a, Data.Aztecs.Query.Queryable a) => Data.Aztecs.Query.Map 'GHC.Types.True i o
+ Data.Aztecs.Query: instance (Data.Aztecs.Entity.FromEntity i, Data.Aztecs.Entity.ToEntity o, Data.Aztecs.Entity.Intersect (Data.Aztecs.Entity.EntityT i) (Data.Aztecs.Entity.EntityT o), Data.Aztecs.Query.Queryable (Data.Aztecs.Entity.IntersectT (Data.Aztecs.Entity.EntityT i) (Data.Aztecs.Entity.EntityT o)), Data.Aztecs.Entity.Difference (Data.Aztecs.Entity.EntityT i) (Data.Aztecs.Entity.EntityT o), Data.Aztecs.Query.Queryable (Data.Aztecs.Entity.DifferenceT (Data.Aztecs.Entity.EntityT i) (Data.Aztecs.Entity.EntityT o)), Data.Aztecs.Entity.Sort (Data.Aztecs.Entity.ConcatT (Data.Aztecs.Entity.DifferenceT (Data.Aztecs.Entity.EntityT i) (Data.Aztecs.Entity.EntityT o)) (Data.Aztecs.Entity.IntersectT (Data.Aztecs.Entity.EntityT i) (Data.Aztecs.Entity.EntityT o))) (Data.Aztecs.Entity.EntityT i), Data.Aztecs.Entity.Sort (Data.Aztecs.Entity.EntityT o) (Data.Aztecs.Entity.IntersectT (Data.Aztecs.Entity.EntityT i) (Data.Aztecs.Entity.EntityT o))) => Data.Aztecs.Query.Map 'GHC.Types.False i o
+ Data.Aztecs.Query: lookup :: forall a. (FromEntity a, Queryable (EntityT a)) => EntityID -> World -> Maybe a
+ Data.Aztecs.Query: lookupQuery :: EntityID -> Query a -> World -> Maybe (Entity a)
+ Data.Aztecs.Query: map' :: Map flag i o => (i -> o) -> Components -> (Set ComponentID -> (Archetype -> ([o], Archetype)) -> a -> ([[o]], a)) -> a -> ([o], a)
+ Data.Aztecs.Query: query :: Queryable a => Query a
+ Data.Aztecs.Query: queryAll :: Query a -> World -> [Entity a]
+ Data.Aztecs.Query: queryAll' :: Query a -> Archetypes -> Components -> [Entity a]
+ Data.Aztecs.Query: type family IsEq a b :: Bool
+ Data.Aztecs.Scheduler: After :: ConstraintKind
+ Data.Aztecs.Scheduler: AmbiguousError :: TypeRep -> Error
+ Data.Aztecs.Scheduler: Before :: ConstraintKind
+ Data.Aztecs.Scheduler: Constraint :: ConstraintKind -> TypeRep -> Constraint
+ Data.Aztecs.Scheduler: MissingError :: TypeRep -> Error
+ Data.Aztecs.Scheduler: Node :: Task m () () -> Set TypeRep -> Set TypeRep -> Node m
+ Data.Aztecs.Scheduler: Schedule :: Map TypeRep [ScheduleNode m] -> Schedule m
+ Data.Aztecs.Scheduler: ScheduleBuilder :: Map TypeRep (ScheduleBuilderStage m) -> ScheduleBuilder m
+ Data.Aztecs.Scheduler: ScheduleBuilderNode :: Task m () () -> Set TypeRep -> ScheduleBuilderNode m
+ Data.Aztecs.Scheduler: ScheduleBuilderStage :: Map TypeRep (ScheduleBuilderNode m) -> ScheduleBuilderStage m
+ Data.Aztecs.Scheduler: ScheduleGraph :: Map TypeRep (ScheduleGraphStage m) -> ScheduleGraph m
+ Data.Aztecs.Scheduler: ScheduleGraphNode :: [Set ComponentID] -> Set TypeRep -> (World -> m (World -> World, Access m ())) -> ScheduleGraphNode m
+ Data.Aztecs.Scheduler: ScheduleGraphStage :: Map TypeRep (ScheduleGraphNode m) -> ScheduleGraphStage m
+ Data.Aztecs.Scheduler: ScheduleNode :: (World -> m (World -> World, Access m ())) -> ScheduleNode m
+ Data.Aztecs.Scheduler: Scheduler :: Map TypeRep (Stage m) -> Scheduler m
+ Data.Aztecs.Scheduler: Stage :: Map TypeRep (Node m) -> Stage m
+ Data.Aztecs.Scheduler: [constraintId] :: Constraint -> TypeRep
+ Data.Aztecs.Scheduler: [constraintKind] :: Constraint -> ConstraintKind
+ Data.Aztecs.Scheduler: [nodeAfter] :: Node m -> Set TypeRep
+ Data.Aztecs.Scheduler: [nodeBefore] :: Node m -> Set TypeRep
+ Data.Aztecs.Scheduler: [nodeTask] :: Node m -> Task m () ()
+ Data.Aztecs.Scheduler: [runScheduleGraphNode] :: ScheduleGraphNode m -> World -> m (World -> World, Access m ())
+ Data.Aztecs.Scheduler: [runScheduleNode] :: ScheduleNode m -> World -> m (World -> World, Access m ())
+ Data.Aztecs.Scheduler: [scheduleBuilderNodeBefore] :: ScheduleBuilderNode m -> Set TypeRep
+ Data.Aztecs.Scheduler: [scheduleBuilderNodeTask] :: ScheduleBuilderNode m -> Task m () ()
+ Data.Aztecs.Scheduler: [scheduleGraphNodeBefore] :: ScheduleGraphNode m -> Set TypeRep
+ Data.Aztecs.Scheduler: [scheduleGraphNodeIds] :: ScheduleGraphNode m -> [Set ComponentID]
+ Data.Aztecs.Scheduler: [unScheduleBuilderStage] :: ScheduleBuilderStage m -> Map TypeRep (ScheduleBuilderNode m)
+ Data.Aztecs.Scheduler: [unScheduleBuilder] :: ScheduleBuilder m -> Map TypeRep (ScheduleBuilderStage m)
+ Data.Aztecs.Scheduler: [unScheduleGraphStage] :: ScheduleGraphStage m -> Map TypeRep (ScheduleGraphNode m)
+ Data.Aztecs.Scheduler: [unScheduleGraph] :: ScheduleGraph m -> Map TypeRep (ScheduleGraphStage m)
+ Data.Aztecs.Scheduler: [unSchedule] :: Schedule m -> Map TypeRep [ScheduleNode m]
+ Data.Aztecs.Scheduler: [unScheduler] :: Scheduler m -> Map TypeRep (Stage m)
+ Data.Aztecs.Scheduler: [unStage] :: Stage m -> Map TypeRep (Node m)
+ Data.Aztecs.Scheduler: after :: forall m a. System m a => Constraint
+ Data.Aztecs.Scheduler: before :: forall m a. System m a => Constraint
+ Data.Aztecs.Scheduler: build :: Functor m => Scheduler m -> World -> (Schedule m, World, [Error])
+ Data.Aztecs.Scheduler: build' :: Functor m => ScheduleGraph m -> Schedule m
+ Data.Aztecs.Scheduler: buildGraph :: Functor m => Scheduler m -> World -> (ScheduleGraph m, World, [Error])
+ Data.Aztecs.Scheduler: buildGraph' :: Functor m => ScheduleBuilder m -> World -> (ScheduleGraph m, World)
+ Data.Aztecs.Scheduler: buildGraphStage :: Functor m => ScheduleBuilderStage m -> World -> (ScheduleGraphStage m, World)
+ Data.Aztecs.Scheduler: buildStage :: ScheduleGraphStage m -> [ScheduleNode m]
+ Data.Aztecs.Scheduler: builder :: Scheduler m -> (ScheduleBuilder m, [Error])
+ Data.Aztecs.Scheduler: data Constraint
+ Data.Aztecs.Scheduler: data ConstraintKind
+ Data.Aztecs.Scheduler: data Error
+ Data.Aztecs.Scheduler: data Node m
+ Data.Aztecs.Scheduler: data ScheduleBuilderNode m
+ Data.Aztecs.Scheduler: data ScheduleGraphNode m
+ Data.Aztecs.Scheduler: data Startup
+ Data.Aztecs.Scheduler: data Update
+ Data.Aztecs.Scheduler: initGraphStage :: ScheduleGraphStage m -> ScheduleGraphStage m
+ Data.Aztecs.Scheduler: instance GHC.Base.Monoid (Data.Aztecs.Scheduler.Scheduler m)
+ Data.Aztecs.Scheduler: instance GHC.Base.Monoid (Data.Aztecs.Scheduler.Stage m)
+ Data.Aztecs.Scheduler: instance GHC.Base.Semigroup (Data.Aztecs.Scheduler.Scheduler m)
+ Data.Aztecs.Scheduler: instance GHC.Base.Semigroup (Data.Aztecs.Scheduler.Stage m)
+ Data.Aztecs.Scheduler: instance GHC.Classes.Eq Data.Aztecs.Scheduler.Constraint
+ Data.Aztecs.Scheduler: instance GHC.Classes.Eq Data.Aztecs.Scheduler.ConstraintKind
+ Data.Aztecs.Scheduler: instance GHC.Show.Show (Data.Aztecs.Scheduler.Node m)
+ Data.Aztecs.Scheduler: instance GHC.Show.Show (Data.Aztecs.Scheduler.ScheduleBuilder m)
+ Data.Aztecs.Scheduler: instance GHC.Show.Show (Data.Aztecs.Scheduler.ScheduleBuilderNode m)
+ Data.Aztecs.Scheduler: instance GHC.Show.Show (Data.Aztecs.Scheduler.ScheduleBuilderStage m)
+ Data.Aztecs.Scheduler: instance GHC.Show.Show (Data.Aztecs.Scheduler.ScheduleGraph m)
+ Data.Aztecs.Scheduler: instance GHC.Show.Show (Data.Aztecs.Scheduler.ScheduleGraphNode m)
+ Data.Aztecs.Scheduler: instance GHC.Show.Show (Data.Aztecs.Scheduler.ScheduleGraphStage m)
+ Data.Aztecs.Scheduler: instance GHC.Show.Show (Data.Aztecs.Scheduler.Scheduler m)
+ Data.Aztecs.Scheduler: instance GHC.Show.Show (Data.Aztecs.Scheduler.Stage m)
+ Data.Aztecs.Scheduler: instance GHC.Show.Show Data.Aztecs.Scheduler.Constraint
+ Data.Aztecs.Scheduler: instance GHC.Show.Show Data.Aztecs.Scheduler.ConstraintKind
+ Data.Aztecs.Scheduler: instance GHC.Show.Show Data.Aztecs.Scheduler.Error
+ Data.Aztecs.Scheduler: newtype Schedule m
+ Data.Aztecs.Scheduler: newtype ScheduleBuilder m
+ Data.Aztecs.Scheduler: newtype ScheduleBuilderStage m
+ Data.Aztecs.Scheduler: newtype ScheduleGraph m
+ Data.Aztecs.Scheduler: newtype ScheduleGraphStage m
+ Data.Aztecs.Scheduler: newtype ScheduleNode m
+ Data.Aztecs.Scheduler: newtype Scheduler m
+ Data.Aztecs.Scheduler: newtype Stage m
+ Data.Aztecs.Scheduler: run :: Monad m => Scheduler m -> m ()
+ Data.Aztecs.Scheduler: runStage :: forall l m. (Typeable l, Monad m) => Schedule m -> World -> m World
+ Data.Aztecs.Scheduler: runWorld :: Monad m => Scheduler m -> World -> m ()
+ Data.Aztecs.Scheduler: schedule :: forall m l a. (Typeable l, System m a) => [Constraint] -> Scheduler m
+ Data.Aztecs.Scheduler: stageBuilder :: Stage m -> (ScheduleBuilderStage m, [Error])
+ Data.Aztecs.System: Task :: (Components -> (Components, [Set ComponentID], i -> World -> m (o, World -> World, Access m ()))) -> Task m i o
+ Data.Aztecs.System: [runTask] :: Task m i o -> Components -> (Components, [Set ComponentID], i -> World -> m (o, World -> World, Access m ()))
+ Data.Aztecs.System: all :: forall m v. (Monad m, ComponentIds v, Queryable v) => Task m () [Entity v]
+ Data.Aztecs.System: class (Typeable a) => System m a
+ Data.Aztecs.System: instance GHC.Base.Functor m => GHC.Base.Functor (Data.Aztecs.System.Task m i)
+ Data.Aztecs.System: instance GHC.Base.Monad m => Control.Arrow.Arrow (Data.Aztecs.System.Task m)
+ Data.Aztecs.System: instance GHC.Base.Monad m => Control.Category.Category (Data.Aztecs.System.Task m)
+ Data.Aztecs.System: instance GHC.Base.Monad m => GHC.Base.Applicative (Data.Aztecs.System.Task m i)
+ Data.Aztecs.System: map :: forall m i o. (Monad m, ComponentIds (EntityT i), Queryable (EntityT i), Map (IsEq (Entity (EntityT i)) (Entity (EntityT o))) i o) => (i -> o) -> Task m () [o]
+ Data.Aztecs.System: mapView :: forall m v a. (Monad m, ComponentIds v, Queryable v) => (View v -> Components -> m (a, View v)) -> Task m () a
+ Data.Aztecs.System: newtype Task m i o
+ Data.Aztecs.System: queue :: Monad m => Access m () -> Task m () ()
+ Data.Aztecs.System: run :: Monad m => (i -> m o) -> Task m i o
+ Data.Aztecs.System: runSystem :: forall m s. (System m s, Monad m) => World -> m World
+ Data.Aztecs.System: task :: System m a => Task m () ()
+ Data.Aztecs.System: view :: forall m v a. (Monad m, ComponentIds v, Queryable v) => (View v -> Components -> m a) -> Task m () a
+ Data.Aztecs.View: View :: Map ArchetypeID Archetype -> Query a -> View a
+ Data.Aztecs.View: [viewArchetypes] :: View a -> Map ArchetypeID Archetype
+ Data.Aztecs.View: [viewQuery] :: View a -> Query a
+ Data.Aztecs.View: data View a
+ Data.Aztecs.View: map :: forall i o. Map (IsEq (Entity (EntityT i)) (Entity (EntityT o))) i o => (i -> o) -> View (EntityT i) -> Components -> ([o], View (EntityT i))
+ Data.Aztecs.View: queryAll :: View a -> Components -> [Entity a]
+ Data.Aztecs.View: unview :: View a -> World -> World
+ Data.Aztecs.View: view :: forall a. (ComponentIds a, Queryable a) => World -> (View a, World)
+ Data.Aztecs.View: view' :: forall a. (ComponentIds a, Queryable a) => Components -> Archetypes -> (View a, Components)
+ Data.Aztecs.World: spawnComponent :: forall a. (Component a, Typeable (StorageT a)) => a -> World -> (EntityID, World)
+ Data.Aztecs.World.Archetype: AnyStorage :: Dynamic -> (Int -> Dynamic -> Dynamic -> Dynamic) -> (Int -> Dynamic -> (Maybe Dynamic, Dynamic)) -> (Int -> Dynamic -> (Maybe AnyStorage, Dynamic)) -> AnyStorage
+ Data.Aztecs.World.Archetype: Archetype :: Map ComponentID AnyStorage -> Archetype
+ Data.Aztecs.World.Archetype: [insertDyn] :: AnyStorage -> Int -> Dynamic -> Dynamic -> Dynamic
+ Data.Aztecs.World.Archetype: [removeAny] :: AnyStorage -> Int -> Dynamic -> (Maybe AnyStorage, Dynamic)
+ Data.Aztecs.World.Archetype: [removeDyn] :: AnyStorage -> Int -> Dynamic -> (Maybe Dynamic, Dynamic)
+ Data.Aztecs.World.Archetype: [storageDyn] :: AnyStorage -> Dynamic
+ Data.Aztecs.World.Archetype: [storages] :: Archetype -> Map ComponentID AnyStorage
+ Data.Aztecs.World.Archetype: all :: Component a => ComponentID -> Archetype -> [(EntityID, a)]
+ Data.Aztecs.World.Archetype: anyStorage :: forall s a. Storage s a => s a -> AnyStorage
+ Data.Aztecs.World.Archetype: class Insert a
+ Data.Aztecs.World.Archetype: data AnyStorage
+ Data.Aztecs.World.Archetype: empty :: Archetype
+ Data.Aztecs.World.Archetype: insert :: Insert a => EntityID -> a -> Archetype -> Components -> (Set ComponentID, Archetype, Components)
+ Data.Aztecs.World.Archetype: insertAscList :: forall a. Component a => ComponentID -> [(EntityID, a)] -> Archetype -> Archetype
+ Data.Aztecs.World.Archetype: insertComponent :: forall a. Component a => EntityID -> ComponentID -> a -> Archetype -> Archetype
+ Data.Aztecs.World.Archetype: instance (Data.Aztecs.Component.Component a, Data.Aztecs.World.Archetype.Insert (Data.Aztecs.Entity.Entity as)) => Data.Aztecs.World.Archetype.Insert (Data.Aztecs.Entity.Entity (a : as))
+ Data.Aztecs.World.Archetype: instance Data.Aztecs.World.Archetype.Insert (Data.Aztecs.Entity.Entity '[])
+ Data.Aztecs.World.Archetype: instance GHC.Show.Show Data.Aztecs.World.Archetype.AnyStorage
+ Data.Aztecs.World.Archetype: instance GHC.Show.Show Data.Aztecs.World.Archetype.Archetype
+ Data.Aztecs.World.Archetype: lookup :: forall a. Component a => EntityID -> ComponentID -> Archetype -> Maybe a
+ Data.Aztecs.World.Archetype: lookupStorage :: Component a => ComponentID -> Archetype -> Maybe (StorageT a a)
+ Data.Aztecs.World.Archetype: newtype Archetype
+ Data.Aztecs.World.Archetype: remove :: EntityID -> Archetype -> (Map ComponentID Dynamic, Archetype)
+ Data.Aztecs.World.Archetype: removeStorages :: EntityID -> Archetype -> (Map ComponentID AnyStorage, Archetype)
+ Data.Aztecs.World.Archetypes: ArchetypeID :: Int -> ArchetypeID
+ Data.Aztecs.World.Archetypes: Archetypes :: Map ArchetypeID Node -> Map (Set ComponentID) ArchetypeID -> ArchetypeID -> Map ComponentID (Set ArchetypeID) -> Archetypes
+ Data.Aztecs.World.Archetypes: Node :: Set ComponentID -> Archetype -> Map ComponentID ArchetypeID -> Map ComponentID ArchetypeID -> Node
+ Data.Aztecs.World.Archetypes: [archetypeIds] :: Archetypes -> Map (Set ComponentID) ArchetypeID
+ Data.Aztecs.World.Archetypes: [componentIds] :: Archetypes -> Map ComponentID (Set ArchetypeID)
+ Data.Aztecs.World.Archetypes: [nextArchetypeId] :: Archetypes -> ArchetypeID
+ Data.Aztecs.World.Archetypes: [nodeAdd] :: Node -> Map ComponentID ArchetypeID
+ Data.Aztecs.World.Archetypes: [nodeArchetype] :: Node -> Archetype
+ Data.Aztecs.World.Archetypes: [nodeComponentIds] :: Node -> Set ComponentID
+ Data.Aztecs.World.Archetypes: [nodeRemove] :: Node -> Map ComponentID ArchetypeID
+ Data.Aztecs.World.Archetypes: [nodes] :: Archetypes -> Map ArchetypeID Node
+ Data.Aztecs.World.Archetypes: [unArchetypeId] :: ArchetypeID -> Int
+ Data.Aztecs.World.Archetypes: adjustArchetype :: ArchetypeID -> (Archetype -> Archetype) -> Archetypes -> Archetypes
+ Data.Aztecs.World.Archetypes: data Archetypes
+ Data.Aztecs.World.Archetypes: data Node
+ Data.Aztecs.World.Archetypes: empty :: Archetypes
+ Data.Aztecs.World.Archetypes: findArchetypeIds :: Set ComponentID -> Archetypes -> Set ArchetypeID
+ Data.Aztecs.World.Archetypes: insertArchetype :: Set ComponentID -> Node -> Archetypes -> (ArchetypeID, Archetypes)
+ Data.Aztecs.World.Archetypes: instance GHC.Classes.Eq Data.Aztecs.World.Archetypes.ArchetypeID
+ Data.Aztecs.World.Archetypes: instance GHC.Classes.Ord Data.Aztecs.World.Archetypes.ArchetypeID
+ Data.Aztecs.World.Archetypes: instance GHC.Show.Show Data.Aztecs.World.Archetypes.ArchetypeID
+ Data.Aztecs.World.Archetypes: instance GHC.Show.Show Data.Aztecs.World.Archetypes.Archetypes
+ Data.Aztecs.World.Archetypes: instance GHC.Show.Show Data.Aztecs.World.Archetypes.Node
+ Data.Aztecs.World.Archetypes: lookup :: Set ComponentID -> Archetypes -> Map ArchetypeID Archetype
+ Data.Aztecs.World.Archetypes: lookupArchetypeId :: Set ComponentID -> Archetypes -> Maybe ArchetypeID
+ Data.Aztecs.World.Archetypes: lookupNode :: ArchetypeID -> Archetypes -> Maybe Node
+ Data.Aztecs.World.Archetypes: map :: Set ComponentID -> (Archetype -> (a, Archetype)) -> Archetypes -> ([a], Archetypes)
+ Data.Aztecs.World.Archetypes: newtype ArchetypeID
+ Data.Aztecs.World.Components: ComponentID :: Int -> ComponentID
+ Data.Aztecs.World.Components: Components :: Map TypeRep ComponentID -> ComponentID -> Components
+ Data.Aztecs.World.Components: [componentIds] :: Components -> Map TypeRep ComponentID
+ Data.Aztecs.World.Components: [nextComponentId] :: Components -> ComponentID
+ Data.Aztecs.World.Components: [unComponentId] :: ComponentID -> Int
+ Data.Aztecs.World.Components: data Components
+ Data.Aztecs.World.Components: empty :: Components
+ Data.Aztecs.World.Components: insert :: forall a. Component a => Components -> (ComponentID, Components)
+ Data.Aztecs.World.Components: insert' :: forall c. Component c => Components -> (ComponentID, Components)
+ Data.Aztecs.World.Components: instance GHC.Show.Show Data.Aztecs.World.Components.Components
+ Data.Aztecs.World.Components: lookup :: forall a. Typeable a => Components -> Maybe ComponentID
+ Data.Aztecs.World.Components: newtype ComponentID
- Data.Aztecs.Access: spawn :: (Monad m, Component a, Typeable (StorageT a)) => a -> Access m EntityID
+ Data.Aztecs.Access: spawn :: (Monad m, ComponentIds (EntityT a), ToEntity a, Insert (Entity (EntityT a))) => a -> Access m EntityID
- Data.Aztecs.Query: Query :: (Components -> (Set ComponentID, Archetype -> ([Entity a], [Entity a] -> Archetype -> Archetype))) -> Query a
+ Data.Aztecs.Query: Query :: (Components -> QueryState a) -> Query a
- Data.Aztecs.Query: [runQuery'] :: Query a -> Components -> (Set ComponentID, Archetype -> ([Entity a], [Entity a] -> Archetype -> Archetype))
+ Data.Aztecs.Query: [runQuery'] :: Query a -> Components -> QueryState a
- Data.Aztecs.Query: all :: forall m a. (Monad m, ToEntity a, FromEntity a, ToQuery (EntityT a)) => Access m [a]
+ Data.Aztecs.Query: all :: forall a. (ToEntity a, FromEntity a, Queryable (EntityT a)) => World -> [a]
- Data.Aztecs.Query: map :: forall m i o. (Monad m, FromEntity i, ToEntity o, Intersect (EntityT i) (EntityT o), ToQuery (IntersectT (EntityT i) (EntityT o)), Difference (EntityT i) (EntityT o), ToQuery (DifferenceT (EntityT i) (EntityT o)), ConcatT (DifferenceT (EntityT i) (EntityT o)) (IntersectT (EntityT i) (EntityT o)) ~ EntityT i, IntersectT (EntityT i) (EntityT o) ~ EntityT o) => (i -> o) -> Access m [o]
+ Data.Aztecs.Query: map :: forall i o. Map (IsEq (Entity (EntityT i)) (Entity (EntityT o))) i o => (i -> o) -> World -> ([o], World)
- Data.Aztecs.World: World :: Map ArchetypeID Archetype -> Map (Set ComponentID) ArchetypeID -> Map ArchetypeID (Set ComponentID) -> ArchetypeID -> Components -> Map EntityID ArchetypeID -> EntityID -> World
+ Data.Aztecs.World: World :: Archetypes -> Components -> Map EntityID ArchetypeID -> EntityID -> World
- Data.Aztecs.World: [archetypes] :: World -> Map ArchetypeID Archetype
+ Data.Aztecs.World: [archetypes] :: World -> Archetypes
- Data.Aztecs.World: spawn :: forall a. (Component a, Typeable (StorageT a)) => a -> World -> (EntityID, World)
+ Data.Aztecs.World: spawn :: forall a. (ComponentIds (EntityT a), ToEntity a, Insert (Entity (EntityT a))) => a -> World -> (EntityID, World)
Files
- aztecs.cabal +75/−60
- bench/Iter.hs +2/−5
- examples/ECS.hs +16/−16
- src/Data/Aztecs.hs +10/−2
- src/Data/Aztecs/Access.hs +64/−4
- src/Data/Aztecs/Archetype.hs +0/−104
- src/Data/Aztecs/Component.hs +19/−0
- src/Data/Aztecs/Core.hs +0/−60
- src/Data/Aztecs/Entity.hs +54/−1
- src/Data/Aztecs/Query.hs +174/−111
- src/Data/Aztecs/Scheduler.hs +231/−0
- src/Data/Aztecs/Storage.hs +13/−1
- src/Data/Aztecs/System.hs +137/−0
- src/Data/Aztecs/View.hs +80/−0
- src/Data/Aztecs/World.hs +144/−78
- src/Data/Aztecs/World/Archetype.hs +138/−0
- src/Data/Aztecs/World/Archetypes.hs +114/−0
- src/Data/Aztecs/World/Components.hs +55/−0
- test/Main.hs +26/−1
aztecs.cabal view
@@ -1,60 +1,75 @@-cabal-version: 3.0-name: aztecs-version: 0.2.0.0-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.Access- Data.Aztecs.Archetype- Data.Aztecs.Core- Data.Aztecs.Entity- Data.Aztecs.Query- Data.Aztecs.Storage- Data.Aztecs.World-- hs-source-dirs: src- default-language: Haskell2010- ghc-options: -Wall- build-depends:- base >=4 && <5,- containers >=0.7,- mtl >=2--executable ecs- main-is: ECS.hs- hs-source-dirs: examples- default-language: Haskell2010- ghc-options: -Wall- build-depends:- base >=4 && <5,- aztecs--test-suite aztecs-test- type: exitcode-stdio-1.0- main-is: Main.hs- hs-source-dirs: test- default-language: Haskell2010- ghc-options: -Wall- build-depends:- base >=4 && <5,- aztecs,- hspec >=2--benchmark aztecs-bench- type: exitcode-stdio-1.0- main-is: Iter.hs- hs-source-dirs: bench- default-language: Haskell2010- ghc-options: -Wall- build-depends:- base >=4 && <5,- aztecs,- criterion >=1+cabal-version: 2.4 +name: aztecs +version: 0.3.0.0 +license: BSD-3-Clause +license-file: LICENSE +maintainer: matt@hunzinger.me +author: Matt Hunzinger +synopsis: A type-safe and friendly Entity-Component-System (ECS) for Haskell +description: The Entity-Component-System (ECS) pattern is commonly used in video game develop to represent world objects. + . + ECS follows the principal of composition over inheritence. Each type of + object (e.g. sword, monster, etc), in the game has a unique EntityId. Each + entity has various Components associated with it (material, weight, damage, etc). + Systems act on entities which have the required Components. +homepage: https://github.com/matthunz/aztecs +category: Game Engine + +source-repository head + type: git + location: https://github.com/matthunz/aztecs.git + +library + exposed-modules: + Data.Aztecs + Data.Aztecs.Access + Data.Aztecs.Component + Data.Aztecs.Entity + Data.Aztecs.Query + Data.Aztecs.Scheduler + Data.Aztecs.Storage + Data.Aztecs.System + Data.Aztecs.View + Data.Aztecs.World + Data.Aztecs.World.Archetype + Data.Aztecs.World.Archetypes + Data.Aztecs.World.Components + + hs-source-dirs: src + default-language: Haskell2010 + ghc-options: -Wall + build-depends: + base >=4 && <5, + containers >=0.7, + mtl >=2 + +executable ecs + main-is: ECS.hs + hs-source-dirs: examples + default-language: Haskell2010 + ghc-options: -Wall + build-depends: + base >=4 && <5, + aztecs + +test-suite aztecs-test + type: exitcode-stdio-1.0 + main-is: Main.hs + hs-source-dirs: test + default-language: Haskell2010 + ghc-options: -Wall + build-depends: + base >=4 && <5, + aztecs, + hspec >=2 + +benchmark aztecs-bench + type: exitcode-stdio-1.0 + main-is: Iter.hs + hs-source-dirs: bench + default-language: Haskell2010 + ghc-options: -Wall + build-depends: + base >=4 && <5, + aztecs, + criterion >=1
bench/Iter.hs view
@@ -3,7 +3,6 @@ import Criterion.Main import Data.Aztecs -import Data.Aztecs.Entity import qualified Data.Aztecs.Query as Q import Data.Aztecs.World (World) import qualified Data.Aztecs.World as W @@ -17,13 +16,11 @@ instance Component Velocity run :: World -> World -run w = - let !(_, w') = Q.mapWith Q.fetch Q.fetch (\(Velocity v :& Position x) -> Position (x + v)) w - in w' +run w = let !(_, w') = Q.mapWorld (\(Velocity v :& Position x) -> Position (x + v)) w in w' main :: IO () main = do - let w = + let !w = foldr ( \_ wAcc -> let (e, wAcc') = W.spawn (Position 0) wAcc
examples/ECS.hs view
@@ -1,13 +1,12 @@-{-# LANGUAGE DataKinds #-} -{-# LANGUAGE MonoLocalBinds #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TypeApplications #-} module Main where -import Control.Monad.IO.Class (MonadIO (..)) +import Control.Arrow ((>>>)) import Data.Aztecs import qualified Data.Aztecs.Access as A -import qualified Data.Aztecs.Query as Q -import qualified Data.Aztecs.World as W +import qualified Data.Aztecs.System as S newtype Position = Position Int deriving (Show) @@ -17,17 +16,18 @@ instance Component Velocity -app :: Access IO () -app = do - -- Spawn an entity with position and velocity components - e <- A.spawn (Position 0) - A.insert e (Velocity 1) +data Setup - -- Query for and update all matching entities - q <- Q.map (\(Velocity v :& Position x) -> Position (x + v)) - liftIO $ print q +instance System IO Setup where + task = S.queue (A.spawn_ (Position 0 :& Velocity 1)) +data Movement + +instance System IO Movement where + task = S.map (\(Position x :& Velocity v) -> Position (x + v)) >>> S.run print + +app :: Scheduler IO +app = schedule @_ @Startup @Setup [] <> schedule @_ @Update @Movement [] + main :: IO () -main = do - _ <- runAccess app W.empty - return () +main = run app
src/Data/Aztecs.hs view
@@ -5,9 +5,17 @@ EntityID, Entity, (:&) (..), + System (..), + Scheduler, + Startup, + Update, + run, + schedule, ) where import Data.Aztecs.Access (Access, runAccess) -import Data.Aztecs.Core (Component (..), EntityID) -import Data.Aztecs.Entity (Entity, (:&) (..)) +import Data.Aztecs.Component (Component (..)) +import Data.Aztecs.Entity (Entity, EntityID, (:&) (..)) +import Data.Aztecs.Scheduler (Scheduler, Startup, Update, run, schedule) +import Data.Aztecs.System (System (..))
src/Data/Aztecs/Access.hs view
@@ -1,30 +1,90 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} -module Data.Aztecs.Access where +module Data.Aztecs.Access + ( Access (..), + runAccess, + spawn, + spawn_, + insert, + all, + map, + lookup, + lookupQuery, + alter, + ) +where import Control.Monad.IO.Class (MonadIO) -import Control.Monad.State (MonadState (..), StateT (..)) -import Data.Aztecs.Core (Component (..), EntityID) +import Control.Monad.State (MonadState (..), StateT (..), gets) +import Data.Aztecs.Component (Component (..)) +import Data.Aztecs.Entity (ComponentIds, Entity, EntityID, EntityT, FromEntity (..), ToEntity) +import Data.Aztecs.Query (IsEq, Map, Query, Queryable (..)) +import qualified Data.Aztecs.Query as Q import Data.Aztecs.World (World) import qualified Data.Aztecs.World as W +import Data.Aztecs.World.Archetype (Insert) import Data.Data (Typeable) +import Prelude hiding (all, lookup, map) +-- | Access into the `World`. newtype Access m a = Access {unAccess :: StateT World m a} deriving (Functor, Applicative, Monad, MonadIO) +-- | Run an `Access` on a `World`, returning the output and updated `World`. runAccess :: Access m a -> World -> m (a, World) runAccess a = runStateT (unAccess a) -spawn :: (Monad m, Component a, Typeable (StorageT a)) => a -> Access m EntityID +-- | Spawn an entity with a component. +spawn :: + (Monad m, ComponentIds (EntityT a), ToEntity a, Insert (Entity (EntityT a))) => + a -> + Access m EntityID spawn c = Access $ do w <- get let (e, w') = W.spawn c w put w' return e +spawn_ :: (Monad m, ComponentIds (EntityT a), ToEntity a, Insert (Entity (EntityT a))) => a -> Access m () +spawn_ c = do + _ <- spawn c + return () + +-- | Insert a component into an entity. insert :: (Monad m, Component a, Typeable (StorageT a)) => EntityID -> a -> Access m () insert e c = Access $ do w <- get let w' = W.insert e c w put w' + +all :: forall m a. (Monad m, ToEntity a, FromEntity a, Queryable (EntityT a)) => Access m [a] +all = Access $ gets (fmap fromEntity . Q.queryAll (query @(EntityT a))) + +-- | Map over all entities that match this query, +-- storing the resulting components in the @World@. +map :: + forall m i o. + (Monad m, Map (IsEq (Entity (EntityT i)) (Entity (EntityT o))) i o) => + (i -> o) -> + Access m [o] +map f = Access $ do + w <- get + let (out, w') = Q.map @i @o f w + put w' + return out + +alter :: forall a m. (Monad m, FromEntity a, ToEntity a, Queryable (EntityT a)) => EntityID -> (a -> a) -> Access m () +alter eId f = Access $ do + w <- get + let w' = Q.alter eId f w + put w' + +lookup :: forall a m. (Monad m, FromEntity a, Queryable (EntityT a)) => EntityID -> Access m (Maybe a) +lookup eId = Access . gets $ Q.lookup eId + +lookupQuery :: (Monad m) => EntityID -> Query a -> Access m (Maybe (Entity a)) +lookupQuery eId q = Access . gets $ Q.lookupQuery eId q
− src/Data/Aztecs/Archetype.hs
@@ -1,104 +0,0 @@-{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} - -module Data.Aztecs.Archetype where - -import Data.Aztecs.Core (Component (..), ComponentID, EntityID (..)) -import qualified Data.Aztecs.Storage as S -import Data.Bifunctor (Bifunctor (..)) -import Data.Dynamic (Dynamic, fromDynamic, toDyn) -import Data.Map (Map) -import qualified Data.Map as Map -import Data.Maybe (fromMaybe) -import Prelude hiding (all, lookup) - -data AnyStorage = AnyStorage - { storageDyn :: Dynamic, - insertDyn :: Int -> Dynamic -> Dynamic -> Dynamic, - removeDyn :: Int -> Dynamic -> (Maybe Dynamic, Dynamic), - removeAny :: Int -> Dynamic -> (Maybe AnyStorage, Dynamic) - } - -instance Show AnyStorage where - show s = "AnyStorage " ++ show (storageDyn s) - -anyStorage :: forall s a. (S.Storage s a) => s a -> AnyStorage -anyStorage s = - AnyStorage - { storageDyn = toDyn s, - insertDyn = \i cDyn sDyn -> - fromMaybe sDyn $ do - s' <- fromDynamic @(s a) sDyn - c <- fromDynamic cDyn - return . toDyn $ S.insert i c s', - removeDyn = \i dyn -> case fromDynamic @(s a) dyn of - Just s' -> let (a, b) = S.remove i s' in (fmap toDyn a, toDyn b) - Nothing -> (Nothing, dyn), - removeAny = \i dyn -> case fromDynamic @(s a) dyn of - Just s' -> let (a, b) = S.remove i s' in (fmap (anyStorage . S.singleton @s i) a, toDyn b) - Nothing -> (Nothing, dyn) - } - -newtype Archetype = Archetype {storages :: Map ComponentID AnyStorage} - deriving (Show) - -empty :: Archetype -empty = Archetype {storages = Map.empty} - -lookupStorage :: (Component a) => ComponentID -> Archetype -> Maybe (StorageT a a) -lookupStorage cId w = do - dynS <- Map.lookup cId (storages w) - fromDynamic (storageDyn dynS) - -insert :: forall a. (Component a) => EntityID -> ComponentID -> a -> Archetype -> Archetype -insert e cId c arch = - let storage = case lookupStorage cId arch of - Just s -> S.insert (unEntityId e) c s - Nothing -> S.singleton @(StorageT a) @a (unEntityId e) c - in arch {storages = Map.insert cId (anyStorage storage) (storages arch)} - -all :: (Component a) => ComponentID -> Archetype -> [(EntityID, a)] -all cId arch = fromMaybe [] $ do - s <- lookupStorage cId arch - return . map (first EntityID) $ S.all s - -lookup :: forall a. (Component a) => EntityID -> ComponentID -> Archetype -> Maybe a -lookup e cId w = lookupStorage cId w >>= S.lookup (unEntityId e) - -insertAscList :: forall a. (Component a) => ComponentID -> [(EntityID, a)] -> Archetype -> Archetype -insertAscList cId as arch = arch {storages = Map.insert cId (anyStorage $ S.fromAscList @(StorageT a) (map (first unEntityId) as)) (storages arch)} - -remove :: EntityID -> Archetype -> (Map ComponentID Dynamic, Archetype) -remove e arch = - foldr - ( \(cId, s) (dynAcc, archAcc) -> - let (dynA, dynS) = removeDyn s (unEntityId e) (storageDyn s) - dynAcc' = case dynA of - Just d -> Map.insert cId d dynAcc - Nothing -> dynAcc - in ( dynAcc', - archAcc {storages = Map.insert cId (s {storageDyn = dynS}) (storages archAcc)} - ) - ) - (Map.empty, arch) - (Map.toList $ storages arch) - -removeStorages :: EntityID -> Archetype -> (Map ComponentID AnyStorage, Archetype) -removeStorages e arch = - foldr - ( \(cId, s) (dynAcc, archAcc) -> - let (dynA, dynS) = removeAny s (unEntityId e) (storageDyn s) - dynAcc' = case dynA of - Just d -> Map.insert cId d dynAcc - Nothing -> dynAcc - in ( dynAcc', - archAcc {storages = Map.insert cId (s {storageDyn = dynS}) (storages archAcc)} - ) - ) - (Map.empty, arch) - (Map.toList $ storages arch)
+ src/Data/Aztecs/Component.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeFamilies #-} + +module Data.Aztecs.Component (Component (..), ComponentID (..)) where + +import Data.Aztecs.Storage (Storage) +import Data.IntMap (IntMap) +import Data.Kind (Type) +import Data.Typeable (Typeable) + +-- | Component ID. +newtype ComponentID = ComponentID {unComponentId :: Int} + deriving (Eq, Ord, Show) + +-- | Component that can be stored in the `World`. +class (Typeable a, Storage (StorageT a) a) => Component a where + -- | `Storage` of this component. + type StorageT a :: Type -> Type + type StorageT a = IntMap
− src/Data/Aztecs/Core.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} - -module Data.Aztecs.Core - ( EntityID (..), - Component (..), - ComponentID (..), - Components (..), - emptyComponents, - insertComponentId, - lookupComponentId, - ) -where - -import Data.Aztecs.Storage (Storage) -import Data.IntMap (IntMap) -import Data.Kind (Type) -import Data.Map (Map) -import qualified Data.Map as Map -import Data.Typeable (Proxy (..), TypeRep, Typeable, typeOf) - -newtype EntityID = EntityID {unEntityId :: Int} - deriving (Eq, Ord, Show) - -newtype ComponentID = ComponentID {unComponentId :: Int} - deriving (Eq, Ord, Show) - -class (Typeable a, Storage (StorageT a) a) => Component a where - type StorageT a :: Type -> Type - type StorageT a = IntMap - -data Components = Components - { componentIds :: Map TypeRep ComponentID, - nextComponentId :: ComponentID - } - deriving (Show) - -emptyComponents :: Components -emptyComponents = - Components - { componentIds = mempty, - nextComponentId = ComponentID 0 - } - -lookupComponentId :: forall a. (Typeable a) => Components -> Maybe ComponentID -lookupComponentId cs = Map.lookup (typeOf (Proxy @a)) (componentIds cs) - -insertComponentId :: forall c. (Component c) => Components -> (ComponentID, Components) -insertComponentId cs = - let cId = nextComponentId cs - in ( cId, - cs - { componentIds = Map.insert (typeOf (Proxy @c)) cId (componentIds cs), - nextComponentId = ComponentID (unComponentId cId + 1) - } - )
src/Data/Aztecs/Entity.hs view
@@ -12,7 +12,8 @@ {-# LANGUAGE UndecidableInstances #-} module Data.Aztecs.Entity - ( Entity (..), + ( EntityID (..), + Entity (..), EntityT, FromEntity (..), ToEntity (..), @@ -23,15 +24,27 @@ Difference (..), SplitT, Split (..), + Has (..), + Sort (..), + ComponentIds(..), concat, (<&>), (:&) (..), ) where +import Data.Aztecs.Component (Component, ComponentID) +import Data.Aztecs.World.Components (Components) +import qualified Data.Aztecs.World.Components as CS import Data.Kind (Type) +import Data.Set (Set) +import qualified Data.Set as Set import Prelude hiding (concat) +-- | Entity ID. +newtype EntityID = EntityID {unEntityId :: Int} + deriving (Eq, Ord, Show) + data Entity (ts :: [Type]) where ENil :: Entity '[] ECons :: t -> Entity ts -> Entity (t ': ts) @@ -51,6 +64,12 @@ instance (Show a, Show' (Entity as)) => Show' (Entity (a ': as)) where showRow (ECons x xs) = ", " ++ show x ++ showRow xs +instance Eq (Entity '[]) where + ENil == ENil = True + +instance (Eq a, Eq (Entity as)) => Eq (Entity (a ': as)) where + ECons x xs == ECons y ys = x == y && xs == ys + (<&>) :: Entity as -> a -> Entity (a : as) (<&>) es c = ECons c es @@ -160,3 +179,37 @@ instance (Difference' (ElemT a bs) (a ': as) bs) => Difference (a ': as) bs where difference = difference' @(ElemT a bs) + +class Has a e where + component :: e -> a + setComponent :: a -> e -> e + +instance {-# OVERLAPPING #-} Has a (Entity (a ': ts)) where + component (ECons x _) = x + setComponent x (ECons _ xs) = ECons x xs + +instance {-# OVERLAPPING #-} (Has a (Entity ts)) => Has a (Entity (b ': ts)) where + component (ECons _ xs) = component xs + setComponent x (ECons y xs) = ECons y (setComponent x xs) + +class Sort (a :: [Type]) (b :: [Type]) where + sort :: Entity a -> Entity b + +instance Sort as '[] where + sort _ = ENil + +instance (Has b (Entity as), Sort as bs) => Sort as (b ': bs) where + sort es = ECons (component es) (sort es) + +class ComponentIds (a :: [Type]) where + componentIds :: Components -> (Set ComponentID, Components) + +instance ComponentIds '[] where + componentIds cs = (Set.empty, cs) + +instance (Component a, ComponentIds as) => ComponentIds (a ': as) where + componentIds cs = + let (cId, cs') = CS.insert @a cs + (cIds, cs'') = componentIds @as cs' + in (Set.insert cId cIds, cs'') +
src/Data/Aztecs/Query.hs view
@@ -3,41 +3,59 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} module Data.Aztecs.Query ( Query (..), (<?>), fetch, + fetchId, all, - allWorld, + queryAll, + queryAll', map, - mapWorld, - mapWith, + Queryable (..), + lookup, + lookupQuery, + alter, + QueryState (..), + IsEq, + Map (..), ) where -import Control.Monad.State (MonadState (..), gets) -import Data.Aztecs.Access (Access (Access)) -import Data.Aztecs.Archetype (Archetype) -import qualified Data.Aztecs.Archetype as A -import Data.Aztecs.Core -import Data.Aztecs.Entity (ConcatT, Difference, DifferenceT, Entity (..), EntityT, FromEntity (..), Intersect, IntersectT, ToEntity (..)) +import Data.Aztecs.Component +import Data.Aztecs.Entity (ConcatT, Difference, DifferenceT, Entity (..), EntityID, EntityT, FromEntity (..), Intersect, IntersectT, Sort, ToEntity (..)) import qualified Data.Aztecs.Entity as E import Data.Aztecs.World (World (..)) +import Data.Aztecs.World.Archetype (Archetype) +import qualified Data.Aztecs.World.Archetype as A +import Data.Aztecs.World.Archetypes (Archetypes, Node (nodeArchetype)) +import qualified Data.Aztecs.World.Archetypes as AS +import Data.Aztecs.World.Components (Components) +import qualified Data.Aztecs.World.Components as CS import Data.Data (Typeable) import qualified Data.Map as Map import Data.Maybe (fromMaybe) import Data.Set (Set) import qualified Data.Set as Set -import Prelude hiding (all, map) +import Prelude hiding (all, lookup, map) +data QueryState a = QueryState + { queryStateComponentIds :: Set ComponentID, + queryStateAll :: Archetype -> ([Entity a], [Entity a] -> Archetype -> Archetype), + queryStateLookup :: EntityID -> Archetype -> Maybe (Entity a), + queryStateInsert :: EntityID -> Archetype -> Entity a -> Archetype + } + -- | Query into the `World`. -newtype Query a - = Query {runQuery' :: Components -> (Set ComponentID, Archetype -> ([Entity a], [Entity a] -> Archetype -> Archetype))} +newtype Query a = Query {runQuery' :: Components -> QueryState a} (<?>) :: (E.Split a (ConcatT a b), E.SplitT a (ConcatT a b) ~ b) => @@ -45,124 +63,169 @@ Query b -> Query (ConcatT a b) (Query a) <?> (Query b) = Query $ \cs -> - let (aIds, a') = a cs - (bIds, b') = b cs - in ( aIds <> bIds, - \arch -> - let (a'', aF) = a' arch - (b'', bF) = b' arch - in ( uncurry E.concat <$> zip a'' b'', - \new newArch -> let (as, bs) = unzip $ fmap E.split new in bF bs $ aF as newArch - ) - ) + let aQS = a cs + bQS = b cs + in QueryState + { queryStateComponentIds = queryStateComponentIds aQS <> queryStateComponentIds bQS, + queryStateAll = \arch -> + let (a'', aF) = queryStateAll aQS arch + (b'', bF) = queryStateAll bQS arch + in ( uncurry E.concat <$> zip a'' b'', + \new newArch -> let (as, bs) = unzip $ fmap E.split new in bF bs $ aF as newArch + ), + queryStateLookup = \eId arch -> do + aE <- queryStateLookup aQS eId arch + bE <- queryStateLookup bQS eId arch + return $ E.concat aE bE, + queryStateInsert = \eId arch e -> + let (aE, bE) = E.split e + arch' = queryStateInsert aQS eId arch aE + in queryStateInsert bQS eId arch' bE + } +-- | Fetch a `Component` by its type. fetch :: forall a. (Component a, Typeable (StorageT a)) => Query '[a] fetch = Query $ \cs -> - let cId = fromMaybe (error "TODO") (lookupComponentId @a cs) - in ( Set.singleton cId, - \arch -> - let as = A.all cId arch - in ( fmap (\x -> ECons (snd x) ENil) as, - A.insertAscList cId . fmap (\((e, _), ECons a ENil) -> (e, a)) . zip as - ) - ) + let cId = fromMaybe (error "TODO") (CS.lookup @a cs) + in QueryState + { queryStateComponentIds = Set.singleton cId, + queryStateAll = \arch -> + let as = A.all cId arch + in ( fmap (\x -> ECons (snd x) ENil) as, + A.insertAscList cId . fmap (\((e, _), ECons a ENil) -> (e, a)) . zip as + ), + queryStateLookup = \eId arch -> do + a <- A.lookup eId cId arch + return $ ECons a ENil, + queryStateInsert = \eId arch e -> let (_, arch', _) = A.insert eId e arch cs in arch' + } -all :: forall m a. (Monad m, ToEntity a, FromEntity a, ToQuery (EntityT a)) => Access m [a] -all = Access $ gets (fmap fromEntity . allWorld (query @(EntityT a))) +-- | Fetch a `Component` by its `ComponentID`. +fetchId :: forall a. (Component a, Typeable (StorageT a)) => ComponentID -> Query '[a] +fetchId cId = fetch' @a (const cId) -allWorld :: Query a -> World -> [Entity a] -allWorld q w = - let (cIds, g) = runQuery' q (components w) - res = do - aId <- Map.lookup cIds (archetypeIds w) - Map.lookup aId (archetypes w) - in case res of - Just arch -> fst $ g arch - Nothing -> [] +fetch' :: forall a. (Component a, Typeable (StorageT a)) => (Components -> ComponentID) -> Query '[a] +fetch' f = Query $ \cs -> + let cId = f cs + in QueryState + { queryStateComponentIds = Set.singleton cId, + queryStateAll = \arch -> + let as = A.all cId arch + in ( fmap (\x -> ECons (snd x) ENil) as, + A.insertAscList cId . fmap (\((e, _), ECons a ENil) -> (e, a)) . zip as + ), + queryStateLookup = \eId arch -> do + a <- A.lookup eId cId arch + return $ ECons a ENil, + queryStateInsert = \eId arch e -> let (_, arch', _) = A.insert eId e arch cs in arch' + } -mapWith :: - (FromEntity i, ToEntity o, EntityT i ~ ConcatT a b, EntityT o ~ b) => - Query a -> - Query b -> - (i -> o) -> - World -> - ([o], World) -mapWith a b f w = - let (aCIds, aG) = runQuery' a (components w) - (bCIds, bG) = runQuery' b (components w) - res = do - aId <- Map.lookup (aCIds <> bCIds) (archetypeIds w) - arch <- Map.lookup aId (archetypes w) - return (aId, arch) - in case res of - Just (aId, arch) -> - let (as, _) = aG arch - (bs, bH) = bG arch - es = fmap (\(aE, bE) -> f $ fromEntity (E.concat aE bE)) (zip as bs) - arch' = bH (fmap (toEntity . toEntity) es) arch - in (es, w {archetypes = Map.insert aId arch' (archetypes w)}) - Nothing -> ([], w) +all :: forall a. (ToEntity a, FromEntity a, Queryable (EntityT a)) => World -> [a] +all = fmap fromEntity . queryAll (query @(EntityT a)) -class ToQuery a where - query :: Query a +queryAll :: Query a -> World -> [Entity a] +queryAll q w = fromMaybe [] $ do + let qS = runQuery' q (components w) + return $ concatMap (fst . queryStateAll qS) (AS.lookup (queryStateComponentIds qS) (archetypes w)) -instance ToQuery '[] where - query = Query $ const (Set.empty, const ([], \_ arch' -> arch')) +queryAll' :: Query a -> Archetypes -> Components -> [Entity a] +queryAll' q as cs = fromMaybe [] $ do + let qS = runQuery' q cs + return $ concatMap (fst . queryStateAll qS) (AS.lookup (queryStateComponentIds qS) as) -instance {-# OVERLAPPING #-} (Component a, Typeable (StorageT a)) => ToQuery '[a] where +class Queryable a where + query :: Query a + +instance {-# OVERLAPPING #-} (Component a, Typeable (StorageT a)) => Queryable '[a] where query = fetch @a -instance (Component a, Typeable (StorageT a), ToQuery as) => ToQuery (a ': as) where +instance (Component a, Typeable (StorageT a), Queryable as) => Queryable (a ': as) where query = fetch @a <?> query @as -mapWorld :: +-- | Map over all entities that match this query, +-- storing the resulting components in the @World@. +map :: forall i o. - ( FromEntity i, - ToEntity o, - Intersect (EntityT i) (EntityT o), - ToQuery (IntersectT (EntityT i) (EntityT o)), - Difference (EntityT i) (EntityT o), - ToQuery (DifferenceT (EntityT i) (EntityT o)), - ConcatT (DifferenceT (EntityT i) (EntityT o)) (IntersectT (EntityT i) (EntityT o)) ~ EntityT i, - IntersectT (EntityT i) (EntityT o) ~ EntityT o - ) => + (Map (IsEq (Entity (EntityT i)) (Entity (EntityT o))) i o) => (i -> o) -> World -> ([o], World) -mapWorld f w = - let i = query @(IntersectT (EntityT i) (EntityT o)) - o = query @(DifferenceT (EntityT i) (EntityT o)) - (aCIds, aG) = runQuery' i (components w) - (bCIds, bG) = runQuery' o (components w) - res = do - aId <- Map.lookup (aCIds <> bCIds) (archetypeIds w) - arch <- Map.lookup aId (archetypes w) - return (aId, arch) - in case res of - Just (aId, arch) -> - let (as, aH) = aG arch - (bs, _) = bG arch - es = fmap (\(aE, bE) -> f $ fromEntity @i (E.concat bE aE)) (zip as bs) - arch' = aH (fmap (\x -> let e = toEntity x in e) es) arch - in (es, w {archetypes = Map.insert aId arch' (archetypes w)}) - Nothing -> ([], w) +map f w = + let (o, arches) = map' @(IsEq (Entity (EntityT i)) (Entity (EntityT o))) f (components w) AS.map (archetypes w) + in (o, w {archetypes = arches}) -map :: - forall m i o. - ( Monad m, - FromEntity i, +-- Returns @True@ if @a@ and @b@ are the same type. +type family IsEq a b :: Bool where + IsEq a a = 'True + IsEq a b = 'False + +-- Map over an entity's components. +-- +-- @flag@ indicates if the input and output entities are the same, +-- in order to specialize at compile-time. +-- +-- If the input and output entities are different, a reader and writer query are combined into one. +-- Otherwise, if the input and output entities are the same, we can use a more efficient implementation. +class Map (flag :: Bool) i o where + map' :: (i -> o) -> Components -> (Set ComponentID -> (Archetype -> ([o], Archetype)) -> a -> ([[o]], a)) -> a -> ([o], a) + +instance (FromEntity i, ToEntity o, EntityT i ~ a, EntityT o ~ a, Queryable a) => Map 'True i o where + map' f cs mapArches arches = fromMaybe ([], arches) $ do + let qS = runQuery' (query @a) cs + go arch = + let (as, h) = queryStateAll qS arch + as' = fmap (f . fromEntity) as + arch' = h (fmap toEntity as') arch + in (as', arch') + let (es, arches') = mapArches (queryStateComponentIds qS) go arches' + return (concat es, arches') + +instance + ( FromEntity i, ToEntity o, Intersect (EntityT i) (EntityT o), - ToQuery (IntersectT (EntityT i) (EntityT o)), + Queryable (IntersectT (EntityT i) (EntityT o)), Difference (EntityT i) (EntityT o), - ToQuery (DifferenceT (EntityT i) (EntityT o)), - ConcatT (DifferenceT (EntityT i) (EntityT o)) (IntersectT (EntityT i) (EntityT o)) ~ EntityT i, - IntersectT (EntityT i) (EntityT o) ~ EntityT o + Queryable (DifferenceT (EntityT i) (EntityT o)), + Sort (ConcatT (DifferenceT (EntityT i) (EntityT o)) (IntersectT (EntityT i) (EntityT o))) (EntityT i), + Sort (EntityT o) (IntersectT (EntityT i) (EntityT o)) ) => - (i -> o) -> - Access m [o] -map f = Access $ do - w <- get - let (out, w') = mapWorld @i @o f w - put w' - return out + Map 'False i o + where + map' f cs mapArches arches = fromMaybe ([], arches) $ do + let i = query @(IntersectT (EntityT i) (EntityT o)) + o = query @(DifferenceT (EntityT i) (EntityT o)) + aQS = runQuery' i cs + bQS = runQuery' o cs + g arch = + let (as, aH) = queryStateAll aQS arch + (bs, _) = queryStateAll bQS arch + es = fmap (\(aE, bE) -> f $ fromEntity @i (E.sort $ E.concat bE aE)) (zip as bs) + arch' = aH (fmap (\x -> let e = E.sort $ toEntity x in e) es) arch + in (es, arch') + (es', arches') = mapArches (queryStateComponentIds aQS <> queryStateComponentIds bQS) g arches + return (concat es', arches') + +lookup :: forall a. (FromEntity a, Queryable (EntityT a)) => EntityID -> World -> Maybe a +lookup eId w = fromEntity <$> lookupQuery eId (query @(EntityT a)) w + +lookupQuery :: EntityID -> Query a -> World -> Maybe (Entity a) +lookupQuery eId q w = do + let qS = runQuery' q (components w) + aId <- Map.lookup eId (entities w) + node <- AS.lookupNode aId (archetypes w) + queryStateLookup qS eId (nodeArchetype node) + +alter :: forall a. (FromEntity a, ToEntity a, Queryable (EntityT a)) => EntityID -> (a -> a) -> World -> World +alter eId f w = fromMaybe w $ do + let qS = runQuery' (query @(EntityT a)) (components w) + aId <- Map.lookup eId (entities w) + node <- AS.lookupNode aId (archetypes w) + e <- queryStateLookup qS eId (nodeArchetype node) + let e' = f (fromEntity e) + arch' = queryStateInsert qS eId (nodeArchetype node) (toEntity e') + return $ + w + { archetypes = + (archetypes w) {AS.nodes = Map.insert aId node {nodeArchetype = arch'} (AS.nodes $ archetypes w)} + }
+ src/Data/Aztecs/Scheduler.hs view
@@ -0,0 +1,231 @@+{-# 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
src/Data/Aztecs/Storage.hs view
@@ -2,18 +2,30 @@ {-# LANGUAGE MonoLocalBinds #-} {-# LANGUAGE MultiParamTypeClasses #-} -module Data.Aztecs.Storage (Storage(..)) where +module Data.Aztecs.Storage (Storage (..)) where import Data.Data (Typeable) import Data.IntMap (IntMap) import qualified Data.IntMap as IntMap +-- | Component storage, containing zero or many components of the same type. class (Typeable (s a), Typeable a) => Storage s a where + -- | Storage with a single component. singleton :: Int -> a -> s a + + -- | All components in the storage. all :: s a -> [(Int, a)] + + -- | Insert a component into the storage. insert :: Int -> a -> s a -> s a + + -- | Lookup a component in the storage. lookup :: Int -> s a -> Maybe a + + -- | Convert a sorted list of components (in ascending order) into a storage. fromAscList :: [(Int, a)] -> s a + + -- | Remove a component from the storage. remove :: Int -> s a -> (Maybe a, s a) instance (Typeable a) => Storage IntMap a where
+ src/Data/Aztecs/System.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} + +module Data.Aztecs.System where + +import Control.Arrow (Arrow (..)) +import Control.Category (Category (..)) +import Data.Aztecs.Access (Access, runAccess) +import Data.Aztecs.Entity (ComponentIds (componentIds), Entity, EntityT) +import Data.Aztecs.Query (IsEq, Queryable) +import qualified Data.Aztecs.Query as Q +import Data.Aztecs.View (View (..)) +import qualified Data.Aztecs.View as V +import Data.Aztecs.World (World (..)) +import Data.Aztecs.World.Components (ComponentID, Components) +import Data.Data (Typeable) +import Data.Set (Set) + +class (Typeable a) => System m a where + task :: Task m () () + +runSystem :: forall m s. (System m s, Monad m) => World -> m World +runSystem w = do + let (cs, _, f) = runTask (task @m @s) (components w) + w' = w {components = cs} + (_, g, access) <- f () w' + (_, w'') <- runAccess access w' + return $ g w'' + +-- | System task. +newtype Task m i o = Task + { runTask :: + Components -> + ( Components, + [Set ComponentID], + i -> World -> m (o, World -> World, Access m ()) + ) + } + deriving (Functor) + +instance (Monad m) => Applicative (Task m i) where + pure a = Task (,[],\_ _ -> pure (a, Prelude.id, pure ())) + f <*> a = + Task $ \w -> + let (w', cIds, f') = runTask f w + (w'', cIds', a') = runTask a w' + in ( w'', + cIds <> cIds', + \i cs -> do + (f'', fG, access) <- f' i cs + (a'', aG, access') <- a' i cs + return (f'' a'', fG Prelude.. aG, access >> access') + ) + +instance (Monad m) => Category (Task m) where + id = Task (,[],\i _ -> pure (i, Prelude.id, pure ())) + (.) t1 t2 = Task $ \w -> + let (w', cIds, f) = runTask t2 w + (w'', cIds', g) = runTask t1 w' + in ( w'', + cIds <> cIds', + \i cs -> do + (o, f', access) <- f i cs + (a, g', access') <- g o cs + return (a, g' Prelude.. f', access >> access') + ) + +instance (Monad m) => Arrow (Task m) where + arr f = Task (,[],\i _ -> pure (f i, Prelude.id, pure ())) + first t = + Task $ \w -> + let (w', cIds, f) = runTask t w + in ( w', + cIds, + \(i, x) cs -> do + (o, f', access) <- f i cs + return ((o, x), f', access) + ) + +all :: forall m v. (Monad m, ComponentIds v, Queryable v) => Task m () [Entity v] +all = view @_ @v (\v cs -> pure $ V.queryAll v cs) + +map :: + forall m i o. + ( Monad m, + ComponentIds (EntityT i), + Queryable (EntityT i), + Q.Map (IsEq (Entity (EntityT i)) (Entity (EntityT o))) i o + ) => + (i -> o) -> + Task m () [o] +map f = mapView (\v cs -> pure $ V.map f v cs) + +view :: + forall m v a. + (Monad m, ComponentIds v, Queryable v) => + (View v -> Components -> m a) -> + Task m () a +view f = Task $ \cs -> + let (cIds, cs') = componentIds @v cs + in ( cs', + [cIds], + \_ w -> + let (v, w') = V.view @v w + in (,Prelude.id,pure ()) <$> f v (components w') + ) + +mapView :: + forall m v a. + (Monad m, ComponentIds v, Queryable v) => + (View v -> Components -> m (a, View v)) -> + Task m () a +mapView f = Task $ \cs -> + let (cIds, cs') = componentIds @v cs + in ( cs', + [cIds], + \_ w -> + let (v, w') = V.view @v w + in (\(a, v') -> (a, V.unview v', pure ())) <$> f v (components w') + ) + +-- | Queue an `Access` to alter the world after this task is complete. +queue :: (Monad m) => Access m () -> Task m () () +queue a = Task (,[],\_ _ -> pure ((), Prelude.id, a)) + +run :: (Monad m) => (i -> m o) -> Task m i o +run f = + Task + (,[],\i _ -> do + o <- f i + return (o, Prelude.id, pure ()))
+ src/Data/Aztecs/View.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +module Data.Aztecs.View where + +import Data.Aztecs.Entity (ComponentIds, EntityT, componentIds, Entity) +import Data.Aztecs.Query (IsEq, Query (..), QueryState (..), Queryable (..)) +import qualified Data.Aztecs.Query as Q +import Data.Aztecs.World (ArchetypeID, World) +import qualified Data.Aztecs.World as W +import Data.Aztecs.World.Archetype (Archetype) +import Data.Aztecs.World.Archetypes (Archetypes) +import qualified Data.Aztecs.World.Archetypes as AS +import Data.Aztecs.World.Components (Components) +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Maybe (fromMaybe) + +data View a = View + { viewArchetypes :: Map ArchetypeID Archetype, + viewQuery :: Query a + } + +view :: forall a. (ComponentIds a, Queryable a) => World -> (View a, World) +view w = + let (v, cs') = view' @a (W.components w) (W.archetypes w) + in (v, w {W.components = cs'}) + +view' :: forall a. (ComponentIds a, Queryable a) => Components -> Archetypes -> (View a, Components) +view' cs as = + let (cIds, cs') = componentIds @a cs + in ( View + { viewArchetypes = AS.lookup cIds as, + viewQuery = query @a + }, + cs' + ) + +unview :: View a -> World -> World +unview v w = + w + { W.archetypes = + foldr + (\(aId, arch) as -> AS.adjustArchetype aId (const arch) as) + (W.archetypes w) + (Map.toList $ viewArchetypes v) + } + +queryAll :: View a -> Components -> [Entity a] +queryAll v cs = fromMaybe [] $ do + let qS = runQuery' (viewQuery v) cs + return $ concatMap (fst . queryStateAll qS) (Map.elems $ viewArchetypes v) + +-- | Map over all entities that match this query, +-- storing the resulting components in the @View@. +map :: + forall i o. + (Q.Map (IsEq (Entity (EntityT i)) (Entity (EntityT o))) i o) => + (i -> o) -> + View (EntityT i) -> + Components -> + ([o], View (EntityT i)) +map f v cs = + let (o, arches) = + Q.map' @(IsEq (Entity (EntityT i)) (Entity (EntityT o))) + f + cs + ( \_ g arches' -> + foldr + ( \(aId, arch) (acc, archAcc) -> + let (os, arch') = g arch + in (os : acc, Map.insert aId arch' archAcc) + ) + ([], Map.empty) + (Map.toList arches') + ) + (viewArchetypes v) + in (o, v {viewArchetypes = arches})
src/Data/Aztecs/World.hs view
@@ -9,35 +9,38 @@ World (..), empty, spawn, + spawnComponent, spawnWithId, spawnWithArchetypeId, spawnEmpty, insert, insertWithId, - insertArchetype, despawn, ) where -import Data.Aztecs.Archetype (Archetype (..)) -import qualified Data.Aztecs.Archetype as A -import Data.Aztecs.Core (Component (..), ComponentID, Components (..), EntityID (..), emptyComponents, insertComponentId) +import Data.Aztecs.Component + ( Component (..), + ComponentID, + ) +import Data.Aztecs.Entity (ComponentIds, Entity, EntityID (..), ToEntity (..), EntityT) +import qualified Data.Aztecs.Entity as E +import Data.Aztecs.World.Archetype (Archetype (..), Insert) +import qualified Data.Aztecs.World.Archetype as A +import Data.Aztecs.World.Archetypes (ArchetypeID, Archetypes, Node (..)) +import qualified Data.Aztecs.World.Archetypes as AS +import Data.Aztecs.World.Components (Components (..)) +import qualified Data.Aztecs.World.Components as CS import Data.Dynamic (Dynamic) import Data.Map (Map) import qualified Data.Map as Map -import Data.Set (Set) +import Data.Maybe (fromMaybe) import qualified Data.Set as Set import Data.Typeable (Proxy (..), Typeable, typeOf) -newtype ArchetypeID = ArchetypeID {unArchetypeId :: Int} - deriving (Eq, Ord, Show) - -- | World of entities and their components. data World = World - { archetypes :: Map ArchetypeID Archetype, - archetypeIds :: Map (Set ComponentID) ArchetypeID, - archetypeComponents :: Map ArchetypeID (Set ComponentID), - nextArchetypeId :: ArchetypeID, + { archetypes :: Archetypes, components :: Components, entities :: Map EntityID ArchetypeID, nextEntityId :: EntityID @@ -48,21 +51,60 @@ empty :: World empty = World - { archetypes = mempty, - archetypeIds = mempty, - archetypeComponents = mempty, - nextArchetypeId = ArchetypeID 0, - components = emptyComponents, + { archetypes = AS.empty, + components = CS.empty, entities = mempty, nextEntityId = EntityID 0 } +spawn :: + forall a. + (ComponentIds (EntityT a), ToEntity a, Insert (Entity (EntityT a))) => + a -> + World -> + (EntityID, World) +spawn e w = + let (eId, w') = spawnEmpty w + (cIds, components') = E.componentIds @(EntityT a) (components w) + in case AS.lookupArchetypeId cIds (archetypes w) of + Just aId -> fromMaybe (eId, w') $ do + node <- AS.lookupNode aId (archetypes w) + let (_, arch', components'') = A.insert eId (toEntity e) (nodeArchetype node) components' + return + ( eId, + w + { archetypes = (archetypes w) {AS.nodes = Map.insert aId node {nodeArchetype = arch'} (AS.nodes $ archetypes w)}, + components = components'', + entities = Map.insert eId aId (entities w) + } + ) + Nothing -> + let (_, arch, components'') = A.insert eId (toEntity e) A.empty components' + (aId, arches) = + AS.insertArchetype + cIds + ( Node + { nodeComponentIds = cIds, + nodeArchetype = arch, + nodeAdd = Map.empty, + nodeRemove = Map.empty + } + ) + (archetypes w') + in ( eId, + w' + { archetypes = arches, + entities = Map.insert eId aId (entities w), + components = components'' + } + ) + -- | Spawn an entity with a component. -spawn :: forall a. (Component a, Typeable (StorageT a)) => a -> World -> (EntityID, World) -spawn c w = case Map.lookup (typeOf (Proxy @a)) (componentIds (components w)) of +spawnComponent :: forall a. (Component a, Typeable (StorageT a)) => a -> World -> (EntityID, World) +spawnComponent c w = case Map.lookup (typeOf (Proxy @a)) (componentIds (components w)) of Just cId -> spawnWithId cId c w Nothing -> - let (cId, cs) = insertComponentId @a (components w) + let (cId, cs) = CS.insert @a (components w) in spawnWithId cId c w {components = cs} -- | Spawn an empty entity. @@ -79,16 +121,21 @@ (EntityID, World) spawnWithId cId c w = let (e, w') = spawnEmpty w - in case Map.lookup (Set.singleton cId) (archetypeIds w) of + in case AS.lookupArchetypeId (Set.singleton cId) (archetypes w) of Just aId -> (e, spawnWithArchetypeId' e aId cId c w') Nothing -> - ( e, - snd $ - insertArchetype - (Set.singleton cId) - (A.insert e cId c A.empty) - w' {entities = Map.insert e (nextArchetypeId w) (entities w)} - ) + let (aId, arches) = + AS.insertArchetype + (Set.singleton cId) + ( Node + { nodeComponentIds = Set.singleton cId, + nodeArchetype = A.insertComponent e cId c A.empty, + nodeAdd = Map.empty, + nodeRemove = Map.empty + } + ) + (archetypes w') + in (e, w' {archetypes = arches, entities = Map.insert e aId (entities w)}) -- | Spawn an entity with a component and its `ComponentID` directly into an archetype. spawnWithArchetypeId :: @@ -113,78 +160,97 @@ World -> World spawnWithArchetypeId' e aId cId c w = - let f = A.insert e cId c + let f n = n {nodeArchetype = A.insertComponent e cId c (nodeArchetype n)} in w - { archetypes = Map.adjust f aId (archetypes w), + { archetypes = (archetypes w) {AS.nodes = Map.adjust f aId (AS.nodes $ archetypes w)}, entities = Map.insert e aId (entities w) } --- | Insert an archetype by its set of `ComponentID`s. -insertArchetype :: Set ComponentID -> Archetype -> World -> (ArchetypeID, World) -insertArchetype cIds a w = - let aId = nextArchetypeId w - in ( aId, - w - { archetypes = Map.insert aId a (archetypes w), - archetypeIds = Map.insert cIds aId (archetypeIds w), - archetypeComponents = Map.insert aId cIds (archetypeComponents w), - nextArchetypeId = ArchetypeID (unArchetypeId aId + 1) - } - ) - +-- | Insert a component into an entity. insert :: forall a. (Component a, Typeable (StorageT a)) => EntityID -> a -> World -> World insert e c w = - let (cId, components') = insertComponentId @a (components w) + let (cId, components') = CS.insert @a (components w) in insertWithId e cId c w {components = components'} +-- | Insert a component into an entity with its `ComponentID`. insertWithId :: (Component a, Typeable (StorageT a)) => EntityID -> ComponentID -> a -> World -> World insertWithId e cId c w = case Map.lookup e (entities w) of - Just aId -> case Map.lookup aId (archetypeComponents w) of - Just cIds -> - if Set.member cId cIds - then w {archetypes = Map.adjust (A.insert e cId c) aId (archetypes w)} - else - let arch = archetypes w Map.! aId - in case Map.lookup (Set.insert cId cIds) (archetypeIds w) of - Just nextAId -> - let (cs, arch') = A.remove e arch - w' = w {archetypes = Map.insert aId arch' (archetypes w)} - f (itemCId, dyn) archAcc = - archAcc - { A.storages = - Map.adjust - (\s -> s {A.storageDyn = A.insertDyn s (unEntityId e) dyn (A.storageDyn s)}) - itemCId - (A.storages archAcc) - } - in w' - { archetypes = + Just aId -> case AS.lookupNode aId (archetypes w) of + Just node -> + if Set.member cId (nodeComponentIds node) + then w {archetypes = (archetypes w) {AS.nodes = Map.adjust (\n -> n {nodeArchetype = A.insertComponent e cId c (nodeArchetype n)}) aId (AS.nodes $ archetypes w)}} + else case AS.lookupArchetypeId (Set.insert cId (nodeComponentIds node)) (archetypes w) of + Just nextAId -> + let (cs, arch') = A.remove e (nodeArchetype node) + w' = w {archetypes = (archetypes w) {AS.nodes = Map.insert aId node {nodeArchetype = arch'} (AS.nodes $ archetypes w)}} + f (itemCId, dyn) archAcc = + archAcc + { A.storages = + Map.adjust + (\s -> s {A.storageDyn = A.insertDyn s (unEntityId e) dyn (A.storageDyn s)}) + itemCId + (A.storages archAcc) + } + in w' + { archetypes = + (archetypes w') + { AS.nodes = Map.adjust - (\nextArch -> foldr f nextArch (Map.toList cs)) + ( \nextNode -> + nextNode + { nodeArchetype = + A.insertComponent e cId c $ + foldr + f + (nodeArchetype nextNode) + (Map.toList cs) + } + ) nextAId - (archetypes w') - } - Nothing -> - let (s, arch') = A.removeStorages e arch - w' = w {archetypes = Map.insert aId arch' (archetypes w)} - newArch = Archetype {A.storages = s} - newArch' = A.insert e cId c newArch - in snd $ insertArchetype (Set.insert cId cIds) newArch' w' + (AS.nodes $ archetypes w') + }, + entities = Map.insert e aId (entities w) + } + Nothing -> + let (s, arch') = A.removeStorages e (nodeArchetype node) + n = + Node + { nodeComponentIds = Set.insert cId (nodeComponentIds node), + nodeArchetype = A.insertComponent e cId c (Archetype {A.storages = s}), + nodeAdd = Map.empty, + nodeRemove = Map.singleton cId aId + } + (nextAId, arches) = AS.insertArchetype (Set.insert cId (nodeComponentIds node)) n (archetypes w) + in w + { archetypes = + arches + { AS.nodes = + Map.insert + aId + node + { nodeArchetype = arch', + nodeAdd = Map.insert cId nextAId (nodeAdd node) + } + (AS.nodes arches) + }, + entities = Map.insert e nextAId (entities w) + } Nothing -> w Nothing -> w +-- | Despawn an entity, returning its components. despawn :: EntityID -> World -> (Map ComponentID Dynamic, World) despawn e w = let res = do aId <- Map.lookup e (entities w) - arch <- Map.lookup aId (archetypes w) - return (aId, arch) + node <- AS.lookupNode aId (archetypes w) + return (aId, node) in case res of - Just (aId, arch) -> - let (dynAcc, arch') = A.remove e arch + Just (aId, node) -> + let (dynAcc, arch') = A.remove e (nodeArchetype node) in ( dynAcc, w - { archetypes = Map.insert aId arch' (archetypes w), + { archetypes = (archetypes w) {AS.nodes = Map.insert aId node {nodeArchetype = arch'} (AS.nodes $ archetypes w)}, entities = Map.delete e (entities w) } )
+ src/Data/Aztecs/World/Archetype.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} + +module Data.Aztecs.World.Archetype + ( Archetype (..), + empty, + all, + lookup, + lookupStorage, + remove, + removeStorages, + insertComponent, + insertAscList, + Insert (..), + AnyStorage (..), + anyStorage, + ) +where + +import Data.Aztecs.Component (Component (..), ComponentID) +import Data.Aztecs.Entity (Entity (..), EntityID (..)) +import qualified Data.Aztecs.Storage as S +import Data.Aztecs.World.Components (Components) +import qualified Data.Aztecs.World.Components as CS +import Data.Bifunctor (Bifunctor (..)) +import Data.Dynamic (Dynamic, fromDynamic, toDyn) +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Maybe (fromMaybe) +import Data.Set (Set) +import qualified Data.Set as Set +import Prelude hiding (all, lookup) + +data AnyStorage = AnyStorage + { storageDyn :: Dynamic, + insertDyn :: Int -> Dynamic -> Dynamic -> Dynamic, + removeDyn :: Int -> Dynamic -> (Maybe Dynamic, Dynamic), + removeAny :: Int -> Dynamic -> (Maybe AnyStorage, Dynamic) + } + +instance Show AnyStorage where + show s = "AnyStorage " ++ show (storageDyn s) + +anyStorage :: forall s a. (S.Storage s a) => s a -> AnyStorage +anyStorage s = + AnyStorage + { storageDyn = toDyn s, + insertDyn = \i cDyn sDyn -> + fromMaybe sDyn $ do + s' <- fromDynamic @(s a) sDyn + c <- fromDynamic cDyn + return . toDyn $ S.insert i c s', + removeDyn = \i dyn -> case fromDynamic @(s a) dyn of + Just s' -> let (a, b) = S.remove i s' in (fmap toDyn a, toDyn b) + Nothing -> (Nothing, dyn), + removeAny = \i dyn -> case fromDynamic @(s a) dyn of + Just s' -> let (a, b) = S.remove i s' in (fmap (anyStorage . S.singleton @s i) a, toDyn b) + Nothing -> (Nothing, dyn) + } + +newtype Archetype = Archetype {storages :: Map ComponentID AnyStorage} + deriving (Show) + +empty :: Archetype +empty = Archetype {storages = Map.empty} + +lookupStorage :: (Component a) => ComponentID -> Archetype -> Maybe (StorageT a a) +lookupStorage cId w = do + dynS <- Map.lookup cId (storages w) + fromDynamic (storageDyn dynS) + +insertComponent :: forall a. (Component a) => EntityID -> ComponentID -> a -> Archetype -> Archetype +insertComponent e cId c arch = + let storage = case lookupStorage cId arch of + Just s -> S.insert (unEntityId e) c s + Nothing -> S.singleton @(StorageT a) @a (unEntityId e) c + in arch {storages = Map.insert cId (anyStorage storage) (storages arch)} + +all :: (Component a) => ComponentID -> Archetype -> [(EntityID, a)] +all cId arch = fromMaybe [] $ do + s <- lookupStorage cId arch + return . map (first EntityID) $ S.all s + +lookup :: forall a. (Component a) => EntityID -> ComponentID -> Archetype -> Maybe a +lookup e cId w = lookupStorage cId w >>= S.lookup (unEntityId e) + +insertAscList :: forall a. (Component a) => ComponentID -> [(EntityID, a)] -> Archetype -> Archetype +insertAscList cId as arch = arch {storages = Map.insert cId (anyStorage $ S.fromAscList @(StorageT a) (map (first unEntityId) as)) (storages arch)} + +remove :: EntityID -> Archetype -> (Map ComponentID Dynamic, Archetype) +remove e arch = + foldr + ( \(cId, s) (dynAcc, archAcc) -> + let (dynA, dynS) = removeDyn s (unEntityId e) (storageDyn s) + dynAcc' = case dynA of + Just d -> Map.insert cId d dynAcc + Nothing -> dynAcc + in ( dynAcc', + archAcc {storages = Map.insert cId (s {storageDyn = dynS}) (storages archAcc)} + ) + ) + (Map.empty, arch) + (Map.toList $ storages arch) + +removeStorages :: EntityID -> Archetype -> (Map ComponentID AnyStorage, Archetype) +removeStorages e arch = + foldr + ( \(cId, s) (dynAcc, archAcc) -> + let (dynA, dynS) = removeAny s (unEntityId e) (storageDyn s) + dynAcc' = case dynA of + Just d -> Map.insert cId d dynAcc + Nothing -> dynAcc + in ( dynAcc', + archAcc {storages = Map.insert cId (s {storageDyn = dynS}) (storages archAcc)} + ) + ) + (Map.empty, arch) + (Map.toList $ storages arch) + +class Insert a where + insert :: EntityID -> a -> Archetype -> Components -> (Set ComponentID, Archetype, Components) + +instance Insert (Entity '[]) where + insert _ ENil arch cs = (Set.empty, arch, cs) + +instance (Component a, Insert (Entity as)) => Insert (Entity (a ': as)) where + insert eId (ECons e es) arch cs = + let (cId, cs') = CS.insert @a cs + arch' = insertComponent eId cId e arch + (cIds, arch'', cs'') = insert eId es arch' cs' + in (Set.insert cId cIds, arch'', cs'')
+ src/Data/Aztecs/World/Archetypes.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} + +module Data.Aztecs.World.Archetypes + ( ArchetypeID (..), + Node (..), + Archetypes (..), + empty, + insertArchetype, + lookupArchetypeId, + findArchetypeIds, + lookupNode, + lookup, + map, + adjustArchetype, + ) +where + +import Data.Aztecs.Component (ComponentID) +import Data.Aztecs.World.Archetype hiding (empty, lookup) +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Maybe (mapMaybe) +import Data.Set (Set) +import qualified Data.Set as Set +import Prelude hiding (all, lookup, map) + +-- | `Archetype` ID. +newtype ArchetypeID = ArchetypeID {unArchetypeId :: Int} + deriving (Eq, Ord, Show) + +-- | Node in `Archetypes`. +data Node = Node + { -- | Unique set of `ComponentID`s of this `Node`. + nodeComponentIds :: Set ComponentID, + -- | `Archetype` of this `Node`. + nodeArchetype :: Archetype, + -- | Edges to other `Archetype`s by adding a `ComponentID`. + nodeAdd :: Map ComponentID ArchetypeID, + -- | Edges to other `Archetype`s by removing a `ComponentID`. + nodeRemove :: Map ComponentID ArchetypeID + } + deriving (Show) + +-- | `Archetype` graph. +data Archetypes = Archetypes + { -- | Archetype nodes in the graph. + nodes :: Map ArchetypeID Node, + -- | Mapping of unique `ComponentID` sets to `ArchetypeID`s. + archetypeIds :: Map (Set ComponentID) ArchetypeID, + -- | Next unique `ArchetypeID`. + nextArchetypeId :: ArchetypeID, + -- | Mapping of `ComponentID`s to `ArchetypeID`s of `Archetypes` that contain them. + componentIds :: Map ComponentID (Set ArchetypeID) + } + deriving (Show) + +-- | Empty `Archetypes`. +empty :: Archetypes +empty = + Archetypes + { nodes = mempty, + archetypeIds = mempty, + nextArchetypeId = ArchetypeID 0, + componentIds = mempty + } + +-- | Insert an archetype by its set of `ComponentID`s. +insertArchetype :: Set ComponentID -> Node -> Archetypes -> (ArchetypeID, Archetypes) +insertArchetype cIds n arches = + let aId = nextArchetypeId arches + in ( aId, + arches + { nodes = Map.insert aId n (nodes arches), + archetypeIds = Map.insert cIds aId (archetypeIds arches), + nextArchetypeId = ArchetypeID (unArchetypeId aId + 1), + componentIds = Map.unionWith (<>) (Map.fromSet (const (Set.singleton aId)) cIds) (componentIds arches) + } + ) + +adjustArchetype :: ArchetypeID -> (Archetype -> Archetype) -> Archetypes -> Archetypes +adjustArchetype aId f arches = arches {nodes = Map.adjust (\node -> node {nodeArchetype = f (nodeArchetype node)}) aId (nodes arches)} + +-- | Find `ArchetypeID`s containing a set of `ComponentID`s. +findArchetypeIds :: Set ComponentID -> Archetypes -> Set ArchetypeID +findArchetypeIds cIds arches = case mapMaybe (\cId -> Map.lookup cId (componentIds arches)) (Set.elems cIds) of + (aId : aIds') -> foldr Set.intersection aId aIds' + [] -> Set.empty + +-- | Lookup `Archetype`s containing a set of `ComponentID`s. +lookup :: Set ComponentID -> Archetypes -> Map ArchetypeID Archetype +lookup cIds arches = Map.fromSet (\aId -> nodeArchetype $ nodes arches Map.! aId) (findArchetypeIds cIds arches) + +-- | Map over `Archetype`s containing a set of `ComponentID`s. +map :: Set ComponentID -> (Archetype -> (a, Archetype)) -> Archetypes -> ([a], Archetypes) +map cIds f arches = + foldr + ( \aId (acc, archAcc) -> + let node = nodes archAcc Map.! aId + (a, arch') = f (nodeArchetype node) + in (a : acc, archAcc {nodes = Map.insert aId (node {nodeArchetype = arch'}) (nodes archAcc)}) + ) + ([], arches) + (findArchetypeIds cIds arches) + +lookupArchetypeId :: Set ComponentID -> Archetypes -> Maybe ArchetypeID +lookupArchetypeId cIds arches = Map.lookup cIds (archetypeIds arches) + +lookupNode :: ArchetypeID -> Archetypes -> Maybe Node +lookupNode aId arches = Map.lookup aId (nodes arches)
+ src/Data/Aztecs/World/Components.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +module Data.Aztecs.World.Components + ( ComponentID (..), + Components (..), + empty, + lookup, + insert, + insert', + ) +where + +import Data.Aztecs.Component (Component, ComponentID (..)) +import Data.Map (Map) +import qualified Data.Map as Map +import Data.Typeable (Proxy (..), TypeRep, Typeable, typeOf) +import Prelude hiding (lookup) + +-- | Component ID map. +data Components = Components + { componentIds :: Map TypeRep ComponentID, + nextComponentId :: ComponentID + } + deriving (Show) + +-- | Empty `Components`. +empty :: Components +empty = + Components + { componentIds = mempty, + nextComponentId = ComponentID 0 + } + +-- | Lookup a component ID by type. +lookup :: forall a. (Typeable a) => Components -> Maybe ComponentID +lookup cs = Map.lookup (typeOf (Proxy @a)) (componentIds cs) + +-- | Insert a component ID by type, if it does not already exist. +insert :: forall a. (Component a) => Components -> (ComponentID, Components) +insert cs = case lookup @a cs of + Just cId -> (cId, cs) + Nothing -> insert' @a cs + +-- | Insert a component ID by type. +insert' :: forall c. (Component c) => Components -> (ComponentID, Components) +insert' cs = + let cId = nextComponentId cs + in ( cId, + cs + { componentIds = Map.insert (typeOf (Proxy @c)) cId (componentIds cs), + nextComponentId = ComponentID (unComponentId cId + 1) + } + )
test/Main.hs view
@@ -1,6 +1,10 @@ module Main (main) where import Data.Aztecs +import Data.Aztecs.Entity (ToEntity (toEntity)) +import qualified Data.Aztecs.Query as Q +import qualified Data.Aztecs.World as W +import Test.Hspec newtype X = X Int deriving (Eq, Show) @@ -10,5 +14,26 @@ instance Component Y +newtype Z = Z Int deriving (Eq, Show) + +instance Component Z + main :: IO () -main = return () +main = hspec $ do + describe "Data.Aztecs.Query.all" $ do + it "queries multiple components" $ do + let (_, w) = W.spawn (X 0) W.empty + (_, w') = W.spawn (X 1) w + xs = Q.all w' + xs `shouldMatchList` [X 0, X 1] + it "queries a group of components" $ do + let (e, w) = W.spawn (X 0) W.empty + w' = W.insert e (Y 1) w + xs = Q.all w' + xs `shouldMatchList` [toEntity (X 0 :& Y 1)] + it "queries a fragmented group of components" $ do + let (e, w) = W.spawn (X 0) W.empty + w' = W.insert e (Y 1) w + w'' = W.insert e (Z 2) w' + xs = Q.all w'' + xs `shouldMatchList` [toEntity (Y 1 :& Z 2)]