packages feed

aztecs 0.16.0 → 0.16.1

raw patch · 9 files changed

+83/−42 lines, 9 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Aztecs.ECS: [unOnChange] :: OnChange a -> a
- Aztecs.ECS: newtype OnChange a
- Aztecs.ECS.Event: [unOnChange] :: OnChange a -> a
- Aztecs.ECS.Event: newtype OnChange a
+ Aztecs.ECS: [onChangeNew] :: OnChange a -> a
+ Aztecs.ECS: [onChangeOld] :: OnChange a -> a
+ Aztecs.ECS: data OnChange a
+ Aztecs.ECS: entity :: DynamicQueryF m f => f EntityID
+ Aztecs.ECS: queryDyn :: (DynamicQueryF m f, Component m a) => ComponentID -> f a
+ Aztecs.ECS: queryFilter :: DynamicQueryF m f => (a -> Bool) -> f a -> f a
+ Aztecs.ECS: queryFilterMap :: DynamicQueryF m f => (a -> Maybe b) -> f a -> f b
+ Aztecs.ECS: queryMapAccum :: forall (m :: Type -> Type) a b. (Monad m, Component m b) => (b -> (a, b)) -> Query m (a, b)
+ Aztecs.ECS: queryMapAccumM :: forall m a b. (Monad m, Component m b) => (b -> m (a, b)) -> Query m (a, b)
+ Aztecs.ECS: queryMapDyn :: (DynamicQueryF m f, Component m a) => (a -> a) -> ComponentID -> f a
+ Aztecs.ECS: queryMapDynM :: (DynamicQueryF m f, Monad m, Component m a) => (a -> m a) -> ComponentID -> f a
+ Aztecs.ECS: queryMapDynWith :: (DynamicQueryF m f, Component m b) => (a -> b -> b) -> ComponentID -> f a -> f b
+ Aztecs.ECS: queryMapDynWithAccum :: (DynamicQueryF m f, Component m c) => (b -> c -> (a, c)) -> ComponentID -> f b -> f (a, c)
+ Aztecs.ECS: queryMapDynWithAccumM :: (DynamicQueryF m f, Monad m, Component m c) => (b -> c -> m (a, c)) -> ComponentID -> f b -> f (a, c)
+ Aztecs.ECS: queryMapDynWithM :: (DynamicQueryF m f, Monad m, Component m b) => (a -> b -> m b) -> ComponentID -> f a -> f b
+ Aztecs.ECS: queryMapDynWith_ :: (DynamicQueryF m f, Component m b) => (a -> b -> b) -> ComponentID -> f a -> f ()
+ Aztecs.ECS: queryMapDyn_ :: (DynamicQueryF m f, Component m a) => (a -> a) -> ComponentID -> f ()
+ Aztecs.ECS: queryMapWithAccum :: forall (m :: Type -> Type) a b c. (Monad m, Component m c) => (b -> c -> (a, c)) -> Query m b -> Query m (a, c)
+ Aztecs.ECS: queryMapWithAccumM :: forall m a b c. (Monad m, Component m c) => (b -> c -> m (a, c)) -> Query m b -> Query m (a, c)
+ Aztecs.ECS: queryMaybeDyn :: (DynamicQueryF m f, Component m a) => ComponentID -> f (Maybe a)
+ Aztecs.ECS: queryUntracked :: DynamicQueryF m f => f a -> f a
+ Aztecs.ECS.Event: [onChangeNew] :: OnChange a -> a
+ Aztecs.ECS.Event: [onChangeOld] :: OnChange a -> a
+ Aztecs.ECS.Event: data OnChange a
+ Aztecs.ECS.Query: queryMapAccum :: forall (m :: Type -> Type) a b. (Monad m, Component m b) => (b -> (a, b)) -> Query m (a, b)
+ Aztecs.ECS.Query: queryMapAccumM :: forall m a b. (Monad m, Component m b) => (b -> m (a, b)) -> Query m (a, b)
- Aztecs.ECS: OnChange :: a -> OnChange a
+ Aztecs.ECS: OnChange :: a -> a -> OnChange a
- Aztecs.ECS: componentOnChange :: Component m a => EntityID -> a -> Access m ()
+ Aztecs.ECS: componentOnChange :: Component m a => EntityID -> a -> a -> Access m ()
- Aztecs.ECS.Component: componentOnChange :: Component m a => EntityID -> a -> Access m ()
+ Aztecs.ECS.Component: componentOnChange :: Component m a => EntityID -> a -> a -> Access m ()
- Aztecs.ECS.Event: OnChange :: a -> OnChange a
+ Aztecs.ECS.Event: OnChange :: a -> a -> OnChange a

Files

aztecs.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4
 name:          aztecs
-version:       0.16.0
+version:       0.16.1
 license:       BSD-3-Clause
 license-file:  LICENSE
 maintainer:    matt@hunzinger.me
examples/Lifecycle.hs view
@@ -10,7 +10,7 @@ 
 instance Component IO X where
   componentOnInsert = exampleHook "insert"
-  componentOnChange = exampleHook "change"
+  componentOnChange e _ = exampleHook "change" e
   componentOnRemove = exampleHook "remove"
 
 exampleHook :: String -> EntityID -> X -> Access IO ()
examples/Observers.hs view
@@ -22,8 +22,8 @@   -- Spawn observers that react to lifecycle events on the player's Health component
   _ <- spawn . bundle . observer @IO @(OnInsert Health) player $ \e (OnInsert h) ->
     observe "insert" e h
-  _ <- spawn . bundle . observer @IO @(OnChange Health) player $ \e (OnChange h) ->
-    observe "change" e h
+  _ <- spawn . bundle . observer @IO @(OnChange Health) player $ \e change ->
+    liftIO . putStrLn $ "change " ++ show e ++ ": old=" ++ show (onChangeOld change) ++ ", new=" ++ show (onChangeNew change)
   _ <- spawn . bundle . observer @IO @(OnRemove Health) player $ \e (OnRemove h) ->
     observe "remove" e h
 
src/Aztecs/ECS.hs view
@@ -67,7 +67,11 @@     queryMapWith,
     queryMapWith_,
     queryMapWithM,
-    DynamicQueryF,
+    queryMapAccum,
+    queryMapAccumM,
+    queryMapWithAccum,
+    queryMapWithAccumM,
+    DynamicQueryF (..),
     QueryFilter,
     with,
     without,
@@ -98,13 +102,17 @@     observerGlobal,
   )
 import Aztecs.ECS.Query
-  ( DynamicQueryF,
+  ( DynamicQueryF (..),
     Query,
     QueryFilter,
     query,
     queryMap,
+    queryMapAccum,
+    queryMapAccumM,
     queryMapM,
     queryMapWith,
+    queryMapWithAccum,
+    queryMapWithAccumM,
     queryMapWithM,
     queryMapWith_,
     queryMaybe,
src/Aztecs/ECS/Component.hs view
@@ -36,8 +36,8 @@   {-# INLINEABLE componentOnInsert #-}
 
   -- | Lifecycle hook called when a component is changed.
-  componentOnChange :: EntityID -> a -> Access m ()
-  componentOnChange _ _ = pure ()
+  componentOnChange :: EntityID -> a -> a -> Access m ()
+  componentOnChange _ _ _ = pure ()
   {-# INLINEABLE componentOnChange #-}
 
   -- | Lifecycle hook called when a component is removed.
src/Aztecs/ECS/Event.hs view
@@ -32,7 +32,10 @@ instance (Typeable a) => Event (OnInsert a)
 
 -- | Event triggered when a component is changed.
-newtype OnChange a = OnChange {unOnChange :: a}
+data OnChange a = OnChange
+  { onChangeOld :: a,
+    onChangeNew :: a
+  }
   deriving (Show, Eq, Generic)
 
 instance (Typeable a) => Event (OnChange a)
src/Aztecs/ECS/Query.hs view
@@ -34,6 +34,8 @@     queryMapWith,
     queryMapWith_,
     queryMapWithM,
+    queryMapAccum,
+    queryMapAccumM,
     queryMapWithAccum,
     queryMapWithAccumM,
 
@@ -179,6 +181,24 @@   Query m b
 queryMapWithM f = queryWriter @m @b $ queryMapDynWithM f
 {-# INLINE queryMapWithM #-}
+
+-- | Query a component with input, returning a tuple of the result and the updated component.
+queryMapAccum ::
+  forall m a b.
+  (Monad m, Component m b) =>
+  (b -> (a, b)) ->
+  Query m (a, b)
+queryMapAccum f = queryMapWithAccum (const f) (pure ())
+{-# INLINE queryMapAccum #-}
+
+-- | Query a component with input and update it with a monadic action, returning a tuple.
+queryMapAccumM ::
+  forall m a b.
+  (Monad m, Component m b) =>
+  (b -> m (a, b)) ->
+  Query m (a, b)
+queryMapAccumM f = queryMapWithAccumM (const f) (pure ())
+{-# INLINE queryMapAccumM #-}
 
 -- | Query a component with input, returning a tuple of the result and the updated component.
 queryMapWithAccum ::
src/Aztecs/ECS/World/Archetype.hs view
@@ -101,11 +101,17 @@ insertComponent ::
   forall m a. (Component m a) => EntityID -> ComponentID -> a -> Archetype m -> (Archetype m, Access m ())
 insertComponent e cId c arch =
-  let !storage =
-        S.fromAscVector @a @(StorageT a) . V.fromList . Map.elems . Map.insert e c $ lookupComponents cId arch
-      hook = do
-        componentOnInsert e c
-        triggerEntityEvent e (OnInsert c)
+  let oldComponents = lookupComponents @m @a cId arch
+      oldValue = oldComponents Map.!? e
+      !storage =
+        S.fromAscVector @a @(StorageT a) . V.fromList . Map.elems . Map.insert e c $ oldComponents
+      hook = case oldValue of
+        Just old -> do
+          componentOnChange e old c
+          triggerEntityEvent e (OnChange old c)
+        Nothing -> do
+          componentOnInsert e c
+          triggerEntityEvent e (OnInsert c)
    in (arch {storages = IntMap.insert (unComponentId cId) (dynStorage @a storage) (storages arch)}, hook)
 
 -- | Insert a component into an archetype without running lifecycle hooks.
@@ -125,7 +131,8 @@ zipWith ::
   forall m a c. (Monad m, Component m c) => Vector a -> (a -> c -> c) -> ComponentID -> Archetype m -> (Vector c, Archetype m, Access m ())
 zipWith as f cId arch =
-  let go maybeDyn = case maybeDyn of
+  let oldCs = lookupComponentsAsc @m @c cId arch
+      go maybeDyn = case maybeDyn of
         Just dyn -> case fromDynamic $ storageDyn dyn of
           Just s -> do
             let !(cs', s') = S.zipWith @c @(StorageT c) f as s
@@ -135,7 +142,7 @@         Nothing -> return Nothing
       (storages', cs) = runWriter $ IntMap.alterF go (unComponentId cId) $ storages arch
       eIds = V.fromList . Set.toList $ entities arch
-      hooks = V.foldl (\acc (e, c) -> acc >> componentOnChange e c >> triggerEntityEvent e (OnChange c)) (return ()) (V.zip eIds cs)
+      hooks = V.foldl (\acc (e, old, new) -> acc >> componentOnChange e old new >> triggerEntityEvent e (OnChange old new)) (return ()) (V.zip3 eIds oldCs cs)
    in (cs, arch {storages = storages'}, hooks)
 {-# INLINE zipWith #-}
 
@@ -144,7 +151,8 @@ zipWithM ::
   forall m a c. (Monad m, Component m c) => Vector a -> (a -> c -> m c) -> ComponentID -> Archetype m -> m (Vector c, Archetype m, Access m ())
 zipWithM as f cId arch = do
-  let go maybeDyn = case maybeDyn of
+  let oldCs = lookupComponentsAsc @m @c cId arch
+      go maybeDyn = case maybeDyn of
         Just dyn -> case fromDynamic $ storageDyn dyn of
           Just s ->
             WriterT $
@@ -156,7 +164,7 @@   res <- runWriterT $ IntMap.alterF go (unComponentId cId) $ storages arch
   let cs = snd res
       eIds = V.fromList . Set.toList $ entities arch
-      hooks = V.foldl (\acc (e, c) -> acc >> componentOnChange e c >> triggerEntityEvent e (OnChange c)) (return ()) (V.zip eIds cs)
+      hooks = V.foldl (\acc (e, old, new) -> acc >> componentOnChange e old new >> triggerEntityEvent e (OnChange old new)) (return ()) (V.zip3 eIds oldCs cs)
   return (cs, arch {storages = fst res}, hooks)
 
 -- | Zip a vector of components with a function and a component storage.
@@ -164,7 +172,8 @@ zipWith_ ::
   forall m a c. (Monad m, Component m c) => Vector a -> (a -> c -> c) -> ComponentID -> Archetype m -> (Archetype m, Access m ())
 zipWith_ as f cId arch =
-  let maybeStorage = case IntMap.lookup (unComponentId cId) $ storages arch of
+  let oldCs = lookupComponentsAsc @m @c cId arch
+      maybeStorage = case IntMap.lookup (unComponentId cId) $ storages arch of
         Just dyn -> case fromDynamic $ storageDyn dyn of
           Just s ->
             let !(cs, s') = S.zipWith @c @(StorageT c) f as s in Just (cs, dyn {storageDyn = toDyn s'})
@@ -173,7 +182,7 @@    in case maybeStorage of
         Just (cs, s) ->
           let eIds = V.fromList . Set.toList $ entities arch
-              hooks = V.foldl (\acc (e, c) -> acc >> componentOnChange e c >> triggerEntityEvent e (OnChange c)) (return ()) (V.zip eIds cs)
+              hooks = V.foldl (\acc (e, old, new) -> acc >> componentOnChange e old new >> triggerEntityEvent e (OnChange old new)) (return ()) (V.zip3 eIds oldCs cs)
            in (empty {storages = IntMap.singleton (unComponentId cId) s}, hooks)
         Nothing -> (empty {storages = IntMap.empty}, return ())
 {-# INLINE zipWith_ #-}
@@ -182,7 +191,8 @@ zipWithAccum ::
   forall m a c o. (Monad m, Component m c) => Vector a -> (a -> c -> (o, c)) -> ComponentID -> Archetype m -> (Vector (o, c), Archetype m, Access m ())
 zipWithAccum as f cId arch =
-  let go maybeDyn = case maybeDyn of
+  let oldCs = lookupComponentsAsc @m @c cId arch
+      go maybeDyn = case maybeDyn of
         Just dyn -> case fromDynamic $ storageDyn dyn of
           Just s -> do
             let !(pairs', s') = S.zipWithAccum @c @(StorageT c) f as s
@@ -192,7 +202,7 @@         Nothing -> return Nothing
       (storages', pairs) = runWriter $ IntMap.alterF go (unComponentId cId) $ storages arch
       eIds = V.fromList . Set.toList $ entities arch
-      hooks = V.foldl (\acc (e, (_, c)) -> acc >> componentOnChange e c >> triggerEntityEvent e (OnChange c)) (return ()) (V.zip eIds pairs)
+      hooks = V.foldl (\acc (e, old, (_, new)) -> acc >> componentOnChange e old new >> triggerEntityEvent e (OnChange old new)) (return ()) (V.zip3 eIds oldCs pairs)
    in (pairs, arch {storages = storages'}, hooks)
 {-# INLINE zipWithAccum #-}
 
@@ -200,7 +210,8 @@ zipWithAccumM ::
   forall m a c o. (Monad m, Component m c) => Vector a -> (a -> c -> m (o, c)) -> ComponentID -> Archetype m -> m (Vector (o, c), Archetype m, Access m ())
 zipWithAccumM as f cId arch = do
-  let go maybeDyn = case maybeDyn of
+  let oldCs = lookupComponentsAsc @m @c cId arch
+      go maybeDyn = case maybeDyn of
         Just dyn -> case fromDynamic $ storageDyn dyn of
           Just s ->
             WriterT $
@@ -212,7 +223,7 @@   res <- runWriterT $ IntMap.alterF go (unComponentId cId) $ storages arch
   let pairs = snd res
       eIds = V.fromList . Set.toList $ entities arch
-      hooks = V.foldl (\acc (e, (_, c)) -> acc >> componentOnChange e c >> triggerEntityEvent e (OnChange c)) (return ()) (V.zip eIds pairs)
+      hooks = V.foldl (\acc (e, old, (_, new)) -> acc >> componentOnChange e old new >> triggerEntityEvent e (OnChange old new)) (return ()) (V.zip3 eIds oldCs pairs)
   return (pairs, arch {storages = fst res}, hooks)
 {-# INLINE zipWithAccumM #-}
 
src/Aztecs/Hierarchy.hs view
@@ -82,20 +82,20 @@ update :: (Monad m) => Access m ()
 update = do
   parents <- A.system . S.readQuery $ do
-    entity <- Q.entity
+    e <- Q.entity
     parent <- Q.query
     maybeParentState <- Q.queryMaybe @_ @ParentState
-    return (entity, unParent parent, maybeParentState)
+    return (e, unParent parent, maybeParentState)
 
   children <- A.system . S.readQuery $ do
-    entity <- Q.entity
+    e <- Q.entity
     cs <- Q.query
     maybeChildState <- Q.queryMaybe @_ @ChildState
-    return (entity, unChildren cs, maybeChildState)
+    return (e, unChildren cs, maybeChildState)
 
   let go = do
         mapM_
-          ( \(entity, parent, maybeParentState) -> case maybeParentState of
+          ( \(e, parent, maybeParentState) -> case maybeParentState of
               Just (ParentState parentState) -> do
                 when (parent /= parentState) $ do
                   A.insert parent . bundle $ ParentState parent
@@ -103,32 +103,32 @@                   -- Remove this entity from the previous parent's children.
                   maybeLastChildren <- A.lookup parentState
                   let lastChildren = maybe mempty unChildren maybeLastChildren
-                  let lastChildren' = Set.filter (/= entity) lastChildren
+                  let lastChildren' = Set.filter (/= e) lastChildren
                   A.insert parentState . bundle . Children $ lastChildren'
 
                   -- Add this entity to the new parent's children.
                   maybeChildren <- A.lookup parent
                   let parentChildren = maybe mempty unChildren maybeChildren
-                  A.insert parent . bundle . Children $ Set.insert entity parentChildren
+                  A.insert parent . bundle . Children $ Set.insert e parentChildren
               Nothing -> do
                 A.spawn_ . bundle $ ParentState parent
                 maybeChildren <- A.lookup parent
                 let parentChildren = maybe mempty unChildren maybeChildren
-                A.insert parent . bundle . Children $ Set.insert entity parentChildren
+                A.insert parent . bundle . Children $ Set.insert e parentChildren
           )
           parents
         mapM_
-          ( \(entity, children', maybeChildState) -> case maybeChildState of
+          ( \(e, children', maybeChildState) -> case maybeChildState of
               Just (ChildState childState) -> do
                 when (children' /= childState) $ do
-                  A.insert entity . bundle $ ChildState children'
+                  A.insert e . bundle $ ChildState children'
                   let added = Set.difference children' childState
                       removed = Set.difference childState children'
-                  mapM_ (\e -> A.insert e . bundle . Parent $ entity) added
+                  mapM_ (\e' -> A.insert e' . bundle . Parent $ e') added
                   mapM_ (A.remove @_ @Parent) removed
               Nothing -> do
-                A.insert entity . bundle $ ChildState children'
-                mapM_ (\e -> A.insert e . bundle . Parent $ entity) children'
+                A.insert e . bundle $ ChildState children'
+                mapM_ (\e' -> A.insert e' . bundle . Parent $ e') children'
           )
           children
   go
@@ -177,11 +177,10 @@   Access m (Maybe (Hierarchy a))
 hierarchy e q = do
   children <- A.system . S.readQuery $ do
-    entity <- Q.entity
+    e' <- Q.entity
     cs <- Q.query
     a <- q
-    return (entity, (unChildren cs, a))
-
+    return (e', (unChildren cs, a))
   let childMap = Map.fromList $ V.toList children
   return $ hierarchy' e childMap
 
@@ -194,10 +193,10 @@ hierarchies q = do
   children <-
     A.system . S.readQuery $ do
-      entity <- Q.entity
+      e <- Q.entity
       cs <- Q.query
       a <- q
-      return (entity, (unChildren cs, a))
+      return (e, (unChildren cs, a))
 
   let childMap = Map.fromList $ V.toList children
   roots <- A.system $ S.readQueryFiltered Q.entity (with @m @Children <> without @m @Parent)