mischief-ecs 0.1.0.0 → 0.2.0.0
raw patch · 102 files changed
+7576/−7217 lines, 102 filesdep +exceptionsdep +string-interpolatedep −binarydep −bytestringdep −hashabledep ~basedep ~containersdep ~data-defaultnew-component:exe:example-startupPVP ok
version bump matches the API change (PVP)
Dependencies added: exceptions, string-interpolate
Dependencies removed: binary, bytestring, hashable
Dependency ranges changed: base, containers, data-default, mischief-ecs, primitive, random, text, time, transformers
API changes (from Hackage documentation)
- Mischief.ECS: TestTest :: TestTest
- Mischief.ECS: class Collectable v storage
- Mischief.ECS: class Semigroup s => EraseIntoStorage v s
- Mischief.ECS: class Typeable m => Message (m :: k)
- Mischief.ECS: collect :: Collectable v storage => v -> storage
- Mischief.ECS: data TestTest
- Mischief.ECS: erase :: EraseIntoStorage v s => v -> s
- Mischief.ECS.App.Plugins: PluginData :: Map TypeRep ErasedPlugin -> PluginData
- Mischief.ECS.App.Plugins: Plugins :: [ErasedPlugin] -> Plugins
- Mischief.ECS.App.Plugins: [ErasedPlugin] :: forall p. (Plugin p, Eq p) => p -> ErasedPlugin
- Mischief.ECS.App.Plugins: [inner] :: PluginData -> Map TypeRep ErasedPlugin
- Mischief.ECS.App.Plugins: addErasedRec :: ErasedPlugin -> PluginData -> PluginData
- Mischief.ECS.App.Plugins: data ErasedPlugin
- Mischief.ECS.App.Plugins: getInit :: ErasedPlugin -> System ()
- Mischief.ECS.App.Plugins: instance GHC.Internal.Base.Monoid Mischief.ECS.App.Plugins.Plugins
- Mischief.ECS.App.Plugins: instance GHC.Internal.Base.Semigroup Mischief.ECS.App.Plugins.Plugins
- Mischief.ECS.App.Plugins: instance GHC.Internal.Show.Show Mischief.ECS.App.Plugins.PluginData
- Mischief.ECS.App.Plugins: instance Mischief.ECS.App.Plugins.Plugin p => Mischief.ECS.Collectable.EraseIntoStorage p Mischief.ECS.App.Plugins.Plugins
- Mischief.ECS.App.Plugins: instance Mischief.ECS.Collectable.EraseIntoStorage () Mischief.ECS.App.Plugins.Plugins
- Mischief.ECS.App.Plugins: newtype PluginData
- Mischief.ECS.App.Plugins: newtype Plugins
- Mischief.ECS.App.Plugins: plug :: Collectable p Plugins => p -> Plugins
- Mischief.ECS.App.Plugins: plugAll :: Plugin p => p -> PluginData
- Mischief.ECS.App.Plugins: plugins :: Plugin p => p -> Plugins
- Mischief.ECS.App.Plugins: runPluginRec :: Plugin p => p -> System ()
- Mischief.ECS.App.Schedules: First :: First
- Mischief.ECS.App.Schedules: Init :: Init
- Mischief.ECS.App.Schedules: PostStartup :: PostStartup
- Mischief.ECS.App.Schedules: PostUpdate :: PostUpdate
- Mischief.ECS.App.Schedules: PreStartup :: PreStartup
- Mischief.ECS.App.Schedules: PreUpdate :: PreUpdate
- Mischief.ECS.App.Schedules: Startup :: Startup
- Mischief.ECS.App.Schedules: Update :: Update
- Mischief.ECS.App.SystemConfig: ModAfter :: s1 -> s2 -> SystemConfigModifier s1 s2
- Mischief.ECS.App.SystemConfig: ModBefore :: s1 -> s2 -> SystemConfigModifier s1 s2
- Mischief.ECS.App.SystemConfig: SystemConfigData :: [System ()] -> [(System (), System ())] -> SystemConfigData
- Mischief.ECS.App.SystemConfig: [edges] :: SystemConfigData -> [(System (), System ())]
- Mischief.ECS.App.SystemConfig: [systems] :: SystemConfigData -> [System ()]
- Mischief.ECS.App.SystemConfig: after :: s1 -> System () -> SystemConfigModifier s1 (System ())
- Mischief.ECS.App.SystemConfig: before :: s1 -> System () -> SystemConfigModifier s1 (System ())
- Mischief.ECS.App.SystemConfig: class SystemConfig s
- Mischief.ECS.App.SystemConfig: data SystemConfigData
- Mischief.ECS.App.SystemConfig: data SystemConfigModifier s1 s2
- Mischief.ECS.App.SystemConfig: instance (Mischief.ECS.App.SystemConfig.SystemConfig s0, Mischief.ECS.App.SystemConfig.SystemConfig s1) => Mischief.ECS.App.SystemConfig.SystemConfig (s0, s1)
- Mischief.ECS.App.SystemConfig: instance Mischief.ECS.App.SystemConfig.SystemConfig (Mischief.ECS.World.System ())
- Mischief.ECS.App.SystemConfig: instance Mischief.ECS.App.SystemConfig.SystemConfig s1 => Mischief.ECS.App.SystemConfig.SystemConfig (Mischief.ECS.App.SystemConfig.SystemConfigModifier s1 (Mischief.ECS.World.System ()))
- Mischief.ECS.App.SystemConfig: systemConfigData :: SystemConfig s => s -> SystemConfigData
- Mischief.ECS.Collectable: class Idk (x :: k) (y :: k1)
- Mischief.ECS.Components: ComponentData :: ErasedComponent -> ComponentTicks -> ComponentData
- Mischief.ECS.Components: ComponentTicks :: Tick -> Tick -> ComponentTicks
- Mischief.ECS.Components: [added] :: ComponentTicks -> Tick
- Mischief.ECS.Components: [changed] :: ComponentTicks -> Tick
- Mischief.ECS.Components: [ticks] :: ComponentData -> ComponentTicks
- Mischief.ECS.Components: [value] :: ComponentData -> ErasedComponent
- Mischief.ECS.Components: data ComponentData
- Mischief.ECS.Components: data ComponentTicks
- Mischief.ECS.Components: hooks :: Component c => Hooks c
- Mischief.ECS.Components: instance GHC.Internal.Show.Show Mischief.ECS.Components.ComponentTicks
- Mischief.ECS.Components: instance Mischief.ECS.Components.IsExclusive 'Mischief.ECS.Components.Exclusive
- Mischief.ECS.Components: instance Mischief.ECS.Components.IsExclusive 'Mischief.ECS.Components.Inclusive
- Mischief.ECS.Components: newtype BundleData e
- Mischief.ECS.Components: type RelExclusivity c = 'Inclusive;
- Mischief.ECS.Components.HooksDef: Hooks :: [ErasedHook c] -> Hooks (c :: k)
- Mischief.ECS.Components.HooksDef: [ErasedHook] :: forall {k} (e :: k -> Type) (c :: k) (m :: Type -> Type). (Event (e c), Typeable m) => (e c -> m ()) -> ErasedHook c
- Mischief.ECS.Components.HooksDef: data ErasedHook (c :: k)
- Mischief.ECS.Components.HooksDef: instance forall k (c :: k). GHC.Internal.Base.Semigroup (Mischief.ECS.Components.HooksDef.Hooks c)
- Mischief.ECS.Components.HooksDef: newtype Hooks (c :: k)
- Mischief.ECS.Components.Runnable: class Runnable (c :: k)
- Mischief.ECS.Components.Runnable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.Components.Bundle.Bundle c) => Mischief.ECS.Components.Runnable.Runnable c
- Mischief.ECS.Components.Runnable: instance (Mischief.ECS.Components.Runnable.Runnable r0, Mischief.ECS.Components.Runnable.Runnable r1) => Mischief.ECS.Components.Runnable.Runnable (r0, r1)
- Mischief.ECS.Components.Runnable: runFor :: forall {k} (c :: k). Runnable c => (forall d. (Component d, Bundle d) => Proxy d -> System ()) -> System ()
- Mischief.ECS.Components.Runnable: runFor' :: Runnable c => Proxy c -> (forall d. (Component d, Bundle d) => Proxy d -> System ()) -> System ()
- Mischief.ECS.Components.Spawn: registerHook :: forall {k} (c :: k). ErasedHook c -> System ()
- Mischief.ECS.Events: OnInsert :: Entity -> OnInsert (c :: k)
- Mischief.ECS.Events: OnInsertRel :: Entity -> Entity -> OnInsertRel (c :: k)
- Mischief.ECS.Events: data OnInsertRel (c :: k)
- Mischief.ECS.Events: flushEvents :: System ()
- Mischief.ECS.Events: instance forall k (c :: k). (GHC.Internal.Data.Typeable.Internal.Typeable c, GHC.Internal.Data.Typeable.Internal.Typeable k) => Mischief.ECS.EventDef.Event (Mischief.ECS.Events.OnInsert c)
- Mischief.ECS.Events: instance forall k (c :: k). (GHC.Internal.Data.Typeable.Internal.Typeable c, GHC.Internal.Data.Typeable.Internal.Typeable k) => Mischief.ECS.EventDef.Event (Mischief.ECS.Events.OnInsertRel c)
- Mischief.ECS.Events: instance forall k (c :: k). GHC.Internal.Show.Show (Mischief.ECS.Events.OnInsert c)
- Mischief.ECS.Events: instance forall k (c :: k). GHC.Internal.Show.Show (Mischief.ECS.Events.OnInsertRel c)
- Mischief.ECS.Events: newtype OnInsert (c :: k)
- Mischief.ECS.Events: runEvent :: ErasedEvent -> System ()
- Mischief.ECS.Graph: Graph :: IORef (Map a Int) -> IORef Int -> IOVec (Maybe a, Set Int) -> Graph a
- Mischief.ECS.Graph: [counter] :: Graph a -> IORef Int
- Mischief.ECS.Graph: [edges] :: Graph a -> IOVec (Maybe a, Set Int)
- Mischief.ECS.Graph: [nodes] :: Graph a -> IORef (Map a Int)
- Mischief.ECS.Graph: addEdge :: Ord a => (a, a) -> Graph a -> IO ()
- Mischief.ECS.Graph: addNode :: Ord a => a -> Graph a -> IO Int
- Mischief.ECS.Graph: data Graph a
- Mischief.ECS.Graph: getNodes :: Graph a -> IO [[a]]
- Mischief.ECS.Graph: getOrAddNode :: Ord a => a -> Graph a -> IO Int
- Mischief.ECS.Graph: newGraph :: IO (Graph a)
- Mischief.ECS.Graph: setNode :: a -> Int -> Graph a -> IO ()
- Mischief.ECS.Graph: takeRemoveableNodes :: IOVec (Maybe a, Set Int) -> IO [a]
- Mischief.ECS.Graph: tryGetNode :: Ord a => a -> Graph a -> IO (Maybe Int)
- Mischief.ECS.Hooks: instance Mischief.ECS.Components.Component c => Mischief.ECS.Components.Component (Mischief.ECS.Hooks.CleanupWatcher c)
- Mischief.ECS.Hooks: instance forall k (c :: k). Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.Components.HooksDef.Hooks c) (Mischief.ECS.Components.HooksDef.Hooks c)
- Mischief.ECS.Hooks: instance forall k (e :: k -> *) (c :: k). Mischief.ECS.EventDef.Event (e c) => Mischief.ECS.Collectable.EraseIntoStorage (e c -> Mischief.ECS.World.System ()) (Mischief.ECS.Components.HooksDef.Hooks c)
- Mischief.ECS.Hooks: relCleanup :: Component a => (CleanupRequest -> System ()) -> Hooks a
- Mischief.ECS.Hooks: relCleanupDespawn :: Component a => Hooks a
- Mischief.ECS.Hooks: relCleanupRemove :: Component a => Hooks a
- Mischief.ECS.Hooks: relComplementary :: (Component a, Component b) => (a -> b) -> Hooks a
- Mischief.ECS.Interval: data Interval
- Mischief.ECS.Interval: start :: MonadSystem w m => Int -> System () -> m Interval
- Mischief.ECS.Interval: stop :: MonadSystem w m => Interval -> m ()
- Mischief.ECS.Log: panic :: (HasCallStack, MonadSystem w m) => Text -> m ()
- Mischief.ECS.Log: text :: Show a => a -> Text
- Mischief.ECS.Mappable: InvalidRelQueryOutput :: RelQueryOutput out
- Mischief.ECS.Mappable: RelQueryOutput :: out -> RelQueryOutput out
- Mischief.ECS.Mappable: class Mappable (flag :: k) a b | flag a -> b
- Mischief.ECS.Mappable: class TryMapId (flag :: k) (flag' :: k1) c out | flag flag' c -> out
- Mischief.ECS.Mappable: data MapQueryVal
- Mischief.ECS.Mappable: data MapQueryValidity
- Mischief.ECS.Mappable: data RelQueryOutput out
- Mischief.ECS.Mappable: instance Mischief.ECS.Mappable.TryMapId 'GHC.Types.False Mischief.ECS.Mappable.MapQueryVal (Mischief.ECS.Tables.Result a) a
- Mischief.ECS.Mappable: instance Mischief.ECS.Mappable.TryMapId 'GHC.Types.False Mischief.ECS.Mappable.MapQueryValidity (Mischief.ECS.Tables.Result a) (Mischief.ECS.Tables.Result a)
- Mischief.ECS.Mappable: instance Mischief.ECS.Mappable.TryMapId 'GHC.Types.True Mischief.ECS.Mappable.MapQueryVal a a
- Mischief.ECS.Mappable: instance Mischief.ECS.Mappable.TryMapId 'GHC.Types.True Mischief.ECS.Mappable.MapQueryValidity a a
- Mischief.ECS.Mappable: instance Mischief.ECS.Mappable.TryMapId (Mischief.ECS.Mappable.MapIsId a) Mischief.ECS.Mappable.MapQueryVal a out => Mischief.ECS.Mappable.TryMapId 'GHC.Types.False Mischief.ECS.Mappable.MapQueryVal (GHC.Internal.Maybe.Maybe a) (GHC.Internal.Maybe.Maybe out)
- Mischief.ECS.Mappable: instance Mischief.ECS.Mappable.TryMapId (Mischief.ECS.Mappable.MapIsId a) Mischief.ECS.Mappable.MapQueryVal a out => Mischief.ECS.Mappable.TryMapId 'GHC.Types.False Mischief.ECS.Mappable.MapQueryVal (Mischief.ECS.Mappable.RelQueryOutput a) (Mischief.ECS.Mappable.RelQueryOutput out)
- Mischief.ECS.Mappable: instance Mischief.ECS.Mappable.TryMapId (Mischief.ECS.Mappable.MapIsId a) Mischief.ECS.Mappable.MapQueryVal a out => Mischief.ECS.Mappable.TryMapId 'GHC.Types.False Mischief.ECS.Mappable.MapQueryVal [a] [out]
- Mischief.ECS.Mappable: instance Mischief.ECS.Mappable.TryMapId (Mischief.ECS.Mappable.MapIsId a) Mischief.ECS.Mappable.MapQueryValidity a out => Mischief.ECS.Mappable.TryMapId 'GHC.Types.False Mischief.ECS.Mappable.MapQueryValidity (GHC.Internal.Maybe.Maybe a) (GHC.Internal.Maybe.Maybe out)
- Mischief.ECS.Mappable: instance Mischief.ECS.Mappable.TryMapId (Mischief.ECS.Mappable.MapIsId a) Mischief.ECS.Mappable.MapQueryValidity a out => Mischief.ECS.Mappable.TryMapId 'GHC.Types.False Mischief.ECS.Mappable.MapQueryValidity (Mischief.ECS.Mappable.RelQueryOutput a) a
- Mischief.ECS.Mappable: instance Mischief.ECS.Mappable.TryMapId (Mischief.ECS.Mappable.MapIsId a) Mischief.ECS.Mappable.MapQueryValidity a out => Mischief.ECS.Mappable.TryMapId 'GHC.Types.False Mischief.ECS.Mappable.MapQueryValidity [a] [out]
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 a8 b8 a9 b9 a10 b10 a11 b11 a12 b12 a13 b13 a14 b14. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4, Mischief.ECS.Mappable.Mappable flag a5 b5, Mischief.ECS.Mappable.Mappable flag a6 b6, Mischief.ECS.Mappable.Mappable flag a7 b7, Mischief.ECS.Mappable.Mappable flag a8 b8, Mischief.ECS.Mappable.Mappable flag a9 b9, Mischief.ECS.Mappable.Mappable flag a10 b10, Mischief.ECS.Mappable.Mappable flag a11 b11, Mischief.ECS.Mappable.Mappable flag a12 b12, Mischief.ECS.Mappable.Mappable flag a13 b13, Mischief.ECS.Mappable.Mappable flag a14 b14) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 a8 b8 a9 b9 a10 b10 a11 b11 a12 b12 a13 b13. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4, Mischief.ECS.Mappable.Mappable flag a5 b5, Mischief.ECS.Mappable.Mappable flag a6 b6, Mischief.ECS.Mappable.Mappable flag a7 b7, Mischief.ECS.Mappable.Mappable flag a8 b8, Mischief.ECS.Mappable.Mappable flag a9 b9, Mischief.ECS.Mappable.Mappable flag a10 b10, Mischief.ECS.Mappable.Mappable flag a11 b11, Mischief.ECS.Mappable.Mappable flag a12 b12, Mischief.ECS.Mappable.Mappable flag a13 b13) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 a8 b8 a9 b9 a10 b10 a11 b11 a12 b12. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4, Mischief.ECS.Mappable.Mappable flag a5 b5, Mischief.ECS.Mappable.Mappable flag a6 b6, Mischief.ECS.Mappable.Mappable flag a7 b7, Mischief.ECS.Mappable.Mappable flag a8 b8, Mischief.ECS.Mappable.Mappable flag a9 b9, Mischief.ECS.Mappable.Mappable flag a10 b10, Mischief.ECS.Mappable.Mappable flag a11 b11, Mischief.ECS.Mappable.Mappable flag a12 b12) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 a8 b8 a9 b9 a10 b10 a11 b11. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4, Mischief.ECS.Mappable.Mappable flag a5 b5, Mischief.ECS.Mappable.Mappable flag a6 b6, Mischief.ECS.Mappable.Mappable flag a7 b7, Mischief.ECS.Mappable.Mappable flag a8 b8, Mischief.ECS.Mappable.Mappable flag a9 b9, Mischief.ECS.Mappable.Mappable flag a10 b10, Mischief.ECS.Mappable.Mappable flag a11 b11) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 a8 b8 a9 b9 a10 b10. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4, Mischief.ECS.Mappable.Mappable flag a5 b5, Mischief.ECS.Mappable.Mappable flag a6 b6, Mischief.ECS.Mappable.Mappable flag a7 b7, Mischief.ECS.Mappable.Mappable flag a8 b8, Mischief.ECS.Mappable.Mappable flag a9 b9, Mischief.ECS.Mappable.Mappable flag a10 b10) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 a8 b8 a9 b9. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4, Mischief.ECS.Mappable.Mappable flag a5 b5, Mischief.ECS.Mappable.Mappable flag a6 b6, Mischief.ECS.Mappable.Mappable flag a7 b7, Mischief.ECS.Mappable.Mappable flag a8 b8, Mischief.ECS.Mappable.Mappable flag a9 b9) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 a8 b8. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4, Mischief.ECS.Mappable.Mappable flag a5 b5, Mischief.ECS.Mappable.Mappable flag a6 b6, Mischief.ECS.Mappable.Mappable flag a7 b7, Mischief.ECS.Mappable.Mappable flag a8 b8) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8) (b0, b1, b2, b3, b4, b5, b6, b7, b8)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4, Mischief.ECS.Mappable.Mappable flag a5 b5, Mischief.ECS.Mappable.Mappable flag a6 b6, Mischief.ECS.Mappable.Mappable flag a7 b7) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7) (b0, b1, b2, b3, b4, b5, b6, b7)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4, Mischief.ECS.Mappable.Mappable flag a5 b5, Mischief.ECS.Mappable.Mappable flag a6 b6) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4, a5, a6) (b0, b1, b2, b3, b4, b5, b6)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4, Mischief.ECS.Mappable.Mappable flag a5 b5) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4, a5) (b0, b1, b2, b3, b4, b5)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3 a4 b4. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3, Mischief.ECS.Mappable.Mappable flag a4 b4) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3, a4) (b0, b1, b2, b3, b4)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2 a3 b3. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2, Mischief.ECS.Mappable.Mappable flag a3 b3) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2, a3) (b0, b1, b2, b3)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1 a2 b2. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1, Mischief.ECS.Mappable.Mappable flag a2 b2) => Mischief.ECS.Mappable.Mappable flag (a0, a1, a2) (b0, b1, b2)
- Mischief.ECS.Mappable: instance forall k (flag :: k) a0 b0 a1 b1. (Mischief.ECS.Mappable.Mappable flag a0 b0, Mischief.ECS.Mappable.Mappable flag a1 b1) => Mischief.ECS.Mappable.Mappable flag (a0, a1) (b0, b1)
- Mischief.ECS.Mappable: instance forall k a (flag :: k) out. Mischief.ECS.Mappable.TryMapId (Mischief.ECS.Mappable.MapIsId a) flag a out => Mischief.ECS.Mappable.Mappable flag a out
- Mischief.ECS.Mappable: mapTuple :: Mappable flag a b => a -> b
- Mischief.ECS.Mappable: tryMapId :: TryMapId flag flag' c out => c -> out
- Mischief.ECS.Mappable: type family MapIsId c :: Bool
- Mischief.ECS.Observer: ObserverOrder :: Int -> ObserverOrder
- Mischief.ECS.Observer: instance Data.Default.Internal.Default Mischief.ECS.Observer.ObserverOrder
- Mischief.ECS.Observer: instance GHC.Classes.Eq Mischief.ECS.Observer.ObserverOrder
- Mischief.ECS.Observer: instance GHC.Classes.Ord Mischief.ECS.Observer.ObserverOrder
- Mischief.ECS.Observer: instance GHC.Internal.Show.Show Mischief.ECS.Observer.ObserverOrder
- Mischief.ECS.Observer: instance Mischief.ECS.Components.Component Mischief.ECS.Observer.ObserverOrder
- Mischief.ECS.Observer: newtype ObserverOrder
- Mischief.ECS.Observers: spawn :: Event e => (e -> System ()) -> System Entity
- Mischief.ECS.Prelude: (|.) :: a -> b -> Or a b
- Mischief.ECS.Prelude: Added :: c -> Added c
- Mischief.ECS.Prelude: Any :: Any
- Mischief.ECS.Prelude: C :: C (a :: k)
- Mischief.ECS.Prelude: Changed :: c -> Changed c
- Mischief.ECS.Prelude: Check :: (c -> Bool) -> Check c
- Mischief.ECS.Prelude: CheckR :: e -> (c -> Bool) -> CheckR e c
- Mischief.ECS.Prelude: E :: E
- Mischief.ECS.Prelude: Exclusive :: Exclusivity
- Mischief.ECS.Prelude: First :: First
- Mischief.ECS.Prelude: Has :: Has (a :: k)
- Mischief.ECS.Prelude: HasR :: b -> HasR (a :: k) b
- Mischief.ECS.Prelude: Inclusive :: Exclusivity
- Mischief.ECS.Prelude: M :: M (a :: k)
- Mischief.ECS.Prelude: MR :: b -> MR (a :: k) b
- Mischief.ECS.Prelude: Not :: c -> Not c
- Mischief.ECS.Prelude: PostStartup :: PostStartup
- Mischief.ECS.Prelude: PostUpdate :: PostUpdate
- Mischief.ECS.Prelude: PreStartup :: PreStartup
- Mischief.ECS.Prelude: PreUpdate :: PreUpdate
- Mischief.ECS.Prelude: Q :: a -> Q a
- Mischief.ECS.Prelude: R :: b -> R (a :: k) b
- Mischief.ECS.Prelude: Rel :: c -> Entity -> Rel c
- Mischief.ECS.Prelude: Startup :: Startup
- Mischief.ECS.Prelude: TimePlugin :: TimePlugin
- Mischief.ECS.Prelude: Update :: Update
- Mischief.ECS.Prelude: Val :: a -> Val a
- Mischief.ECS.Prelude: With :: c -> With c
- Mischief.ECS.Prelude: Without :: c -> Without c
- Mischief.ECS.Prelude: [comp] :: Rel c -> c
- Mischief.ECS.Prelude: [target] :: Rel c -> Entity
- Mischief.ECS.Prelude: after :: s1 -> System () -> SystemConfigModifier s1 (System ())
- Mischief.ECS.Prelude: before :: s1 -> System () -> SystemConfigModifier s1 (System ())
- Mischief.ECS.Prelude: class (Typeable c, IsExclusive RelExclusivity c) => Component c where {
- Mischief.ECS.Prelude: class Typeable e => Event e
- Mischief.ECS.Prelude: class (Typeable p, Eq p) => Plugin p
- Mischief.ECS.Prelude: data Any
- Mischief.ECS.Prelude: data C (a :: k)
- Mischief.ECS.Prelude: data CheckR e c
- Mischief.ECS.Prelude: data E
- Mischief.ECS.Prelude: data Entity
- Mischief.ECS.Prelude: data Exclusivity
- Mischief.ECS.Prelude: data First
- Mischief.ECS.Prelude: data Has (a :: k)
- Mischief.ECS.Prelude: data M (a :: k)
- Mischief.ECS.Prelude: data PostStartup
- Mischief.ECS.Prelude: data PostUpdate
- Mischief.ECS.Prelude: data PreStartup
- Mischief.ECS.Prelude: data PreUpdate
- Mischief.ECS.Prelude: data Rel c
- Mischief.ECS.Prelude: data Startup
- Mischief.ECS.Prelude: data System a
- Mischief.ECS.Prelude: data Time
- Mischief.ECS.Prelude: data TimePlugin
- Mischief.ECS.Prelude: data Update
- Mischief.ECS.Prelude: defer :: Defer m => System a -> m ()
- Mischief.ECS.Prelude: delay :: MonadSystem w m => Int -> System () -> m ()
- Mischief.ECS.Prelude: deltaSecs :: Time -> Float
- Mischief.ECS.Prelude: deltaTime :: System Float
- Mischief.ECS.Prelude: despawn :: Entity -> System ()
- Mischief.ECS.Prelude: expect :: HasCallStack => forall (m :: Type -> Type) w a. MonadSystem w m => Text -> Maybe a -> m a
- Mischief.ECS.Prelude: get :: forall qd m w out. (Queryable qd out, MonadSystem w m) => qd -> Entity -> m (Maybe out)
- Mischief.ECS.Prelude: get' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> Entity -> m (Maybe out)
- Mischief.ECS.Prelude: hooks :: Component c => Hooks c
- Mischief.ECS.Prelude: infix 9 |.
- Mischief.ECS.Prelude: init :: Plugin p => p -> System ()
- Mischief.ECS.Prelude: insert :: HasCallStack => forall b. Bundle b => b -> Entity -> System ()
- Mischief.ECS.Prelude: insertIfNeq :: BundleEq b => b -> Entity -> System ()
- Mischief.ECS.Prelude: insertNew :: Bundle b => b -> Entity -> System ()
- Mischief.ECS.Prelude: modify :: (Updateable (Result c), Settable (Result c) i, DeepValue (Result c) i) => Result c -> (i -> i) -> System ()
- Mischief.ECS.Prelude: modify' :: (Settable (Result c) i, DeepValue (Result c) i) => Result c -> (i -> i) -> System ()
- Mischief.ECS.Prelude: newApp :: Plugin p => p -> IO App
- Mischief.ECS.Prelude: newtype Added c
- Mischief.ECS.Prelude: newtype Changed c
- Mischief.ECS.Prelude: newtype Check c
- Mischief.ECS.Prelude: newtype HasR (a :: k) b
- Mischief.ECS.Prelude: newtype MR (a :: k) b
- Mischief.ECS.Prelude: newtype Not c
- Mischief.ECS.Prelude: newtype Q a
- Mischief.ECS.Prelude: newtype R (a :: k) b
- Mischief.ECS.Prelude: newtype Val a
- Mischief.ECS.Prelude: newtype With c
- Mischief.ECS.Prelude: newtype Without c
- Mischief.ECS.Prelude: plug :: Collectable p Plugins => p -> Plugins
- Mischief.ECS.Prelude: plugins :: Plugin p => p -> Plugins
- Mischief.ECS.Prelude: query :: forall qd output m w. (Queryable qd output, MonadSystem w m) => qd -> m [output]
- Mischief.ECS.Prelude: query' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> m [out]
- Mischief.ECS.Prelude: remove :: Collectable c ToRemove => c -> Entity -> System ()
- Mischief.ECS.Prelude: require :: forall {k} (b :: k). RequiredBundle b => Set DefaultComponentType
- Mischief.ECS.Prelude: required :: Component c => Set DefaultComponentType
- Mischief.ECS.Prelude: runAfter :: MonadSystem w m => IO a -> (a -> System ()) -> m ()
- Mischief.ECS.Prelude: runApp :: App -> IO ()
- Mischief.ECS.Prelude: set :: Settable c i => c -> i -> System ()
- Mischief.ECS.Prelude: setIfNeq :: (Eq i, Settable c i) => c -> i -> System ()
- Mischief.ECS.Prelude: single :: forall qd m w out. (Queryable qd out, MonadSystem w m) => qd -> m (Maybe out)
- Mischief.ECS.Prelude: single' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> m (Maybe out)
- Mischief.ECS.Prelude: spawn :: (HasCallStack, Bundle b) => b -> System Entity
- Mischief.ECS.Prelude: spawnDefer :: Bundle b => b -> ParSystem Entity
- Mischief.ECS.Prelude: trigger :: Event e => e -> System ()
- Mischief.ECS.Prelude: type RelExclusivity c = 'Inclusive;
- Mischief.ECS.Prelude: type family RelExclusivity c :: Exclusivity
- Mischief.ECS.Prelude: unwrap :: HasCallStack => Maybe a -> a
- Mischief.ECS.Prelude: update :: Updateable (Result c) => Result c -> System (Maybe (Result c))
- Mischief.ECS.Prelude: }
- Mischief.ECS.Relationships: IsExclusiveRelationship :: IsExclusiveRelationship
- Mischief.ECS.Relationships: data IsExclusiveRelationship
- Mischief.ECS.Relationships: instance GHC.Internal.Show.Show Mischief.ECS.Relationships.IsExclusiveRelationship
- Mischief.ECS.Relationships: instance Mischief.ECS.Components.Component Mischief.ECS.Relationships.IsExclusiveRelationship
- Mischief.ECS.Systems: add :: (HasCallStack, Schedule sc, SystemConfig s) => sc -> s -> System ()
- Mischief.ECS.Systems: remove :: (Schedule sc, Collectable a [System ()]) => sc -> a -> System ()
- Mischief.ECS.Systems: removeOne :: Schedule sc => sc -> System () -> System ()
- Mischief.ECS.Tables: Result :: (c, Entity) -> Result c
- Mischief.ECS.Tables: [ErasedResult] :: forall c. Result c -> ErasedResult
- Mischief.ECS.Tables: class DeepValue c i | c -> i
- Mischief.ECS.Tables: class DeepValue' (flag :: k) c i | flag c -> i
- Mischief.ECS.Tables: data ErasedResult
- Mischief.ECS.Tables: deepValue :: DeepValue c i => c -> i
- Mischief.ECS.Tables: deepValue' :: DeepValue' flag c i => c -> i
- Mischief.ECS.Tables: entityOf :: Result c -> Entity
- Mischief.ECS.Tables: instance GHC.Classes.Eq c => GHC.Classes.Eq (Mischief.ECS.Tables.Result c)
- Mischief.ECS.Tables: instance GHC.Classes.Ord c => GHC.Classes.Ord (Mischief.ECS.Tables.Result c)
- Mischief.ECS.Tables: instance GHC.Internal.Show.Show c => GHC.Internal.Show.Show (Mischief.ECS.Tables.Result c)
- Mischief.ECS.Tables: instance Mischief.ECS.Tables.DeepValue' 'GHC.Types.False (Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c)) c
- Mischief.ECS.Tables: instance Mischief.ECS.Tables.DeepValue' 'GHC.Types.True (Mischief.ECS.Tables.Result c) c
- Mischief.ECS.Tables: instance Mischief.ECS.Tables.DeepValue' (Mischief.ECS.Tables.IsComp c) (Mischief.ECS.Tables.Result c) i => Mischief.ECS.Tables.DeepValue (Mischief.ECS.Tables.Result c) i
- Mischief.ECS.Tables: instance forall k (a :: k) b c. GHC.Internal.Records.HasField a b c => GHC.Internal.Records.HasField a (Mischief.ECS.Tables.Result b) c
- Mischief.ECS.Tables: newtype Result c
- Mischief.ECS.Tables: tryGetEntityTicksFromArchetype :: ArchetypeId -> IOVec Table -> EntityPointer -> ComponentId -> IO (Maybe ComponentTicks)
- Mischief.ECS.Tables: tryGetEntityTicksFromColumn :: Column -> EntityPointer -> IO ComponentTicks
- Mischief.ECS.Tables: tryGetEntityTicksFromTable :: Table -> EntityPointer -> ComponentId -> IO (Maybe ComponentTicks)
- Mischief.ECS.Tables: tryGetEntityTicksFromTables :: Tables -> EntityPointer -> ComponentId -> IO (Maybe ComponentTicks)
- Mischief.ECS.Tables: tryGetTicksFromColumn :: Column -> IO [ComponentTicks]
- Mischief.ECS.Tables: type family IsComp a :: Bool
- Mischief.ECS.Tables: value :: Result c -> c
- Mischief.ECS.Time: TimePlugin :: TimePlugin
- Mischief.ECS.Time: deltaSecs :: Time -> Float
- Mischief.ECS.Time: deltaTime :: System Float
- Mischief.ECS.Time: instance GHC.Classes.Eq Mischief.ECS.Time.TimePlugin
- Mischief.ECS.Time: instance GHC.Internal.Show.Show Mischief.ECS.Time.Time
- Mischief.ECS.Time: instance Mischief.ECS.Components.Component Mischief.ECS.Time.Time
- Mischief.ECS.Utils: (>>+) :: Monad m => m a -> (a -> m b) -> m a
- Mischief.ECS.World: [events] :: World -> IORef [ErasedEvent]
- Mischief.ECS.World: forkPrefs :: (WorldPrefs -> WorldPrefs) -> System a -> System a
- Mischief.ECS.World: setPrefs :: WorldPrefs -> World -> World
- Mischief.ECS.World.Defer: class Defer (m :: Type -> Type)
- Mischief.ECS.World.Defer: instance Mischief.ECS.World.Defer.Defer Mischief.ECS.World.ParSystem
- Mischief.ECS.World.Defer: instance Mischief.ECS.World.Defer.Defer Mischief.ECS.World.System
- Mischief.ECS.World.Insert: class Settable c i | c -> i
- Mischief.ECS.World.Insert: class Settable' (isRel :: k) c i | isRel c -> i
- Mischief.ECS.World.Insert: class Updateable r
- Mischief.ECS.World.Insert: class Updateable' (flag :: k) r
- Mischief.ECS.World.Insert: getOrInsert :: (Updateable (Result qd), Bundle qd) => qd -> Entity -> System (Result qd)
- Mischief.ECS.World.Insert: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.IsComponentC c GHC.Types.~ Mischief.ECS.World.Query.Queryable.HTrue) => Mischief.ECS.World.Insert.Settable' 'GHC.Types.True (Mischief.ECS.Tables.Result c) c
- Mischief.ECS.World.Insert: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.IsComponentC c GHC.Types.~ Mischief.ECS.World.Query.Queryable.HTrue) => Mischief.ECS.World.Insert.Updateable' 'GHC.Types.True (Mischief.ECS.Tables.Result c)
- Mischief.ECS.World.Insert: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Insert.Settable' 'GHC.Types.False (Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c)) c
- Mischief.ECS.World.Insert: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Insert.Updateable' 'GHC.Types.False (Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c))
- Mischief.ECS.World.Insert: instance Mischief.ECS.World.Insert.Settable' (Mischief.ECS.Tables.IsComp c) (Mischief.ECS.Tables.Result c) i => Mischief.ECS.World.Insert.Settable (Mischief.ECS.Tables.Result c) i
- Mischief.ECS.World.Insert: instance Mischief.ECS.World.Insert.Updateable' (Mischief.ECS.Tables.IsComp c) (Mischief.ECS.Tables.Result c) => Mischief.ECS.World.Insert.Updateable (Mischief.ECS.Tables.Result c)
- Mischief.ECS.World.Insert: set :: Settable c i => c -> i -> System ()
- Mischief.ECS.World.Insert: setIfNeq :: (Eq i, Settable c i) => c -> i -> System ()
- Mischief.ECS.World.Insert: setIfNeqInner :: Settable c i => c -> i -> System ()
- Mischief.ECS.World.Insert: setIfNeqInner' :: Settable' isRel c i => c -> i -> System ()
- Mischief.ECS.World.Insert: setInner :: Settable c i => c -> i -> System ()
- Mischief.ECS.World.Insert: setInner' :: Settable' isRel c i => c -> i -> System ()
- Mischief.ECS.World.Insert: triggerInsertEvent :: ProcessedBundleData -> Entity -> System ()
- Mischief.ECS.World.Insert: triggerInsertEventC :: ErasedComponent -> Entity -> System ()
- Mischief.ECS.World.Insert: triggerInsertEventR :: ErasedComponent -> Entity -> Entity -> System ()
- Mischief.ECS.World.Insert: update :: Updateable (Result c) => Result c -> System (Maybe (Result c))
- Mischief.ECS.World.Insert: updateInner :: Updateable r => r -> System (Maybe r)
- Mischief.ECS.World.Insert: updateInner' :: Updateable' flag r => r -> System (Maybe r)
- Mischief.ECS.World.Modify: alter :: (Queryable (C c) (Result c), Bundle c, Component c) => (Maybe c -> Maybe c) -> Entity -> System ()
- Mischief.ECS.World.Modify: modify :: (Updateable (Result c), Settable (Result c) i, DeepValue (Result c) i) => Result c -> (i -> i) -> System ()
- Mischief.ECS.World.Modify: modify' :: (Settable (Result c) i, DeepValue (Result c) i) => Result c -> (i -> i) -> System ()
- Mischief.ECS.World.Par: group :: Int -> [a] -> [[a]]
- Mischief.ECS.World.Par: parIter :: (MonadSystem w m, Foldable t) => t a -> (a -> ParSystem b) -> m [b]
- Mischief.ECS.World.Par: parIter_ :: (MonadSystem w m, Foldable t) => t a -> (a -> ParSystem b) -> m ()
- Mischief.ECS.World.Prefs: WorldPrefs :: Bool -> WorldPrefs
- Mischief.ECS.World.Prefs: [supressEvents] :: WorldPrefs -> Bool
- Mischief.ECS.World.Prefs: newPrefs :: WorldPrefs
- Mischief.ECS.World.Prefs: newtype WorldPrefs
- Mischief.ECS.World.Prefs: supressEvents :: Bool -> WorldPrefs -> WorldPrefs
- Mischief.ECS.World.Query: added :: forall c m w. (MonadSystem w m, GetResultComponentId (Result c)) => Result c -> m Bool
- Mischief.ECS.World.Query: addedChanged :: forall c m w. (MonadSystem w m, GetResultComponentId (Result c)) => (ComponentTicks -> Tick -> Tick -> Bool) -> Result c -> m Bool
- Mischief.ECS.World.Query: changed :: forall c m w. (MonadSystem w m, GetResultComponentId (Result c)) => Result c -> m Bool
- Mischief.ECS.World.Query: class GetResultComponentId c
- Mischief.ECS.World.Query: class GetResultComponentId' (flag :: k) c
- Mischief.ECS.World.Query: get :: forall qd m w out. (Queryable qd out, MonadSystem w m) => qd -> Entity -> m (Maybe out)
- Mischief.ECS.World.Query: get' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> Entity -> m (Maybe out)
- Mischief.ECS.World.Query: getResultComponentId :: (GetResultComponentId c, MonadSystem w m) => c -> m (Maybe ComponentId)
- Mischief.ECS.World.Query: getResultComponentId' :: (GetResultComponentId' flag c, MonadSystem w m) => c -> m (Maybe ComponentId)
- Mischief.ECS.World.Query: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.GetResultComponentId' 'GHC.Types.False (Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c))
- Mischief.ECS.World.Query: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.GetResultComponentId' 'GHC.Types.True (Mischief.ECS.Tables.Result c)
- Mischief.ECS.World.Query: instance Mischief.ECS.World.Query.GetResultComponentId' (Mischief.ECS.Tables.IsComp c) (Mischief.ECS.Tables.Result c) => Mischief.ECS.World.Query.GetResultComponentId (Mischief.ECS.Tables.Result c)
- Mischief.ECS.World.Query: query' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> m [out]
- Mischief.ECS.World.Query: runQuery :: forall qd m w output. (Queryable qd output, MonadSystem w m) => qd -> QueryFilter -> World -> m [output]
- Mischief.ECS.World.Query: single' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> m (Maybe out)
- Mischief.ECS.World.Query.QueryFilter: (&.) :: a -> b -> And a b
- Mischief.ECS.World.Query.QueryFilter: (|.) :: a -> b -> Or a b
- Mischief.ECS.World.Query.QueryFilter: Added :: c -> Added c
- Mischief.ECS.World.Query.QueryFilter: And :: a -> b -> And a b
- Mischief.ECS.World.Query.QueryFilter: Changed :: c -> Changed c
- Mischief.ECS.World.Query.QueryFilter: Check :: (c -> Bool) -> Check c
- Mischief.ECS.World.Query.QueryFilter: CheckFilterType :: [(TypeRep, Maybe Entity, Maybe Any, ErasedCheck)] -> CheckFilterType
- Mischief.ECS.World.Query.QueryFilter: CheckR :: e -> (c -> Bool) -> CheckR e c
- Mischief.ECS.World.Query.QueryFilter: FilterType :: [(TypeRep, Maybe Entity, Maybe Any)] -> FilterType
- Mischief.ECS.World.Query.QueryFilter: NoFilter :: QueryFilter
- Mischief.ECS.World.Query.QueryFilter: Not :: c -> Not c
- Mischief.ECS.World.Query.QueryFilter: Or :: a -> b -> Or a b
- Mischief.ECS.World.Query.QueryFilter: QFAnd :: QueryFilter -> QueryFilter -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: QFChanged :: (TypeRep, Maybe Entity) -> (ComponentTicks -> Tick -> Tick -> Bool) -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: QFChangedRelAny :: TypeRep -> (ComponentTicks -> Tick -> Tick -> Bool) -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: QFCheckRaw :: (TypeRep, Maybe Entity, ErasedCheck) -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: QFCheckRawRelAny :: (TypeRep, ErasedCheck) -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: QFNot :: QueryFilter -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: QFOr :: QueryFilter -> QueryFilter -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: QFWith :: (TypeRep, Maybe Entity) -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: QFWithRelAny :: TypeRep -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: With :: c -> With c
- Mischief.ECS.World.Query.QueryFilter: Without :: c -> Without c
- Mischief.ECS.World.Query.QueryFilter: [ErasedCheck] :: forall c. Component c => (c -> Bool) -> ErasedCheck
- Mischief.ECS.World.Query.QueryFilter: addedF :: (TypeRep, Maybe Entity, Maybe Any) -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: and' :: [QueryFilter] -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: changedF :: (TypeRep, Maybe Entity, Maybe Any) -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: checkF :: (TypeRep, Maybe Entity, Maybe Any, ErasedCheck) -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: class IntoQueryFilter qf
- Mischief.ECS.World.Query.QueryFilter: data And a b
- Mischief.ECS.World.Query.QueryFilter: data CheckR e c
- Mischief.ECS.World.Query.QueryFilter: data ErasedCheck
- Mischief.ECS.World.Query.QueryFilter: data Or a b
- Mischief.ECS.World.Query.QueryFilter: extractArchetypeFilters :: QueryFilter -> (QueryFilter, QueryFilter)
- Mischief.ECS.World.Query.QueryFilter: filterArchetype :: QueryFilter -> [ComponentId] -> World -> IO Bool
- Mischief.ECS.World.Query.QueryFilter: infix 8 &.
- Mischief.ECS.World.Query.QueryFilter: infix 9 |.
- Mischief.ECS.World.Query.QueryFilter: instance (Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter a, Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter b) => Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter (Mischief.ECS.World.Query.QueryFilter.And a b)
- Mischief.ECS.World.Query.QueryFilter: instance (Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter a, Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter b) => Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter (Mischief.ECS.World.Query.QueryFilter.Or a b)
- Mischief.ECS.World.Query.QueryFilter: instance GHC.Internal.Base.Semigroup Mischief.ECS.World.Query.QueryFilter.CheckFilterType
- Mischief.ECS.World.Query.QueryFilter: instance GHC.Internal.Base.Semigroup Mischief.ECS.World.Query.QueryFilter.FilterType
- Mischief.ECS.World.Query.QueryFilter: instance GHC.Internal.Show.Show Mischief.ECS.World.Query.QueryFilter.ErasedCheck
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Collectable.Collectable c Mischief.ECS.World.Query.QueryFilter.FilterType => Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter (Mischief.ECS.World.Query.QueryFilter.Added c)
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Collectable.Collectable c Mischief.ECS.World.Query.QueryFilter.FilterType => Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter (Mischief.ECS.World.Query.QueryFilter.Changed c)
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Collectable.Collectable c Mischief.ECS.World.Query.QueryFilter.FilterType => Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter (Mischief.ECS.World.Query.QueryFilter.With c)
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Collectable.Collectable c Mischief.ECS.World.Query.QueryFilter.FilterType => Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter (Mischief.ECS.World.Query.QueryFilter.Without c)
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Collectable.EraseIntoStorage Mischief.ECS.World.Query.QueryFilter.QueryFilter Mischief.ECS.World.Query.QueryFilter.QueryFilter
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.World.Query.Markers.C c) Mischief.ECS.World.Query.QueryFilter.FilterType
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.World.Query.Markers.C c, c -> GHC.Types.Bool) Mischief.ECS.World.Query.QueryFilter.CheckFilterType
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.EntityDef.Entity) Mischief.ECS.World.Query.QueryFilter.FilterType
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.EntityDef.Entity, c -> GHC.Types.Bool) Mischief.ECS.World.Query.QueryFilter.CheckFilterType
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any) Mischief.ECS.World.Query.QueryFilter.FilterType
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any, c -> GHC.Types.Bool) Mischief.ECS.World.Query.QueryFilter.CheckFilterType
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter (Mischief.ECS.World.Query.QueryFilter.Check c)
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter (Mischief.ECS.World.Query.QueryFilter.CheckR Mischief.ECS.EntityDef.Entity c)
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter (Mischief.ECS.World.Query.QueryFilter.CheckR Mischief.ECS.World.Query.Markers.Any c)
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter q => Mischief.ECS.Collectable.EraseIntoStorage q Mischief.ECS.World.Query.QueryFilter.QueryFilter
- Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter q => Mischief.ECS.World.Query.QueryFilter.IntoQueryFilter (Mischief.ECS.World.Query.QueryFilter.Not q)
- Mischief.ECS.World.Query.QueryFilter: intoQueryFilter :: IntoQueryFilter qf => qf -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: isArchetypeFilter :: QueryFilter -> Bool
- Mischief.ECS.World.Query.QueryFilter: newtype Added c
- Mischief.ECS.World.Query.QueryFilter: newtype Changed c
- Mischief.ECS.World.Query.QueryFilter: newtype Check c
- Mischief.ECS.World.Query.QueryFilter: newtype CheckFilterType
- Mischief.ECS.World.Query.QueryFilter: newtype FilterType
- Mischief.ECS.World.Query.QueryFilter: newtype Not c
- Mischief.ECS.World.Query.QueryFilter: newtype With c
- Mischief.ECS.World.Query.QueryFilter: newtype Without c
- Mischief.ECS.World.Query.QueryFilter: preprocessFilter :: QueryFilter -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: propagateQFNot :: QueryFilter -> QueryFilter
- Mischief.ECS.World.Query.QueryFilter: qfAddedF :: ComponentTicks -> Tick -> Tick -> Bool
- Mischief.ECS.World.Query.QueryFilter: type family InnerC a
- Mischief.ECS.World.Query.QueryFilter: withF :: (TypeRep, Maybe Entity, Maybe Any) -> QueryFilter
- Mischief.ECS.World.Query.QueryType: type QueryType a = (Mappable MapQueryVal a a, Mappable MapQueryValidity a a, Component a)
- Mischief.ECS.World.Query.Queryable: addedChanged' :: forall m w. MonadSystem w m => (ComponentTicks -> Tick -> Tick -> Bool) -> ComponentId -> Entity -> m Bool
- Mischief.ECS.World.Query.Queryable: filterQuery :: MonadSystem w m => QueryFilter -> Entity -> m Bool
- Mischief.ECS.World.Query.Queryable: filterQueryIO :: QueryFilter -> World -> Entity -> IO Bool
- Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Exclusive (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q q)) (GHC.Internal.Maybe.Maybe out)
- Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Exclusive (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q q)) out
- Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Inclusive (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q q)) (GHC.Internal.Maybe.Maybe [out])
- Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Inclusive (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q q)) [out]
- Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out, Mischief.ECS.Collectable.Collectable f Mischief.ECS.World.Query.QueryFilter.QueryFilter) => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.HasR c (Mischief.ECS.World.Query.Markers.Q' q f)) GHC.Types.Bool
- Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out, Mischief.ECS.Collectable.Collectable f Mischief.ECS.World.Query.QueryFilter.QueryFilter) => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Exclusive (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q' q f)) (GHC.Internal.Maybe.Maybe out)
- Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out, Mischief.ECS.Collectable.Collectable f Mischief.ECS.World.Query.QueryFilter.QueryFilter) => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Exclusive (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q' q f)) out
- Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out, Mischief.ECS.Collectable.Collectable f Mischief.ECS.World.Query.QueryFilter.QueryFilter) => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Inclusive (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q' q f)) (GHC.Internal.Maybe.Maybe [out])
- Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out, Mischief.ECS.Collectable.Collectable f Mischief.ECS.World.Query.QueryFilter.QueryFilter) => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Inclusive (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q' q f)) [out]
- Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.World.Query.Queryable.Queryable qd out, Mischief.ECS.Mappable.Mappable Mischief.ECS.Mappable.MapQueryVal out out') => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.Val qd) out'
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.C c) (Mischief.ECS.Tables.Result c)
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.M c) (GHC.Internal.Maybe.Maybe (Mischief.ECS.Tables.Result c))
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.MR c Mischief.ECS.EntityDef.Entity) (GHC.Internal.Maybe.Maybe (Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c)))
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.EntityDef.Entity) (Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c))
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Exclusive (Mischief.ECS.World.Query.Markers.MR c Mischief.ECS.World.Query.Markers.Any) (GHC.Internal.Maybe.Maybe (Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c)))
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Exclusive (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any) (Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c))
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Inclusive (Mischief.ECS.World.Query.Markers.MR c Mischief.ECS.World.Query.Markers.Any) (GHC.Internal.Maybe.Maybe [Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c)])
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Inclusive (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any) [Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c)]
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.RelExclusivity c) (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q q)) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q q)) out
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.RelExclusivity c) (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q' q f)) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q' q f)) out
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.RelExclusivity c) (Mischief.ECS.World.Query.Markers.MR c Mischief.ECS.World.Query.Markers.Any) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.MR c Mischief.ECS.World.Query.Markers.Any) out
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.RelExclusivity c) (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q q)) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q q)) out
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.RelExclusivity c) (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q' q f)) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q' q f)) out
- Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.RelExclusivity c) (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any) out
- Mischief.ECS.World.Query.Queryable: instance forall k (c :: k) e out. Mischief.ECS.World.Query.Queryable.RelQuery 'Mischief.ECS.Components.Inclusive (Mischief.ECS.World.Query.Markers.R c e) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.R' c e) out
- Mischief.ECS.World.Query.TH: g :: QuasiQuoter
- Mischief.ECS.World.Query.TH: instance GHC.Internal.Show.Show Mischief.ECS.World.Query.TH.Query
- Mischief.ECS.World.Query.TH: s :: QuasiQuoter
- Mischief.ECS.World.Query.TH.Common: getTypeName :: Text -> Q Name
- Mischief.ECS.World.Query.TH.QD: TestG :: TestG (a :: k) (b :: k1)
- Mischief.ECS.World.Query.TH.QD: data TestG (a :: k) (b :: k1)
- Mischief.ECS.World.Query.TH.QD: instance forall k1 (a :: k1) k2 (b :: k2). (GHC.Internal.Data.Typeable.Internal.Typeable a, GHC.Internal.Data.Typeable.Internal.Typeable b, GHC.Internal.Data.Typeable.Internal.Typeable k1, GHC.Internal.Data.Typeable.Internal.Typeable k2) => Mischief.ECS.Components.Component (Mischief.ECS.World.Query.TH.QD.TestG a b)
- Mischief.ECS.World.Query.TH.QD: pName :: Parser Text
- Mischief.ECS.World.Query.TH.QD: pVal :: Parser Qd
- Mischief.ECS.World.Query.TH.QF: Added' :: [QfType] -> Qf
- Mischief.ECS.World.Query.TH.QF: Check' :: CompType -> Text -> Qf
- Mischief.ECS.World.Query.TH.QF: Not' :: Qf -> Qf
- Mischief.ECS.World.Query.TH.QF: pAdded :: Parser Qf
- Mischief.ECS.World.Query.TH.QF: pCheck :: Parser Qf
- Mischief.ECS.World.Query.TH.QF: pF :: Parser Text
- Mischief.ECS.World.Query.TH.QF: pNot :: Parser Qf
- Mischief.ECS.World.Query.TH.QF: processCheck :: CompType -> Exp -> Q Exp
- Mischief.ECS.World.Remove: delete :: Delete r => r -> System ()
- Mischief.ECS.World.Remove: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Remove.Delete' (Mischief.ECS.Tables.Result (Mischief.ECS.Components.Rel c)) 'GHC.Types.False
- Mischief.ECS.World.Remove: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Remove.Delete' (Mischief.ECS.Tables.Result c) 'GHC.Types.True
- Mischief.ECS.World.Remove: instance Mischief.ECS.World.Remove.Delete' (Mischief.ECS.Tables.Result r) (Mischief.ECS.Tables.IsComp r) => Mischief.ECS.World.Remove.Delete (Mischief.ECS.Tables.Result r)
- Mischief.ECS.World.Spawn: spawnObserver :: Event e => Observer e -> System ()
- Mischief.ECS.World.Spawn: spawnObserverOrdered :: Event e => Observer e -> Int -> System ()
- Mischief.ECS.World.Utils: expect :: HasCallStack => forall (m :: Type -> Type) w a. MonadSystem w m => Text -> Maybe a -> m a
- Mischief.ECS.World.Utils: setAddedTickOfComponents :: ProcessedBundleData -> (ComponentId -> Bool) -> Tick -> ProcessedBundleData
- Mischief.ECS.World.Utils: setChangedTickOfComponents :: ProcessedBundleData -> (ComponentId -> Bool) -> Tick -> ProcessedBundleData
- Mischief.ECS.World.Utils: tryGetEntityTicks :: Entity -> ComponentId -> World -> IO (Maybe ComponentTicks)
+ Mischief.ECS: (&) :: a -> (a -> b) -> b
+ Mischief.ECS: -- or not.
+ Mischief.ECS: -- | Set whether the relationships this component is used in are exclusive
+ Mischief.ECS: Any :: Any
+ Mischief.ECS: Before :: Before
+ Mischief.ECS: C :: C (a :: k)
+ Mischief.ECS: ChildOf :: ChildOf
+ Mischief.ECS: E :: E
+ Mischief.ECS: FixedFrequency :: Float -> FixedFrequency
+ Mischief.ECS: From :: Entity -> c -> From c
+ Mischief.ECS: Has :: Has (a :: k)
+ Mischief.ECS: HasR :: b -> HasR (a :: k) b
+ Mischief.ECS: HookContext :: Entity -> HookContext
+ Mischief.ECS: HookContextRel :: Entity -> Entity -> HookContextRel
+ Mischief.ECS: M :: M (a :: k)
+ Mischief.ECS: MR :: b -> MR (a :: k) b
+ Mischief.ECS: Observer :: (e -> System ()) -> Observer e
+ Mischief.ECS: Q :: a -> Q a
+ Mischief.ECS: Q' :: a -> b -> Q' a b
+ Mischief.ECS: R :: b -> R (a :: k) b
+ Mischief.ECS: R' :: b -> R' (a :: k) b
+ Mischief.ECS: Rel :: c -> Entity -> Rel c
+ Mischief.ECS: Res :: c -> Res c
+ Mischief.ECS: StartupSchedule :: StartupSchedule
+ Mischief.ECS: Tick :: Word64 -> Tick
+ Mischief.ECS: UpdateSchedule :: UpdateSchedule
+ Mischief.ECS: [And] :: QueryFilter -> QueryFilter -> QueryFilter
+ Mischief.ECS: [Changed] :: forall a. ToFilterComponent a => a -> (Tick -> Tick -> Bool) -> (Tick -> Tick -> Bool) -> QueryFilter
+ Mischief.ECS: [NoFilter] :: QueryFilter
+ Mischief.ECS: [Or] :: QueryFilter -> QueryFilter -> QueryFilter
+ Mischief.ECS: [With] :: forall a. ToFilterComponent a => a -> QueryFilter
+ Mischief.ECS: [Without] :: forall a. ToFilterComponent a => a -> QueryFilter
+ Mischief.ECS: [comp] :: Res c -> c
+ Mischief.ECS: [entity] :: HookContextRel -> Entity
+ Mischief.ECS: [target] :: HookContextRel -> Entity
+ Mischief.ECS: addPlugin :: Plugin p => App -> IO ()
+ Mischief.ECS: after :: ToSystems a => a -> SystemConfig -> SystemConfig
+ Mischief.ECS: before :: ToSystems a => a -> SystemConfig -> SystemConfig
+ Mischief.ECS: bundle :: Bundle b => b -> BundleBuilder
+ Mischief.ECS: bundleEq :: BundleEq b => b -> BundleEqBuilder
+ Mischief.ECS: check :: forall m w. MonadSystem w m => QueryFilter -> Entity -> m Bool
+ Mischief.ECS: class (Typeable c, IsExclusive IsExclusiveRel c) => Component c where {
+ Mischief.ECS: class Typeable e => Event e
+ Mischief.ECS: class (GetWorld w, MonadReader w a, Applicative a, MonadFail a, Functor a, Monad a, MonadIO a, PrimMonad a, PrimState a ~ RealWorld, MonadThrow a, SystemAny a) => MonadSystem w (a :: Type -> Type)
+ Mischief.ECS: class Typeable p => Plugin p
+ Mischief.ECS: class Queryable qd output | qd -> output
+ Mischief.ECS: class Typeable s => Schedule (s :: k)
+ Mischief.ECS: data Any
+ Mischief.ECS: data App
+ Mischief.ECS: data Before
+ Mischief.ECS: data C (a :: k)
+ Mischief.ECS: data ChildOf
+ Mischief.ECS: data Dependency
+ Mischief.ECS: data E
+ Mischief.ECS: data Entity
+ Mischief.ECS: data FixedPlugin
+ Mischief.ECS: data FixedPostUpdate
+ Mischief.ECS: data FixedPreUpdate
+ Mischief.ECS: data FixedUpdate
+ Mischief.ECS: data From c
+ Mischief.ECS: data Has (a :: k)
+ Mischief.ECS: data Hook (a :: k)
+ Mischief.ECS: data HookContextRel
+ Mischief.ECS: data HookRel (a :: k)
+ Mischief.ECS: data M (a :: k)
+ Mischief.ECS: data ParSystem a
+ Mischief.ECS: data PostStartup
+ Mischief.ECS: data PostUpdate
+ Mischief.ECS: data PreStartup
+ Mischief.ECS: data PreUpdate
+ Mischief.ECS: data Q' a b
+ Mischief.ECS: data Query (m :: Type -> Type) a
+ Mischief.ECS: data QueryFilter
+ Mischief.ECS: data Rel c
+ Mischief.ECS: data Startup
+ Mischief.ECS: data StartupSchedule
+ Mischief.ECS: data System a
+ Mischief.ECS: data Time
+ Mischief.ECS: data TimePlugin
+ Mischief.ECS: data Timer
+ Mischief.ECS: data Update
+ Mischief.ECS: data UpdateSchedule
+ Mischief.ECS: defer :: MonadSystem w m => System a -> m ()
+ Mischief.ECS: delay :: MonadSystem w m => Float -> System a -> m ()
+ Mischief.ECS: dep :: Plugin p => Dependency
+ Mischief.ECS: deps :: Plugin p => [Dependency]
+ Mischief.ECS: despawn :: Entity -> System ()
+ Mischief.ECS: expect :: (HasCallStack, Unwrappable f) => Text -> f a -> a
+ Mischief.ECS: flush :: System ()
+ Mischief.ECS: infixl 1 &
+ Mischief.ECS: init :: Plugin p => System ()
+ Mischief.ECS: insert :: HasCallStack => forall b. Bundle b => b -> Entity -> System ()
+ Mischief.ECS: insertIfNeq :: BundleEq b => b -> Entity -> System ()
+ Mischief.ECS: insertNew :: Bundle b => b -> Entity -> System ()
+ Mischief.ECS: insertRes :: (Component r, Bundle r) => r -> System ()
+ Mischief.ECS: mapLeft :: (a -> c) -> Either a b -> Either c b
+ Mischief.ECS: meta :: Component c => System Entity
+ Mischief.ECS: mkGet :: forall qd out (m :: Type -> Type). Queryable qd out => Entity -> qd -> Query m out
+ Mischief.ECS: mkGet' :: forall qd out (m :: Type -> Type). Queryable qd out => Entity -> qd -> QueryFilter -> Query m out
+ Mischief.ECS: mkQuery :: forall qd out (m :: Type -> Type). Queryable qd out => qd -> Query m out
+ Mischief.ECS: mkQuery' :: forall qd out (m :: Type -> Type). Queryable qd out => qd -> QueryFilter -> Query m out
+ Mischief.ECS: newApp :: IO App
+ Mischief.ECS: newtype FixedFrequency
+ Mischief.ECS: newtype HasR (a :: k) b
+ Mischief.ECS: newtype HookContext
+ Mischief.ECS: newtype MR (a :: k) b
+ Mischief.ECS: newtype Observer e
+ Mischief.ECS: newtype Q a
+ Mischief.ECS: newtype R (a :: k) b
+ Mischief.ECS: newtype R' (a :: k) b
+ Mischief.ECS: newtype Res c
+ Mischief.ECS: newtype Tick
+ Mischief.ECS: onAdd :: Component c => [Hook c]
+ Mischief.ECS: onAddRel :: Component c => [HookRel c]
+ Mischief.ECS: onRemove :: Component c => [Hook c]
+ Mischief.ECS: onRemoveRel :: Component c => [HookRel c]
+ Mischief.ECS: onSet :: Component c => [Hook c]
+ Mischief.ECS: onSetRel :: Component c => [HookRel c]
+ Mischief.ECS: order :: forall {k} (sc :: k) a b. (Schedule sc, ToSystems a, ToSystems b) => (a, b) -> System ()
+ Mischief.ECS: query :: MonadSystem w m => Query m out -> m [out]
+ Mischief.ECS: queryOne :: MonadSystem w m => Query m out -> m (Maybe out)
+ Mischief.ECS: query_ :: MonadSystem w m => Query m out -> m ()
+ Mischief.ECS: remove :: Collectable c ToRemove => c -> Entity -> System ()
+ Mischief.ECS: require :: forall {k} (b :: k). RequiredBundle b => Set DefaultComponentType
+ Mischief.ECS: required :: Component c => Set DefaultComponentType
+ Mischief.ECS: res :: forall c m w. (MonadSystem w m, Component c) => m (Maybe c)
+ Mischief.ECS: resOrInsert :: (Component r, Bundle r) => r -> System r
+ Mischief.ECS: resOrInsertM :: (Component r, Bundle r) => System r -> System r
+ Mischief.ECS: runAfter :: MonadSystem w m => IO a -> (a -> System ()) -> m ()
+ Mischief.ECS: runApp :: App -> IO ()
+ Mischief.ECS: runWhen :: Typeable a => System (Maybe a) -> (a -> System b) -> System ()
+ Mischief.ECS: runWhenOne :: Typeable a => Query System a -> (a -> System b) -> System ()
+ Mischief.ECS: runWhenQ :: Typeable a => Query System a -> ([a] -> System b) -> System ()
+ Mischief.ECS: schedule :: forall {k} (sc :: k). Schedule sc => SystemConfig -> System ()
+ Mischief.ECS: scheduleEntity :: forall {k} (sch :: k). Schedule sch => System Entity
+ Mischief.ECS: single :: MonadSystem w m => Query m out -> m (Maybe out)
+ Mischief.ECS: spawn :: (HasCallStack, Bundle b) => b -> System Entity
+ Mischief.ECS: spawnDefer :: Bundle b => b -> ParSystem Entity
+ Mischief.ECS: spawn_ :: (HasCallStack, Bundle b) => b -> System ()
+ Mischief.ECS: systems :: ToSystems a => a -> SystemConfig
+ Mischief.ECS: type Bundle b = Collectable b BundleData ErasedComponent
+ Mischief.ECS: type BundleBuilder = BundleData ErasedComponent
+ Mischief.ECS: type BundleEq b = Collectable b BundleData ErasedComponentEq
+ Mischief.ECS: type BundleEqBuilder = BundleData ErasedComponentEq
+ Mischief.ECS: type IsExclusiveRel c = 'False;
+ Mischief.ECS: type family IsExclusiveRel c :: Bool
+ Mischief.ECS: unschedule :: forall {k} (sc :: k) a. (Schedule sc, ToSystems a) => a -> System ()
+ Mischief.ECS: unschedule' :: forall {k} (sc :: k) a. (Schedule sc, ToSystems a) => a -> System ()
+ Mischief.ECS: unwrap :: (HasCallStack, Unwrappable f) => f a -> a
+ Mischief.ECS: }
+ Mischief.ECS.App: addPlugin :: Plugin p => App -> IO ()
+ Mischief.ECS.App: getTick :: MonadSystem w m => m Tick
+ Mischief.ECS.App: setSystemTicks :: Tick -> Tick -> System ()
+ Mischief.ECS.App: setTick :: Tick -> System ()
+ Mischief.ECS.App: simulateSystem :: System () -> System ()
+ Mischief.ECS.App: tryRun :: System ()
+ Mischief.ECS.App.Plugins: addPluginRec :: Plugin p => System ()
+ Mischief.ECS.App.Plugins: data Dependency
+ Mischief.ECS.App.Plugins: dep :: Plugin p => Dependency
+ Mischief.ECS.App.Plugins: deps :: Plugin p => [Dependency]
+ Mischief.ECS.App.Plugins: instance GHC.Classes.Eq Mischief.ECS.App.Plugins.Dependency
+ Mischief.ECS.App.Plugins: instance GHC.Classes.Ord Mischief.ECS.App.Plugins.Dependency
+ Mischief.ECS.App.Plugins: instance GHC.Internal.Show.Show Mischief.ECS.App.Plugins.Dependency
+ Mischief.ECS.App.Plugins: instance forall k (p :: k). (GHC.Internal.Data.Typeable.Internal.Typeable p, GHC.Internal.Data.Typeable.Internal.Typeable k) => Mischief.ECS.Components.Component (Mischief.ECS.App.Plugins.PluginMarker p)
+ Mischief.ECS.App.Schedules: FixedSchedule :: FixedSchedule
+ Mischief.ECS.App.Schedules: data FixedSchedule
+ Mischief.ECS.App.Schedules: data Last
+ Mischief.ECS.App.Schedules: instance Mischief.ECS.App.Schedules.Schedule Mischief.ECS.App.Schedules.Last
+ Mischief.ECS.App.Schedules: instance Mischief.ECS.Components.Component Mischief.ECS.App.Schedules.FixedSchedule
+ Mischief.ECS.Components: -- or not.
+ Mischief.ECS.Components: -- | Set whether the relationships this component is used in are exclusive
+ Mischief.ECS.Components: ComponentTick :: Tick -> ComponentTick
+ Mischief.ECS.Components: From :: Entity -> c -> From c
+ Mischief.ECS.Components: IsExclusiveRelationship :: IsExclusiveRelationship
+ Mischief.ECS.Components: Res :: c -> Res c
+ Mischief.ECS.Components: [entity] :: From c -> Entity
+ Mischief.ECS.Components: [external] :: BundleData e -> Set (Entity, BundleData e)
+ Mischief.ECS.Components: [resources] :: BundleData e -> Set (BundleElement e)
+ Mischief.ECS.Components: data BundleData e
+ Mischief.ECS.Components: data From c
+ Mischief.ECS.Components: data IsExclusiveRelationship
+ Mischief.ECS.Components: instance (Mischief.ECS.Components.Component c, GHC.Classes.Eq c) => Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.Components.Res c) (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponentEq)
+ Mischief.ECS.Components: instance GHC.Classes.Eq (Mischief.ECS.Components.BundleData e)
+ Mischief.ECS.Components: instance GHC.Classes.Eq c => GHC.Classes.Eq (Mischief.ECS.Components.From c)
+ Mischief.ECS.Components: instance GHC.Classes.Eq c => GHC.Classes.Eq (Mischief.ECS.Components.Rel c)
+ Mischief.ECS.Components: instance GHC.Classes.Eq c => GHC.Classes.Eq (Mischief.ECS.Components.Res c)
+ Mischief.ECS.Components: instance GHC.Classes.Ord (Mischief.ECS.Components.BundleData e)
+ Mischief.ECS.Components: instance GHC.Internal.Base.Functor Mischief.ECS.Components.From
+ Mischief.ECS.Components: instance GHC.Internal.Base.Functor Mischief.ECS.Components.Rel
+ Mischief.ECS.Components: instance GHC.Internal.Base.Functor Mischief.ECS.Components.Res
+ Mischief.ECS.Components: instance GHC.Internal.Show.Show Mischief.ECS.Components.ComponentId
+ Mischief.ECS.Components: instance GHC.Internal.Show.Show Mischief.ECS.Components.ComponentTick
+ Mischief.ECS.Components: instance GHC.Internal.Show.Show Mischief.ECS.Components.IsExclusiveRelationship
+ Mischief.ECS.Components: instance GHC.Internal.Show.Show c => GHC.Internal.Show.Show (Mischief.ECS.Components.From c)
+ Mischief.ECS.Components: instance GHC.Internal.Show.Show c => GHC.Internal.Show.Show (Mischief.ECS.Components.Res c)
+ Mischief.ECS.Components: instance Mischief.ECS.Collectable.Collectable c (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponent) => Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.Components.From c) (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponent)
+ Mischief.ECS.Components: instance Mischief.ECS.Collectable.Collectable c (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponent) => Mischief.ECS.Collectable.EraseIntoStorage [c] (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponent)
+ Mischief.ECS.Components: instance Mischief.ECS.Collectable.Collectable c (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponentEq) => Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.Components.From c) (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponentEq)
+ Mischief.ECS.Components: instance Mischief.ECS.Collectable.Collectable c (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponentEq) => Mischief.ECS.Collectable.EraseIntoStorage [c] (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponentEq)
+ Mischief.ECS.Components: instance Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponentEq) (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponentEq)
+ Mischief.ECS.Components: instance Mischief.ECS.Components.Component Mischief.ECS.Components.IsExclusiveRelationship
+ Mischief.ECS.Components: instance Mischief.ECS.Components.Component c => Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.Components.Res c) (Mischief.ECS.Components.BundleData Mischief.ECS.Components.ErasedComponent)
+ Mischief.ECS.Components: instance Mischief.ECS.Components.IsExclusive 'GHC.Types.False
+ Mischief.ECS.Components: instance Mischief.ECS.Components.IsExclusive 'GHC.Types.True
+ Mischief.ECS.Components: newtype ComponentTick
+ Mischief.ECS.Components: newtype Res c
+ Mischief.ECS.Components: onAdd :: Component c => [Hook c]
+ Mischief.ECS.Components: onAddRel :: Component c => [HookRel c]
+ Mischief.ECS.Components: onRemove :: Component c => [Hook c]
+ Mischief.ECS.Components: onRemoveRel :: Component c => [HookRel c]
+ Mischief.ECS.Components: onSet :: Component c => [Hook c]
+ Mischief.ECS.Components: onSetRel :: Component c => [HookRel c]
+ Mischief.ECS.Components: type IsExclusiveRel c = 'False;
+ Mischief.ECS.Components.Bundle: bundle :: Bundle b => b -> BundleBuilder
+ Mischief.ECS.Components.Bundle: bundleEq :: BundleEq b => b -> BundleEqBuilder
+ Mischief.ECS.Components.Bundle: type BundleBuilder = BundleData ErasedComponent
+ Mischief.ECS.Components.Bundle: type BundleEqBuilder = BundleData ErasedComponentEq
+ Mischief.ECS.Components.HooksDef: HookContext :: Entity -> HookContext
+ Mischief.ECS.Components.HooksDef: HookContextRel :: Entity -> Entity -> HookContextRel
+ Mischief.ECS.Components.HooksDef: [HookRel] :: forall {k} (m :: Type -> Type) (a :: k). Typeable m => (HookContextRel -> m ()) -> HookRel a
+ Mischief.ECS.Components.HooksDef: [Hook] :: forall {k} (m :: Type -> Type) (a :: k). Typeable m => (HookContext -> m ()) -> Hook a
+ Mischief.ECS.Components.HooksDef: [entity] :: HookContextRel -> Entity
+ Mischief.ECS.Components.HooksDef: [target] :: HookContextRel -> Entity
+ Mischief.ECS.Components.HooksDef: data Hook (a :: k)
+ Mischief.ECS.Components.HooksDef: data HookContextRel
+ Mischief.ECS.Components.HooksDef: data HookRel (a :: k)
+ Mischief.ECS.Components.HooksDef: newtype HookContext
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4, Mischief.ECS.Components.Required.RequiredBundle b5) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4, b5)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4, Mischief.ECS.Components.Required.RequiredBundle b5, Mischief.ECS.Components.Required.RequiredBundle b6) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4, b5, b6)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4, Mischief.ECS.Components.Required.RequiredBundle b5, Mischief.ECS.Components.Required.RequiredBundle b6, Mischief.ECS.Components.Required.RequiredBundle b7) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4, Mischief.ECS.Components.Required.RequiredBundle b5, Mischief.ECS.Components.Required.RequiredBundle b6, Mischief.ECS.Components.Required.RequiredBundle b7, Mischief.ECS.Components.Required.RequiredBundle b8) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4, Mischief.ECS.Components.Required.RequiredBundle b5, Mischief.ECS.Components.Required.RequiredBundle b6, Mischief.ECS.Components.Required.RequiredBundle b7, Mischief.ECS.Components.Required.RequiredBundle b8, Mischief.ECS.Components.Required.RequiredBundle b9) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4, Mischief.ECS.Components.Required.RequiredBundle b5, Mischief.ECS.Components.Required.RequiredBundle b6, Mischief.ECS.Components.Required.RequiredBundle b7, Mischief.ECS.Components.Required.RequiredBundle b8, Mischief.ECS.Components.Required.RequiredBundle b9, Mischief.ECS.Components.Required.RequiredBundle b10) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4, Mischief.ECS.Components.Required.RequiredBundle b5, Mischief.ECS.Components.Required.RequiredBundle b6, Mischief.ECS.Components.Required.RequiredBundle b7, Mischief.ECS.Components.Required.RequiredBundle b8, Mischief.ECS.Components.Required.RequiredBundle b9, Mischief.ECS.Components.Required.RequiredBundle b10, Mischief.ECS.Components.Required.RequiredBundle b11) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4, Mischief.ECS.Components.Required.RequiredBundle b5, Mischief.ECS.Components.Required.RequiredBundle b6, Mischief.ECS.Components.Required.RequiredBundle b7, Mischief.ECS.Components.Required.RequiredBundle b8, Mischief.ECS.Components.Required.RequiredBundle b9, Mischief.ECS.Components.Required.RequiredBundle b10, Mischief.ECS.Components.Required.RequiredBundle b11, Mischief.ECS.Components.Required.RequiredBundle b12) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4, Mischief.ECS.Components.Required.RequiredBundle b5, Mischief.ECS.Components.Required.RequiredBundle b6, Mischief.ECS.Components.Required.RequiredBundle b7, Mischief.ECS.Components.Required.RequiredBundle b8, Mischief.ECS.Components.Required.RequiredBundle b9, Mischief.ECS.Components.Required.RequiredBundle b10, Mischief.ECS.Components.Required.RequiredBundle b11, Mischief.ECS.Components.Required.RequiredBundle b12, Mischief.ECS.Components.Required.RequiredBundle b13) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13)
+ Mischief.ECS.Components.Required: instance (Mischief.ECS.Components.Required.RequiredBundle b0, Mischief.ECS.Components.Required.RequiredBundle b1, Mischief.ECS.Components.Required.RequiredBundle b2, Mischief.ECS.Components.Required.RequiredBundle b3, Mischief.ECS.Components.Required.RequiredBundle b4, Mischief.ECS.Components.Required.RequiredBundle b5, Mischief.ECS.Components.Required.RequiredBundle b6, Mischief.ECS.Components.Required.RequiredBundle b7, Mischief.ECS.Components.Required.RequiredBundle b8, Mischief.ECS.Components.Required.RequiredBundle b9, Mischief.ECS.Components.Required.RequiredBundle b10, Mischief.ECS.Components.Required.RequiredBundle b11, Mischief.ECS.Components.Required.RequiredBundle b12, Mischief.ECS.Components.Required.RequiredBundle b13, Mischief.ECS.Components.Required.RequiredBundle b14) => Mischief.ECS.Components.Required.RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14)
+ Mischief.ECS.Components.Spawn: ComponentAddHooks :: [HookContext -> System ()] -> ComponentAddHooks
+ Mischief.ECS.Components.Spawn: ComponentAddHooksRel :: [HookContextRel -> System ()] -> ComponentAddHooksRel
+ Mischief.ECS.Components.Spawn: ComponentRemoveHooks :: [HookContext -> System ()] -> ComponentRemoveHooks
+ Mischief.ECS.Components.Spawn: ComponentRemoveHooksRel :: [HookContextRel -> System ()] -> ComponentRemoveHooksRel
+ Mischief.ECS.Components.Spawn: ComponentSetHooks :: [HookContext -> System ()] -> ComponentSetHooks
+ Mischief.ECS.Components.Spawn: ComponentSetHooksRel :: [HookContextRel -> System ()] -> ComponentSetHooksRel
+ Mischief.ECS.Components.Spawn: getHook :: forall {k} (c :: k). Hook c -> HookContext -> System ()
+ Mischief.ECS.Components.Spawn: getHookRel :: forall {k} (c :: k). HookRel c -> HookContextRel -> System ()
+ Mischief.ECS.Components.Spawn: instance Mischief.ECS.Components.Component Mischief.ECS.Components.Spawn.ComponentAddHooks
+ Mischief.ECS.Components.Spawn: instance Mischief.ECS.Components.Component Mischief.ECS.Components.Spawn.ComponentAddHooksRel
+ Mischief.ECS.Components.Spawn: instance Mischief.ECS.Components.Component Mischief.ECS.Components.Spawn.ComponentRemoveHooks
+ Mischief.ECS.Components.Spawn: instance Mischief.ECS.Components.Component Mischief.ECS.Components.Spawn.ComponentRemoveHooksRel
+ Mischief.ECS.Components.Spawn: instance Mischief.ECS.Components.Component Mischief.ECS.Components.Spawn.ComponentSetHooks
+ Mischief.ECS.Components.Spawn: instance Mischief.ECS.Components.Component Mischief.ECS.Components.Spawn.ComponentSetHooksRel
+ Mischief.ECS.Components.Spawn: newtype ComponentAddHooks
+ Mischief.ECS.Components.Spawn: newtype ComponentAddHooksRel
+ Mischief.ECS.Components.Spawn: newtype ComponentRemoveHooks
+ Mischief.ECS.Components.Spawn: newtype ComponentRemoveHooksRel
+ Mischief.ECS.Components.Spawn: newtype ComponentSetHooks
+ Mischief.ECS.Components.Spawn: newtype ComponentSetHooksRel
+ Mischief.ECS.EntityDef: nullEntity :: Entity
+ Mischief.ECS.Events: OnAdd :: Entity -> OnAdd (c :: k)
+ Mischief.ECS.Events: OnAddRel :: Entity -> Entity -> OnAddRel (c :: k)
+ Mischief.ECS.Events: OnSet :: Entity -> OnSet (c :: k)
+ Mischief.ECS.Events: OnSetRel :: Entity -> Entity -> OnSetRel (c :: k)
+ Mischief.ECS.Events: data OnAddRel (c :: k)
+ Mischief.ECS.Events: data OnSetRel (c :: k)
+ Mischief.ECS.Events: instance forall k (c :: k). (GHC.Internal.Data.Typeable.Internal.Typeable c, GHC.Internal.Data.Typeable.Internal.Typeable k) => Mischief.ECS.EventDef.Event (Mischief.ECS.Events.OnAdd c)
+ Mischief.ECS.Events: instance forall k (c :: k). (GHC.Internal.Data.Typeable.Internal.Typeable c, GHC.Internal.Data.Typeable.Internal.Typeable k) => Mischief.ECS.EventDef.Event (Mischief.ECS.Events.OnAddRel c)
+ Mischief.ECS.Events: instance forall k (c :: k). (GHC.Internal.Data.Typeable.Internal.Typeable c, GHC.Internal.Data.Typeable.Internal.Typeable k) => Mischief.ECS.EventDef.Event (Mischief.ECS.Events.OnSet c)
+ Mischief.ECS.Events: instance forall k (c :: k). (GHC.Internal.Data.Typeable.Internal.Typeable c, GHC.Internal.Data.Typeable.Internal.Typeable k) => Mischief.ECS.EventDef.Event (Mischief.ECS.Events.OnSetRel c)
+ Mischief.ECS.Events: instance forall k (c :: k). GHC.Internal.Show.Show (Mischief.ECS.Events.OnAdd c)
+ Mischief.ECS.Events: instance forall k (c :: k). GHC.Internal.Show.Show (Mischief.ECS.Events.OnAddRel c)
+ Mischief.ECS.Events: instance forall k (c :: k). GHC.Internal.Show.Show (Mischief.ECS.Events.OnSet c)
+ Mischief.ECS.Events: instance forall k (c :: k). GHC.Internal.Show.Show (Mischief.ECS.Events.OnSetRel c)
+ Mischief.ECS.Events: newtype OnAdd (c :: k)
+ Mischief.ECS.Events: newtype OnSet (c :: k)
+ Mischief.ECS.Exports: class Generic a
+ Mischief.ECS.Exports: i :: QuasiQuoter
+ Mischief.ECS.Fixed: FixedExtra :: Float -> FixedExtra
+ Mischief.ECS.Fixed: FixedFrequency :: Float -> FixedFrequency
+ Mischief.ECS.Fixed: FixedTime :: TimeSpec -> TimeSpec -> FixedTime
+ Mischief.ECS.Fixed: [fixedDelta] :: FixedTime -> TimeSpec
+ Mischief.ECS.Fixed: [fixedElapsed] :: FixedTime -> TimeSpec
+ Mischief.ECS.Fixed: data Fixed
+ Mischief.ECS.Fixed: data FixedFirst
+ Mischief.ECS.Fixed: data FixedLast
+ Mischief.ECS.Fixed: data FixedPlugin
+ Mischief.ECS.Fixed: data FixedPostUpdate
+ Mischief.ECS.Fixed: data FixedPreUpdate
+ Mischief.ECS.Fixed: data FixedTime
+ Mischief.ECS.Fixed: data FixedUpdate
+ Mischief.ECS.Fixed: instance Data.Default.Internal.Default Mischief.ECS.Fixed.FixedFrequency
+ Mischief.ECS.Fixed: instance GHC.Internal.Show.Show Mischief.ECS.Fixed.FixedFirst
+ Mischief.ECS.Fixed: instance GHC.Internal.Show.Show Mischief.ECS.Fixed.FixedLast
+ Mischief.ECS.Fixed: instance GHC.Internal.Show.Show Mischief.ECS.Fixed.FixedPostUpdate
+ Mischief.ECS.Fixed: instance GHC.Internal.Show.Show Mischief.ECS.Fixed.FixedPreUpdate
+ Mischief.ECS.Fixed: instance GHC.Internal.Show.Show Mischief.ECS.Fixed.FixedUpdate
+ Mischief.ECS.Fixed: instance Mischief.ECS.App.Plugins.Plugin Mischief.ECS.Fixed.FixedPlugin
+ Mischief.ECS.Fixed: instance Mischief.ECS.App.Schedules.Schedule Mischief.ECS.Fixed.Fixed
+ Mischief.ECS.Fixed: instance Mischief.ECS.App.Schedules.Schedule Mischief.ECS.Fixed.FixedFirst
+ Mischief.ECS.Fixed: instance Mischief.ECS.App.Schedules.Schedule Mischief.ECS.Fixed.FixedLast
+ Mischief.ECS.Fixed: instance Mischief.ECS.App.Schedules.Schedule Mischief.ECS.Fixed.FixedPostUpdate
+ Mischief.ECS.Fixed: instance Mischief.ECS.App.Schedules.Schedule Mischief.ECS.Fixed.FixedPreUpdate
+ Mischief.ECS.Fixed: instance Mischief.ECS.App.Schedules.Schedule Mischief.ECS.Fixed.FixedUpdate
+ Mischief.ECS.Fixed: instance Mischief.ECS.Components.Component Mischief.ECS.Fixed.FixedExtra
+ Mischief.ECS.Fixed: instance Mischief.ECS.Components.Component Mischief.ECS.Fixed.FixedFrequency
+ Mischief.ECS.Fixed: instance Mischief.ECS.Components.Component Mischief.ECS.Fixed.FixedTime
+ Mischief.ECS.Fixed: instance Mischief.ECS.Time.ToTime Mischief.ECS.Fixed.FixedTime
+ Mischief.ECS.Fixed: newtype FixedExtra
+ Mischief.ECS.Fixed: newtype FixedFrequency
+ Mischief.ECS.Fixed: runFixedLoop :: System ()
+ Mischief.ECS.Foreign: alloca :: Storable a => (Ptr a -> System b) -> System b
+ Mischief.ECS.Hooks: hook :: forall {k} (a :: k). (HookContext -> System ()) -> Hook a
+ Mischief.ECS.Hooks: hookRel :: forall {k} (a :: k). (HookContextRel -> System ()) -> HookRel a
+ Mischief.ECS.HooksRel: addCleaner :: Component a => (CleanupRequest -> System ()) -> HookRel a
+ Mischief.ECS.HooksRel: addDespawnCleaner :: Component a => HookRel a
+ Mischief.ECS.HooksRel: addOther :: (Component a, Component b) => (a -> b) -> HookRel a
+ Mischief.ECS.HooksRel: addRemoveCleaner :: Component a => HookRel a
+ Mischief.ECS.HooksRel: instance Mischief.ECS.Components.Component c => Mischief.ECS.Components.Component (Mischief.ECS.HooksRel.CleanupWatcher c)
+ Mischief.ECS.HooksRel: removeCleaner :: Component a => HookRel a
+ Mischief.ECS.HooksRel: removeOther :: forall {k} (a :: k) b. Component b => HookRel a
+ Mischief.ECS.Relationships.ChildOf: children :: forall m w. MonadSystem w m => Entity -> m [Entity]
+ Mischief.ECS.Relationships.ChildOf: parent :: forall m w. MonadSystem w m => Entity -> m (Maybe Entity)
+ Mischief.ECS.Relationships.Graph: class ListToOutgoing b
+ Mischief.ECS.Relationships.Graph: incoming :: forall c m w. (Component c, BundleTypes c, MonadSystem w m) => Entity -> m [Entity]
+ Mischief.ECS.Relationships.Graph: instance Mischief.ECS.Relationships.Graph.ListToOutgoing (GHC.Internal.Maybe.Maybe Mischief.ECS.EntityDef.Entity)
+ Mischief.ECS.Relationships.Graph: instance Mischief.ECS.Relationships.Graph.ListToOutgoing [Mischief.ECS.EntityDef.Entity]
+ Mischief.ECS.Relationships.Graph: listToOutgoing :: ListToOutgoing b => [Entity] -> b
+ Mischief.ECS.Relationships.Graph: outgoing :: forall c m w. (Component c, MonadSystem w m, ListToOutgoing (RelOutgoing (IsExclusiveRel c) Entity)) => Entity -> m (RelOutgoing (IsExclusiveRel c) Entity)
+ Mischief.ECS.Relationships.Graph: outgoing' :: forall c m w. (Component c, MonadSystem w m) => Entity -> m [Entity]
+ Mischief.ECS.Relationships.Graph: type family RelOutgoing (b :: Bool) :: Type -> Type
+ Mischief.ECS.Relationships.Tree: ancestors :: forall c m w. (Component c, MonadSystem w m, ListToOutgoing (RelOutgoing (IsExclusiveRel c) Entity)) => Entity -> m (RelOutgoing (IsExclusiveRel c) Entity)
+ Mischief.ECS.Relationships.Tree: ancestors' :: forall c m w. (Component c, MonadSystem w m) => Entity -> m [Entity]
+ Mischief.ECS.Relationships.Tree: descendants :: forall c m w. (Component c, BundleTypes c, MonadSystem w m) => Entity -> m [Entity]
+ Mischief.ECS.Relationships.Tree: leaves :: forall c m w. (Component c, BundleTypes c, MonadSystem w m) => Entity -> m [Entity]
+ Mischief.ECS.Relationships.Tree: root :: forall c m w. (Component c, MonadSystem w m) => Entity -> m (Maybe Entity)
+ Mischief.ECS.Repeater: Repeater :: Float -> System () -> Repeater
+ Mischief.ECS.Repeater: data Repeater
+ Mischief.ECS.Repeater: instance Mischief.ECS.Components.Component Mischief.ECS.Repeater.Repeater
+ Mischief.ECS.Repeater: instance Mischief.ECS.Components.Component Mischief.ECS.Repeater.RepeaterRef
+ Mischief.ECS.Resources: modifyRes :: (Component r, Bundle r) => (Maybe r -> r) -> System ()
+ Mischief.ECS.Resources: modifyRes' :: (Component r, Bundle r) => (r -> r) -> System ()
+ Mischief.ECS.Resources: resOrInsertM :: (Component r, Bundle r) => System r -> System r
+ Mischief.ECS.Systems: SystemConfig :: [System ()] -> [(System (), System ())] -> SystemConfig
+ Mischief.ECS.Systems: Systems :: [System ()] -> Systems
+ Mischief.ECS.Systems: [edges] :: SystemConfig -> [(System (), System ())]
+ Mischief.ECS.Systems: [systems] :: SystemConfig -> [System ()]
+ Mischief.ECS.Systems: after :: ToSystems a => a -> SystemConfig -> SystemConfig
+ Mischief.ECS.Systems: before :: ToSystems a => a -> SystemConfig -> SystemConfig
+ Mischief.ECS.Systems: data SystemConfig
+ Mischief.ECS.Systems: instance GHC.Internal.Base.Semigroup Mischief.ECS.Systems.Systems
+ Mischief.ECS.Systems: instance Mischief.ECS.Collectable.EraseIntoStorage (Mischief.ECS.World.System ()) Mischief.ECS.Systems.Systems
+ Mischief.ECS.Systems: newtype Systems
+ Mischief.ECS.Systems: order :: forall {k} (sc :: k) a b. (Schedule sc, ToSystems a, ToSystems b) => (a, b) -> System ()
+ Mischief.ECS.Systems: systems :: ToSystems a => a -> SystemConfig
+ Mischief.ECS.Systems: type ToSystems a = Collectable a Systems
+ Mischief.ECS.Systems: unschedule' :: forall {k} (sc :: k) a. (Schedule sc, ToSystems a) => a -> System ()
+ Mischief.ECS.Tables: [ticks] :: Table -> IORef (Map ComponentId ComponentTick)
+ Mischief.ECS.Tables: type AddedComponents = [ComponentId]
+ Mischief.ECS.Tables: type ChangedComponents = [ComponentId]
+ Mischief.ECS.Time: VirtualTime :: TimeSpec -> TimeSpec -> VirtualTime
+ Mischief.ECS.Time: [TimeTag] :: forall t. (Component t, ToTime t) => Proxy t -> TimeTag
+ Mischief.ECS.Time: [virtualDelta] :: VirtualTime -> TimeSpec
+ Mischief.ECS.Time: [virtualElapsed] :: VirtualTime -> TimeSpec
+ Mischief.ECS.Time: class ToTime t
+ Mischief.ECS.Time: data TimeTag
+ Mischief.ECS.Time: data TimeTypePref
+ Mischief.ECS.Time: data VirtualTime
+ Mischief.ECS.Time: delta :: (HasCallStack, MonadSystem w m) => m Float
+ Mischief.ECS.Time: elapsed :: (HasCallStack, MonadSystem w m) => m Float
+ Mischief.ECS.Time: instance GHC.Internal.Show.Show Mischief.ECS.Time.VirtualTime
+ Mischief.ECS.Time: instance Mischief.ECS.Components.Component Mischief.ECS.Time.VirtualTime
+ Mischief.ECS.Time: instance Mischief.ECS.Time.ToTime Mischief.ECS.Time.VirtualTime
+ Mischief.ECS.Time: instance Mischief.ECS.World.PrefType Mischief.ECS.Time.TimeTypePref Mischief.ECS.Time.TimeTag
+ Mischief.ECS.Time: qdelta :: forall w (m :: Type -> Type). MonadSystem w m => Query m Float
+ Mischief.ECS.Time: qelapsed :: forall w (m :: Type -> Type). MonadSystem w m => Query m Float
+ Mischief.ECS.Time: qtime :: forall w (m :: Type -> Type). MonadSystem w m => Query m Time
+ Mischief.ECS.Time: secsToSpec :: Float -> TimeSpec
+ Mischief.ECS.Time: specToSecs :: TimeSpec -> Float
+ Mischief.ECS.Time: timeMaybe :: MonadSystem w m => m (Maybe Time)
+ Mischief.ECS.Time: toTime :: ToTime t => t -> Time
+ Mischief.ECS.Utils: [ErasedValue] :: forall a. Typeable a => a -> ErasedValue
+ Mischief.ECS.Utils: andM :: (Traversable t, Applicative f) => t (f Bool) -> f Bool
+ Mischief.ECS.Utils: anyM :: (Traversable t, Monad f) => (a -> f Bool) -> t a -> f Bool
+ Mischief.ECS.Utils: class Functor f => Unwrappable (f :: Type -> Type)
+ Mischief.ECS.Utils: data ErasedValue
+ Mischief.ECS.Utils: eraseValue :: Typeable a => a -> ErasedValue
+ Mischief.ECS.Utils: expect :: (HasCallStack, Unwrappable f) => Text -> f a -> a
+ Mischief.ECS.Utils: fromMaybeM :: Applicative m => m a -> Maybe a -> m a
+ Mischief.ECS.Utils: instance Mischief.ECS.Utils.Unwrappable (GHC.Internal.Data.Either.Either a)
+ Mischief.ECS.Utils: instance Mischief.ECS.Utils.Unwrappable GHC.Internal.Maybe.Maybe
+ Mischief.ECS.Utils: mapLeft :: (a -> c) -> Either a b -> Either c b
+ Mischief.ECS.Utils: orM :: (Traversable t, Applicative f) => t (f Bool) -> f Bool
+ Mischief.ECS.Utils: uneraseValue :: Typeable a => ErasedValue -> Maybe a
+ Mischief.ECS.Utils: unsafeUneraseValue :: ErasedValue -> a
+ Mischief.ECS.Utils: unwrap' :: (Unwrappable f, HasCallStack) => Text -> f a -> a
+ Mischief.ECS.World: [loggerErr] :: World -> LogAction IO Message
+ Mischief.ECS.World: [trySystems] :: World -> IORef [(System (Maybe ErasedValue), ErasedValue -> System ())]
+ Mischief.ECS.World: class (Typeable a, Typeable b) => PrefType (a :: k) (b :: k1) | a -> b
+ Mischief.ECS.World: class SystemAny (m :: Type -> Type)
+ Mischief.ECS.World: data SupressEventsPref
+ Mischief.ECS.World: getPref :: forall {k} (a :: k) m b w. (PrefType a b, MonadSystem w m) => m (Maybe b)
+ Mischief.ECS.World: instance Control.Monad.Catch.MonadThrow Mischief.ECS.World.ParSystem
+ Mischief.ECS.World: instance Control.Monad.Catch.MonadThrow Mischief.ECS.World.System
+ Mischief.ECS.World: instance Mischief.ECS.World.PrefType Mischief.ECS.World.SupressEventsPref GHC.Types.Bool
+ Mischief.ECS.World: instance Mischief.ECS.World.SystemAny Mischief.ECS.World.ParSystem
+ Mischief.ECS.World: instance Mischief.ECS.World.SystemAny Mischief.ECS.World.System
+ Mischief.ECS.World: parLift :: ParSystem a -> System a
+ Mischief.ECS.World: runWhen :: Typeable a => System (Maybe a) -> (a -> System b) -> System ()
+ Mischief.ECS.World: setPref :: forall {k} (a :: k) b c. PrefType a b => b -> System c -> System c
+ Mischief.ECS.World: sysUnliftIO :: System (System a -> IO a)
+ Mischief.ECS.World: systemAny :: SystemAny m => System a -> ParSystem a -> m a
+ Mischief.ECS.World.Insert: triggerAddEvent :: ProcessedBundleData -> Entity -> System ()
+ Mischief.ECS.World.Insert: triggerAddEventC :: ErasedComponent -> Entity -> System ()
+ Mischief.ECS.World.Insert: triggerAddEventR :: ErasedComponent -> Entity -> Entity -> System ()
+ Mischief.ECS.World.Insert: triggerSetEvent :: ProcessedBundleData -> Entity -> System ()
+ Mischief.ECS.World.Insert: triggerSetEventC :: ErasedComponent -> Entity -> System ()
+ Mischief.ECS.World.Insert: triggerSetEventR :: ErasedComponent -> Entity -> Entity -> System ()
+ Mischief.ECS.World.Par: par' :: MonadSystem w m => [ParSystem ()] -> m ()
+ Mischief.ECS.World.Par: parFor :: (Foldable t, MonadSystem w m) => t a -> (a -> ParSystem b) -> m [b]
+ Mischief.ECS.World.Par: parFor' :: (MonadSystem w m, Foldable t) => t a -> (a -> ParSystem b) -> m [b]
+ Mischief.ECS.World.Par: parFor'_ :: (MonadSystem w m, Foldable t) => t a -> (a -> ParSystem b) -> m ()
+ Mischief.ECS.World.Par: parFor_ :: Foldable t => t a -> (a -> ParSystem b) -> System ()
+ Mischief.ECS.World.Query: [AppQuery] :: forall (m :: Type -> Type) a1 a. Query m (a1 -> a) -> Query m a1 -> Query m a
+ Mischief.ECS.World.Query: [BindQuery] :: forall (m :: Type -> Type) a1 a. Query m a1 -> (a1 -> Query m a) -> Query m a
+ Mischief.ECS.World.Query: [BuildQuery] :: forall qd a (m :: Type -> Type). Queryable qd a => qd -> QueryFilter -> Maybe [Entity] -> Query m a
+ Mischief.ECS.World.Query: [CollectQuery] :: forall (m :: Type -> Type) a1. Query m a1 -> Query m [From a1]
+ Mischief.ECS.World.Query: [DoQuery] :: forall (m :: Type -> Type) a b. Query m a -> (Entity -> a -> m b) -> Query m a
+ Mischief.ECS.World.Query: [EmptyQuery] :: forall (m :: Type -> Type) a. Query m a
+ Mischief.ECS.World.Query: [FilterQuery] :: forall (m :: Type -> Type) a. Query m a -> (Entity -> a -> m Bool) -> Query m a
+ Mischief.ECS.World.Query: [IterQuery] :: forall a (m :: Type -> Type). [From a] -> Query m a
+ Mischief.ECS.World.Query: [MapQuery] :: forall (m :: Type -> Type) b a. Query m b -> (Entity -> b -> m a) -> Query m a
+ Mischief.ECS.World.Query: [PairEntityQuery] :: forall (m :: Type -> Type) a1. Query m a1 -> Query m (Entity, a1)
+ Mischief.ECS.World.Query: [ParQuery] :: forall a (m :: Type -> Type). Query ParSystem a -> Query m a
+ Mischief.ECS.World.Query: [PureQuery] :: forall a (m :: Type -> Type). a -> Query m a
+ Mischief.ECS.World.Query: [RefocusQuery] :: forall (m :: Type -> Type) a. Query m a -> Entity -> Query m a
+ Mischief.ECS.World.Query: [StopQuery] :: forall (m :: Type -> Type) a. Query m a
+ Mischief.ECS.World.Query: check :: forall m w. MonadSystem w m => QueryFilter -> Entity -> m Bool
+ Mischief.ECS.World.Query: class GetComponentId c
+ Mischief.ECS.World.Query: data Query (m :: Type -> Type) a
+ Mischief.ECS.World.Query: findArchetypes :: forall qd m w output. (Queryable qd output, MonadSystem w m) => qd -> m [ArchetypeId]
+ Mischief.ECS.World.Query: getComponentId' :: forall w (m :: Type -> Type). (GetComponentId c, MonadSystem w m) => c -> FilterComponent
+ Mischief.ECS.World.Query: has :: forall c m w. (MonadSystem w m, ToFilterComponent c) => c -> Entity -> m Bool
+ Mischief.ECS.World.Query: instance Mischief.ECS.World.MonadSystem w m => GHC.Internal.Base.Applicative (Mischief.ECS.World.Query.Query m)
+ Mischief.ECS.World.Query: instance Mischief.ECS.World.MonadSystem w m => GHC.Internal.Base.Functor (Mischief.ECS.World.Query.Query m)
+ Mischief.ECS.World.Query: instance Mischief.ECS.World.MonadSystem w m => GHC.Internal.Base.Monad (Mischief.ECS.World.Query.Query m)
+ Mischief.ECS.World.Query: mkGet :: forall qd out (m :: Type -> Type). Queryable qd out => Entity -> qd -> Query m out
+ Mischief.ECS.World.Query: mkGet' :: forall qd out (m :: Type -> Type). Queryable qd out => Entity -> qd -> QueryFilter -> Query m out
+ Mischief.ECS.World.Query: mkQuery :: forall qd out (m :: Type -> Type). Queryable qd out => qd -> Query m out
+ Mischief.ECS.World.Query: mkQuery' :: forall qd out (m :: Type -> Type). Queryable qd out => qd -> QueryFilter -> Query m out
+ Mischief.ECS.World.Query: qrun :: MonadSystem w m => Query m out -> m [(Entity, out)]
+ Mischief.ECS.World.Query: qrunPar :: Query ParSystem a -> ParSystem [(Entity, a)]
+ Mischief.ECS.World.Query: queryOne :: MonadSystem w m => Query m out -> m (Maybe out)
+ Mischief.ECS.World.Query: query_ :: MonadSystem w m => Query m out -> m ()
+ Mischief.ECS.World.Query: runWhenOne :: Typeable a => Query System a -> (a -> System b) -> System ()
+ Mischief.ECS.World.Query: runWhenQ :: Typeable a => Query System a -> ([a] -> System b) -> System ()
+ Mischief.ECS.World.Query: tryJoin :: (Entity, a) -> (Entity, a -> b) -> Maybe (Entity, b)
+ Mischief.ECS.World.Query.Markers: HasRes :: HasRes (a :: k)
+ Mischief.ECS.World.Query.Markers: MRes :: MRes (a :: k)
+ Mischief.ECS.World.Query.Markers: data HasRes (a :: k)
+ Mischief.ECS.World.Query.Markers: data MRes (a :: k)
+ Mischief.ECS.World.Query.Pipe: qcollect :: forall w (m :: Type -> Type) a. MonadSystem w m => Query m a -> Query m [From a]
+ Mischief.ECS.World.Query.Pipe: qdespawn :: Query System a -> Query System a
+ Mischief.ECS.World.Query.Pipe: qentity :: forall w (m :: Type -> Type) a. MonadSystem w m => Query m a -> Query m (Entity, a)
+ Mischief.ECS.World.Query.Pipe: qerr :: forall w (m :: Type -> Type) a. (HasCallStack, MonadSystem w m) => (a -> Text) -> Query m a -> Query m a
+ Mischief.ECS.World.Query.Pipe: qextend :: forall w (m :: Type -> Type) qd out a c. (MonadSystem w m, Queryable qd out) => qd -> (a -> out -> c) -> Query m a -> Query m c
+ Mischief.ECS.World.Query.Pipe: qfilter :: forall w (m :: Type -> Type) a. MonadSystem w m => (a -> Bool) -> Query m a -> Query m a
+ Mischief.ECS.World.Query.Pipe: qfilterM :: (Entity -> a -> m Bool) -> Query m a -> Query m a
+ Mischief.ECS.World.Query.Pipe: qget :: forall w (m :: Type -> Type) qd out. (MonadSystem w m, Queryable qd out) => Entity -> qd -> Query m out
+ Mischief.ECS.World.Query.Pipe: qgetAll :: forall w (m :: Type -> Type) qd out. (MonadSystem w m, Queryable qd out) => [Entity] -> qd -> Query m out
+ Mischief.ECS.World.Query.Pipe: qinfo :: forall w (m :: Type -> Type) a. (HasCallStack, MonadSystem w m) => (a -> Text) -> Query m a -> Query m a
+ Mischief.ECS.World.Query.Pipe: qinsert :: Bundle b => (a -> b) -> Query System a -> Query System a
+ Mischief.ECS.World.Query.Pipe: qinsertIfNeq :: BundleEq b => (a -> b) -> Query System a -> Query System a
+ Mischief.ECS.World.Query.Pipe: qinsertNew :: Bundle b => (a -> b) -> Query System a -> Query System a
+ Mischief.ECS.World.Query.Pipe: qiter :: forall w (m :: Type -> Type) a. MonadSystem w m => [From a] -> Query m a
+ Mischief.ECS.World.Query.Pipe: qiterE :: forall w (m :: Type -> Type). MonadSystem w m => [Entity] -> Query m Entity
+ Mischief.ECS.World.Query.Pipe: qjoin :: forall w (m :: Type -> Type) a b c. MonadSystem w m => (a -> Query m b) -> (a -> [From b] -> c) -> Query m a -> Query m c
+ Mischief.ECS.World.Query.Pipe: qlift :: MonadSystem w m => m a -> Query m a
+ Mischief.ECS.World.Query.Pipe: qlift_ :: MonadSystem w m => m a -> Query m ()
+ Mischief.ECS.World.Query.Pipe: qmap :: forall w (m :: Type -> Type) a b. MonadSystem w m => (a -> b) -> Query m a -> Query m b
+ Mischief.ECS.World.Query.Pipe: qmapM :: MonadSystem w m => (Entity -> a -> m b) -> Query m a -> Query m b
+ Mischief.ECS.World.Query.Pipe: qmapMaybe :: forall w (m :: Type -> Type) a b. MonadSystem w m => (a -> Maybe b) -> Query m a -> Query m b
+ Mischief.ECS.World.Query.Pipe: qmapMaybeM :: MonadSystem w m => (Entity -> a -> m (Maybe b)) -> Query m a -> Query m b
+ Mischief.ECS.World.Query.Pipe: qmaybe :: forall w (m :: Type -> Type) a. MonadSystem w m => Maybe a -> Query m a
+ Mischief.ECS.World.Query.Pipe: qmodify :: Bundle b => (a -> b) -> Query System a -> Query System b
+ Mischief.ECS.World.Query.Pipe: qmodifyIfNeq :: BundleEq b => (a -> b) -> Query System a -> Query System b
+ Mischief.ECS.World.Query.Pipe: qmodifyNew :: Bundle b => (a -> b) -> Query System a -> Query System b
+ Mischief.ECS.World.Query.Pipe: qpar :: forall w (m :: Type -> Type) a. MonadSystem w m => Query ParSystem a -> Query m a
+ Mischief.ECS.World.Query.Pipe: qpure :: forall w (m :: Type -> Type). MonadSystem w m => Entity -> Query m ()
+ Mischief.ECS.World.Query.Pipe: qrefocus :: forall (m :: Type -> Type) a. Entity -> Query m a -> Query m a
+ Mischief.ECS.World.Query.Pipe: qrelateMany :: (MonadSystem w m, Queryable (E, qd) (Entity, out)) => (Entity -> m [Entity]) -> qd -> (a -> [From out] -> c) -> Query m a -> Query m c
+ Mischief.ECS.World.Query.Pipe: qrelateOne :: (MonadSystem w m, Queryable qd out) => (Entity -> m (Maybe Entity)) -> qd -> (a -> From out -> c) -> Query m a -> Query m c
+ Mischief.ECS.World.Query.Pipe: qres :: forall c (m :: Type -> Type) w. (MonadSystem w m, Component c) => Query m c
+ Mischief.ECS.World.Query.Pipe: qstop :: forall w (m :: Type -> Type) a. MonadSystem w m => Query m a
+ Mischief.ECS.World.Query.Pipe: qstopIf :: forall w (m :: Type -> Type). MonadSystem w m => Bool -> Query m ()
+ Mischief.ECS.World.Query.Pipe: qtap :: (Entity -> a -> m b) -> Query m a -> Query m a
+ Mischief.ECS.World.Query.Pipe: qtraverse :: (Entity -> a -> m b) -> Query m a -> Query m b
+ Mischief.ECS.World.Query.Pipe: qwarn :: forall w (m :: Type -> Type) a. (HasCallStack, MonadSystem w m) => (a -> Text) -> Query m a -> Query m a
+ Mischief.ECS.World.Query.QueryFilter: ArchetypeFilter :: FilterType
+ Mischief.ECS.World.Query.QueryFilter: EntityFilter :: FilterType
+ Mischief.ECS.World.Query.QueryFilter: FilterComponent :: (TypeRep, Maybe Entity, Maybe Any) -> FilterComponent
+ Mischief.ECS.World.Query.QueryFilter: [And] :: QueryFilter -> QueryFilter -> QueryFilter
+ Mischief.ECS.World.Query.QueryFilter: [Changed] :: forall a. ToFilterComponent a => a -> (Tick -> Tick -> Bool) -> (Tick -> Tick -> Bool) -> QueryFilter
+ Mischief.ECS.World.Query.QueryFilter: [NoFilter] :: QueryFilter
+ Mischief.ECS.World.Query.QueryFilter: [Or] :: QueryFilter -> QueryFilter -> QueryFilter
+ Mischief.ECS.World.Query.QueryFilter: [With] :: forall a. ToFilterComponent a => a -> QueryFilter
+ Mischief.ECS.World.Query.QueryFilter: [Without] :: forall a. ToFilterComponent a => a -> QueryFilter
+ Mischief.ECS.World.Query.QueryFilter: class ToFilterComponent a
+ Mischief.ECS.World.Query.QueryFilter: data FilterType
+ Mischief.ECS.World.Query.QueryFilter: filterArchetype' :: FilterComponent -> World -> [ComponentId] -> IO Bool
+ Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.QueryFilter.ToFilterComponent (Mischief.ECS.World.Query.Markers.C c)
+ Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.QueryFilter.ToFilterComponent (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.EntityDef.Entity)
+ Mischief.ECS.World.Query.QueryFilter: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.QueryFilter.ToFilterComponent (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any)
+ Mischief.ECS.World.Query.QueryFilter: newtype FilterComponent
+ Mischief.ECS.World.Query.QueryFilter: toFilterComponent :: ToFilterComponent a => a -> FilterComponent
+ Mischief.ECS.World.Query.Queryable: addedChanged :: forall m w. MonadSystem w m => (ComponentTick -> Tick -> Tick -> Bool) -> FilterComponent -> Map ComponentId ComponentTick -> Table -> m Bool
+ Mischief.ECS.World.Query.Queryable: filterArchetype :: QueryFilter -> World -> Table -> IO Bool
+ Mischief.ECS.World.Query.Queryable: filterEntity :: QueryFilter -> World -> Entity -> IO Bool
+ Mischief.ECS.World.Query.Queryable: filterEntity' :: FilterComponent -> Entity -> World -> IO Bool
+ Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.HasR c (Mischief.ECS.World.Query.Markers.Q' q Mischief.ECS.World.Query.QueryFilter.QueryFilter)) GHC.Types.Bool
+ Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.False (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q q)) (GHC.Internal.Maybe.Maybe [Mischief.ECS.Components.From out])
+ Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.False (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q' q Mischief.ECS.World.Query.QueryFilter.QueryFilter)) (GHC.Internal.Maybe.Maybe [Mischief.ECS.Components.From out])
+ Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.False (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q q)) [Mischief.ECS.Components.From out]
+ Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.False (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q' q Mischief.ECS.World.Query.QueryFilter.QueryFilter)) [Mischief.ECS.Components.From out]
+ Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.True (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q q)) (GHC.Internal.Maybe.Maybe (Mischief.ECS.Components.From out))
+ Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.True (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q' q Mischief.ECS.World.Query.QueryFilter.QueryFilter)) (GHC.Internal.Maybe.Maybe (Mischief.ECS.Components.From out))
+ Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.True (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q q)) (Mischief.ECS.Components.From out)
+ Mischief.ECS.World.Query.Queryable: instance (Mischief.ECS.Components.Component c, Mischief.ECS.World.Query.Queryable.Queryable q out) => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.True (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q' q Mischief.ECS.World.Query.QueryFilter.QueryFilter)) (Mischief.ECS.Components.From out)
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.C c) c
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.HasRes c) GHC.Types.Bool
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.M c) (GHC.Internal.Maybe.Maybe c)
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.MR c Mischief.ECS.EntityDef.Entity) (GHC.Internal.Maybe.Maybe (Mischief.ECS.Components.Rel c))
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.MRes c) (GHC.Internal.Maybe.Maybe (Mischief.ECS.Components.Res c))
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.EntityDef.Entity) (Mischief.ECS.Components.Rel c)
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.Queryable (c -> Mischief.ECS.Components.Res c) (Mischief.ECS.Components.Res c)
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.False (Mischief.ECS.World.Query.Markers.MR c Mischief.ECS.World.Query.Markers.Any) (GHC.Internal.Maybe.Maybe [Mischief.ECS.Components.Rel c])
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.False (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any) [Mischief.ECS.Components.Rel c]
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.True (Mischief.ECS.World.Query.Markers.MR c Mischief.ECS.World.Query.Markers.Any) (GHC.Internal.Maybe.Maybe (Mischief.ECS.Components.Rel c))
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.Components.Component c => Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.True (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any) (Mischief.ECS.Components.Rel c)
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.Queryable () ()
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.IsExclusiveRel c) (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q q)) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q q)) out
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.IsExclusiveRel c) (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q' q f)) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.MR c (Mischief.ECS.World.Query.Markers.Q' q f)) out
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.IsExclusiveRel c) (Mischief.ECS.World.Query.Markers.MR c Mischief.ECS.World.Query.Markers.Any) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.MR c Mischief.ECS.World.Query.Markers.Any) out
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.IsExclusiveRel c) (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q q)) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q q)) out
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.IsExclusiveRel c) (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q' q f)) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.R c (Mischief.ECS.World.Query.Markers.Q' q f)) out
+ Mischief.ECS.World.Query.Queryable: instance Mischief.ECS.World.Query.Queryable.RelQuery (Mischief.ECS.Components.IsExclusiveRel c) (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.R c Mischief.ECS.World.Query.Markers.Any) out
+ Mischief.ECS.World.Query.Queryable: instance forall k (c :: k) e out. Mischief.ECS.World.Query.Queryable.RelQuery 'GHC.Types.False (Mischief.ECS.World.Query.Markers.R c e) out => Mischief.ECS.World.Query.Queryable.Queryable (Mischief.ECS.World.Query.Markers.R' c e) out
+ Mischief.ECS.World.Query.TH: instance GHC.Internal.Show.Show Mischief.ECS.World.Query.TH.QueryBuilder
+ Mischief.ECS.World.Query.TH: qd :: QuasiQuoter
+ Mischief.ECS.World.Query.TH: qf :: QuasiQuoter
+ Mischief.ECS.World.Query.TH.Common: Resource :: CompType
+ Mischief.ECS.World.Query.TH.Common: getType :: Text -> Type
+ Mischief.ECS.World.Query.TH.QD: pRes :: Maybe Mod -> Parser Qd
+ Mischief.ECS.World.Query.TH.QD: processHasRes :: Text -> Q Exp
+ Mischief.ECS.World.Query.TH.QD: processMRes :: Text -> Q Exp
+ Mischief.ECS.World.Query.TH.QD: processRes :: Text -> Q Exp
+ Mischief.ECS.World.Query.TH.QF: And' :: Qf -> Qf -> Qf
+ Mischief.ECS.World.Query.TH.QF: Without' :: [QfType] -> Qf
+ Mischief.ECS.World.Query.TH.QF: pAnd :: Parser Qf
+ Mischief.ECS.World.Query.TH.QF: processChanged :: Exp -> [QfType] -> Exp -> Exp -> Q Exp
+ Mischief.ECS.World.Spawn: spawn_ :: (HasCallStack, Bundle b) => b -> System ()
- Mischief.ECS.App: newApp :: Plugin p => p -> IO App
+ Mischief.ECS.App: newApp :: IO App
- Mischief.ECS.App: register :: forall {k} (c :: k). Runnable c => System ()
+ Mischief.ECS.App: register :: Component c => System ()
- Mischief.ECS.App: runSchedule :: Schedule sch => sch -> System ()
+ Mischief.ECS.App: runSchedule :: forall {k} (sch :: k). Schedule sch => System ()
- Mischief.ECS.App: toolsGet :: forall c m w. (MonadSystem w m, QueryType c) => Proxy c -> Entity -> m (Maybe c)
+ Mischief.ECS.App: toolsGet :: forall c m w. (MonadSystem w m, Component c) => Proxy c -> Entity -> m (Maybe c)
- Mischief.ECS.App: toolsGetRAny :: forall c m w. (Component c, MonadSystem w m, RelExclusivity c ~ 'Inclusive) => Proxy c -> Entity -> m (Maybe [Rel c])
+ Mischief.ECS.App: toolsGetRAny :: forall c m w. (Component c, MonadSystem w m, IsExclusiveRel c ~ 'False) => Proxy c -> Entity -> m (Maybe [Rel c])
- Mischief.ECS.App.Plugins: class (Typeable p, Eq p) => Plugin p
+ Mischief.ECS.App.Plugins: class Typeable p => Plugin p
- Mischief.ECS.App.Plugins: init :: Plugin p => p -> System ()
+ Mischief.ECS.App.Plugins: init :: Plugin p => System ()
- Mischief.ECS.App.Schedules: scheduleEntity :: Schedule sch => sch -> System Entity
+ Mischief.ECS.App.Schedules: scheduleEntity :: forall {k} (sch :: k). Schedule sch => System Entity
- Mischief.ECS.App.Systems: systemEntity :: (HasCallStack, Schedule sch) => sch -> System () -> System Entity
+ Mischief.ECS.App.Systems: systemEntity :: forall {k} (sch :: k). (HasCallStack, Schedule sch) => System () -> System Entity
- Mischief.ECS.Archetypes.Graph: allArchetypes :: forall m w. MonadSystem w m => m [([ComponentId], ArchetypeId)]
+ Mischief.ECS.Archetypes.Graph: allArchetypes :: forall m w. MonadSystem w m => m [ArchetypeId]
- Mischief.ECS.Archetypes.Graph: findMatchingArchetypes :: forall m w. MonadSystem w m => [(ComponentId, ComponentQuery)] -> Archetypes -> m [([ComponentId], ArchetypeId)]
+ Mischief.ECS.Archetypes.Graph: findMatchingArchetypes :: forall m w. MonadSystem w m => [(ComponentId, ComponentQuery)] -> Archetypes -> m [ArchetypeId]
- Mischief.ECS.Components: BundleData :: Set (BundleElement e) -> BundleData e
+ Mischief.ECS.Components: BundleData :: Set (BundleElement e) -> Set (BundleElement e) -> Set (Entity, BundleData e) -> BundleData e
- Mischief.ECS.Components: Tick :: (Int, Int) -> Tick
+ Mischief.ECS.Components: Tick :: Word64 -> Tick
- Mischief.ECS.Components: [comp] :: Rel c -> c
+ Mischief.ECS.Components: [comp] :: Res c -> c
- Mischief.ECS.Components: class (Typeable c, IsExclusive RelExclusivity c) => Component c where {
+ Mischief.ECS.Components: class (Typeable c, IsExclusive IsExclusiveRel c) => Component c where {
- Mischief.ECS.Components: class IsExclusive (e :: Exclusivity)
+ Mischief.ECS.Components: class IsExclusive (e :: Bool)
- Mischief.ECS.Components.Bundle: ProcessedBundleElement :: ComponentId -> ComponentData -> ProcessedBundleElement
+ Mischief.ECS.Components.Bundle: ProcessedBundleElement :: ComponentId -> ErasedComponent -> ProcessedBundleElement
- Mischief.ECS.Components.Bundle: [component] :: ProcessedBundleElement -> ComponentData
+ Mischief.ECS.Components.Bundle: [component] :: ProcessedBundleElement -> ErasedComponent
- Mischief.ECS.Components.Spawn: registerHooks :: forall {k} (c :: k). Hooks c -> System ()
+ Mischief.ECS.Components.Spawn: registerHooks :: Component c => Entity -> System ()
- Mischief.ECS.Resources: res :: QueryType c => System (Maybe c)
+ Mischief.ECS.Resources: res :: forall c m w. (MonadSystem w m, Component c) => m (Maybe c)
- Mischief.ECS.Resources: resOrInsert :: (Component r, Updateable (Result r), Bundle r) => r -> System (Result r)
+ Mischief.ECS.Resources: resOrInsert :: (Component r, Bundle r) => r -> System r
- Mischief.ECS.Schedules: get :: Schedule sc => sc -> System Entity
+ Mischief.ECS.Schedules: get :: forall {k} (sc :: k). Schedule sc => System Entity
- Mischief.ECS.Schedules: run :: Schedule sc => sc -> System ()
+ Mischief.ECS.Schedules: run :: forall {k} (sc :: k). Schedule sc => System ()
- Mischief.ECS.Systems: get :: Schedule sc => sc -> System () -> System Entity
+ Mischief.ECS.Systems: get :: forall {k} (sc :: k). Schedule sc => System () -> System Entity
- Mischief.ECS.Systems: schedule :: Schedule sc => sc -> System () -> System ()
+ Mischief.ECS.Systems: schedule :: forall {k} (sc :: k). Schedule sc => SystemConfig -> System ()
- Mischief.ECS.Systems: unschedule :: Schedule sc => sc -> System () -> System ()
+ Mischief.ECS.Systems: unschedule :: forall {k} (sc :: k) a. (Schedule sc, ToSystems a) => a -> System ()
- Mischief.ECS.Tables: Column :: IOVec ComponentData -> Column
+ Mischief.ECS.Tables: Column :: IOVec ErasedComponent -> Column
- Mischief.ECS.Tables: Table :: IORef (Map ComponentId Column) -> [ComponentId] -> IOVec (Entity, IORef EntityPointer) -> Table
+ Mischief.ECS.Tables: Table :: IORef (Map ComponentId Column) -> [ComponentId] -> IORef (Map ComponentId ComponentTick) -> IOVec (Entity, IORef EntityPointer) -> Table
- Mischief.ECS.Tables: insertComponentsIntoTable :: ProcessedBundleData -> Table -> IO ()
+ Mischief.ECS.Tables: insertComponentsIntoTable :: ProcessedBundleData -> Tick -> ChangedComponents -> Table -> IO ()
- Mischief.ECS.Tables: insertEntityIntoTables :: ProcessedBundleData -> Tables -> ArchetypeId -> (Entity, IORef EntityPointer) -> IO ()
+ Mischief.ECS.Tables: insertEntityIntoTables :: ProcessedBundleData -> Tick -> ChangedComponents -> Tables -> ArchetypeId -> (Entity, IORef EntityPointer) -> IO ()
- Mischief.ECS.Tables: replaceComponentsIntoMap :: ProcessedBundleData -> Maybe Tick -> EntityPointer -> Map ComponentId Column -> IO ()
+ Mischief.ECS.Tables: replaceComponentsIntoMap :: ProcessedBundleData -> EntityPointer -> Map ComponentId Column -> IO ()
- Mischief.ECS.Tables: takeFromColumn :: EntityPointer -> Column -> IO ComponentData
+ Mischief.ECS.Tables: takeFromColumn :: EntityPointer -> Column -> IO ErasedComponent
- Mischief.ECS.Tables: tryGetComponentsFromArchetype :: Component c => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, Result c)]
+ Mischief.ECS.Tables: tryGetComponentsFromArchetype :: Component c => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, c)]
- Mischief.ECS.Tables: tryGetComponentsFromArchetypeMaybe :: Component c => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, Maybe (Result c))]
+ Mischief.ECS.Tables: tryGetComponentsFromArchetypeMaybe :: Component c => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, Maybe c)]
- Mischief.ECS.Tables: tryGetComponentsFromTable :: Component c => Table -> ComponentId -> IO [(Entity, Result c)]
+ Mischief.ECS.Tables: tryGetComponentsFromTable :: Component c => Table -> ComponentId -> IO [(Entity, c)]
- Mischief.ECS.Tables: tryGetComponentsFromTableMaybe :: Component c => Table -> ComponentId -> IO [(Entity, Maybe (Result c))]
+ Mischief.ECS.Tables: tryGetComponentsFromTableMaybe :: Component c => Table -> ComponentId -> IO [(Entity, Maybe c)]
- Mischief.ECS.Tables: tryGetComponentsFromTables :: Component c => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, Result c)]
+ Mischief.ECS.Tables: tryGetComponentsFromTables :: Component c => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, c)]
- Mischief.ECS.Tables: tryGetComponentsFromTablesMaybe :: Component c => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, Maybe (Result c))]
+ Mischief.ECS.Tables: tryGetComponentsFromTablesMaybe :: Component c => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, Maybe c)]
- Mischief.ECS.Tables: tryGetRelCollectionFromTable :: Component c => Table -> Entity -> EntityPointer -> ComponentId -> IO (Maybe [Result (Rel c)])
+ Mischief.ECS.Tables: tryGetRelCollectionFromTable :: Component c => Table -> Entity -> EntityPointer -> ComponentId -> IO (Maybe [(Entity, Rel c)])
- Mischief.ECS.Tables: tryGetRelCollectionFromTables :: Component c => Tables -> Entity -> EntityPointer -> ComponentId -> IO (Maybe [Result (Rel c)])
+ Mischief.ECS.Tables: tryGetRelCollectionFromTables :: Component c => Tables -> Entity -> EntityPointer -> ComponentId -> IO (Maybe [(Entity, Rel c)])
- Mischief.ECS.Tables: tryGetRelCollectionsFromArchetype :: Component c => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, [Result (Rel c)])]
+ Mischief.ECS.Tables: tryGetRelCollectionsFromArchetype :: Component c => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, [Rel c])]
- Mischief.ECS.Tables: tryGetRelCollectionsFromTable :: Component c => Table -> ComponentId -> IO [(Entity, [Result (Rel c)])]
+ Mischief.ECS.Tables: tryGetRelCollectionsFromTable :: Component c => Table -> ComponentId -> IO [(Entity, [Rel c])]
- Mischief.ECS.Tables: tryGetRelCollectionsFromTables :: Component c => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, [Result (Rel c)])]
+ Mischief.ECS.Tables: tryGetRelCollectionsFromTables :: Component c => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, [Rel c])]
- Mischief.ECS.Tables: tryGetTicksFromArchetype :: ArchetypeId -> IOVec Table -> ComponentId -> IO [Maybe ComponentTicks]
+ Mischief.ECS.Tables: tryGetTicksFromArchetype :: ArchetypeId -> IOVec Table -> ComponentId -> IO (Maybe ComponentTick)
- Mischief.ECS.Tables: tryGetTicksFromTable :: Table -> ComponentId -> IO [Maybe ComponentTicks]
+ Mischief.ECS.Tables: tryGetTicksFromTable :: Table -> ComponentId -> IO (Maybe ComponentTick)
- Mischief.ECS.Tables: tryGetTicksFromTables :: Tables -> [ArchetypeId] -> ComponentId -> IO [Maybe ComponentTicks]
+ Mischief.ECS.Tables: tryGetTicksFromTables :: Tables -> [ArchetypeId] -> ComponentId -> IO [Maybe ComponentTick]
- Mischief.ECS.Time: time :: System Time
+ Mischief.ECS.Time: time :: (HasCallStack, MonadSystem w m) => m Time
- Mischief.ECS.Utils: unwrap :: HasCallStack => Maybe a -> a
+ Mischief.ECS.Utils: unwrap :: (HasCallStack, Unwrappable f) => f a -> a
- Mischief.ECS.World: SystemTools :: (forall c (m :: Type -> Type) w. (MonadSystem w m, QueryType c) => Proxy c -> Entity -> m (Maybe c)) -> (forall c (m :: Type -> Type) w. (Component c, MonadSystem w m, RelExclusivity c ~ 'Inclusive) => Proxy c -> Entity -> m (Maybe [Rel c])) -> (forall c. Bundle c => c -> Entity -> System ()) -> (forall b. Bundle b => Entity -> b -> System ()) -> SystemTools
+ Mischief.ECS.World: SystemTools :: (forall c (m :: Type -> Type) w. (MonadSystem w m, Component c) => Proxy c -> Entity -> m (Maybe c)) -> (forall c (m :: Type -> Type) w. (Component c, MonadSystem w m, IsExclusiveRel c ~ 'False) => Proxy c -> Entity -> m (Maybe [Rel c])) -> (forall c. Bundle c => c -> Entity -> System ()) -> (forall b. Bundle b => Entity -> b -> System ()) -> SystemTools
- Mischief.ECS.World: World :: Archetypes -> Components -> Entities -> Tables -> IORef [ErasedEvent] -> IORef [System ()] -> TVar [System ()] -> IORef Tick -> SystemId -> IORef Frame -> WorldPrefs -> LogAction IO Message -> SystemTools -> World
+ Mischief.ECS.World: World :: Archetypes -> Components -> Entities -> Tables -> IORef [System ()] -> TVar [System ()] -> IORef [(System (Maybe ErasedValue), ErasedValue -> System ())] -> IORef Tick -> SystemId -> IORef Frame -> Prefs -> LogAction IO Message -> LogAction IO Message -> SystemTools -> World
- Mischief.ECS.World: [getRAny] :: SystemTools -> forall c (m :: Type -> Type) w. (Component c, MonadSystem w m, RelExclusivity c ~ 'Inclusive) => Proxy c -> Entity -> m (Maybe [Rel c])
+ Mischief.ECS.World: [getRAny] :: SystemTools -> forall c (m :: Type -> Type) w. (Component c, MonadSystem w m, IsExclusiveRel c ~ 'False) => Proxy c -> Entity -> m (Maybe [Rel c])
- Mischief.ECS.World: [get] :: SystemTools -> forall c (m :: Type -> Type) w. (MonadSystem w m, QueryType c) => Proxy c -> Entity -> m (Maybe c)
+ Mischief.ECS.World: [get] :: SystemTools -> forall c (m :: Type -> Type) w. (MonadSystem w m, Component c) => Proxy c -> Entity -> m (Maybe c)
- Mischief.ECS.World: [prefs] :: World -> WorldPrefs
+ Mischief.ECS.World: [prefs] :: World -> Prefs
- Mischief.ECS.World: class (GetWorld w, MonadReader w a, Applicative a, MonadFail a, Functor a, Monad a, MonadIO a, PrimMonad a, PrimState a ~ RealWorld) => MonadSystem w (a :: Type -> Type)
+ Mischief.ECS.World: class (GetWorld w, MonadReader w a, Applicative a, MonadFail a, Functor a, Monad a, MonadIO a, PrimMonad a, PrimState a ~ RealWorld, MonadThrow a, SystemAny a) => MonadSystem w (a :: Type -> Type)
- Mischief.ECS.World: worldGet :: forall c m w. (MonadSystem w m, QueryType c) => Proxy c -> Entity -> m (Maybe c)
+ Mischief.ECS.World: worldGet :: forall c m w. (MonadSystem w m, Component c) => Proxy c -> Entity -> m (Maybe c)
- Mischief.ECS.World: worldGetRAny :: forall c m w. (Component c, MonadSystem w m, RelExclusivity c ~ 'Inclusive) => Proxy c -> Entity -> m (Maybe [Rel c])
+ Mischief.ECS.World: worldGetRAny :: forall c m w. (Component c, MonadSystem w m, IsExclusiveRel c ~ 'False) => Proxy c -> Entity -> m (Maybe [Rel c])
- Mischief.ECS.World.Defer: defer :: Defer m => System a -> m ()
+ Mischief.ECS.World.Defer: defer :: MonadSystem w m => System a -> m ()
- Mischief.ECS.World.Defer: delay :: MonadSystem w m => Int -> System () -> m ()
+ Mischief.ECS.World.Defer: delay :: MonadSystem w m => Float -> System a -> m ()
- Mischief.ECS.World.Query: query :: forall qd output m w. (Queryable qd output, MonadSystem w m) => qd -> m [output]
+ Mischief.ECS.World.Query: query :: MonadSystem w m => Query m out -> m [out]
- Mischief.ECS.World.Query: single :: forall qd m w out. (Queryable qd out, MonadSystem w m) => qd -> m (Maybe out)
+ Mischief.ECS.World.Query: single :: MonadSystem w m => Query m out -> m (Maybe out)
- Mischief.ECS.World.Query.QueryFilter: [inner] :: CheckFilterType -> [(TypeRep, Maybe Entity, Maybe Any, ErasedCheck)]
+ Mischief.ECS.World.Query.QueryFilter: [inner] :: FilterComponent -> (TypeRep, Maybe Entity, Maybe Any)
- Mischief.ECS.World.Query.QueryFilter: qfChangedF :: ComponentTicks -> Tick -> Tick -> Bool
+ Mischief.ECS.World.Query.QueryFilter: qfChangedF :: (Tick -> Tick -> Bool) -> (Tick -> Tick -> Bool) -> ComponentTick -> Tick -> Tick -> Bool
- Mischief.ECS.World.Query.Queryable: class RelQuery (exclusive :: Exclusivity) qd output | qd exclusive -> output
+ Mischief.ECS.World.Query.Queryable: class RelQuery (exclusive :: Bool) qd output | qd exclusive -> output
- Mischief.ECS.World.Query.TH.QF: Changed' :: [QfType] -> Qf
+ Mischief.ECS.World.Query.TH.QF: Changed' :: [QfType] -> Text -> Text -> Qf
- Mischief.ECS.World.Query.TH.QF: processTypes :: [QfType] -> Q Exp
+ Mischief.ECS.World.Query.TH.QF: processTypes :: Exp -> [QfType] -> Q Exp
- Mischief.ECS.World.Utils: processBundleElement :: World -> ComponentTicks -> BundleElement ErasedComponent -> IO ProcessedBundleElement
+ Mischief.ECS.World.Utils: processBundleElement :: World -> BundleElement ErasedComponent -> IO ProcessedBundleElement
- Mischief.ECS.World.Utils: processBundleElements :: World -> ComponentTicks -> Set (BundleElement ErasedComponent) -> IO ProcessedBundleData
+ Mischief.ECS.World.Utils: processBundleElements :: World -> Set (BundleElement ErasedComponent) -> IO ProcessedBundleData
- Mischief.ECS.World.Utils: tryGetComponents :: Component c => World -> [ArchetypeId] -> IO [(Entity, Result c)]
+ Mischief.ECS.World.Utils: tryGetComponents :: Component c => World -> [ArchetypeId] -> IO [(Entity, c)]
- Mischief.ECS.World.Utils: tryGetComponentsMaybe :: Component c => World -> [ArchetypeId] -> IO [(Entity, Maybe (Result c))]
+ Mischief.ECS.World.Utils: tryGetComponentsMaybe :: Component c => World -> [ArchetypeId] -> IO [(Entity, Maybe c)]
- Mischief.ECS.World.Utils: tryGetEntityRelCollection :: Component c => World -> Entity -> IO (Maybe (Maybe [Result (Rel c)]))
+ Mischief.ECS.World.Utils: tryGetEntityRelCollection :: Component c => World -> Entity -> IO (Maybe (Maybe [Rel c]))
- Mischief.ECS.World.Utils: tryGetRelCollections :: Component c => World -> [ArchetypeId] -> IO [(Entity, [Result (Rel c)])]
+ Mischief.ECS.World.Utils: tryGetRelCollections :: Component c => World -> [ArchetypeId] -> IO [(Entity, [Rel c])]
- Mischief.ECS.World.Utils: tryGetRels :: Component c => Entity -> World -> [ArchetypeId] -> IO [(Entity, Result (Rel c))]
+ Mischief.ECS.World.Utils: tryGetRels :: Component c => Entity -> World -> [ArchetypeId] -> IO [(Entity, Rel c)]
- Mischief.ECS.World.Utils: tryGetRelsMaybe :: Component c => Entity -> World -> [ArchetypeId] -> IO [(Entity, Maybe (Result (Rel c)))]
+ Mischief.ECS.World.Utils: tryGetRelsMaybe :: Component c => Entity -> World -> [ArchetypeId] -> IO [(Entity, Maybe (Rel c))]
- Mischief.ECS.World.Utils: tryGetTicks :: ComponentId -> World -> [ArchetypeId] -> IO [Maybe ComponentTicks]
+ Mischief.ECS.World.Utils: tryGetTicks :: ComponentId -> World -> [ArchetypeId] -> IO [Maybe ComponentTick]
Files
- CHANGELOG.md +6/−2
- README.md +11/−0
- benchmarks/bench-insert.hs +3/−6
- benchmarks/bench-spawn.hs +5/−9
- benchmarks/bench-update.hs +7/−9
- examples/Dungeon.hs +164/−134
- examples/Startup.hs +67/−0
- mischief-ecs.cabal +86/−56
- src/Mischief/ECS.hs +86/−168
- src/Mischief/ECS/App.hs +118/−62
- src/Mischief/ECS/App/Plugins.hs +39/−37
- src/Mischief/ECS/App/Schedules.hs +21/−20
- src/Mischief/ECS/App/SystemConfig.hs +0/−42
- src/Mischief/ECS/App/SystemDef.hs +0/−1
- src/Mischief/ECS/App/Systems.hs +27/−36
- src/Mischief/ECS/Archetypes.hs +4/−8
- src/Mischief/ECS/Archetypes/Graph.hs +100/−107
- src/Mischief/ECS/Collectable.hs +0/−2
- src/Mischief/ECS/Components.hs +112/−48
- src/Mischief/ECS/Components/Bundle.hs +24/−10
- src/Mischief/ECS/Components/Common.hs +1/−1
- src/Mischief/ECS/Components/HooksDef.hs +9/−4
- src/Mischief/ECS/Components/Required.hs +44/−8
- src/Mischief/ECS/Components/Runnable.hs +0/−25
- src/Mischief/ECS/Components/Spawn.hs +66/−42
- src/Mischief/ECS/Entities.hs +40/−36
- src/Mischief/ECS/EntityDef.hs +13/−10
- src/Mischief/ECS/Events.hs +14/−32
- src/Mischief/ECS/Exports.hs +12/−0
- src/Mischief/ECS/Fixed.hs +108/−0
- src/Mischief/ECS/Foreign.hs +11/−0
- src/Mischief/ECS/Graph.hs +0/−92
- src/Mischief/ECS/Hidden.hs +1/−1
- src/Mischief/ECS/Hooks.hs +5/−88
- src/Mischief/ECS/HooksRel.hs +115/−0
- src/Mischief/ECS/Interval.hs +0/−30
- src/Mischief/ECS/Log.hs +9/−18
- src/Mischief/ECS/Mappable.hs +0/−106
- src/Mischief/ECS/Messages.hs +10/−17
- src/Mischief/ECS/Observer.hs +1/−6
- src/Mischief/ECS/Observers.hs +0/−10
- src/Mischief/ECS/Prelude.hs +0/−57
- src/Mischief/ECS/Relationships.hs +0/−7
- src/Mischief/ECS/Relationships/ChildOf.hs +9/−11
- src/Mischief/ECS/Relationships/Graph.hs +28/−11
- src/Mischief/ECS/Relationships/Order.hs +8/−13
- src/Mischief/ECS/Relationships/Tree.hs +28/−25
- src/Mischief/ECS/Repeater.hs +47/−0
- src/Mischief/ECS/Resources.hs +52/−10
- src/Mischief/ECS/Schedules.hs +7/−4
- src/Mischief/ECS/Stdin.hs +2/−0
- src/Mischief/ECS/Systems.hs +68/−38
- src/Mischief/ECS/Tables.hs +110/−113
- src/Mischief/ECS/Time.hs +70/−19
- src/Mischief/ECS/Timer.hs +4/−0
- src/Mischief/ECS/Tutorial/App.hs +0/−179
- src/Mischief/ECS/Tutorial/Components.hs +0/−573
- src/Mischief/ECS/Tutorial/Dungeon.hs +0/−1229
- src/Mischief/ECS/Tutorial/Events.hs +0/−99
- src/Mischief/ECS/Tutorial/Guides/Basics.hs +281/−0
- src/Mischief/ECS/Tutorial/Guides/Change.hs +140/−0
- src/Mischief/ECS/Tutorial/Guides/Events.hs +91/−0
- src/Mischief/ECS/Tutorial/Guides/MonadicQueries.hs +263/−0
- src/Mischief/ECS/Tutorial/Guides/Organizing.hs +76/−0
- src/Mischief/ECS/Tutorial/Guides/Parallelism.hs +118/−0
- src/Mischief/ECS/Tutorial/Guides/QuasiConversion.hs +125/−0
- src/Mischief/ECS/Tutorial/Guides/QueryBasics.hs +150/−0
- src/Mischief/ECS/Tutorial/Guides/QueryPiping.hs +205/−0
- src/Mischief/ECS/Tutorial/Guides/Scheduling.hs +187/−0
- src/Mischief/ECS/Tutorial/Guides/Time.hs +83/−0
- src/Mischief/ECS/Tutorial/Introduction/Dungeon.hs +1194/−0
- src/Mischief/ECS/Tutorial/Introduction/Startup.hs +426/−0
- src/Mischief/ECS/Tutorial/Queries.hs +0/−322
- src/Mischief/ECS/Tutorial/Relationships.hs +0/−402
- src/Mischief/ECS/Tutorial/Startup.hs +0/−431
- src/Mischief/ECS/Tutorial/Systems.hs +0/−397
- src/Mischief/ECS/Utils.hs +52/−4
- src/Mischief/ECS/Vec.hs +65/−65
- src/Mischief/ECS/World.hs +143/−60
- src/Mischief/ECS/World/Change.hs +51/−44
- src/Mischief/ECS/World/Defer.hs +46/−68
- src/Mischief/ECS/World/Insert.hs +132/−141
- src/Mischief/ECS/World/Modify.hs +0/−70
- src/Mischief/ECS/World/Par.hs +67/−28
- src/Mischief/ECS/World/Prefs.hs +0/−14
- src/Mischief/ECS/World/Query.hs +349/−101
- src/Mischief/ECS/World/Query/Markers.hs +4/−0
- src/Mischief/ECS/World/Query/Pipe.hs +359/−0
- src/Mischief/ECS/World/Query/QueryFilter.hs +32/−219
- src/Mischief/ECS/World/Query/QueryType.hs +0/−6
- src/Mischief/ECS/World/Query/Queryable.hs +420/−343
- src/Mischief/ECS/World/Query/TH.hs +50/−59
- src/Mischief/ECS/World/Query/TH/Common.hs +34/−38
- src/Mischief/ECS/World/Query/TH/QD.hs +75/−67
- src/Mischief/ECS/World/Query/TH/QF.hs +73/−89
- src/Mischief/ECS/World/Remove.hs +48/−65
- src/Mischief/ECS/World/Spawn.hs +63/−57
- src/Mischief/ECS/World/Systems.hs +0/−1
- src/Mischief/ECS/World/Utils.hs +117/−150
- test/Changes.hs +114/−0
- test/Main.hs +107/−305
- test/Utils.hs +64/−0
CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for mischief -## 0.1.0.0 -- YYYY-mm-dd+## 0.2.0.0 -* First version. Released on an unsuspecting world.+Almost a full rework of the high-level API in the previous version. The ECS is now designed to encourage functional coding. Also many performance improvements and bug fixes. ++## 0.1.0.0 ++First version. Released on an unsuspecting world.
+ README.md view
@@ -0,0 +1,11 @@+# Mischief ++This is the ECS library for the Mischief game engine. It is the root of every other library that's part of the engine. In other words, this is the source of all Mischief. ++It is meant to be a simple, yet powerful archetype ECS written fully in Haskell, that provides a unique blend of Data-Oriented Design+and Functional Programming.++To contribute, you can file issues, such as bug reports and feature requests, in the official [repository](https://codeberg.org/mischief/mischief).+You can also join the official server on discord to be kept up to date with updates and chat with the rest of the community. Link in the aforementioned repo. ++Check out the [Learn You an ECS for Great Mischief](https://hackage-content.haskell.org/package/mischief-ecs/docs/Mischief-ECS.html) book to learn about using this library!
benchmarks/bench-insert.hs view
@@ -12,12 +12,11 @@ main :: IO () main = defaultMainWith- defaultConfig {reportFile = Just "/home/pvd/GameRepos/mischief/benches/insert.html"}+ defaultConfig {reportFile = Just "/home/pvd/mischief/benches/insert.html"} [ benchInsert 10, benchInsert 100, benchInsert 1000,- benchInsert 10000,- benchInsert 100000+ benchInsert 10000 ] benchInsert :: Int -> Benchmark@@ -31,11 +30,9 @@ mkEs :: Int -> IO (Es, W) mkEs n = do- app <- newApp P+ app <- newApp es <- runSystem (replicateM n (spawn ())) app.world pure (Es es, W app.world)--data P = P deriving (Eq, Plugin) data Comp1 = Comp1 deriving (Component)
benchmarks/bench-spawn.hs view
@@ -13,21 +13,19 @@ flame :: IO () flame = do- app <- newApp P+ app <- newApp runSystem (spawnEntities 100000 2 5) app.world main :: IO () main = -- flame defaultMainWith- defaultConfig {reportFile = Just "/home/pvd/GameRepos/mischief/benches/spawn.html"}+ defaultConfig {reportFile = Just "/home/pvd/mischief/benches/spawn.html"} [ bgroup "Spawn" [ benchSpawnEntities 10, benchSpawnEntities 100,- benchSpawnEntities 1000,- benchSpawnEntities 10000,- benchSpawnEntities 100000+ benchSpawnEntities 10000 ] ] @@ -35,14 +33,14 @@ benchSpawnEntities a = bgroup (show a ++ " Entities") $ [benchSpawnComponents a b | b <- [2, 5]] benchSpawnComponents :: Int -> Int -> Benchmark-benchSpawnComponents a b = bgroup (show b ++ " Components") $ [env mkWorld (benchSpawnArchetypes a b c) | c <- [1, 2, 4, 5]]+benchSpawnComponents a b = bgroup (show b ++ " Components") $ [env mkWorld (benchSpawnArchetypes a b c) | c <- [1, 5]] benchSpawnArchetypes :: Int -> Int -> Int -> W -> Benchmark benchSpawnArchetypes a b c w = bench (show c ++ " Archetypes") $ nfIO (benchSpawn a b c w) mkWorld :: IO W mkWorld = do- app <- newApp P+ app <- newApp pure $ W app.world newtype W = W World@@ -54,8 +52,6 @@ benchSpawn :: Int -> Int -> Int -> W -> IO () benchSpawn ne nc na (W world) = runSystem (spawnEntities ne nc na) world--data P = P deriving (Eq, Plugin) data Comp11 = Comp11 deriving (Component)
benchmarks/bench-update.hs view
@@ -10,16 +10,15 @@ main :: IO () main = defaultMainWith- defaultConfig {reportFile = Just "/home/pvd/GameRepos/mischief/benches/update.html"}+ defaultConfig {reportFile = Just "/home/pvd/mischief/benches/update.html"} [ benchEntities 10, benchEntities 100, benchEntities 1000,- benchEntities 10000,- benchEntities 100000+ benchEntities 10000 ] benchEntities :: Int -> Benchmark-benchEntities a = bgroup (show a ++ " Entities") $ [env (mkWorld a) $ benchUpdates a b | b <- [1, 4, 8]]+benchEntities a = bgroup (show a ++ " Entities") $ [env (mkWorld a) $ benchUpdates a b | b <- [1]] benchUpdates :: Int -> Int -> W -> Benchmark benchUpdates a b w = bench (show b ++ " Updates") $ nfIO $ benchUpdate a b w@@ -36,16 +35,15 @@ mkWorld :: Int -> IO W mkWorld n = do- app <- newApp P+ app <- newApp runSystem (pre n) app.world pure $ W app.world -data P = P deriving (Eq, Plugin)- pre :: Int -> System () pre m = replicateM_ m (spawn Comp1) up :: System () up = do- q <- query (C @Comp1)- for_ q $ flip set Comp1+ [q|Comp1|]+ & qinsert (const Comp1)+ & query_
examples/Dungeon.hs view
@@ -1,87 +1,78 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE MultiWayIf #-}+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}+{-# OPTIONS_GHC -Wno-type-defaults #-} {- HLINT ignore "Use newtype instead of data" -} module Main where -import Control.Monad (void, when)+import Control.Monad import Control.Monad.IO.Class import Data.Default import Data.Foldable import Data.List ((!?)) import Data.Traversable import Mischief.ECS-import Mischief.ECS.Hooks qualified as Hooks-import Mischief.ECS.Interval qualified as Interval-import Mischief.ECS.Observers qualified as Observers import Mischief.ECS.Stdin qualified as Stdin import Mischief.ECS.Stdout-import Mischief.ECS.Systems qualified as Systems-import Mischief.ECS.Timer (Timer)+import Mischief.ECS.Time qualified as Time import Mischief.ECS.Timer qualified as Timer-import Mischief.ECS.World.Query.QueryType-import System.Exit+import System.Exit (exitSuccess) import System.Random import System.Random.Stateful -data Likes = Likes Int deriving (Show)--instance Component Likes where- hooks = Hooks.relCleanupRemove- main :: IO () main = do- app <- newApp MainPlugin+ app <- newApp+ addPlugin @MainPlugin app runApp app -data MainPlugin = MainPlugin deriving (Eq)+data MainPlugin instance Plugin MainPlugin where- init _ = do+ init = do Stdin.init- Systems.add Startup (spawnGrid, spawnWalls)- Systems.add Update printGrid - interval <- Interval.start 2000000 spawnCoin+ systems spawnGrid+ & schedule @Startup + systems spawnWalls+ & after spawnGrid+ & schedule @Startup++ systems printGrid+ & schedule @Update++ spawn_ $ Repeater 2 spawnCoin+ insertRes =<< newGen- insertRes $ Coins 0 - plugins _ = plug (PlayerPlugin, EnemyPlugin, TimePlugin)+ deps = [dep @PlayerPlugin, dep @EnemyPlugin, dep @TimePlugin] -data PlayerPlugin = PlayerPlugin deriving (Eq)+data PlayerPlugin instance Plugin PlayerPlugin where- init _ = do- Systems.add Startup $ spawnPlayer `after` spawnGrid- Systems.add Update movePlayer- Systems.add Update $ collectCoins `after` movePlayer+ init = do+ systems spawnPlayer+ & after spawnGrid+ & schedule @Startup - void $ Observers.spawn onDamage+ systems movePlayer+ & schedule @Update -data EnemyPlugin = EnemyPlugin deriving (Eq)+ systems collectCoins+ & after movePlayer+ & schedule @Update -instance Plugin EnemyPlugin where- init _ = do- Systems.add Startup spawnEnemies- Systems.add Update moveEnemies- Systems.add Update $ tryDamage `after` movePlayer `after` moveEnemies+ void $ spawn (Observer onDamage) data Tile = Tile deriving (Component) -newtype Pos = Pos {pos :: (Int, Int)} deriving (Component, Show)+data Pos = Pos (Int, Int) deriving (Component) data Grid = Grid [[Entity]] deriving (Component) -getTile :: (Int, Int) -> System (Maybe Entity)-getTile (x, y) = do- grid <- res @Grid- pure $ do- Grid tiles <- grid- line <- tiles !? x- line !? y- gridH :: Int gridH = 10 @@ -90,37 +81,44 @@ spawnGrid :: System () spawnGrid = do- tiles <- for [0 .. gridH - 1] $ \i -> for [0 .. gridW - 1] $ \j ->- spawn (Tile, Pos (i, j))+ tiles <-+ for [0 .. gridH - 1] $ \i ->+ for [0 .. gridW - 1] $ \j ->+ spawn (Tile, Pos (i, j)) insertRes $ Grid tiles -moveBy :: (Int, Int) -> Entity -> System (Maybe Entity)-moveBy (x, y) entity = do- Just (Pos (x', y')) <- [g|*Pos|] entity- getTile (x' + x, y' + y)+getTile :: (Int, Int) -> Grid -> Maybe Entity+getTile (x, y) (Grid tiles) = do+ line <- tiles !? x+ line !? y +moveBy :: (Int, Int) -> Pos -> Grid -> Maybe Entity+moveBy (x, y) (Pos (x', y')) = getTile (x' + x, y' + y)+ data Player = Player instance Component Player where- required = require @Health+ required = require @(Health, Coins) data OnTile = OnTile instance Component OnTile where- type RelExclusivity OnTile = Exclusive+ type IsExclusiveRel OnTile = True spawnPlayer :: System () spawnPlayer = do- Just tile <- getTile (5, 5)+ Just grid <- res @Grid+ let Just tile = getTile (5, 5) grid void $ spawn (Player, Rel OnTile tile) data Wall = Wall deriving (Component) -spawnWall :: (Int, Int) -> System Entity+spawnWall :: (Int, Int) -> System (Maybe Entity) spawnWall pos = do- Just tile <- getTile pos- spawn (Wall, Rel OnTile tile)+ Just grid <- res @Grid+ for (getTile pos grid) $ \tile ->+ spawn (Wall, Rel OnTile tile) spawnWalls :: System () spawnWalls = do@@ -129,6 +127,12 @@ for_ [1 .. gridH - 2] $ \i -> spawnWall (i, 0) for_ [1 .. gridH - 2] $ \i -> spawnWall (i, gridW - 1) +tileHas :: forall c. (Component c) => Entity -> System Bool+tileHas tile = not . null <$> query [q|Entity / With (c, OnTile -> tile)|]++hasWall :: Entity -> System Bool+hasWall = tileHas @Wall+ showTile :: Entity -> System Char showTile tile = do player <- tileHas @Player tile@@ -148,17 +152,7 @@ showGrid = do Just (Grid tiles) <- res @Grid lines <- for tiles $ traverse showTile- return $ unlines lines--showHealth :: System String-showHealth = do- Just health <- [s|Health / With Player|]- pure $ "Health: " ++ show health.hp--showCoins :: System String-showCoins = do- Just (Coins c) <- res @Coins- pure $ "Coins: " ++ show c+ pure $ unlines lines printGrid :: System () printGrid = do@@ -167,6 +161,15 @@ coins <- showCoins printClear $ health ++ "\n" ++ grid ++ "\n" ++ coins ++ "\n" +movePlayerBy :: (Int, Int) -> System ()+movePlayerBy dir = do+ Just grid <- res @Grid+ [q|OnTile -> (Pos) / With Player|]+ & qmapMaybe (\pos -> moveBy dir pos.comp grid)+ & qfilterM (const tileIsFree)+ & qinsert (Rel OnTile)+ & query_+ movePlayer :: System () movePlayer = do c <- Stdin.readLast@@ -177,29 +180,6 @@ 'd' -> movePlayerBy (0, 1) _ -> pure () -movePlayerBy :: (Int, Int) -> System ()-movePlayerBy dir = do- Just player <- single' E (With (C @Player))-- Just (tile, pos) <- [g|OnTile -> (Entity, *Pos)|] player- newTile <- moveBy dir tile-- for_ newTile $ \t ->- tileIsFree t >>= flip when (insert (Rel OnTile t) player)--hasWall :: Entity -> System Bool-hasWall = tileHas @Wall--data Enemy = Enemy--instance Component Enemy where- required = require @Cooldown--data Cooldown = Cooldown {timer :: Timer} deriving (Component)--instance Default Cooldown where- def = Cooldown $ Timer.new 0.5 Timer.Repeat- data Rand = Rand (IOGenM StdGen) deriving (Component) newGen :: System Rand@@ -208,13 +188,22 @@ randomPos :: System (Int, Int) randomPos = do Just (Rand gen) <- res @Rand- i <- applyIOGen (uniformR (1, gridH - 1)) gen- j <- applyIOGen (uniformR (1, gridW - 1)) gen+ i <- applyIOGen (uniformR (0, gridH - 1)) gen+ j <- applyIOGen (uniformR (0, gridW - 1)) gen return (i, j) randomTile :: System Entity-randomTile = unwrap <$> (getTile =<< randomPos)+randomTile = do+ Just grid <- res @Grid+ randomPos <- randomPos + pure . unwrap $ getTile randomPos grid++data Enemy = Enemy++instance Component Enemy where+ required = require @Cooldown+ spawnEnemy :: System Entity spawnEnemy = do tile <- randomTile@@ -223,6 +212,22 @@ spawnEnemies :: System () spawnEnemies = for_ [0 .. 4] $ const spawnEnemy +data EnemyPlugin++instance Plugin EnemyPlugin where+ init = do+ systems spawnEnemies+ & after spawnGrid+ & schedule @Startup++ systems moveEnemies+ & schedule @Update++ systems tryDamage+ & after moveEnemies+ & after movePlayer+ & schedule @Update+ decideEnemyDir :: Pos -> Pos -> System (Int, Int) decideEnemyDir (Pos (ex, ey)) (Pos (px, py)) = do left <- tileAtPosIsFree (ex - 1, ey)@@ -240,28 +245,38 @@ moveEnemies :: System () moveEnemies = do- Just pos <- [s|OnTile -> (*Pos) / With Player|]- delta <- deltaTime+ Just (From _ playerPos) <- single [q|OnTile -> (Pos) / With Player|]+ Just grid <- res @Grid - enemies <- [q|Entity, OnTile -> (Entity, *Pos), Cooldown / With Enemy|]- for_ enemies $ \(enemy, (enemyTile, enemyPos), cooldown) -> do- let (timer, finished) = Timer.tick delta cooldown.timer- set cooldown $ Cooldown timer+ [q|OnTile -> (Pos) / With Enemy|]+ & qfilterCooldown+ & qdecideEnemyTile grid playerPos+ & qinsert (Rel OnTile)+ & query_ - when finished $ do- diff <- decideEnemyDir enemyPos pos+qdecideEnemyTile :: Grid -> Pos -> Query System (From Pos) -> Query System Entity+qdecideEnemyTile grid playerPos x =+ x+ & qtraverse (\_ pos -> (,pos) <$> decideEnemyDir pos.comp playerPos)+ & qmapMaybe (\(diff, pos) -> moveBy diff pos.comp grid) - newTile <- moveBy diff enemyTile- for_ newTile $ \t -> do- insert (Rel OnTile t) enemy+data Cooldown = Cooldown {timer :: Timer} deriving (Component) -tileHas :: forall c. (QueryType c) => Entity -> System Bool-tileHas tile = not . null <$> [q|Entity / With (c, OnTile -> tile)|]+instance Default Cooldown where+ def = Cooldown $ Timer.new 1 Timer.Repeat -tileAtPosIsFree :: (Int, Int) -> System Bool-tileAtPosIsFree pos = do- tile <- getTile pos- maybe (pure False) tileIsFree tile+qfilterCooldown :: Query System a -> Query System a+qfilterCooldown x = do+ x+ & qextend [qd|Cooldown|] (,)+ & qfilterM+ ( \entity (_, Cooldown timer) -> do+ delta <- Time.delta+ let (timer', justFinished) = Timer.tick delta timer+ insert (Cooldown timer') entity+ pure justFinished+ )+ & qmap fst tileIsFree :: Entity -> System Bool tileIsFree tile = do@@ -270,25 +285,43 @@ player <- tileHas @Player tile pure $ not (wall || enemy || player) +tileAtPosIsFree :: (Int, Int) -> System Bool+tileAtPosIsFree pos = do+ Just grid <- res @Grid+ maybe (pure False) tileIsFree (getTile pos grid)+ data Health = Health {hp :: Int} deriving (Component) instance Default Health where def = Health 100 +showHealth :: System String+showHealth = do+ Just health <- single [q|Health / With Player|]+ pure $ "Health: " ++ show health.hp+ data Damage = Damage {amount :: Int} deriving (Event) onDamage :: Damage -> System () onDamage dmg = do- player <- [s|(Entity, Health) / With Player, Without Invincible|]-- for_ player $ \(entity, health) -> do- modify health $ \(Health x) -> Health $ max (x - dmg.amount) 0+ [q|Health / With Player, Without Invincible|]+ & qmodify (\(Health x) -> (Health $ max (x - dmg.amount) 0, Invincible))+ & qtap+ ( \e (Health hp, _) -> do+ delay 1 $ remove (C @Invincible) e+ when (hp == 0) $ liftIO exitSuccess+ )+ & query_ - insert Invincible entity- delay 1000000 $ remove (C @Invincible) entity+tryDamage :: System ()+tryDamage = do+ adjacentEnemies <-+ [q|OnTile -> (Pos) / With Player|]+ & qjoin (\pos -> [q|OnTile -> (Pos) / With Enemy|] & qfilter (\a -> isAdjacent a.comp pos.comp)) (,)+ & query - Just health <- update health- when (health.hp == 0) $ liftIO exitSuccess+ unless (null adjacentEnemies) $ do+ trigger $ Damage 5 isAdjacent :: Pos -> Pos -> Bool isAdjacent (Pos (x1, y1)) (Pos (x2, y2)) =@@ -296,18 +329,9 @@ dy = abs (y1 - y2) in (dx == 1 && dy == 0) || (dx == 0 && dy == 1) -tryDamage :: System ()-tryDamage = do- Just player <- [s|OnTile -> (*Pos) / With Player|]- enemies <- [q|OnTile -> (*Pos) / With Enemy|]-- for_ enemies $ \pos -> do- when (isAdjacent pos player) $ do- trigger (Damage 5)- data Invincible = Invincible deriving (Component) -data Coin = Coin deriving (Component)+data Coin = Coin Int deriving (Component) spawnCoin :: System () spawnCoin = do@@ -315,18 +339,24 @@ free <- tileIsFree tile if free then- void $ spawn (Coin, Rel OnTile tile)+ void $ spawn (Coin 5, Rel OnTile tile) else spawnCoin -data Coins = Coins Int deriving (Component)+data Coins = Coins Int deriving (Component, Generic, Default) +showCoins :: System String+showCoins = do+ Just (Coins c) <- single $ [q|Coins|]+ pure $ "Coins: " ++ show c+ collectCoins :: System () collectCoins = do- Just playerTile <- [s|OnTile -> (Entity) / With Player|]- coins <- [q|Entity / With OnTile -> playerTile, With Coin|]-- Just (Coins c) <- res @Coins- insertRes $ Coins $ c + length coins+ players <- query [q|Entity, OnTile -> (Entity), Coins / With Player|]+ for_ players $ \(player, From _ tile, Coins coins) -> do+ collected <-+ [q|Coin / With OnTile -> tile|]+ & qtap (\e _ -> despawn e)+ & query - for_ coins despawn+ insert (Coins $ foldr (\(Coin x) -> (+ x)) coins collected) player
+ examples/Startup.hs view
@@ -0,0 +1,67 @@+module Main where++import Mischief.ECS++main :: IO ()+main = do+ app <- newApp+ addPlugin @MyPlugin app+ runApp app++data MyPlugin++instance Plugin MyPlugin where+ init = do+ systems (helloWorld, greetPeople, showLikes)+ & schedule @Update++ systems addPeople+ & schedule @Startup++ systems updateFlo+ & before (greetPeople, showLikes)+ & schedule @Update++ insertRes (Greeting "Hey")++helloWorld :: System ()+helloWorld = info "Hello World!"++data Person = Person deriving (Component)++addPeople :: System ()+addPeople = do+ kim <- spawn (Person, Name "Kimberly")+ nick <- spawn (Person, Name "Nicholas")+ flo <- spawn (Person, Name "Florian")++ insert (Rel Likes kim) flo+ insert (Rel Likes nick, Rel Likes flo) kim++greetPeople :: System ()+greetPeople = do+ Just greeting <- res @Greeting++ [q|Name / With Person|]+ & qinfo (\name -> [i|#{greeting} #{name}!|])+ & query_++updateFlo :: System ()+updateFlo = do+ [q|Name / With Person|]+ & qfilter (== Name "Florian")+ & qinsert (\_ -> Name "Florianne")+ & query_++data Greeting = Greeting String deriving (Component)++instance Show Greeting where+ show (Greeting a) = a++data Likes = Likes deriving (Component)++showLikes :: System ()+showLikes = do+ [q|Name, Likes -> (Name)|]+ & qinfo (\(name, likes) -> [i|#{name} likes #{likes}|])+ & query_
mischief-ecs.cabal view
@@ -18,16 +18,13 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.1.0.0+version: 0.2.0.0 -- A short (one-line) description of the package.-synopsis: An opinionated archetype ECS engine built in Haskell.+synopsis: An archetype ECS engine built in Haskell.+homepage: https://codeberg.org/mischief/mischief -- A longer description of the package. description:- The ECS at the core of the Mischief game engine. It is mainly focused on ergonomics and- accessibility. Check the "Learn You an ECS for Great Mischief" book in the haddock documentation- to learn about using it.-- The high-level API is quite opinionated compared to other Haskell libraries.+ The ECS at the core of the Mischief game engine. category: Game Engine, ECS, Game, Control, Data, Framework -- The license under which the package is released.@@ -42,7 +39,9 @@ -- copyright: build-type: Simple -- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.-extra-doc-files: CHANGELOG.md+extra-doc-files:+ CHANGELOG.md+ README.md -- Extra source files to be distributed with the package, such as examples, or a tutorial module. -- extra-source-files:@@ -60,7 +59,6 @@ Mischief.ECS.App Mischief.ECS.App.Plugins Mischief.ECS.App.Schedules- Mischief.ECS.App.SystemConfig Mischief.ECS.App.SystemDef Mischief.ECS.App.Systems Mischief.ECS.Archetypes@@ -72,26 +70,24 @@ Mischief.ECS.Components.Common Mischief.ECS.Components.HooksDef Mischief.ECS.Components.Required- Mischief.ECS.Components.Runnable Mischief.ECS.Components.Spawn Mischief.ECS.Entities Mischief.ECS.EntityDef Mischief.ECS.EventDef Mischief.ECS.Events- Mischief.ECS.Graph+ Mischief.ECS.Exports+ Mischief.ECS.Fixed+ Mischief.ECS.Foreign Mischief.ECS.Hooks- Mischief.ECS.Interval+ Mischief.ECS.HooksRel Mischief.ECS.Log- Mischief.ECS.Mappable Mischief.ECS.Messages Mischief.ECS.Observer- Mischief.ECS.Observers- Mischief.ECS.Prelude- Mischief.ECS.Relationships Mischief.ECS.Relationships.ChildOf Mischief.ECS.Relationships.Graph Mischief.ECS.Relationships.Order Mischief.ECS.Relationships.Tree+ Mischief.ECS.Repeater Mischief.ECS.Resources Mischief.ECS.Schedules Mischief.ECS.Stdin@@ -100,35 +96,37 @@ Mischief.ECS.Tables Mischief.ECS.Time Mischief.ECS.Timer- Mischief.ECS.Tutorial.App- Mischief.ECS.Tutorial.Components- Mischief.ECS.Tutorial.Dungeon- Mischief.ECS.Tutorial.Events- Mischief.ECS.Tutorial.Queries- Mischief.ECS.Tutorial.Relationships- Mischief.ECS.Tutorial.Startup- Mischief.ECS.Tutorial.Systems+ Mischief.ECS.Tutorial.Guides.Basics+ Mischief.ECS.Tutorial.Guides.Change+ Mischief.ECS.Tutorial.Guides.Events+ Mischief.ECS.Tutorial.Guides.MonadicQueries+ Mischief.ECS.Tutorial.Guides.Organizing+ Mischief.ECS.Tutorial.Guides.Parallelism+ Mischief.ECS.Tutorial.Guides.QuasiConversion+ Mischief.ECS.Tutorial.Guides.QueryBasics+ Mischief.ECS.Tutorial.Guides.QueryPiping+ Mischief.ECS.Tutorial.Guides.Scheduling+ Mischief.ECS.Tutorial.Guides.Time+ Mischief.ECS.Tutorial.Introduction.Dungeon+ Mischief.ECS.Tutorial.Introduction.Startup Mischief.ECS.Utils Mischief.ECS.Vec Mischief.ECS.World Mischief.ECS.World.Change Mischief.ECS.World.Defer Mischief.ECS.World.Insert- Mischief.ECS.World.Modify Mischief.ECS.World.Par- Mischief.ECS.World.Prefs Mischief.ECS.World.Query Mischief.ECS.World.Query.Markers+ Mischief.ECS.World.Query.Pipe Mischief.ECS.World.Query.Queryable Mischief.ECS.World.Query.QueryFilter- Mischief.ECS.World.Query.QueryType Mischief.ECS.World.Query.TH Mischief.ECS.World.Query.TH.Common Mischief.ECS.World.Query.TH.QD Mischief.ECS.World.Query.TH.QF Mischief.ECS.World.Remove Mischief.ECS.World.Spawn- Mischief.ECS.World.Systems Mischief.ECS.World.Utils -- Modules included in this library but not exported.@@ -162,23 +160,21 @@ build-depends: ansi-terminal >=1.1.5 && <1.2, async >=2.2.6 && <2.3,- base ^>=4.21.0.0,- binary >=0.8.9 && <0.9,- bytestring >=0.12.2 && <0.13,+ base >=4.21.0.0 && <4.22, clock >=0.8.4 && <0.9, co-log >=0.7.0 && <0.8, containers >=0.7 && <0.8, data-default >=0.8.0 && <0.9,- hashable >=1.5.1 && <1.6,+ exceptions >=0.10.9 && <0.11, hashtables >=1.4.2 && <1.5, haskell-src-meta >=0.8.16 && <0.9, megaparsec >=9.8.1 && <9.9, mtl >=2.3.1 && <2.4, primitive >=0.9.1 && <0.10, stm >=2.5.3 && <2.6,+ string-interpolate >=1.0.0 && <1.1, template-haskell >=2.23.0 && <2.24, text >=2.1.2 && <2.2,- time >=1.14 && <1.15, transformers >=0.6.1 && <0.7, vector >=0.13.2 && <0.14, @@ -219,27 +215,34 @@ hs-source-dirs: test -- The entrypoint to the test suite. main-is: Main.hs- other-modules: Relationships+ other-modules:+ Changes+ Relationships+ Utils+ -- Test dependencies. build-depends:- base ^>=4.21.0.0,+ base >=4.21.0.0 && <4.22, containers, data-default, mischief-ecs, primitive,- random,+ random >=1.3.1 && <1.4,+ text, transformers, executable example-dungeon main-is: Dungeon.hs hs-source-dirs: examples build-depends:- base ^>=4.21.0.0,- containers >=0.7 && <0.8,- data-default >=0.8.0 && <0.9,- primitive >=0.9.1 && <0.10,+ base >=4.21.0.0 && <4.22,+ containers,+ data-default,+ mischief-ecs,+ primitive, random >=1.3.1 && <1.4,- transformers >=0.6.1 && <0.7,+ text,+ transformers, default-language: GHC2024 default-extensions:@@ -254,19 +257,44 @@ TemplateHaskell TypeFamilyDependencies +executable example-startup+ main-is: Startup.hs+ hs-source-dirs: examples+ build-depends:+ base >=4.21.0.0 && <4.22,+ containers,+ mischief-ecs ==0.2.0.0,+ primitive,+ text,+ transformers,++ default-language: GHC2024+ default-extensions:+ DeriveAnyClass+ DuplicateRecordFields+ NoFieldSelectors+ NoGeneralizedNewtypeDeriving+ OverloadedRecordDot+ OverloadedStrings+ QuasiQuotes+ RequiredTypeArguments+ TemplateHaskell+ TypeFamilyDependencies+ benchmark bench-spawn type: exitcode-stdio-1.0 main-is: bench-spawn.hs hs-source-dirs: benchmarks default-language: GHC2024 build-depends:- base >=4.21.0 && <4.22,- containers >=0.7 && <0.8,+ base >=4.21.0.0 && <4.22,+ containers, criterion >=1.6.5 && <1.7, deepseq >=1.5.1 && <1.6,- primitive >=0.9.1 && <0.10,- time >=1.14 && <1.15,- transformers >=0.6.1 && <0.7,+ mischief-ecs,+ primitive,+ time,+ transformers, default-extensions: DeriveAnyClass@@ -286,13 +314,14 @@ hs-source-dirs: benchmarks default-language: GHC2024 build-depends:- base >=4.21.0 && <4.22,- containers >=0.7 && <0.8,+ base >=4.21.0.0 && <4.22,+ containers, criterion >=1.6.5 && <1.7, deepseq >=1.5.1 && <1.6,- primitive >=0.9.1 && <0.10,- time >=1.14 && <1.15,- transformers >=0.6.1 && <0.7,+ mischief-ecs,+ primitive,+ time,+ transformers, default-extensions: DeriveAnyClass@@ -312,13 +341,14 @@ hs-source-dirs: benchmarks default-language: GHC2024 build-depends:- base >=4.21.0 && <4.22,- containers >=0.7 && <0.8,+ base >=4.21.0.0 && <4.22,+ containers, criterion >=1.6.5 && <1.7, deepseq >=1.5.1 && <1.6,- primitive >=0.9.1 && <0.10,- time >=1.14 && <1.15,- transformers >=0.6.1 && <0.7,+ mischief-ecs,+ primitive,+ time,+ transformers, default-extensions: DeriveAnyClass
src/Mischief/ECS.hs view
@@ -1,3 +1,4 @@+{-# OPTIONS_GHC -Wno-unused-imports #-} {-# OPTIONS_GHC -Wno-unused-top-binds #-} -- |@@ -7,191 +8,108 @@ -- -- This library contains the ECS used by the Mischief Game Engine. module Mischief.ECS- ( -- * What is Mischief?- -- $mischief-- -- * Learn You an ECS for Great Mischief!+ ( -- * Learn You an ECS for Great Mischief! -- $tutorials- module Mischief.ECS.App,- module Mischief.ECS.App.Plugins,- module Mischief.ECS.App.Schedules,- module Mischief.ECS.App.SystemConfig,- module Mischief.ECS.App.Systems,- module Mischief.ECS.Archetypes,- module Mischief.ECS.Archetypes.Graph,- module Mischief.ECS.Collectable,+ module Mischief.ECS.Exports, module Mischief.ECS.Components,- module Mischief.ECS.Components.Bundle,- module Mischief.ECS.Components.BundleTypes,- module Mischief.ECS.Components.Common, module Mischief.ECS.Components.Required,- module Mischief.ECS.Components.Runnable,- module Mischief.ECS.Components.Spawn,+ module Mischief.ECS.Components.Common, module Mischief.ECS.Entities,- module Mischief.ECS.Events,- module Mischief.ECS.Hooks,- module Mischief.ECS.Log,- module Mischief.ECS.Mappable,- module Mischief.ECS.Messages,- module Mischief.ECS.Prelude,- module Mischief.ECS.Relationships,- module Mischief.ECS.Relationships.ChildOf,- module Mischief.ECS.Relationships.Graph,- module Mischief.ECS.Relationships.Order,- module Mischief.ECS.Relationships.Tree,- module Mischief.ECS.Tables,- module Mischief.ECS.Time,- module Mischief.ECS.Utils,- module Mischief.ECS.World,- module Mischief.ECS.World.Change,- module Mischief.ECS.World.Defer,- module Mischief.ECS.World.Insert,- module Mischief.ECS.World.Modify,- module Mischief.ECS.World.Par,- module Mischief.ECS.World.Prefs,- module Mischief.ECS.Resources, module Mischief.ECS.World.Query,- module Mischief.ECS.World.Query.Queryable, module Mischief.ECS.World.Query.QueryFilter, module Mischief.ECS.World.Query.Markers,- module Mischief.ECS.EventDef, module Mischief.ECS.World.Query.TH,+ module Mischief.ECS.World.Insert, module Mischief.ECS.World.Remove, module Mischief.ECS.World.Spawn,- module Mischief.ECS.World.Utils,- TestTest (..),+ module Mischief.ECS.Resources,+ module Mischief.ECS.EventDef,+ module Mischief.ECS.World,+ module Mischief.ECS.Time,+ module Mischief.ECS.World.Defer,+ module Mischief.ECS.App,+ module Mischief.ECS.App.Plugins,+ module Mischief.ECS.App.Schedules,+ module Mischief.ECS.Systems,+ module Mischief.ECS.Log,+ module Mischief.ECS.Utils,+ module Mischief.ECS.Events,+ module Mischief.ECS.Components.HooksDef,+ module Mischief.ECS.Hooks,+ module Mischief.ECS.Components.Bundle,+ module Mischief.ECS.Timer,+ module Mischief.ECS.World.Query.Pipe,+ module Mischief.ECS.Observer,+ module Mischief.ECS.Fixed,+ module Data.Function,+ module Mischief.ECS.Relationships.ChildOf,+ module Mischief.ECS.World.Query.Queryable,+ module Mischief.ECS.Relationships.Order,+ module Mischief.ECS.Repeater,+ module Mischief.ECS.Components.Spawn, ) where -import Control.Monad (void)-import Control.Monad.IO.Class (MonadIO (liftIO))-import Data.Foldable (for_)-import Data.Text (Text)-import Language.Haskell.TH-import Language.Haskell.TH.Syntax-import Mischief.ECS.App-import Mischief.ECS.App.Plugins-import Mischief.ECS.App.Schedules-import Mischief.ECS.App.SystemConfig-import Mischief.ECS.App.Systems-import Mischief.ECS.Archetypes-import Mischief.ECS.Archetypes.Graph-import Mischief.ECS.Collectable (Collectable, EraseIntoStorage (..), collect)-import Mischief.ECS.Components-import Mischief.ECS.Components.Bundle-import Mischief.ECS.Components.BundleTypes-import Mischief.ECS.Components.Common-import Mischief.ECS.Components.Required-import Mischief.ECS.Components.Runnable-import Mischief.ECS.Components.Spawn-import Mischief.ECS.Entities-import Mischief.ECS.EventDef-import Mischief.ECS.Events-import Mischief.ECS.Graph-import Mischief.ECS.Hooks+import Data.Function ((&))+import Mischief.ECS.App (App, addPlugin, newApp, runApp)+import Mischief.ECS.App.Plugins (Dependency, Plugin (..), dep)+import Mischief.ECS.App.Schedules (PostStartup, PostUpdate, PreStartup, PreUpdate, Schedule, Startup, StartupSchedule (..), Update, UpdateSchedule (..), scheduleEntity)+import Mischief.ECS.Components (Component (..), From (..), Rel (..), Res (..), Tick (..))+import Mischief.ECS.Components.Bundle (Bundle, BundleBuilder, BundleEq, BundleEqBuilder, bundle, bundleEq)+import Mischief.ECS.Components.Common (Name (..))+import Mischief.ECS.Components.HooksDef (Hook, HookContext (..), HookContextRel (..), HookRel)+import Mischief.ECS.Components.Required (require)+import Mischief.ECS.Components.Spawn (meta)+import Mischief.ECS.Entities (Entity)+import Mischief.ECS.EventDef (Event)+import Mischief.ECS.Events (OnAdd (..), OnAddRel (..), OnRemove (..), OnRemoveRel (..), OnSet (..), OnSetRel (..), trigger)+import Mischief.ECS.Exports+import Mischief.ECS.Fixed (FixedFrequency (..), FixedPlugin, FixedPostUpdate, FixedPreUpdate, FixedUpdate)+import Mischief.ECS.Hooks (hook, hookRel) import Mischief.ECS.Log-import Mischief.ECS.Mappable-import Mischief.ECS.Messages (Message)-import Mischief.ECS.Prelude-import Mischief.ECS.Relationships-import Mischief.ECS.Relationships.ChildOf-import Mischief.ECS.Relationships.Graph-import Mischief.ECS.Relationships.Order-import Mischief.ECS.Relationships.Tree-import Mischief.ECS.Resources-import Mischief.ECS.Systems-import Mischief.ECS.Tables-import Mischief.ECS.Time-import Mischief.ECS.Utils-import Mischief.ECS.Vec-import Mischief.ECS.World-import Mischief.ECS.World.Change-import Mischief.ECS.World.Defer-import Mischief.ECS.World.Insert-import Mischief.ECS.World.Modify-import Mischief.ECS.World.Par-import Mischief.ECS.World.Prefs-import Mischief.ECS.World.Query-import Mischief.ECS.World.Query.Markers-import Mischief.ECS.World.Query.QueryFilter-import Mischief.ECS.World.Query.Queryable-import Mischief.ECS.World.Query.TH-import Mischief.ECS.World.Remove-import Mischief.ECS.World.Spawn-import Mischief.ECS.World.Systems-import Mischief.ECS.World.Utils--data TestTest = TestTest---- x = quoteD---- x = $quoteE--qq :: System ()-qq = do- -- x <- $(quoteC ''ChildOf)- undefined---- $pre--- This version of Mischief isn't meant for public use. It's just a pre-release published mostly to showcase the documentation.+import Mischief.ECS.Observer (Observer (..))+import Mischief.ECS.Relationships.ChildOf (ChildOf (..))+import Mischief.ECS.Relationships.Order (Before (..))+import Mischief.ECS.Repeater+import Mischief.ECS.Resources (insertRes, res, resOrInsert, resOrInsertM)+import Mischief.ECS.Systems (after, before, order, schedule, systems, unschedule, unschedule')+import Mischief.ECS.Time (Time, TimePlugin)+import Mischief.ECS.Timer (Timer)+import Mischief.ECS.Utils (expect, mapLeft, unwrap)+import Mischief.ECS.World (MonadSystem, ParSystem, System, runWhen)+import Mischief.ECS.World.Defer (defer, delay, flush, runAfter)+import Mischief.ECS.World.Insert (insert, insertIfNeq, insertNew)+import Mischief.ECS.World.Par (par, par', parFor, parFor', parFor'_, parFor_)+import Mischief.ECS.World.Query (Query, check, mkGet, mkGet', mkQuery, mkQuery', query, queryOne, query_, runWhenOne, runWhenQ, single)+import Mischief.ECS.World.Query.Markers (Any (..), C (..), E (..), Has (..), HasR (..), M (..), MR (..), Q (..), Q' (..), R (..), R' (..))+import Mischief.ECS.World.Query.Pipe+import Mischief.ECS.World.Query.QueryFilter (QueryFilter (..))+import Mischief.ECS.World.Query.Queryable (Queryable)+import Mischief.ECS.World.Query.TH (q, qd, qf)+import Mischief.ECS.World.Remove (remove)+import Mischief.ECS.World.Spawn (despawn, spawn, spawnDefer, spawn_) --- $mischief--- Mischief has various meanings. It can be a group of rats. Or it can refer to being naughty and playful.------ This Mischief, however, is an @ECS Game Engine@! In other words...------ @--- Mischief :: Entity -> Component c => System ()--- @------ Mischief takes great inspiration from [@Bevy@](https://bevy.org/) and [@Flecs@](https://www.flecs.dev/flecs/), and--- was written in @100% Haskell@, taking advantage of its great ergonomics and strong type system.--- It is a refreshing spin on the functional and data-driven paradigms.------ == Package------ This package contains only @mischief-ecs@, the ECS at the core of Mischief. Other packages, such as @mischief-input@, @mischief-assets@,--- @mischief-render@ are planned, but haven't yet been developed to the same level as the ECS itself.------ == Design Goals------ These are the main design goals of Mischief:------ * __Ergonomic and Accessible__. Mischief's main purpose is to provide a very clean, intuitive, inherently opinionated, high-level API. This is the main way we differentiate ourselves from other ECS's.--- Learning to use Mischief should be a painless and intuitive process.--- * __Functional and Data Driven__. We strive to find a balance between functional programming and data-driven design. These are two concepts that don't--- interact very often, but that both stand at the core of Msichief.--- * __Modular__. Mischief is meant to be modular, allowing you to plug packages in and out, whether they are made by us or a third party.------ == Highlights------ Here are some highlights of Mischief:+-- $tutorials+-- The official Mischief book. ----- * __Highly Dynamic__ . Want to disable a system? Despawn it!--- * __Fancy Queries__. Complete with transitive queries and filters.--- * __Quasi-Queries__. Scripting language for writing queries.--- * __Rich Systems__. Monadic Systems? Yes please.--- * __Book__. Mischief has its very own book! See below.--- * __Relationships__. Bob Likes Charlie.--- * __Events__.--- * And much much more..+-- You can read chapters and subchapters in any order.+-- The @Startup Guide@ (1.1) is heavily recommended if this is your first time using Mischief. --+-- * 1. Introduction ----- == Performance+-- * 1.1 [Startup Guide]("Mischief.ECS.Tutorial.Introduction.Startup")+-- * 1.2 [Coding a Dungeon Game]("Mischief.ECS.Tutorial.Introduction.Dungeon") ----- Performance-wise, Mischief still has a long way to go; there are many easy performance gains that we have just been too busy to implement, as we've--- been mostly focusing on ergonomics, modularity, and ease-of-use.--- This is a pretty strong @Archetype ECS@ however, and it is possible to bring it to about the same /asymptotic/ performance as Bevy or Flecs, although--- there will probably always be a layer of indirection (or 20) that makes it a bit slower, due to the high-level, boxed, nature of Haskell.---- $tutorials--- In order to learn how to use Mischief, you can read through the official book:+-- * 2. Guides ----- (1) [Startup Guide]("Mischief.ECS.Tutorial.Startup")--- (2) [Coding a Dungeon Game]("Mischief.ECS.Tutorial.Dungeon")--- (3) [App and Plugins]("Mischief.ECS.Tutorial.App")--- (4) [Components]("Mischief.ECS.Tutorial.Components")--- (5) [Relationships]("Mischief.ECS.Tutorial.Relationships")--- (6) [Queries]("Mischief.ECS.Tutorial.Queries")--- (7) [Systems]("Mischief.ECS.Tutorial.Systems")--- (8) [Events and Messages]("Mischief.ECS.Tutorial.Events")+-- * 2.1 [Organizing your Project]("Mischief.ECS.Tutorial.Guides.Organizing")+-- * 2.2 [Entities and Components Basics]("Mischief.ECS.Tutorial.Guides.Basics")+-- * 2.3 [Query Basics]("Mischief.ECS.Tutorial.Guides.QueryBasics")+-- * 2.4 [Query Piping]("Mischief.ECS.Tutorial.Guides.QueryPiping")+-- * 2.5 [Quasi-Query Conversion]("Mischief.ECS.Tutorial.Guides.QuasiConversion")+-- * 2.6 [Monadic Queries]("Mischief.ECS.Tutorial.Guides.MonadicQueries")+-- * 2.7 [Scheduling and Running Systems]("Mischief.ECS.Tutorial.Guides.Scheduling")+-- * 2.8 [Keeping Track of Time]("Mischief.ECS.Tutorial.Guides.Time")+-- * 2.9 [Parallelism and Asynchronicity]("Mischief.ECS.Tutorial.Guides.Parallelism")+-- * 2.10 [Events and Messages]("Mischief.ECS.Tutorial.Guides.Events")+-- * 2.11 [Change Detection]("Mischief.ECS.Tutorial.Guides.Change")
src/Mischief/ECS/App.hs view
@@ -5,135 +5,182 @@ -- import Data.Map -- import Data.Map qualified as Map -import Control.Monad (forever, void)+import Control.Monad (filterM, forever, unless, when) import Control.Monad.IO.Class (MonadIO (liftIO))-import Control.Monad.Reader (MonadReader (..), asks)-import Control.Monad.Trans.Reader (ReaderT (..))+import Control.Monad.Reader (MonadReader (..)) import Data.Data import Data.Default import Data.Foldable import Data.IORef import Mischief.ECS.App.Plugins import Mischief.ECS.App.Schedules-import Mischief.ECS.App.SystemConfig hiding (Before) import Mischief.ECS.App.SystemDef-import Mischief.ECS.App.Systems (ScheduledIn (ScheduledIn), SystemFunction (SystemFunction), Systems, systemEntity)+import Mischief.ECS.App.Systems (ScheduledIn, SystemFunction, Systems) import Mischief.ECS.App.Systems qualified as Systems import Mischief.ECS.Components import Mischief.ECS.Components.Bundle-import Mischief.ECS.Components.Runnable (Runnable, runFor)-import Mischief.ECS.Components.Spawn (getOrAddComponentId, meta)+import Mischief.ECS.Components.Spawn (getOrAddComponentId) import Mischief.ECS.Entities-import Mischief.ECS.Events import Mischief.ECS.Hidden import Mischief.ECS.Log-import Mischief.ECS.Mappable import Mischief.ECS.Relationships.Order import Mischief.ECS.Resources-import Mischief.ECS.Systems qualified as Systems-import Mischief.ECS.Tables import Mischief.ECS.World import Mischief.ECS.World.Defer import Mischief.ECS.World.Insert import Mischief.ECS.World.Query import Mischief.ECS.World.Query.Markers import Mischief.ECS.World.Query.QueryFilter-import Mischief.ECS.World.Query.QueryType-import Mischief.ECS.World.Query.Queryable import Mischief.ECS.World.Spawn +-- | The App is a wrapper around the World through which plugins can be added. data App = App { world :: World, systems :: Systems } -newApp :: (Plugin p) => p -> IO App-newApp plugin = do- world <- newWorld getTools- systems <- Systems.newSystems+-- | Create a new App.+newApp :: IO App+newApp = do+ !world <- newWorld getTools+ runSystem (spawnEntity (Entity (# 0##, 0## #)) ()) world - let app = App {world, systems}+ !systems <- Systems.newSystems + let !app = App {world, systems}+ runSystem appInit app.world- runSystem (Systems.add Init $ runPluginRec plugin) app.world+ pure app - return app+-- | Add a plugin to the App.+addPlugin :: forall p. (Plugin p) => App -> IO ()+addPlugin !app = runSystem (addPluginRec @p) app.world +-- | Run the App. runApp :: App -> IO ()-runApp app = flip runSystem app.world $ do- startups <- orderEntities =<< query' E (With (C @StartupSchedule))- updates <- orderEntities =<< query' E (With (C @UpdateSchedule))+runApp !app = flip runSystem app.world $ do+ !x <- scheduleEntity @Init+ liftIO $ runSchedules [x] + !startups <- orderEntities =<< query (mkQuery' E (With (C @StartupSchedule)))+ !updates <- orderEntities =<< query (mkQuery' E (With (C @UpdateSchedule)))+ liftIO $ runSchedules startups liftIO $ runSchedulesLoop updates where- runSchedulesLoop schedules = do+ runSchedulesLoop !schedules = do forever $ do runSchedules schedules modifyIORef' app.world.frame (\(Frame x) -> Frame $ x + 1) - runSchedules schedules =- for_ schedules $ \schedule -> do+ runSchedules !schedules =+ for_ schedules $ \(!schedule) -> do runSystem (runSchedule' schedule) app.world -runSchedule :: (Schedule sch) => sch -> System ()-runSchedule sch = scheduleEntity sch >>= runSchedule'+-- | Run all systems of a Schedule.+runSchedule :: forall sch. (Schedule sch) => System ()+runSchedule = scheduleEntity @sch >>= runSchedule' runSchedule' :: Entity -> System () runSchedule' schedule = do- world <- unsafeGetWorld- systems <- orderEntities =<< query' E (With (R @ScheduledIn schedule))+ !world <- unsafeGetWorld+ !systems <- orderEntities =<< query (mkQuery' E (With (R @ScheduledIn schedule))) - for_ systems $ \systemId -> do- Just (systemFunction, lastSystemTick) <- get (C @SystemFunction, C @SystemTick) systemId- currentSystemTick <- liftIO $ readIORef world.tick+ for_ systems $ \(!systemId) -> do+ Just (!systemFunction, !lastSystemTick) <- single $ mkGet systemId (C @SystemFunction, C @SystemTick)+ !currentSystemTick <- liftIO $ readIORef world.tick - set lastSystemTick (SystemTick currentSystemTick)+ insert (SystemTick currentSystemTick) systemId insert (LastSystemTick lastSystemTick.inner) systemId Control.Monad.Reader.local (hide . setSystemId (SystemId systemId) . unhide) $ do systemFunction.inner flush flushAsync- flushEvents+ tryRun tick + when (null systems) $ do+ flush+ flushAsync+ tryRun+ tick++simulateSystem :: System () -> System ()+simulateSystem s = do+ e <- spawn (SystemTick (Tick 0), LastSystemTick (Tick 0))+ Control.Monad.Reader.local (hide . setSystemId (SystemId e) . unhide) s++setSystemTicks :: Tick -> Tick -> System ()+setSystemTicks a b = do+ world <- unsafeGetWorld+ let SystemId id = world.systemId+ insert (LastSystemTick a, SystemTick b) id++tryRun :: System ()+tryRun = do+ World {trySystems = !trySystems} <- unsafeGetWorld+ newSystems <- liftIO $ newIORef []+ !trySystems' <- liftIO $ readIORef trySystems+ liftIO $ writeIORef trySystems []++ !trySystems'' <-+ filterM+ ( \(!s1, !s2) -> do+ !b <- s1+ case b of+ Nothing -> pure True+ Just x -> do+ s2 x++ World {trySystems = !trySystemsNew} <- unsafeGetWorld+ t <- liftIO $ readIORef trySystemsNew+ liftIO $ modifyIORef' newSystems (++ t)++ pure False+ )+ trySystems'+ t <- liftIO $ readIORef newSystems+ liftIO $ writeIORef trySystems (t ++ trySystems'')+ unless (null t) tryRun+ appInit :: System () appInit = do insertRes $ def @Schedules - systems <- liftIO Systems.newSystems+ !systems <- liftIO Systems.newSystems insertRes systems - init <- scheduleEntity Init- pre <- scheduleEntity PreStartup- startup <- scheduleEntity Startup- post <- scheduleEntity PostStartup+ !pre <- scheduleEntity @PreStartup+ !startup <- scheduleEntity @Startup+ !post <- scheduleEntity @PostStartup - for_ [init, pre, startup, post] $ insert StartupSchedule+ for_ [pre, startup, post] $ insert StartupSchedule - insert (Rel Before pre) init insert (Rel Before startup) pre insert (Rel Before post) startup - first <- scheduleEntity First- pre <- scheduleEntity PreUpdate- update <- scheduleEntity Update- post <- scheduleEntity PostUpdate+ !first <- scheduleEntity @First+ !pre <- scheduleEntity @PreUpdate+ !update <- scheduleEntity @Update+ !post <- scheduleEntity @PostUpdate+ !last <- scheduleEntity @Last - for_ [first, pre, update, post] $ insert UpdateSchedule+ for_ [first, pre, update, post, last] $ insert UpdateSchedule insert (Rel Before pre) first insert (Rel Before update) pre insert (Rel Before post) update+ insert (Rel Before last) post -register :: forall c. (Runnable c) => System ()-register = runFor @c registerComponent+-- | Register a Component. This is done automatically when inserting it for the first time.+register :: forall c. (Component c) => System ()+register = registerComponent $ Proxy @c registerComponent :: forall c. (Component c) => Proxy c -> System ()-registerComponent c = do+registerComponent !c = do _ <- getOrAddComponentId (ComponentType c)- return ()+ pure () getTools :: SystemTools getTools =@@ -144,30 +191,39 @@ spawnByInsert = toolsSpawnByInsert } -toolsGet :: forall c m w. (MonadSystem w m, QueryType c) => Proxy c -> Entity -> m (Maybe c)-toolsGet _ = get (Val (C @c))+toolsGet :: forall c m w. (MonadSystem w m, Component c) => Proxy c -> Entity -> m (Maybe c)+toolsGet _ !e = single $ mkGet e (C @c) toolsSet :: forall c. (Bundle c) => c -> Entity -> System () toolsSet = insert -toolsGetRAny :: forall c m w. (Component c, MonadSystem w m, RelExclusivity c ~ Inclusive) => Proxy c -> Entity -> m (Maybe [Rel c])-toolsGetRAny _ = get (Val (R @c Any))+toolsGetRAny :: forall c m w. (Component c, MonadSystem w m, IsExclusiveRel c ~ False) => Proxy c -> Entity -> m (Maybe [Rel c])+toolsGetRAny _ !e = single $ mkGet e (R @c Any) toolsSpawnByInsert :: forall b. (Bundle b) => Entity -> b -> System () toolsSpawnByInsert = spawnEntityByInsert incTick :: Tick -> Maybe Tick-incTick (Tick (a, b)) | a == maxBound && b == maxBound = Nothing-incTick (Tick (a, b)) | b == maxBound = Just $ Tick (a + 1, 0)-incTick (Tick (a, b)) = Just $ Tick (a, b + 1)+incTick (Tick !a) | a == maxBound = Nothing+incTick (Tick !a) = Just $ Tick (a + 1) -- | Increment the World's Tick. tick :: System () tick = do- world <- unsafeGetWorld- tick <- liftIO $ incTick <$> readIORef world.tick+ !world <- unsafeGetWorld+ !tick <- liftIO $ incTick <$> readIORef world.tick case tick of Nothing -> do warn "Reached maximum Tick. Resetting count. Previous changed will not be detected."- liftIO $ writeIORef world.tick $ Tick (0, 0)- Just t -> liftIO $ writeIORef world.tick t+ liftIO $ writeIORef world.tick $ Tick 0+ Just !t -> liftIO $ writeIORef world.tick t++getTick :: (MonadSystem w m) => m Tick+getTick = do+ world <- unsafeGetWorld+ liftIO $ readIORef world.tick++setTick :: Tick -> System ()+setTick tick = do+ world <- unsafeGetWorld+ liftIO $ writeIORef world.tick tick
src/Mischief/ECS/App/Plugins.hs view
@@ -1,56 +1,58 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+ {- HLINT ignore "Use newtype instead of data" -}-module Mischief.ECS.App.Plugins where+module Mischief.ECS.App.Plugins (Plugin (..), Dependency, dep, addPluginRec) where +import Control.Monad import Data.Foldable-import Data.Map (Map)-import Data.Map qualified as Map+import Data.Kind+import Data.Set (Set)+import Data.Set qualified as Set import Data.Typeable-import Mischief.ECS.Collectable-import Mischief.ECS.Components.Bundle+import Mischief.ECS.Components import Mischief.ECS.Components.Common import Mischief.ECS.World-import Unsafe.Coerce+import Mischief.ECS.World.Query+import Mischief.ECS.World.Query.Markers+import Mischief.ECS.World.Query.QueryFilter+import Mischief.ECS.World.Spawn -class (Typeable p, Eq p) => Plugin p where- plugins :: p -> Plugins- plugins _ = collect ()+class (Typeable p) => Plugin (p :: Type) where+ deps :: [Dependency]+ deps = [] - init :: p -> System ()- init _ = pure ()+ init :: System ()+ init = pure () -data ErasedPlugin where- ErasedPlugin :: (Plugin p, Eq p) => p -> ErasedPlugin+data Dependency where+ Dependency :: (Plugin p) => Proxy p -> Dependency -getInit :: ErasedPlugin -> System ()-getInit (ErasedPlugin p) = Mischief.ECS.App.Plugins.init p+instance Eq Dependency where+ (Dependency !p) == (Dependency !p') = typeRep p == typeRep p' -newtype Plugins = Plugins {inner :: [ErasedPlugin]} deriving newtype (Semigroup, Monoid)+instance Ord Dependency where+ compare (Dependency !p) (Dependency !p') = compare (typeRep p) (typeRep p') -instance (Plugin p) => EraseIntoStorage p Plugins where- erase p = Plugins [ErasedPlugin p]+instance Show Dependency where+ show (Dependency !p) = show (typeRep p) -instance {-# OVERLAPPING #-} EraseIntoStorage () Plugins where- erase _ = Plugins []+dep :: forall p. (Plugin p) => Dependency+dep = Dependency (Proxy @p) -newtype PluginData = PluginData {inner :: Map TypeRep ErasedPlugin}+data PluginMarker p = PluginMarker deriving (Component) -instance Show PluginData where- show p = show $ map fst $ Map.toList p.inner+addPluginRec :: forall p. (Plugin p) => System ()+addPluginRec = addPluginRec' @p Set.empty -plug :: (Collectable p Plugins) => p -> Plugins-plug = collect+addPluginRec' :: forall p. (Plugin p) => Set Dependency -> System ()+addPluginRec' !set = do+ !x <- query $ mkQuery' E (With (C @(PluginMarker p))) -addErasedRec :: ErasedPlugin -> PluginData -> PluginData-addErasedRec (ErasedPlugin (plugin :: p)) d =- case Map.lookup (typeOf plugin) d.inner of- Nothing -> foldr addErasedRec (PluginData $ Map.insert (typeOf plugin) (ErasedPlugin plugin) d.inner) (plugins plugin).inner- Just other ->- if plugin == unsafeCoerce other- then d- else undefined+ when (null x) $ do+ when (Set.member (Dependency (Proxy @p)) set) $ error $ "Cyclic Plugin Dependency: " ++ show set -plugAll :: (Plugin p) => p -> PluginData-plugAll p = addErasedRec (ErasedPlugin p) (PluginData Map.empty)+ for_ (Mischief.ECS.App.Plugins.deps @p) $ \(Dependency (_ :: Proxy p')) -> do+ addPluginRec' @p' (Set.insert (Dependency (Proxy @p)) set) -runPluginRec :: (Plugin p) => p -> System ()-runPluginRec p = for_ (map snd $ Map.toList (plugAll p).inner) getInit+ void $ spawn (Name . show . typeRep $ Proxy @p, PluginMarker @p)+ Mischief.ECS.App.Plugins.init @p
src/Mischief/ECS/App/Schedules.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+ module Mischief.ECS.App.Schedules where import Data.Data@@ -7,11 +9,8 @@ import GHC.Generics import Mischief.ECS.Components import Mischief.ECS.Entities-import Mischief.ECS.Log import Mischief.ECS.Resources import Mischief.ECS.World-import Mischief.ECS.World.Modify-import Mischief.ECS.World.Query import Mischief.ECS.World.Spawn newtype ScheduleLabel = ScheduleLabel {rep :: TypeRep}@@ -20,26 +19,30 @@ class (Typeable s) => Schedule s -data Init = Init deriving (Schedule, Show)+data Init deriving (Schedule, Show) -data PreStartup = PreStartup deriving (Schedule)+data PreStartup deriving (Schedule) -data Startup = Startup deriving (Schedule, Show)+data Startup deriving (Schedule, Show) -data PostStartup = PostStartup deriving (Schedule)+data PostStartup deriving (Schedule) -data First = First deriving (Schedule, Show)+data First deriving (Schedule, Show) -data Update = Update deriving (Schedule, Show)+data Update deriving (Schedule, Show) -data PreUpdate = PreUpdate deriving (Schedule)+data PreUpdate deriving (Schedule) -data PostUpdate = PostUpdate deriving (Schedule)+data PostUpdate deriving (Schedule) +data Last deriving (Schedule)+ data StartupSchedule = StartupSchedule deriving (Component) data UpdateSchedule = UpdateSchedule deriving (Component) +data FixedSchedule = FixedSchedule deriving (Component)+ newtype ScheduleId = ScheduleId {id :: Entity} deriving (Ord, Eq, Show) newtype Schedules = Schedules {inner :: Map TypeRep ScheduleId}@@ -47,18 +50,16 @@ deriving anyclass (Component, Default) getScheduleId :: ScheduleLabel -> System ScheduleId-getScheduleId sch = do- Just schedules <- res @Schedules+getScheduleId !sch = do+ Just !schedules <- res @Schedules case Map.lookup sch.rep schedules.inner of- Just x -> return x+ Just !x -> return x Nothing -> do- e <- spawn sch+ !e <- spawn sch insertRes $ Schedules $ Map.insert sch.rep (ScheduleId e) schedules.inner return $ ScheduleId e -scheduleEntity :: (Schedule sch) => sch -> System Entity-scheduleEntity sch = do- ScheduleId id <- getScheduleId $ ScheduleLabel $ typeOf sch+scheduleEntity :: forall sch. (Schedule sch) => System Entity+scheduleEntity = do+ ScheduleId !id <- getScheduleId $ ScheduleLabel $ typeRep (Proxy @sch) return id---- runSystemsIn :: (Schedule sch) => sch -> System ()
− src/Mischief/ECS/App/SystemConfig.hs
@@ -1,42 +0,0 @@-module Mischief.ECS.App.SystemConfig where--import Mischief.ECS.World--data SystemConfigData = SystemConfigData- { systems :: [System ()],- edges :: [(System (), System ())]- }---- | First type will be a SystemConfig. Second type will be a System ().-data SystemConfigModifier s1 s2 = ModAfter s1 s2 | ModBefore s1 s2--after :: s1 -> System () -> SystemConfigModifier s1 (System ())-after = ModAfter--before :: s1 -> System () -> SystemConfigModifier s1 (System ())-before = ModBefore--class SystemConfig s where- systemConfigData :: s -> SystemConfigData--instance SystemConfig (System ()) where- systemConfigData :: System () -> SystemConfigData- systemConfigData system = SystemConfigData {systems = [system], edges = []}--instance (SystemConfig s1) => SystemConfig (SystemConfigModifier s1 (System ())) where- systemConfigData :: SystemConfigModifier s1 (System ()) -> SystemConfigData- systemConfigData (ModAfter s1 s2) =- let SystemConfigData {systems, edges} = systemConfigData s1- newEdges = [(s2, system) | system <- systems]- in SystemConfigData {systems, edges = newEdges ++ edges}- systemConfigData (ModBefore s1 s2) =- let SystemConfigData {systems, edges} = systemConfigData s1- newEdges = [(system, s2) | system <- systems]- in SystemConfigData {systems, edges = newEdges ++ edges}--instance (SystemConfig s0, SystemConfig s1) => SystemConfig (s0, s1) where- systemConfigData :: (s0, s1) -> SystemConfigData- systemConfigData (s0, s1) =- let SystemConfigData {systems = systems0, edges = edges0} = systemConfigData s0- SystemConfigData {systems = systems1, edges = edges1} = systemConfigData s1- in SystemConfigData {systems = systems0 ++ systems1, edges = edges0 ++ edges1}
src/Mischief/ECS/App/SystemDef.hs view
@@ -3,7 +3,6 @@ import Data.Default import GHC.Generics import Mischief.ECS.Components-import Mischief.ECS.World newtype SystemTick = SystemTick {inner :: Tick} deriving stock (Generic)
src/Mischief/ECS/App/Systems.hs view
@@ -3,31 +3,22 @@ module Mischief.ECS.App.Systems where import Control.Monad.IO.Class-import Control.Monad.Reader-import Data.Default import Data.Foldable import Data.IORef import Data.Map (Map) import Data.Map qualified as Map-import GHC.Generics import GHC.StableName (StableName, eqStableName, hashStableName, makeStableName) import GHC.Stack.Types import Mischief.ECS.App.Schedules import Mischief.ECS.App.SystemDef import Mischief.ECS.Components-import Mischief.ECS.Components.Bundle import Mischief.ECS.Components.Required import Mischief.ECS.Entities-import Mischief.ECS.Log-import Mischief.ECS.Relationships.Order import Mischief.ECS.Resources-import Mischief.ECS.Tables import Mischief.ECS.Utils import Mischief.ECS.World-import Mischief.ECS.World.Insert import Mischief.ECS.World.Query import Mischief.ECS.World.Query.Markers-import Mischief.ECS.World.Query.Queryable import Mischief.ECS.World.Spawn newtype Systems = Systems@@ -42,68 +33,68 @@ newSystems :: IO Systems newSystems = do- systemMap <- newIORef Map.empty- return Systems {systemMap}+ !systemMap <- newIORef Map.empty+ pure Systems {systemMap} getSystemId' :: (HasCallStack) => ScheduleId -> System () -> StableName (System ()) -> IORef [(StableName (System ()), SystemId)] -> System SystemId-getSystemId' schedule system stableName list = do- list' <- liftIO $ readIORef list+getSystemId' !schedule !system !stableName !list = do+ !list' <- liftIO $ readIORef list case find (\x -> fst x `eqStableName` stableName) list' of- Just (_, x) -> return x+ Just (_, !x) -> return x Nothing -> do- index <- spawn (SystemFunction system, Rel ScheduledIn schedule.id)+ !index <- spawn (SystemFunction system, Rel ScheduledIn schedule.id) liftIO $ modifyIORef' list (++ [(stableName, SystemId index)])- return $ SystemId index+ pure $ SystemId index getSystemId :: (HasCallStack) => ScheduleId -> System () -> System SystemId-getSystemId sch system = do+getSystemId !sch !system = do Systems {systemMap} <- unwrap <$> (res @Systems) - stableName <- liftIO $ makeStableName system- systemMap' <- liftIO $ readIORef systemMap+ !stableName <- liftIO $ makeStableName system+ !systemMap' <- liftIO $ readIORef systemMap case Map.lookup (sch, hashStableName stableName) systemMap' of- Just x -> getSystemId' sch system stableName x+ Just !x -> getSystemId' sch system stableName x Nothing -> do- l <- liftIO $ newIORef []+ !l <- liftIO $ newIORef [] liftIO $ modifyIORef' systemMap (Map.insert (sch, hashStableName stableName) l) getSystemId' sch system stableName l removeSystemFromMap' :: StableName (System ()) -> IORef [(StableName (System ()), SystemId)] -> System ()-removeSystemFromMap' stableName list = do+removeSystemFromMap' !stableName !list = do liftIO $ modifyIORef' list $ filter (\x -> not $ fst x `eqStableName` stableName) removeSystemFromMap :: ScheduleId -> System () -> System ()-removeSystemFromMap sch system = do+removeSystemFromMap !sch !system = do Systems {systemMap} <- unwrap <$> (res @Systems) - stableName <- liftIO $ makeStableName system- systemMap' <- liftIO $ readIORef systemMap+ !stableName <- liftIO $ makeStableName system+ !systemMap' <- liftIO $ readIORef systemMap forM_ (Map.lookup (sch, hashStableName stableName) systemMap') (removeSystemFromMap' stableName) -systemEntity :: (HasCallStack, Schedule sch) => sch -> System () -> System Entity-systemEntity sch s = do- schId <- scheduleEntity sch- x <- getSystemId (ScheduleId schId) s- return x.id+systemEntity :: forall sch. (HasCallStack, Schedule sch) => System () -> System Entity+systemEntity !s = do+ !schId <- scheduleEntity @sch+ !x <- getSystemId (ScheduleId schId) s+ pure x.id self :: forall m w. (MonadSystem w m) => m Entity self = do- world <- unsafeGetWorld- let (SystemId sys) = world.systemId- return sys+ !world <- unsafeGetWorld+ let (SystemId !sys) = world.systemId+ pure sys data ScheduledIn = ScheduledIn deriving (Component) getSystemTicks :: World -> IO (Tick, Tick)-getSystemTicks world = do- let (SystemId sys) = world.systemId+getSystemTicks !world = do+ let (SystemId !sys) = world.systemId runSystem ( do- Just (a, b) <- get (C @LastSystemTick, C @SystemTick) sys+ Just (!a, !b) <- single $ mkGet sys (C @LastSystemTick, C @SystemTick) return (a.inner, b.inner) ) world
src/Mischief/ECS/Archetypes.hs view
@@ -1,12 +1,8 @@ module Mischief.ECS.Archetypes where -import Control.Monad-import Data.Foldable import Data.IORef-import Data.List import Data.Map (Map) import Data.Map qualified as Map-import Data.Maybe import Data.Set (Set) import Data.Set qualified as Set import Mischief.ECS.Components@@ -19,14 +15,14 @@ newArchetypeGraph :: IO ArchetypeGraph newArchetypeGraph = do- nodes <- Vec.new 1024+ !nodes <- Vec.new 1024 -- Add the empty archetype to the graph. Vec.pushBack nodes ArchetypeNode {archetype = ArchetypeData {id = ArchetypeId 0, components = Set.empty}, insert = Map.empty, remove = Map.empty} - counter <- newIORef 1- lookup <- newIORef $ Map.singleton Set.empty 0- return $ ArchetypeGraph {nodes, lookup, counter}+ !counter <- newIORef 1+ !lookup <- newIORef $ Map.singleton Set.empty 0+ pure $ ArchetypeGraph {nodes, lookup, counter} data ArchetypeNode = ArchetypeNode { archetype :: ArchetypeData,
src/Mischief/ECS/Archetypes/Graph.hs view
@@ -2,13 +2,10 @@ import Control.Monad import Control.Monad.IO.Class-import Control.Monad.Primitive-import Control.Monad.Reader import Data.Data import Data.Foldable import Data.IORef import Data.List-import Data.Map (Map, mapMaybe) import Data.Map qualified as Map import Data.Maybe import Data.Set (Set)@@ -16,33 +13,25 @@ import GHC.Base (eqWord#, isTrue#) import Mischief.ECS.Archetypes import Mischief.ECS.Components-import Mischief.ECS.Components.Common import Mischief.ECS.Entities-import Mischief.ECS.EntityDef-import Mischief.ECS.Log-import Mischief.ECS.Relationships import Mischief.ECS.Tables-import Mischief.ECS.Utils-import Mischief.ECS.Vec (IOVec) import Mischief.ECS.Vec qualified as Vec import Mischief.ECS.World-import Mischief.ECS.World (SystemTools (get))-import Mischief.ECS.World.Query.Queryable data ArchetypeTransition = Inserted ComponentId | Removed ComponentId getNewId :: ArchetypeGraph -> IO Int-getNewId ArchetypeGraph {counter} = do- x <- readIORef counter+getNewId ArchetypeGraph {counter = !counter} = do+ !x <- readIORef counter modifyIORef' counter (+ 1)- return x+ pure x createNode :: Set ComponentId -> System Int-createNode components = do- world <- unsafeGetWorld- let Archetypes {graph} = world.archetypes+createNode !components = do+ !world <- unsafeGetWorld+ let Archetypes {graph = !graph} = world.archetypes - id <- liftIO $ getNewId graph+ !id <- liftIO $ getNewId graph liftIO $ modifyIORef' graph.lookup $ Map.insert components id Vec.pushBack graph.nodes ArchetypeNode {archetype = ArchetypeData {id = ArchetypeId id, components}, insert = Map.empty, remove = Map.empty} @@ -54,24 +43,29 @@ -- ) -- (Set.toList components) - -- debug $ "archetype " <> text id <> " = " <> text (catMaybes comps)+ -- debug $ "archetype " <> T.show id <> " = " <> T.show (catMaybes comps) <> " (length = " <> T.show (length $ Set.toList components) <> ")"+ -- debug $ T.show $ Set.toList components let Tables tables = world.tables Vec.pushBack tables =<< liftIO (newTable $ Set.toList components) - for_ components $ \(ComponentId (# id', target' #)) -> do+ for_ components $ \(ComponentId (# !id', !target' #)) -> do case target' of -- Component isn't a pair. Nothing -> do- set <- worldGet (Proxy @ComponentArchetypes) (Entity (# id', 0## #))+ !set <- worldGet (Proxy @ComponentArchetypes) (Entity (# id', 0## #))++ when (isNothing set) $ do+ worldSet (ComponentArchetypes {inner = Set.singleton (ArchetypeId id)}) (Entity (# id', 0## #))+ for_ set $ \set -> do worldSet (ComponentArchetypes {inner = Set.insert (ArchetypeId id) set.inner}) (Entity (# id', 0## #)) -- modify set $ \ComponentArchetypes {inner} -> ComponentArchetypes {inner = Set.insert (ArchetypeId id) inner} -- Component is a pair.- Just e -> do+ Just !e -> do set <- worldGet (Proxy @ComponentPairs) (Entity (# id', 0## #)) for_ set $ \set -> do- let ComponentPairs {any, pairs} = set+ let !ComponentPairs {any, pairs} = set flip worldSet (Entity (# id', 0## #)) $ ComponentPairs { any = Set.insert (ArchetypeId id) any,@@ -79,52 +73,53 @@ Map.alter ( \case Nothing -> Just $ Set.singleton $ ArchetypeId id- Just s -> Just $ Set.insert (ArchetypeId id) s+ Just !s -> Just $ Set.insert (ArchetypeId id) s ) e pairs } - return id+ pure id getOrCreateNode :: Set ComponentId -> System Int-getOrCreateNode components = do- world <- unsafeGetWorld- let Archetypes {graph} = world.archetypes+getOrCreateNode !components = do+ !world <- unsafeGetWorld+ let Archetypes {graph = !graph} = world.archetypes - lookup <- liftIO $ readIORef graph.lookup+ !lookup <- liftIO $ readIORef graph.lookup+ -- warn $ T.show lookup case Map.lookup components lookup of- Just x -> return x+ Just !x -> pure x Nothing -> createNode components addEdge :: Int -> Int -> ComponentId -> System ()-addEdge a b component = do- world <- unsafeGetWorld- let Archetypes {graph} = world.archetypes+addEdge !a !b !component = do+ !world <- unsafeGetWorld+ let Archetypes {graph = !graph} = world.archetypes Vec.modify_ graph.nodes a $ \ArchetypeNode {insert, remove, archetype} -> ArchetypeNode {insert = Map.insert component b insert, remove, archetype} Vec.modify_ graph.nodes b $ \ArchetypeNode {insert, remove, archetype} -> ArchetypeNode {insert, remove = Map.insert component a remove, archetype} addEdgeI :: Int -> Int -> ComponentId -> System ()-addEdgeI a b component = do- world <- unsafeGetWorld- let Archetypes {graph} = world.archetypes+addEdgeI !a !b !component = do+ !world <- unsafeGetWorld+ let Archetypes {graph = !graph} = world.archetypes Vec.modify_ graph.nodes a $ \ArchetypeNode {insert, remove, archetype} -> ArchetypeNode {insert = Map.insert component b insert, remove, archetype} addEdgeR :: Int -> Int -> ComponentId -> System ()-addEdgeR a b component = do- world <- unsafeGetWorld- let Archetypes {graph} = world.archetypes+addEdgeR !a !b !component = do+ !world <- unsafeGetWorld+ let Archetypes {graph = !graph} = world.archetypes Vec.modify_ graph.nodes b $ \ArchetypeNode {insert, remove, archetype} -> ArchetypeNode {insert, remove = Map.insert component a remove, archetype} getArchetypeOnRemoveSingle :: ArchetypeId -> ComponentId -> System ArchetypeData-getArchetypeOnRemoveSingle (ArchetypeId id) component = do- world <- unsafeGetWorld- let Archetypes {graph} = world.archetypes+getArchetypeOnRemoveSingle (ArchetypeId !id) !component = do+ !world <- unsafeGetWorld+ let Archetypes {graph = !graph} = world.archetypes - node <- Vec.read graph.nodes id+ !node <- Vec.read graph.nodes id case Map.lookup component node.remove of- Just x -> do+ Just !x -> do newNode <- Vec.read graph.nodes x return newNode.archetype Nothing -> do@@ -132,136 +127,134 @@ -- TODO: check if another component requires this one! let newComponents = Set.delete component components- newId <- getOrCreateNode newComponents+ !newId <- getOrCreateNode newComponents addEdgeR newId id component - newNode <- Vec.read graph.nodes newId- return newNode.archetype+ !newNode <- Vec.read graph.nodes newId+ pure newNode.archetype getArchetypeOnInsertSingle :: ArchetypeId -> ComponentId -> System ArchetypeData-getArchetypeOnInsertSingle (ArchetypeId id) component = do- world <- unsafeGetWorld- let Archetypes {graph} = world.archetypes+getArchetypeOnInsertSingle (ArchetypeId !id) !component = do+ !world <- unsafeGetWorld+ let Archetypes {graph = !graph} = world.archetypes - node <- Vec.read graph.nodes id+ !node <- Vec.read graph.nodes id case Map.lookup component node.insert of- Just x -> do- newNode <- Vec.read graph.nodes x- return newNode.archetype+ Just !x -> do+ !newNode <- Vec.read graph.nodes x+ pure newNode.archetype Nothing -> do components <- do let components = node.archetype.components-- let !(ComponentId (# id, _ #)) = component+ let !(ComponentId (# !id, _ #)) = component - isExclusiveRel <- isJust <$> worldGet (Proxy @IsExclusiveRelationship) (Entity (# id, 0## #))+ !isExclusiveRel <- isJust <$> worldGet (Proxy @IsExclusiveRelationship) (Entity (# id, 0## #)) case isExclusiveRel of True ->- return $ Set.filter (\(ComponentId (# id', a #)) -> not (isTrue# $ eqWord# id' id) || isNothing a) components+ pure $ Set.filter (\(ComponentId (# id', a #)) -> not (isTrue# $ eqWord# id' id) || isNothing a) components _ ->- return components+ pure components - requirements <- getRequirements component+ !requirements <- getRequirements component - let newComponents = Set.union (Set.insert component components) requirements+ let !newComponents = Set.union (Set.insert component components) requirements - newId <- getOrCreateNode newComponents+ !newId <- getOrCreateNode newComponents addEdgeI id newId component - newNode <- Vec.read graph.nodes newId- return newNode.archetype+ !newNode <- Vec.read graph.nodes newId+ pure newNode.archetype getArchetypeOnInsert :: ArchetypeId -> [ComponentId] -> System ArchetypeData-getArchetypeOnInsert archetype components =+getArchetypeOnInsert !archetype !components = do- world <- unsafeGetWorld- let Archetypes {graph} = world.archetypes- let d = ArchetypeData {id = archetype, components = Set.empty}+ !world <- unsafeGetWorld+ let Archetypes {graph = !graph} = world.archetypes+ let !d = ArchetypeData {id = archetype, components = Set.empty} f d components graph where- f archetype [] _ = return archetype- f archetype (component : xs) graph = do- x <- getArchetypeOnInsertSingle archetype.id component+ f !archetype [] _ = pure archetype+ f !archetype (!component : !xs) graph = do+ !x <- getArchetypeOnInsertSingle archetype.id component f x xs graph newtype ArchetypeRemovalResult = ArchetypeRemovalResult {removed :: [ComponentId]} getArchetypeOnRemove :: ArchetypeId -> [ComponentId] -> System (ArchetypeData, [ComponentId])-getArchetypeOnRemove archetype components =+getArchetypeOnRemove !archetype !components = do- world <- unsafeGetWorld- let Archetypes {graph} = world.archetypes- let d = ArchetypeData {id = archetype, components = Set.empty}+ !world <- unsafeGetWorld+ let Archetypes {graph = !graph} = world.archetypes+ let !d = ArchetypeData {id = archetype, components = Set.empty} f d components graph where- f archetype [] _ = return (archetype, [])- f archetype (component : xs) graph = do- x <- getArchetypeOnRemoveSingle archetype.id component+ f !archetype [] _ = pure (archetype, [])+ f !archetype (!component : !xs) !graph = do+ !x <- getArchetypeOnRemoveSingle archetype.id component - (a, b) <- f x xs graph+ (!a, !b) <- f x xs graph if x.id /= archetype.id- then return (a, b ++ [component])- else return (a, b)+ then pure (a, b ++ [component])+ else pure (a, b) getArchetypeOnSpawn :: [ComponentId] -> System ArchetypeData-getArchetypeOnSpawn components =+getArchetypeOnSpawn !components = do- world <- unsafeGetWorld- let Archetypes {graph} = world.archetypes+ !world <- unsafeGetWorld+ let Archetypes {graph = !graph} = world.archetypes - regs <- mapM getRequirements components+ !regs <- mapM getRequirements components - let allComps = foldr' (flip Set.union) (Set.fromList components) regs- node <- getOrCreateNode allComps+ let !allComps = foldr' (flip Set.union) (Set.fromList components) regs+ !node <- getOrCreateNode allComps - nodeData <- Vec.read graph.nodes node+ !nodeData <- Vec.read graph.nodes node - return nodeData.archetype+ pure nodeData.archetype getRequirements :: ComponentId -> System (Set ComponentId)-getRequirements (ComponentId (# id, _ #)) = do- x <- worldGetRAny (Proxy @Requires) (Entity (# id, 0## #))- return $ case x of+getRequirements (ComponentId (# !id, _ #)) = do+ !x <- worldGetRAny (Proxy @Requires) (Entity (# id, 0## #))+ pure $ case x of Nothing -> Set.empty- Just x -> Set.fromList $ map ((\(Entity (# id, _ #)) -> ComponentId (# id, Nothing #)) . (\x -> x.target)) x+ Just !x -> Set.fromList $ map ((\(Entity (# id, _ #)) -> ComponentId (# id, Nothing #)) . (\x -> x.target)) x data ComponentQuery = ComponentQuery | RelationshipQueryAny | RelationshipQuery -findMatchingArchetypes :: forall m w. (MonadSystem w m) => [(ComponentId, ComponentQuery)] -> Archetypes -> m [([ComponentId], ArchetypeId)]+findMatchingArchetypes :: forall m w. (MonadSystem w m) => [(ComponentId, ComponentQuery)] -> Archetypes -> m [ArchetypeId] findMatchingArchetypes [] _ = allArchetypes findMatchingArchetypes components Archetypes {graph} = do- archetypes'' <- forM components $ \(ComponentId (# id, target #), q) -> do+ !archetypes'' <- forM components $ \(ComponentId (# id, target #), q) -> do case q of ComponentQuery -> do- Just x <- worldGet (Proxy @ComponentArchetypes) (Entity (# id, 0## #))+ !x <- fromMaybe (ComponentArchetypes Set.empty) <$> worldGet (Proxy @ComponentArchetypes) (Entity (# id, 0## #)) return x.inner RelationshipQueryAny -> do- Just x <- worldGet (Proxy @ComponentPairs) (Entity (# id, 0## #))+ Just !x <- worldGet (Proxy @ComponentPairs) (Entity (# id, 0## #)) return x.any RelationshipQuery -> do case target of Nothing -> undefined- Just target -> do- Just x <- worldGet (Proxy @ComponentPairs) (Entity (# id, 0## #))+ Just !target -> do+ Just !x <- worldGet (Proxy @ComponentPairs) (Entity (# id, 0## #)) return $ fromMaybe undefined $ Map.lookup target x.pairs case map Set.toList archetypes'' of [] -> return []- h : tail -> do- let archetypes = foldr intersect h tail+ !h : !tail -> do+ let !archetypes = foldr intersect h tail mapM- ( \(ArchetypeId x) -> do- x' <- Vec.read graph.nodes x- return (Set.toList x'.archetype.components, ArchetypeId x)+ ( \(ArchetypeId !x) -> do+ pure $ ArchetypeId x ) archetypes -allArchetypes :: forall m w. (MonadSystem w m) => m [([ComponentId], ArchetypeId)]+allArchetypes :: forall m w. (MonadSystem w m) => m [ArchetypeId] allArchetypes = do- world <- unsafeGetWorld- map (\x -> (Set.toList x.archetype.components, x.archetype.id)) <$> Vec.toList world.archetypes.graph.nodes+ !world <- unsafeGetWorld+ map (\(!x) -> x.archetype.id) <$> Vec.toList world.archetypes.graph.nodes
src/Mischief/ECS/Collectable.hs view
@@ -1,7 +1,5 @@ module Mischief.ECS.Collectable where -class Idk x y- class (Semigroup s) => EraseIntoStorage v s where erase :: v -> s
src/Mischief/ECS/Components.hs view
@@ -27,8 +27,7 @@ BundleElement (..), -- * Tables- ComponentTicks (..),- ComponentData (..),+ ComponentTick (..), -- * Storage Components (..),@@ -38,25 +37,27 @@ -- * Utils Pair (..), Rel (..),+ From (..),+ Res (..), ComponentRep (..), Tick (..), ErasedComponentEq (..), IsExclusive (..), isPair, setCompIdTarget,+ IsExclusiveRelationship (..), ) where import Data.Default import Data.HashTable.IO qualified as H-import Data.IORef import Data.Kind import Data.List qualified as List import Data.Map (Map)-import Data.Map qualified as Map import Data.Set (Set) import Data.Set qualified as Set import Data.Typeable+import Data.Word (Word64) import GHC.Base (Word (W#), Word#, compareWord#, eqWord#, isTrue#) import GHC.Generics import Mischief.ECS.Collectable@@ -67,7 +68,7 @@ data Exclusivity = Inclusive | Exclusive -- | The @Component@ typeclass.-class (Typeable c, IsExclusive (RelExclusivity c)) => Component c where+class (Typeable c, IsExclusive (IsExclusiveRel c)) => Component c where -- | List of components required by this one. -- All required components must be 'Default' --@@ -87,31 +88,51 @@ required :: Set DefaultComponentType required = Set.empty - type RelExclusivity c :: Exclusivity- type RelExclusivity c = Inclusive+ -- | Set whether the relationships this component is used in are exclusive or not.+ type IsExclusiveRel c :: Bool - hooks :: Hooks c- hooks = Hooks []+ type IsExclusiveRel c = False -class IsExclusive (e :: Exclusivity) where+ onAdd :: [Hook c]+ onAdd = []++ onSet :: [Hook c]+ onSet = []++ onRemove :: [Hook c]+ onRemove = []++ onAddRel :: [HookRel c]+ onAddRel = []++ onSetRel :: [HookRel c]+ onSetRel = []++ onRemoveRel :: [HookRel c]+ onRemoveRel = []++class IsExclusive (e :: Bool) where isExclusive :: Bool -instance IsExclusive Inclusive where+instance IsExclusive False where isExclusive = False -instance IsExclusive Exclusive where+instance IsExclusive True where isExclusive = True -- | Unique id for components and component pairs. data ComponentId = ComponentId (# Word#, Maybe Entity #) +instance Show ComponentId where+ show (ComponentId (# !a, !b #)) = "Component (" ++ show (W# a) ++ ", " ++ show b ++ ")"+ instance Eq ComponentId where (==) :: ComponentId -> ComponentId -> Bool- (==) (ComponentId (# a, b #)) (ComponentId (# x, y #)) = isTrue# (eqWord# a x) && b == y+ (==) (ComponentId (# !a, !b #)) (ComponentId (# !x, !y #)) = isTrue# (eqWord# a x) && b == y instance Ord ComponentId where compare :: ComponentId -> ComponentId -> Ordering- compare (ComponentId (# a, b #)) ((ComponentId (# x, y #))) =+ compare (ComponentId (# !a, !b #)) ((ComponentId (# !x, !y #))) = case compareWord# a x of EQ -> compare b y x -> x@@ -128,8 +149,8 @@ isPair _ = False setCompIdTarget :: Maybe Entity -> ComponentId -> ComponentId-setCompIdTarget Nothing (ComponentId (# a, _ #)) = ComponentId (# a, Nothing #)-setCompIdTarget (Just e) (ComponentId (# a, _ #)) = ComponentId (# a, Just e #)+setCompIdTarget Nothing (ComponentId (# !a, _ #)) = ComponentId (# a, Nothing #)+setCompIdTarget (Just !e) (ComponentId (# !a, _ #)) = ComponentId (# a, Just e #) newtype Pair = Pair (ComponentType, Entity) @@ -163,41 +184,62 @@ -- | Get the id of a component through IO. getComponentId :: TypeRep -> Components -> IO (Maybe ComponentId)-getComponentId t Components {components} = do- comp <- H.lookup components t- return $ case comp of+getComponentId !t Components {components = !components} = do+ !comp <- H.lookup components t+ pure $ case comp of Nothing -> Nothing- Just (W# t) -> Just $ ComponentId (# t, Nothing #)+ Just (W# !t) -> Just $ ComponentId (# t, Nothing #) -- | Try to get the inner data of a 'ErasedComponent'. tryGetComponent :: forall c. (Component c) => ErasedComponent -> Maybe c-tryGetComponent (ErasedComponent (s :: c')) =+tryGetComponent (ErasedComponent (!s :: c')) = case eqT @c @c' of Just Refl -> Just s Nothing -> Nothing instance {-# OVERLAPPING #-} EraseIntoStorage () (BundleData ErasedComponent) where- erase _ = BundleData Set.empty+ erase _ = BundleData Set.empty Set.empty Set.empty instance {-# OVERLAPPING #-} EraseIntoStorage (BundleData ErasedComponent) (BundleData ErasedComponent) where erase = id instance (Component c) => EraseIntoStorage c (BundleData ErasedComponent) where- erase c =- BundleData $ Set.singleton BundleElement {rep = ComponentRep $ ComponentType $ Proxy @c, component = ErasedComponent c}+ erase !c =+ BundleData (Set.singleton BundleElement {rep = ComponentRep $ ComponentType $ Proxy @c, component = ErasedComponent c}) Set.empty Set.empty instance {-# OVERLAPPING #-} (Component c) => EraseIntoStorage (Rel c) (BundleData ErasedComponent) where- erase (Rel c entity) =- BundleData $ Set.singleton BundleElement {rep = PairRep (ComponentType $ Proxy @c, entity), component = ErasedComponent c}+ erase (Rel !c !entity) =+ BundleData (Set.singleton BundleElement {rep = PairRep (ComponentType $ Proxy @c, entity), component = ErasedComponent c}) Set.empty Set.empty +instance {-# OVERLAPPING #-} (Component c) => EraseIntoStorage (Res c) (BundleData ErasedComponent) where+ erase (Res !c) = BundleData Set.empty (Set.singleton BundleElement {rep = ComponentRep $ ComponentType $ Proxy @c, component = ErasedComponent c}) Set.empty++instance {-# OVERLAPPING #-} (Collectable c (BundleData ErasedComponent)) => EraseIntoStorage (From c) (BundleData ErasedComponent) where+ erase (From !e !c) = BundleData Set.empty Set.empty (Set.singleton (e, collect c))++instance {-# OVERLAPPING #-} (Collectable c (BundleData ErasedComponent)) => EraseIntoStorage [c] (BundleData ErasedComponent) where+ erase = foldr ((<>) . collect) (BundleData Set.empty Set.empty Set.empty)+ instance (Component c, Eq c) => EraseIntoStorage c (BundleData ErasedComponentEq) where- erase c =- BundleData $ Set.singleton BundleElement {rep = ComponentRep $ ComponentType $ Proxy @c, component = ErasedComponentEq c}+ erase !c =+ BundleData (Set.singleton BundleElement {rep = ComponentRep $ ComponentType $ Proxy @c, component = ErasedComponentEq c}) Set.empty Set.empty instance {-# OVERLAPPING #-} (Component c, Eq c) => EraseIntoStorage (Rel c) (BundleData ErasedComponentEq) where- erase (Rel c entity) =- BundleData $ Set.singleton BundleElement {rep = PairRep (ComponentType $ Proxy @c, entity), component = ErasedComponentEq c}+ erase (Rel !c !entity) =+ BundleData (Set.singleton BundleElement {rep = PairRep (ComponentType $ Proxy @c, entity), component = ErasedComponentEq c}) Set.empty Set.empty +instance {-# OVERLAPPING #-} (Component c, Eq c) => EraseIntoStorage (Res c) (BundleData ErasedComponentEq) where+ erase (Res !c) = BundleData Set.empty (Set.singleton BundleElement {rep = ComponentRep $ ComponentType $ Proxy @c, component = ErasedComponentEq c}) Set.empty++instance {-# OVERLAPPING #-} (Collectable c (BundleData ErasedComponentEq)) => EraseIntoStorage (From c) (BundleData ErasedComponentEq) where+ erase (From !e !c) = BundleData Set.empty Set.empty (Set.singleton (e, collect c))++instance {-# OVERLAPPING #-} (Collectable c (BundleData ErasedComponentEq)) => EraseIntoStorage [c] (BundleData ErasedComponentEq) where+ erase = foldr ((<>) . collect) (BundleData Set.empty Set.empty Set.empty)++instance {-# OVERLAPPING #-} EraseIntoStorage (BundleData ErasedComponentEq) (BundleData ErasedComponentEq) where+ erase = id+ -- | Unique id corresponding to an archetype. newtype ArchetypeId = ArchetypeId { id :: Int@@ -205,22 +247,42 @@ deriving (Show, Eq, Ord) -- | Data extracted from a 'Mischief.ECS.Components.Bundle.Bundle'.-newtype BundleData e = BundleData {elements :: Set (BundleElement e)} deriving newtype (Semigroup)+data BundleData e = BundleData {elements :: Set (BundleElement e), resources :: Set (BundleElement e), external :: Set (Entity, BundleData e)} deriving (Eq, Ord) +instance Semigroup (BundleData e) where+ (<>) (BundleData a0 b0 c0) (BundleData a1 b1 c1) = BundleData (a0 <> a1) (b0 <> b1) (c0 <> c1)+ instance Show (BundleData e) where- show BundleData {elements} = mconcat ["BundleData e [", List.intercalate ", " ts, "]"]+ show BundleData {elements = !elements} = mconcat ["BundleData e [", List.intercalate ", " ts, "]"] where- ts = map (\bundle -> show bundle.rep) (Set.toList elements)+ !ts = map (\bundle -> show bundle.rep) (Set.toList elements) -- | Change ticks for a specific component.-data ComponentTicks = ComponentTicks {changed :: Tick, added :: Tick} deriving (Show)---- | Data for a component that's stored in a table.-data ComponentData = ComponentData {value :: ErasedComponent, ticks :: ComponentTicks}+newtype ComponentTick = ComponentTick Tick deriving (Show) -- | Type used for querying and inserting relationships.-data Rel c = Rel {comp :: c, target :: Entity} deriving (Show)+data Rel c = Rel {comp :: c, target :: Entity} deriving (Eq) +instance Functor Rel where+ fmap !f (Rel !c !target) = Rel (f c) target++instance (Show c) => Show (Rel c) where+ show Rel {comp = !comp, target = !target} = "Rel (" ++ show comp ++ ", " ++ show target ++ ")"++data From c = From {entity :: Entity, comp :: c} deriving (Eq)++instance Functor From where+ fmap :: (a -> b) -> From a -> From b+ fmap !f (From !c !x) = From c (f x)++instance (Show c) => Show (From c) where+ show From {entity = !entity, comp = !comp} = "From (" ++ show entity ++ ", " ++ show comp ++ ")"++newtype Res c = Res {comp :: c} deriving newtype (Show, Eq)++instance Functor Res where+ fmap !f (Res !c) = Res (f c)+ -- | @Meta@ component with the /erased/ default value of this component. Added to components required by other components. newtype DefaultValue = DefaultValue ErasedComponent deriving anyclass (Component) @@ -247,15 +309,15 @@ instance Show (BundleElement a) where show :: BundleElement a -> String- show e = show e.rep+ show !e = show e.rep instance Eq (BundleElement a) where (==) :: BundleElement a -> BundleElement a -> Bool- (==) BundleElement {rep = rep1} BundleElement {rep = rep2} = rep1 == rep2+ (==) BundleElement {rep = !rep1} BundleElement {rep = !rep2} = rep1 == rep2 instance Ord (BundleElement a) where compare :: BundleElement a -> BundleElement a -> Ordering- compare BundleElement {rep = rep1} BundleElement {rep = rep2} = compare rep1 rep2+ compare BundleElement {rep = !rep1} BundleElement {rep = !rep2} = compare rep1 rep2 -- @Meta@ component containing the erased type of this component. data ComponentType where@@ -263,30 +325,30 @@ instance Show ComponentType where show :: ComponentType -> String- show x = show $ getRep x+ show !x = show $ getRep x instance Eq ComponentType where (==) :: ComponentType -> ComponentType -> Bool- (==) a b = getRep a == getRep b+ (==) !a !b = getRep a == getRep b instance Ord ComponentType where compare :: ComponentType -> ComponentType -> Ordering- compare a b = compare (getRep a) (getRep b)+ compare !a !b = compare (getRep a) (getRep b) data DefaultComponentType where DefaultComponentType :: forall (c :: Type). (Component c, Default c) => (Proxy c) -> DefaultComponentType instance Show DefaultComponentType where show :: DefaultComponentType -> String- show x = show $ getRep x+ show !x = show $ getRep x instance Eq DefaultComponentType where (==) :: DefaultComponentType -> DefaultComponentType -> Bool- (==) a b = getRep a == getRep b+ (==) !a !b = getRep a == getRep b instance Ord DefaultComponentType where compare :: DefaultComponentType -> DefaultComponentType -> Ordering- compare a b = compare (getRep a) (getRep b)+ compare !a !b = compare (getRep a) (getRep b) instance GetRep ComponentType where getRep :: ComponentType -> TypeRep@@ -300,6 +362,8 @@ getRep :: DefaultComponentType -> TypeRep getRep (DefaultComponentType (_ :: (Proxy t))) = typeRep $ Proxy @t -newtype Tick = Tick (Int, Int)+newtype Tick = Tick Word64 deriving stock (Show, Eq, Ord) deriving newtype (Default)++data IsExclusiveRelationship = IsExclusiveRelationship deriving (Show, Component)
src/Mischief/ECS/Components/Bundle.hs view
@@ -2,6 +2,7 @@ module Mischief.ECS.Components.Bundle where +import Data.Bifunctor qualified import Data.Set qualified as Set import Data.Typeable import Mischief.ECS.Collectable (Collectable (collect))@@ -9,33 +10,46 @@ newtype ProcessedBundleData = ProcessedBundleData {elements :: [ProcessedBundleElement]} -data ProcessedBundleElement = ProcessedBundleElement {id :: ComponentId, component :: ComponentData}+data ProcessedBundleElement = ProcessedBundleElement {id :: ComponentId, component :: ErasedComponent} addComponentToBundleData :: forall c. (Component c) => c -> BundleData ErasedComponent -> BundleData ErasedComponent-addComponentToBundleData c (BundleData {elements}) =- let rep = ComponentRep $ ComponentType (Proxy @c)- component = ErasedComponent c- element = BundleElement {rep, component}- in BundleData {elements = Set.union elements (Set.singleton element)}+addComponentToBundleData !c (BundleData {elements = !elements, resources = !resources, external = !external}) =+ let !rep = ComponentRep $ ComponentType (Proxy @c)+ !component = ErasedComponent c+ !element = BundleElement {rep, component}+ in BundleData {elements = Set.union elements (Set.singleton element), resources, external} instance Eq ProcessedBundleElement where (==) :: ProcessedBundleElement -> ProcessedBundleElement -> Bool- (==) ProcessedBundleElement {id = id1} ProcessedBundleElement {id = id2} = id1 == id2+ (==) ProcessedBundleElement {id = !id1} ProcessedBundleElement {id = !id2} = id1 == id2 instance Ord ProcessedBundleElement where compare :: ProcessedBundleElement -> ProcessedBundleElement -> Ordering- compare ProcessedBundleElement {id = id1} ProcessedBundleElement {id = id2} = compare id1 id2+ compare ProcessedBundleElement {id = !id1} ProcessedBundleElement {id = !id2} = compare id1 id2 type Bundle b = Collectable b (BundleData ErasedComponent) bundleData :: (Bundle b) => b -> BundleData ErasedComponent bundleData = collect +type BundleBuilder = BundleData ErasedComponent++bundle :: (Bundle b) => b -> BundleBuilder+bundle = collect++type BundleEqBuilder = BundleData ErasedComponentEq++bundleEq :: (BundleEq b) => b -> BundleEqBuilder+bundleEq = collect+ type BundleEq b = Collectable b (BundleData ErasedComponentEq) bundleDataEq :: (BundleEq b) => b -> BundleData ErasedComponentEq bundleDataEq = collect bundleEqToSimple :: BundleData ErasedComponentEq -> BundleData ErasedComponent-bundleEqToSimple BundleData {elements} =- BundleData $ Set.map (\BundleElement {rep, component = ErasedComponentEq (a :: s)} -> BundleElement {rep, component = ErasedComponent a}) elements+bundleEqToSimple BundleData {elements = !elements, resources = !resources, external = !external} =+ BundleData+ (Set.map (\BundleElement {rep, component = ErasedComponentEq (!a :: s)} -> BundleElement {rep, component = ErasedComponent a}) elements)+ (Set.map (\BundleElement {rep, component = ErasedComponentEq (!a :: s)} -> BundleElement {rep, component = ErasedComponent a}) resources)+ (Set.map (Data.Bifunctor.second bundleEqToSimple) external)
src/Mischief/ECS/Components/Common.hs view
@@ -11,4 +11,4 @@ instance Show Name where show :: Name -> String- show (Name name) = show name+ show (Name !name) = name
src/Mischief/ECS/Components/HooksDef.hs view
@@ -1,9 +1,14 @@ module Mischief.ECS.Components.HooksDef where import Data.Data-import Mischief.ECS.EventDef+import Mischief.ECS.EntityDef -data ErasedHook c where- ErasedHook :: forall e c m. (Event (e c), Typeable m) => (e c -> m ()) -> ErasedHook c+data Hook a where+ Hook :: (Typeable m) => (HookContext -> m ()) -> Hook a -newtype Hooks c = Hooks [ErasedHook c] deriving newtype (Semigroup)+data HookRel a where+ HookRel :: (Typeable m) => (HookContextRel -> m ()) -> HookRel a++newtype HookContext = HookContext {entity :: Entity}++data HookContextRel = HookContextRel {entity :: Entity, target :: Entity}
src/Mischief/ECS/Components/Required.hs view
@@ -20,11 +20,47 @@ defaultBundleData _ = Set.singleton $ DefaultComponentType $ Proxy @c instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1) => RequiredBundle (b0, b1) where- defaultBundleData _ =- let b0 = defaultBundleData (Proxy @b0)- b1 = defaultBundleData (Proxy @b1)- in Set.union b0 b1+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1] +instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2) => RequiredBundle (b0, b1, b2) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3) => RequiredBundle (b0, b1, b2, b3) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4) => RequiredBundle (b0, b1, b2, b3, b4) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4, RequiredBundle b5) => RequiredBundle (b0, b1, b2, b3, b4, b5) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4, defaultBundleData $ Proxy @b5]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4, RequiredBundle b5, RequiredBundle b6) => RequiredBundle (b0, b1, b2, b3, b4, b5, b6) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4, defaultBundleData $ Proxy @b5, defaultBundleData $ Proxy @b6]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4, RequiredBundle b5, RequiredBundle b6, RequiredBundle b7) => RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4, defaultBundleData $ Proxy @b5, defaultBundleData $ Proxy @b6, defaultBundleData $ Proxy @b7]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4, RequiredBundle b5, RequiredBundle b6, RequiredBundle b7, RequiredBundle b8) => RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4, defaultBundleData $ Proxy @b5, defaultBundleData $ Proxy @b6, defaultBundleData $ Proxy @b7, defaultBundleData $ Proxy @b8]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4, RequiredBundle b5, RequiredBundle b6, RequiredBundle b7, RequiredBundle b8, RequiredBundle b9) => RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4, defaultBundleData $ Proxy @b5, defaultBundleData $ Proxy @b6, defaultBundleData $ Proxy @b7, defaultBundleData $ Proxy @b8, defaultBundleData $ Proxy @b9]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4, RequiredBundle b5, RequiredBundle b6, RequiredBundle b7, RequiredBundle b8, RequiredBundle b9, RequiredBundle b10) => RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4, defaultBundleData $ Proxy @b5, defaultBundleData $ Proxy @b6, defaultBundleData $ Proxy @b7, defaultBundleData $ Proxy @b8, defaultBundleData $ Proxy @b9, defaultBundleData $ Proxy @b10]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4, RequiredBundle b5, RequiredBundle b6, RequiredBundle b7, RequiredBundle b8, RequiredBundle b9, RequiredBundle b10, RequiredBundle b11) => RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4, defaultBundleData $ Proxy @b5, defaultBundleData $ Proxy @b6, defaultBundleData $ Proxy @b7, defaultBundleData $ Proxy @b8, defaultBundleData $ Proxy @b9, defaultBundleData $ Proxy @b10, defaultBundleData $ Proxy @b11]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4, RequiredBundle b5, RequiredBundle b6, RequiredBundle b7, RequiredBundle b8, RequiredBundle b9, RequiredBundle b10, RequiredBundle b11, RequiredBundle b12) => RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4, defaultBundleData $ Proxy @b5, defaultBundleData $ Proxy @b6, defaultBundleData $ Proxy @b7, defaultBundleData $ Proxy @b8, defaultBundleData $ Proxy @b9, defaultBundleData $ Proxy @b10, defaultBundleData $ Proxy @b11, defaultBundleData $ Proxy @b12]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4, RequiredBundle b5, RequiredBundle b6, RequiredBundle b7, RequiredBundle b8, RequiredBundle b9, RequiredBundle b10, RequiredBundle b11, RequiredBundle b12, RequiredBundle b13) => RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4, defaultBundleData $ Proxy @b5, defaultBundleData $ Proxy @b6, defaultBundleData $ Proxy @b7, defaultBundleData $ Proxy @b8, defaultBundleData $ Proxy @b9, defaultBundleData $ Proxy @b10, defaultBundleData $ Proxy @b11, defaultBundleData $ Proxy @b12, defaultBundleData $ Proxy @b13]++instance {-# OVERLAPPING #-} (RequiredBundle b0, RequiredBundle b1, RequiredBundle b2, RequiredBundle b3, RequiredBundle b4, RequiredBundle b5, RequiredBundle b6, RequiredBundle b7, RequiredBundle b8, RequiredBundle b9, RequiredBundle b10, RequiredBundle b11, RequiredBundle b12, RequiredBundle b13, RequiredBundle b14) => RequiredBundle (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14) where+ defaultBundleData _ = Set.unions [defaultBundleData $ Proxy @b0, defaultBundleData $ Proxy @b1, defaultBundleData $ Proxy @b2, defaultBundleData $ Proxy @b3, defaultBundleData $ Proxy @b4, defaultBundleData $ Proxy @b5, defaultBundleData $ Proxy @b6, defaultBundleData $ Proxy @b7, defaultBundleData $ Proxy @b8, defaultBundleData $ Proxy @b9, defaultBundleData $ Proxy @b10, defaultBundleData $ Proxy @b11, defaultBundleData $ Proxy @b12, defaultBundleData $ Proxy @b13, defaultBundleData $ Proxy @b14]+ require :: forall b. (RequiredBundle b) => Set DefaultComponentType require = defaultBundleData (Proxy @b) @@ -32,13 +68,13 @@ requireAll = requireAll' $ required @c requireAll' :: Set DefaultComponentType -> Set DefaultComponentType-requireAll' set =- let nextSet = expandAll set+requireAll' !set =+ let !nextSet = expandAll set in if null nextSet then set else requireAll' $ Set.union set nextSet expandAll :: Set DefaultComponentType -> Set DefaultComponentType-expandAll set =- let newSet = Set.unions $ map expandOne (Set.toList set)+expandAll !set =+ let !newSet = Set.unions $ map expandOne (Set.toList set) in Set.difference newSet set expandOne :: DefaultComponentType -> Set DefaultComponentType
− src/Mischief/ECS/Components/Runnable.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# OPTIONS_GHC -Wno-redundant-constraints #-}-{-# OPTIONS_GHC -Wno-unused-foralls #-}--module Mischief.ECS.Components.Runnable where--import Data.Data-import Data.Kind-import Mischief.ECS.Components-import Mischief.ECS.Components.Bundle-import Mischief.ECS.World--class Runnable c where- runFor' :: Proxy c -> (forall d. (Component d, Bundle d) => Proxy d -> System ()) -> System ()--instance {-# OVERLAPPABLE #-} (Component c, Bundle c) => Runnable c where- runFor' c s = s c--instance {-# OVERLAPPING #-} (Runnable r0, Runnable r1) => Runnable (r0, r1) where- runFor' _ s = do- runFor' (Proxy @r0) s- runFor' (Proxy @r1) s--runFor :: forall c. (Runnable c) => (forall (d :: Type). (Component d, Bundle d) => Proxy d -> System ()) -> System ()-runFor = runFor' (Proxy @c)
src/Mischief/ECS/Components/Spawn.hs view
@@ -4,7 +4,6 @@ import Control.Monad import Control.Monad.IO.Class-import Control.Monad.Reader import Data.Data import Data.Default import Data.Foldable@@ -15,45 +14,34 @@ -- import Mischief.ECS.Events import Data.HashTable.IO qualified as H-import Data.IORef-import Data.Kind-import Data.Map qualified as Map-import Data.Maybe-import Data.Set qualified as Set import GHC.Base (Word (..)) import Mischief.ECS.Components-import Mischief.ECS.Components.Bundle import Mischief.ECS.Components.Common import Mischief.ECS.Components.HooksDef import Mischief.ECS.Components.Required (requireAll) import Mischief.ECS.Entities-import Mischief.ECS.EntityDef-import Mischief.ECS.Log-import Mischief.ECS.Observer-import Mischief.ECS.Relationships import Mischief.ECS.World-import Mischief.ECS.World.Prefs -- import Mischief.ECS.World.Spawn -- | Get the id of a component - entity pair. In case the component isn't registered, it will give it a new id. getOrAddPairId :: Pair -> System ComponentId-getOrAddPairId (Pair (t, entity)) = do- (ComponentId (# id, _ #)) <- getOrAddComponentId t- return $ ComponentId (# id, Just entity #)+getOrAddPairId (Pair (!t, !entity)) = do+ (ComponentId (# !id, _ #)) <- getOrAddComponentId t+ pure $ ComponentId (# id, Just entity #) -- | Get the id of a component. In case the component isn't registered, it will give it a new id. getOrAddComponentId :: ComponentType -> System ComponentId getOrAddComponentId (ComponentType (_ :: Proxy c)) = do- world <- unsafeGetWorld- let comp = world.components- w <- liftIO $ H.lookup comp.components (typeRep $ Proxy @c)+ !world <- unsafeGetWorld+ let !comp = world.components+ !w <- liftIO $ H.lookup comp.components (typeRep $ Proxy @c) case w of- Just (W# t) -> return $ ComponentId (# t, Nothing #)+ Just (W# !t) -> pure $ ComponentId (# t, Nothing #) Nothing -> do- result <- liftIO $ getNewEntityComp world.entities- let !(Entity (# id, _ #)) = result+ !result <- liftIO $ getNewEntityComp world.entities+ let !(Entity (# !id, _ #)) = result liftIO $ H.insert comp.components (typeRep $ Proxy @c) (W# id) -- liftIO $ modifyIORef' comp.components $ Map.insert (typeRep $ Proxy @c) (W# id)@@ -61,54 +49,90 @@ -- l <- liftIO $ newIORef Set.empty -- liftIO $ modifyIORef' comp.archetypes $ Map.insert result l - forkPrefs (supressEvents True) $+ setPref @SupressEventsPref True $ worldSpawnByInsert result ( ( ComponentType $ Proxy @c,- ( def @ComponentArchetypes,- def @ComponentPairs- )+ def @ComponentPairs ), Name $ "Meta entity for " ++ show (typeRep $ Proxy @c) ) - when (isExclusive @(RelExclusivity c)) $ do+ when (isExclusive @(IsExclusiveRel c)) $ do worldSet IsExclusiveRelationship result for_ (requireAll @c) $ \(DefaultComponentType (_ :: (Proxy other))) -> do- (ComponentId (# otherId', _ #)) <- getOrAddComponentId (ComponentType $ Proxy @other)+ (ComponentId (# !otherId', _ #)) <- getOrAddComponentId (ComponentType $ Proxy @other) - let otherId = Entity (# otherId', 0## #)+ let !otherId = Entity (# otherId', 0## #) worldSet (Rel RequiredBy result) otherId worldSet (Rel Requires otherId) result worldSet (DefaultValue $ ErasedComponent $ def @other) otherId - registerHooks $ hooks @c+ registerHooks @c result - return $ ComponentId (# id, Nothing #)+ pure $ ComponentId (# id, Nothing #) -- l <- liftIO $ newIORef Set.empty -- liftIO $ modifyIORef' archetypes $ Map.insert id l +-- | Return the meta entity corresponding to a given component. meta :: forall c. (Component c) => System Entity meta = do- (ComponentId (# id, _ #)) <- getOrAddComponentId (ComponentType $ Proxy @c)- return $ Entity (# id, 0## #)+ (ComponentId (# !id, _ #)) <- getOrAddComponentId (ComponentType $ Proxy @c)+ pure $ Entity (# id, 0## #) tryMeta :: forall c m w. (Component c, MonadSystem w m) => m (Maybe Entity) tryMeta = do- world <- unsafeGetWorld- component <- liftIO $ getComponentId (typeRep $ Proxy @c) world.components- return $ fmap (\(ComponentId (# id, _ #)) -> Entity (# id, 0## #)) component+ !world <- unsafeGetWorld+ !component <- liftIO $ getComponentId (typeRep $ Proxy @c) world.components+ pure $ fmap (\(ComponentId (# !id, _ #)) -> Entity (# id, 0## #)) component -registerHooks :: Hooks c -> System ()-registerHooks (Hooks h) = for_ h registerHook+registerHooks :: forall c. (Component c) => Entity -> System ()+registerHooks !entity = do+ let !a = map getHook (onAdd @c)+ let !s = map getHook (onSet @c)+ let !r = map getHook (onRemove @c)+ let !ar = map getHookRel (onAddRel @c)+ let !sr = map getHookRel (onSetRel @c)+ let !rr = map getHookRel (onRemoveRel @c) -registerHook :: ErasedHook c -> System ()-registerHook (ErasedHook (h :: e c -> m ())) = do- world <- unsafeGetWorld- e <- liftIO $ getNewEntity world.entities+ worldSet (ComponentAddHooks a) entity+ worldSet (ComponentSetHooks s) entity+ worldSet (ComponentRemoveHooks r) entity+ worldSet (ComponentAddHooksRel ar) entity+ worldSet (ComponentSetHooksRel sr) entity+ worldSet (ComponentRemoveHooksRel rr) entity++getHook :: Hook c -> (HookContext -> System ())+getHook (Hook (!h :: HookContext -> m ())) = do case eqT @m @System of- Just Refl -> void $ worldSpawnByInsert e $ Observer h Nothing -> undefined+ Just Refl -> h++getHookRel :: HookRel c -> (HookContextRel -> System ())+getHookRel (HookRel (!h :: HookContextRel -> m ())) = do+ case eqT @m @System of+ Nothing -> undefined+ Just Refl -> h++-- registerHook :: ErasedHook c -> System ()+-- registerHook (ErasedHook (h :: e c -> m ())) = do+-- world <- unsafeGetWorld+-- e <- liftIO $ getNewEntity world.entities+-- case eqT @m @System of+-- Just Refl -> void $ worldSpawnByInsert e $ Observer h+-- Nothing -> undefined++newtype ComponentAddHooks = ComponentAddHooks [HookContext -> System ()] deriving anyclass (Component)++newtype ComponentSetHooks = ComponentSetHooks [HookContext -> System ()] deriving anyclass (Component)++newtype ComponentRemoveHooks = ComponentRemoveHooks [HookContext -> System ()] deriving anyclass (Component)++newtype ComponentAddHooksRel = ComponentAddHooksRel [HookContextRel -> System ()] deriving anyclass (Component)++newtype ComponentSetHooksRel = ComponentSetHooksRel [HookContextRel -> System ()] deriving anyclass (Component)++newtype ComponentRemoveHooksRel = ComponentRemoveHooksRel [HookContextRel -> System ()] deriving anyclass (Component)
src/Mischief/ECS/Entities.hs view
@@ -21,12 +21,8 @@ import Control.Concurrent.STM.TVar import Control.Monad import Data.IORef-import Data.Map (Map)-import Data.Map qualified as Map-import Data.Maybe (isJust) import GHC.Base import GHC.Conc-import Mischief.ECS.Components import Mischief.ECS.EntityDef import Mischief.ECS.Vec (IOVec) import Mischief.ECS.Vec qualified as Vec@@ -44,7 +40,7 @@ -- | A storage for entity ids and pointers. data Entities = Entities { -- | Associates each @Entity@ to an @EntityPointer@.- pointers :: IOVec (IORef EntityPointer),+ pointers :: IOVec (Word, IORef EntityPointer), nullPtr :: IORef EntityPointer, -- | A counter for assigning new entity ids. It is in a TVar so -- it can be used in parallel systems.@@ -56,81 +52,89 @@ -- | Associate an Entity with a new pointer. insertPointer :: Entity -> IORef EntityPointer -> Entities -> IO ()-insertPointer (Entity (# i, _ #)) pointer entities = do- Vec.write entities.pointers (I# $ word2Int# i) pointer+insertPointer (Entity (# !i, g #)) pointer entities = do+ let !i' = I# $ word2Int# i+ fillVec entities i'+ Vec.write entities.pointers i' (W# g, pointer) -- | Get the pointer to an entity. getPointer :: Entity -> Entities -> IO (Maybe (IORef EntityPointer))-getPointer (Entity (# i', _ #)) entities = do- let i = I# (word2Int# i')- l <- Vec.length entities.pointers+getPointer (Entity (# !i', g #)) entities = do+ let !i = I# (word2Int# i')+ !l <- Vec.length entities.pointers if i < l then do- p <- Vec.unsafeRead entities.pointers i- pure $ if p /= entities.nullPtr then Just p else Nothing+ (!g', !p) <- Vec.unsafeRead entities.pointers i+ pure $ if p /= entities.nullPtr && g' == W# g then Just p else Nothing else pure Nothing -- | Create a new entity. getNewEntity :: Entities -> IO Entity-getNewEntity entities = do- (e, reused) <- atomically $ do+getNewEntity !entities = do+ (!e, _) <- atomically $ do EntityCounter {counter, free} <- readTVar entities.counter case free of [] -> do writeTVar entities.counter EntityCounter {counter = plusWord# counter 1##, free} pure (Entity (# counter, 1## #), False)- (Entity (# id, gen #) : xs) -> do+ (Entity (# !id, !gen #) : xs) -> do writeTVar entities.counter EntityCounter {counter = counter, free = xs} pure (Entity (# id, plusWord# gen 1## #), True) - unless reused $ do- Vec.pushBack entities.pointers entities.nullPtr+ -- unless reused $ do+ -- Vec.pushBack entities.pointers entities.nullPtr pure e -- | Creates a new entity using a fresh id, never incrementing the generation of a previous one. getNewEntityComp :: Entities -> IO Entity-getNewEntityComp entities = do- Vec.pushBack entities.pointers entities.nullPtr+getNewEntityComp !entities = do+ -- Vec.pushBack entities.pointers entities.nullPtr atomically $ do- EntityCounter {counter, free} <- readTVar entities.counter- let e = Entity (# counter, 0## #)+ EntityCounter {counter = !counter, free = !free} <- readTVar entities.counter+ let !e = Entity (# counter, 0## #) writeTVar entities.counter EntityCounter {counter = plusWord# counter 1##, free} pure e -- | Remove an entity from storage. removeEntity :: Entity -> Entities -> IO ()-removeEntity (Entity (# i', g #)) entities = do- let i = I# (word2Int# i')- l <- Vec.length entities.pointers+removeEntity (Entity (# !i', !g #)) entities = do+ let !i = I# (word2Int# i')+ !l <- Vec.length entities.pointers when (i < l) $ do- Vec.unsafeWrite entities.pointers i entities.nullPtr+ Vec.unsafeWrite entities.pointers i (0, entities.nullPtr) atomically $ do- EntityCounter {counter, free} <- readTVar entities.counter+ EntityCounter {counter = !counter, free = !free} <- readTVar entities.counter writeTVar entities.counter EntityCounter {counter, free = Entity (# i', g #) : free} +fillVec :: Entities -> Int -> IO ()+fillVec !entities !n = do+ !l <- Vec.length entities.pointers+ when (n >= l) $ do+ replicateM_ (n - l + 1) $ Vec.pushBack entities.pointers (0, entities.nullPtr)+ -- | Create a new storage for entities. emptyEntities :: IO Entities emptyEntities = do- map <- Vec.new 256- counter <- newTVarIO EntityCounter {counter = 1##, free = []}- nullPtr <- newIORef $ EntityPointer (# 0#, 0# #)+ !map <- Vec.new 256+ !counter <- newTVarIO EntityCounter {counter = 1##, free = []}+ !nullPtr <- newIORef $ EntityPointer (# 0#, 0# #) - Vec.pushBack map nullPtr- return $ Entities map nullPtr counter+ Vec.pushBack map (0, nullPtr)+ pure $ Entities map nullPtr counter -- | Check if an Entity is alive through IO. isAliveIO :: Entity -> Entities -> IO Bool-isAliveIO (Entity (# i', _ #)) entities = do- let i = I# (word2Int# i')- l <- Vec.length entities.pointers+isAliveIO (Entity (# !i', g #)) !entities = do+ let !i = I# (word2Int# i')+ !l <- Vec.length entities.pointers if i < l then do- p <- Vec.unsafeRead entities.pointers i- pure $ p /= entities.nullPtr+ (!g', !p) <- Vec.unsafeRead entities.pointers i+ pure $ p /= entities.nullPtr && g' == W# g else pure False
src/Mischief/ECS/EntityDef.hs view
@@ -12,33 +12,36 @@ instance Show Entity where show :: Entity -> String- show (Entity (# id, 0## #)) = show (W# id) ++ "v" ++ "."- show (Entity (# id, gen #)) = show (W# id) ++ "v" ++ show (W# gen)+ show (Entity (# !id, 0## #)) = show (W# id) ++ "v" ++ "."+ show (Entity (# !id, !gen #)) = show (W# id) ++ "v" ++ show (W# gen) instance Eq Entity where (==) :: Entity -> Entity -> Bool- (==) (Entity (# id1, gen1 #)) (Entity (# id2, gen2 #)) = isTrue# (eqWord# id1 id2) && isTrue# (eqWord# gen1 gen2)+ (==) (Entity (# !id1, !gen1 #)) (Entity (# !id2, !gen2 #)) = isTrue# (eqWord# id1 id2) && isTrue# (eqWord# gen1 gen2) instance Ord Entity where compare :: Entity -> Entity -> Ordering- compare (Entity (# id1, gen1 #)) (Entity (# id2, gen2 #)) =+ compare (Entity (# !id1, !gen1 #)) (Entity (# !id2, !gen2 #)) = case compareWord# id1 id2 of EQ -> compareWord# gen1 gen2- x -> x+ !x -> x newtype Entity# = Entity# (# Word#, Word# #) liftEntity :: Entity# -> Entity-liftEntity (Entity# a) = Entity a+liftEntity (Entity# !a) = Entity a unliftEntity :: Entity -> Entity#-unliftEntity (Entity a) = Entity# a+unliftEntity (Entity !a) = Entity# a eqEntity# :: Entity# -> Entity# -> Bool-eqEntity# (Entity# (# id1, gen1 #)) (Entity# (# id2, gen2 #)) = isTrue# (eqWord# id1 id2) && isTrue# (eqWord# gen1 gen2)+eqEntity# (Entity# (# !id1, !gen1 #)) (Entity# (# !id2, !gen2 #)) = isTrue# (eqWord# id1 id2) && isTrue# (eqWord# gen1 gen2) compareEntity# :: Entity# -> Entity# -> Ordering-compareEntity# (Entity# (# id1, gen1 #)) (Entity# (# id2, gen2 #)) =+compareEntity# (Entity# (# !id1, !gen1 #)) (Entity# (# !id2, !gen2 #)) = case compareWord# id1 id2 of EQ -> compareWord# gen1 gen2- x -> x+ !x -> x++nullEntity :: Entity+nullEntity = Entity (# 0##, 0## #)
src/Mischief/ECS/Events.hs view
@@ -1,51 +1,33 @@ module Mischief.ECS.Events where -import Control.Monad.IO.Class-import Control.Monad.Reader (MonadReader (..))-import Data.Data (Typeable)-import Data.Default (Default) import Data.Foldable (for_)-import Data.IORef (modifyIORef', readIORef, writeIORef)-import Data.List-import GHC.Generics (Generic)-import GHC.Records-import Mischief.ECS.Components-import Mischief.ECS.Components.Bundle-import Mischief.ECS.Components.Required (require) import Mischief.ECS.Entities import Mischief.ECS.EventDef import Mischief.ECS.Observer-import Mischief.ECS.Tables import Mischief.ECS.World import Mischief.ECS.World.Query import Mischief.ECS.World.Query.Markers-import Mischief.ECS.World.Query.Queryable -trigger :: (Event e) => e -> System ()-trigger event = do- world <- unsafeGetWorld- liftIO $ modifyIORef' world.events (++ [eraseEvent event])+trigger :: forall e. (Event e) => e -> System ()+trigger !event = do+ !observers <- query $ mkQuery (C @(Observer e), C @(EventProxy e))+ for_ observers $ \(!observer, _) -> do+ let Observer !f = observer+ f event -flushEvents :: System ()-flushEvents = do- world <- unsafeGetWorld- events <- liftIO $ readIORef world.events- for_ events runEvent- liftIO $ writeIORef world.events []+newtype OnSet c = OnSet {entity :: Entity}+ deriving anyclass (Event)+ deriving stock (Show) -runEvent :: ErasedEvent -> System ()-runEvent (ErasedEvent (event :: e)) = do- observers' <- query (C @(Observer e), C @(EventProxy e), C @ObserverOrder)- let observers = sortBy (\(_, _, a) (_, _, b) -> compare a b) observers'- for_ observers $ \(observer, _, _) -> do- let Observer f = value observer- f event+data OnSetRel c = OnSetRel {entity :: Entity, target :: Entity}+ deriving anyclass (Event)+ deriving stock (Show) -newtype OnInsert c = OnInsert {entity :: Entity}+newtype OnAdd c = OnAdd {entity :: Entity} deriving anyclass (Event) deriving stock (Show) -data OnInsertRel c = OnInsertRel {entity :: Entity, target :: Entity}+data OnAddRel c = OnAddRel {entity :: Entity, target :: Entity} deriving anyclass (Event) deriving stock (Show)
+ src/Mischief/ECS/Exports.hs view
@@ -0,0 +1,12 @@+{-# OPTIONS_GHC -Wno-dodgy-exports #-}++module Mischief.ECS.Exports+ ( module Data.String.Interpolate,+ module Data.Default,+ module GHC.Generics,+ )+where++import Data.Default (Default (..))+import Data.String.Interpolate (i)+import GHC.Generics (Generic)
+ src/Mischief/ECS/Fixed.hs view
@@ -0,0 +1,108 @@+module Mischief.ECS.Fixed where++import Control.Monad+import Data.Data+import Data.Default+import Data.Foldable+import Data.Function+import Data.Maybe+import Mischief.ECS.App+import Mischief.ECS.App.Plugins+import Mischief.ECS.App.Schedules+import Mischief.ECS.Components+import Mischief.ECS.Relationships.Order (Before (..), orderEntities)+import Mischief.ECS.Resources+import Mischief.ECS.Systems+import Mischief.ECS.Time (Time (Time), TimePlugin, TimeTag (..), TimeTypePref, ToTime)+import Mischief.ECS.Time qualified as Time+import Mischief.ECS.World+import Mischief.ECS.World.Insert+import Mischief.ECS.World.Query+import Mischief.ECS.World.Query.Markers+import Mischief.ECS.World.Query.QueryFilter+import System.Clock (TimeSpec)++-- | Resource determining the frequency at which Fixed schedules are ran. Defaults to @0.03@ (30fps).+newtype FixedFrequency = FixedFrequency Float deriving anyclass (Component)++instance Default FixedFrequency where+ def = FixedFrequency 0.033333333++data FixedFirst deriving (Schedule, Show)++data FixedPreUpdate deriving (Schedule, Show)++data FixedUpdate deriving (Schedule, Show)++data FixedPostUpdate deriving (Schedule, Show)++data FixedLast deriving (Schedule, Show)++-- | Plugin that enables running systems in Fixed schedules such as 'FixedUpdate' and 'FixedPostUpdate'.+--+-- Overwrite the 'FixedFrequency' resource to change the frequency at which Fixed schedules are ran.+data FixedPlugin++instance Plugin FixedPlugin where+ init = do+ first <- scheduleEntity @FixedFirst+ pre <- scheduleEntity @FixedPreUpdate+ update <- scheduleEntity @FixedUpdate+ post <- scheduleEntity @FixedPostUpdate+ last <- scheduleEntity @FixedLast++ for_ [first, pre, update, post, last] $ insert FixedSchedule++ insert (Rel Before pre) first+ insert (Rel Before update) pre+ insert (Rel Before post) update+ insert (Rel Before last) post++ insertRes $ def @FixedFrequency+ insertRes $ FixedTime 0 0++ fixed <- scheduleEntity @Fixed+ last' <- scheduleEntity @Last+ post' <- scheduleEntity @PostUpdate++ insert UpdateSchedule fixed+ insert (Rel Before last') fixed+ insert (Rel Before fixed) post'++ systems runFixedLoop+ & schedule @Fixed++ deps = [dep @TimePlugin]++data Fixed deriving (Schedule)++data FixedTime = FixedTime+ { fixedElapsed :: TimeSpec,+ fixedDelta :: TimeSpec+ }+ deriving (Component)++instance ToTime FixedTime where+ toTime FixedTime {fixedElapsed, fixedDelta} = Time {elapsed = fixedElapsed, delta = fixedDelta}++newtype FixedExtra = FixedExtra Float deriving anyclass (Component)++runFixedLoop :: System ()+runFixedLoop = do+ FixedExtra extra <- fromMaybe (FixedExtra 0) <$> (res @FixedExtra)+ delta <- Time.delta+ Just (FixedFrequency freq) <- res @FixedFrequency++ let extra' = extra + delta+ let runs :: Int = floor $ extra' / freq++ schedules <- orderEntities =<< query (mkQuery' E (With (C @FixedSchedule)))++ setPref @TimeTypePref (TimeTag $ Proxy @FixedTime) $ do+ modifyRes' (\FixedTime {fixedElapsed} -> FixedTime {fixedElapsed, fixedDelta = Time.secsToSpec freq})++ replicateM_ runs $ do+ for_ schedules runSchedule'+ modifyRes' (\FixedTime {fixedElapsed, fixedDelta} -> FixedTime {fixedElapsed = fixedElapsed + fixedDelta, fixedDelta})++ insertRes $ FixedExtra $ extra' - (fromIntegral runs * freq)
+ src/Mischief/ECS/Foreign.hs view
@@ -0,0 +1,11 @@+module Mischief.ECS.Foreign where++import Control.Monad.IO.Class+import Foreign (Ptr, Storable)+import Foreign.Marshal.Alloc as A+import Mischief.ECS.World++alloca :: (Storable a) => (Ptr a -> System b) -> System b+alloca s = do+ x <- sysUnliftIO+ liftIO $ A.alloca $ x . s
− src/Mischief/ECS/Graph.hs
@@ -1,92 +0,0 @@-{- HLINT ignore "Use second" -}-module Mischief.ECS.Graph where--import Control.Monad-import Data.Foldable-import Data.IORef-import Data.List qualified as List-import Data.Map (Map)-import Data.Map qualified as Map-import Data.Maybe-import Data.Set (Set)-import Data.Set qualified as Set-import Mischief.ECS.Vec (IOVec)-import Mischief.ECS.Vec qualified as Vec--data Graph a = Graph {nodes :: IORef (Map a Int), counter :: IORef Int, edges :: IOVec (Maybe a, Set Int)}--newGraph :: forall a. IO (Graph a)-newGraph = do- nodes <- newIORef Map.empty- counter <- newIORef 0- edges <- Vec.new 16-- return Graph {nodes, counter, edges}--addNode :: (Ord a) => a -> Graph a -> IO Int-addNode node Graph {nodes, counter, edges} = do- maybeNode <- tryGetNode node Graph {nodes, counter, edges}- case maybeNode of- Just x -> do- setNode node x Graph {nodes, counter, edges}- return x- Nothing -> do- index <- readIORef counter- modifyIORef' counter (+ 1)- Vec.pushBack edges (Just node, Set.empty)- modifyIORef' nodes (Map.insert node index)- return index--setNode :: a -> Int -> Graph a -> IO ()-setNode node index Graph {edges} = do- Vec.modify_ edges index (\(_, x) -> (Just node, x))--tryGetNode :: (Ord a) => a -> Graph a -> IO (Maybe Int)-tryGetNode node graph = do- nodes <- readIORef graph.nodes- return $ Map.lookup node nodes---- data GetOrAddResult = AddedNode Int | GotNode Int--getOrAddNode :: (Ord a) => a -> Graph a -> IO Int-getOrAddNode node graph = do- nodes' <- readIORef graph.nodes- case Map.lookup node nodes' of- Just x -> return x- Nothing -> do- index <- readIORef graph.counter- modifyIORef' graph.counter (+ 1)- Vec.pushBack graph.edges (Nothing, Set.empty)- modifyIORef' graph.nodes (Map.insert node index)- return index--addEdge :: (Ord a) => (a, a) -> Graph a -> IO ()-addEdge (a, b) graph = do- a' <- getOrAddNode a graph- b' <- getOrAddNode b graph- Vec.modify_ graph.edges b' (\(x, l) -> (x, Set.insert a' l))--takeRemoveableNodes :: IOVec (Maybe a, Set Int) -> IO [a]-takeRemoveableNodes edges = do- edgeList <- Vec.toList edges- len <- Vec.length edges- let nodes = concatMap unwrap (filter (\((a, x), _) -> null x && isJust a) (zip edgeList [0 :: Int ..]))- for_ nodes $ \(_, i) -> Vec.modify_ edges i (\(_, l) -> (Nothing, l))- for_ nodes $ \(_, i) -> for_ [0 .. len - 1] $ \j -> Vec.modify_ edges j (\(a, l) -> (a, Set.delete i l))- return (map fst nodes)- where- unwrap ((Just a, _), i) = [(a, i)]- unwrap _ = []--getNodes :: Graph a -> IO [[a]]-getNodes Graph {edges} = do- edges' <- Vec.clone edges- res <- newIORef []- step res edges'- readIORef res- where- step res edges' = do- nodes <- takeRemoveableNodes edges'- unless (null nodes) $ do- modifyIORef' res (++ [nodes])- step res edges'
src/Mischief/ECS/Hidden.hs view
@@ -12,4 +12,4 @@ hide = Hidden unhide :: Hidden a -> a-unhide (Hidden a) = a+unhide (Hidden !a) = a
src/Mischief/ECS/Hooks.hs view
@@ -1,93 +1,10 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# OPTIONS_GHC -Wno-orphans #-}---- {-# OPTIONS_GHC -Wno-redundant-constraints #-}--module Mischief.ECS.Hooks- ( relComplementary,- relCleanup,- relCleanupRemove,- relCleanupDespawn,- )-where+module Mischief.ECS.Hooks (hook, hookRel) where -import Control.Monad-import Data.Data-import Data.Foldable-import Data.Kind-import Data.Maybe-import Mischief.ECS.Collectable-import Mischief.ECS.Components-import Mischief.ECS.Components.Bundle-import Mischief.ECS.Components.Common import Mischief.ECS.Components.HooksDef-import Mischief.ECS.Entities-import Mischief.ECS.EventDef-import Mischief.ECS.Events-import Mischief.ECS.Log-import Mischief.ECS.Tables-import Mischief.ECS.Utils import Mischief.ECS.World-import Mischief.ECS.World.Insert-import Mischief.ECS.World.Query-import Mischief.ECS.World.Query.Markers-import Mischief.ECS.World.Query.Queryable-import Mischief.ECS.World.Remove-import Mischief.ECS.World.Spawn-import Mischief.ECS.World.Utils -instance (Event (e c)) => EraseIntoStorage (e c -> System ()) (Hooks c) where- erase :: (e c -> System ()) -> Hooks c- erase x = Hooks [ErasedHook x]--instance EraseIntoStorage (Hooks c) (Hooks c) where- erase = id---- | When applied on a component @A@, this hook takes a function @(A -> B)@ and does two things:------ 1. When @'Rel' (A, y)@ is inserted on entity @x@, @'Rel' (B, x)@ will be inserted on @y@ (the value of @B@ obtained through the provided function).------ 2. When @'Rel' (a, y)@ is removed from an entity @x@, @'Rel' (B, x)@ will be removed from @y@.-relComplementary :: forall (a :: Type) b. (Component a, Component b) => (a -> b) -> Hooks a-relComplementary f = collect (insertComplementary f, removeComplementary @b @a)--insertComplementary :: forall (a :: Type) b. (Component b, Component a) => (a -> b) -> OnInsertRel a -> System ()-insertComplementary f event = do- -- warn $ text event.target- Just val <- get (R @a event.target) event.entity- insert (Rel (f val.comp) event.entity) event.target--removeComplementary :: forall b a. (Component b) => OnRemoveRel a -> System ()-removeComplementary event = removeRel @b event.entity event.target--relCleanup :: forall (a :: Type). (Component a) => (CleanupRequest -> System ()) -> Hooks a-relCleanup f = collect (insertCleanupWatcher @a f, removeCleanupWatcher @a)--relCleanupRemove :: forall (a :: Type). (Component a) => Hooks a-relCleanupRemove = relCleanup (\r -> removeRel @a r.target r.entity)--relCleanupDespawn :: forall (a :: Type). (Component a) => Hooks a-relCleanupDespawn = relCleanup (\r -> despawn r.entity)--insertCleanupWatcher :: forall c. (Component c) => (CleanupRequest -> System ()) -> OnInsertRel c -> System ()-insertCleanupWatcher f e = insert (Rel (CleanupWatcher @c f) e.entity) e.target--removeCleanupWatcher :: forall c. (Component c) => OnRemoveRel c -> System ()-removeCleanupWatcher e = do- insert (Rel (CleanupWatcher @c (pure . pure ())) e.entity) e.target- removeRel @(CleanupWatcher c) e.entity e.target--newtype CleanupWatcher c = CleanupWatcher {function :: CleanupRequest -> System ()}--data CleanupRequest = CleanupRequest- { entity :: Entity,- target :: Entity- }--instance (Component c) => Component (CleanupWatcher c) where- hooks = collect $ triggerCleanup @c+hook :: (HookContext -> System ()) -> Hook a+hook = Hook -triggerCleanup :: forall c. (Component c) => OnRemoveRel (CleanupWatcher c) -> System ()-triggerCleanup e = do- Just watcher <- get (R @(CleanupWatcher c) e.target) e.entity- watcher.comp.function CleanupRequest {entity = e.target, target = e.entity}+hookRel :: (HookContextRel -> System ()) -> HookRel a+hookRel = HookRel
+ src/Mischief/ECS/HooksRel.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE AllowAmbiguousTypes #-}++module Mischief.ECS.HooksRel+ ( addOther,+ removeOther,+ addDespawnCleaner,+ addRemoveCleaner,+ addCleaner,+ removeCleaner,+ )+where++import Data.Kind+import Mischief.ECS.Components+import Mischief.ECS.Components.HooksDef+import Mischief.ECS.EntityDef+import Mischief.ECS.Hooks+import Mischief.ECS.World+import Mischief.ECS.World.Insert+import Mischief.ECS.World.Query+import Mischief.ECS.World.Query.Markers+import Mischief.ECS.World.Remove+import Mischief.ECS.World.Spawn++-- | Relationship Hook for @Component a@.+--+-- Takes a @a -> b@. If @a@ is a relationship from @x@ to @y@, this hook will add the corresponding @b@ from @y@ to @x@.+--+-- Example usage:+--+-- @+-- data After = After+-- data Before = Before+--+-- instance 'Component' Before where+-- onAddRel = [addOther ('const' After)]+--+-- instance 'Component' After where+-- onAddRel = [addOther ('const' Before)]+-- @+addOther :: forall (a :: Type) b. (Component a, Component b) => (a -> b) -> HookRel a+addOther f = hookRel $ insertComplementary f++-- | Relationship Hook for @Component a@.+--+-- Removes @b@ from the target entity of a relationship.+--+-- Example usage:+--+-- @+-- data Likes = Likes+-- data LikedBy = LikedBy deriving (Component)+--+-- instance 'Component' Likes where+-- onAddRel = [addOther ('const' Likes)]+-- onRemoveRel = [removeOther \@Likes]+-- @`+removeOther :: forall a b. (Component b) => HookRel a+removeOther = hookRel $ removeComplementary @b++insertComplementary :: forall (a :: Type) b. (Component b, Component a) => (a -> b) -> HookContextRel -> System ()+insertComplementary f event = do+ Just val <- single $ mkGet event.entity (R @a event.target)+ insert (Rel (f val.comp) event.entity) event.target++removeComplementary :: forall b. (Component b) => HookContextRel -> System ()+removeComplementary event = remove (R @b event.entity) event.target++addCleaner :: forall (a :: Type). (Component a) => (CleanupRequest -> System ()) -> HookRel a+addCleaner f = hookRel $ insertCleanupWatcher @a f++-- | Relationship Hook for @Component a@.+--+-- Adds a cleaner that will remove this relationship if the target is despawned.+--+-- Note that one component can have only one cleaner at a time.+addRemoveCleaner :: forall (a :: Type). (Component a) => HookRel a+addRemoveCleaner = addCleaner @a (\r -> removeRel @a r.target r.entity)++-- | Relationship Hook for @Component a@.+--+-- Adds a cleaner that will despawn this relationship's entity if the target of the relationship is despawned.+--+-- Note that one component can have only one cleaner at a time.+addDespawnCleaner :: forall (a :: Type). (Component a) => HookRel a+addDespawnCleaner = addCleaner @a (\r -> despawn r.entity)++insertCleanupWatcher :: forall c. (Component c) => (CleanupRequest -> System ()) -> HookContextRel -> System ()+insertCleanupWatcher f e = insert (Rel (CleanupWatcher @c f) e.entity) e.target++-- | Relationship Hook for @Component a@.+--+-- Removes the cleaner associated to this component, if any.+removeCleaner :: forall (a :: Type). (Component a) => HookRel a+removeCleaner = hookRel (removeCleanupWatcher @a)++removeCleanupWatcher :: forall c. (Component c) => HookContextRel -> System ()+removeCleanupWatcher e = do+ insert (Rel (CleanupWatcher @c (pure . pure ())) e.entity) e.target+ removeRel @(CleanupWatcher c) e.entity e.target++newtype CleanupWatcher c = CleanupWatcher {function :: CleanupRequest -> System ()}++data CleanupRequest = CleanupRequest+ { entity :: Entity,+ target :: Entity+ }++instance (Component c) => Component (CleanupWatcher c) where+ onRemoveRel = [hookRel (triggerCleanup @c)]++triggerCleanup :: forall c. (Component c) => HookContextRel -> System ()+triggerCleanup e = do+ Just watcher <- single $ mkGet e.entity (R @(CleanupWatcher c) e.target)+ watcher.comp.function CleanupRequest {entity = e.target, target = e.entity}
− src/Mischief/ECS/Interval.hs
@@ -1,30 +0,0 @@-module Mischief.ECS.Interval (start, stop, Interval) where--import Control.Concurrent (threadDelay)-import Control.Concurrent.Async (async)-import Control.Concurrent.STM-import Control.Monad-import Control.Monad.IO.Class-import Data.IORef-import Mischief.ECS.World--newtype Interval = Interval (IORef Bool)--start :: (MonadSystem w m) => Int -> System () -> m Interval-start t s = do- world <- unsafeGetWorld- i <- Interval <$> liftIO (newIORef False)- _ <- liftIO $ async $ run world i t s- return i--run :: World -> Interval -> Int -> System () -> IO ()-run world (Interval break) t s = do- b <- liftIO $ readIORef break- unless b $ do- liftIO $ threadDelay t-- liftIO $ atomically $ modifyTVar' world.deferredAsync (++ [s])- run world (Interval break) t s--stop :: (MonadSystem w m) => Interval -> m ()-stop (Interval break) = liftIO $ writeIORef break True
src/Mischief/ECS/Log.hs view
@@ -3,34 +3,25 @@ import Colog qualified import Control.Monad.IO.Class import Data.Text (Text)-import Data.Text qualified as Text import GHC.Stack import Mischief.ECS.World debug :: (HasCallStack) => (MonadSystem w m) => Text -> m ()-debug msg = withFrozenCallStack $ do- world <- unsafeGetWorld+debug !msg = withFrozenCallStack $ do+ !world <- unsafeGetWorld liftIO $ Colog.usingLoggerT world.logger $ Colog.logDebug msg info :: (HasCallStack) => (MonadSystem w m) => Text -> m ()-info msg = withFrozenCallStack $ do- world <- unsafeGetWorld+info !msg = withFrozenCallStack $ do+ !world <- unsafeGetWorld liftIO $ Colog.usingLoggerT world.logger $ Colog.logInfo msg warn :: (HasCallStack) => (MonadSystem w m) => Text -> m ()-warn msg = withFrozenCallStack $ do- world <- unsafeGetWorld+warn !msg = withFrozenCallStack $ do+ !world <- unsafeGetWorld liftIO $ Colog.usingLoggerT world.logger $ Colog.logWarning msg err :: (HasCallStack) => (MonadSystem w m) => Text -> m ()-err msg = withFrozenCallStack $ do- world <- unsafeGetWorld- liftIO $ Colog.usingLoggerT world.logger $ Colog.logError msg--panic :: (HasCallStack) => (MonadSystem w m) => Text -> m ()-panic msg = withFrozenCallStack $ do- err msg- undefined--text :: (Show a) => a -> Text-text = Text.pack . show+err !msg = withFrozenCallStack $ do+ !world <- unsafeGetWorld+ liftIO $ Colog.usingLoggerT world.loggerErr $ Colog.logError msg
− src/Mischief/ECS/Mappable.hs
@@ -1,106 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}--module Mischief.ECS.Mappable where--import Mischief.ECS.Components-import Mischief.ECS.Entities-import Mischief.ECS.Tables--class Mappable flag a b | flag a -> b where- mapTuple :: a -> b--class TryMapId flag flag' c out | flag flag' c -> out where- tryMapId :: c -> out--data RelQueryOutput out = RelQueryOutput out | InvalidRelQueryOutput--type family MapIsId c where- MapIsId (Maybe a) = False- MapIsId (Result a) = False- MapIsId (RelQueryOutput a) = False- MapIsId [a] = False- MapIsId a = True--data MapQueryVal--instance TryMapId True MapQueryVal a a where- tryMapId = id--instance TryMapId False MapQueryVal (Result a) a where- tryMapId = value--instance (TryMapId (MapIsId a) MapQueryVal a out) => TryMapId False MapQueryVal (Maybe a) (Maybe out) where- tryMapId = fmap (tryMapId @(MapIsId a) @MapQueryVal)--instance (TryMapId (MapIsId a) MapQueryVal a out) => TryMapId False MapQueryVal [a] [out] where- tryMapId = map (tryMapId @(MapIsId a) @MapQueryVal)--instance (TryMapId (MapIsId a) MapQueryVal a out) => TryMapId False MapQueryVal (RelQueryOutput a) (RelQueryOutput out) where- tryMapId (RelQueryOutput a) = RelQueryOutput $ tryMapId @(MapIsId a) @MapQueryVal a- tryMapId InvalidRelQueryOutput = InvalidRelQueryOutput--instance {-# OVERLAPPABLE #-} (TryMapId (MapIsId a) flag a out) => Mappable flag a out where- mapTuple = tryMapId @(MapIsId a) @flag--data MapQueryValidity--instance TryMapId True MapQueryValidity a a where- tryMapId = id--instance TryMapId False MapQueryValidity (Result a) (Result a) where- tryMapId = id--instance (TryMapId (MapIsId a) MapQueryValidity a out) => TryMapId False MapQueryValidity (RelQueryOutput a) a where- tryMapId (RelQueryOutput a) = a- tryMapId InvalidRelQueryOutput = undefined--instance (TryMapId (MapIsId a) MapQueryValidity a out) => TryMapId False MapQueryValidity (Maybe a) (Maybe out) where- tryMapId = fmap (tryMapId @(MapIsId a) @MapQueryValidity)--instance (TryMapId (MapIsId a) MapQueryValidity a out) => TryMapId False MapQueryValidity [a] [out] where- tryMapId = map (tryMapId @(MapIsId a) @MapQueryValidity)---- instance {-# OVERLAPPABLE #-} (TryMapId (MapIsId a) MapQueryValidity a out) => Mappable flag a out where--- mapTuple = tryMapId @(MapIsId a) @MapQueryValidity--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1) => Mappable flag (a0, a1) (b0, b1) where- mapTuple (a0, a1) = (mapTuple @flag a0, mapTuple @flag a1)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2) => Mappable flag (a0, a1, a2) (b0, b1, b2) where- mapTuple (a0, a1, a2) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3) => Mappable flag (a0, a1, a2, a3) (b0, b1, b2, b3) where- mapTuple (a0, a1, a2, a3) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4) => Mappable flag (a0, a1, a2, a3, a4) (b0, b1, b2, b3, b4) where- mapTuple (a0, a1, a2, a3, a4) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4, Mappable flag a5 b5) => Mappable flag (a0, a1, a2, a3, a4, a5) (b0, b1, b2, b3, b4, b5) where- mapTuple (a0, a1, a2, a3, a4, a5) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4, mapTuple @flag a5)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4, Mappable flag a5 b5, Mappable flag a6 b6) => Mappable flag (a0, a1, a2, a3, a4, a5, a6) (b0, b1, b2, b3, b4, b5, b6) where- mapTuple (a0, a1, a2, a3, a4, a5, a6) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4, mapTuple @flag a5, mapTuple @flag a6)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4, Mappable flag a5 b5, Mappable flag a6 b6, Mappable flag a7 b7) => Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7) (b0, b1, b2, b3, b4, b5, b6, b7) where- mapTuple (a0, a1, a2, a3, a4, a5, a6, a7) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4, mapTuple @flag a5, mapTuple @flag a6, mapTuple @flag a7)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4, Mappable flag a5 b5, Mappable flag a6 b6, Mappable flag a7 b7, Mappable flag a8 b8) => Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8) (b0, b1, b2, b3, b4, b5, b6, b7, b8) where- mapTuple (a0, a1, a2, a3, a4, a5, a6, a7, a8) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4, mapTuple @flag a5, mapTuple @flag a6, mapTuple @flag a7, mapTuple @flag a8)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4, Mappable flag a5 b5, Mappable flag a6 b6, Mappable flag a7 b7, Mappable flag a8 b8, Mappable flag a9 b9) => Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9) where- mapTuple (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4, mapTuple @flag a5, mapTuple @flag a6, mapTuple @flag a7, mapTuple @flag a8, mapTuple @flag a9)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4, Mappable flag a5 b5, Mappable flag a6 b6, Mappable flag a7 b7, Mappable flag a8 b8, Mappable flag a9 b9, Mappable flag a10 b10) => Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10) where- mapTuple (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4, mapTuple @flag a5, mapTuple @flag a6, mapTuple @flag a7, mapTuple @flag a8, mapTuple @flag a9, mapTuple @flag a10)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4, Mappable flag a5 b5, Mappable flag a6 b6, Mappable flag a7 b7, Mappable flag a8 b8, Mappable flag a9 b9, Mappable flag a10 b10, Mappable flag a11 b11) => Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11) where- mapTuple (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4, mapTuple @flag a5, mapTuple @flag a6, mapTuple @flag a7, mapTuple @flag a8, mapTuple @flag a9, mapTuple @flag a10, mapTuple @flag a11)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4, Mappable flag a5 b5, Mappable flag a6 b6, Mappable flag a7 b7, Mappable flag a8 b8, Mappable flag a9 b9, Mappable flag a10 b10, Mappable flag a11 b11, Mappable flag a12 b12) => Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12) where- mapTuple (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4, mapTuple @flag a5, mapTuple @flag a6, mapTuple @flag a7, mapTuple @flag a8, mapTuple @flag a9, mapTuple @flag a10, mapTuple @flag a11, mapTuple @flag a12)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4, Mappable flag a5 b5, Mappable flag a6 b6, Mappable flag a7 b7, Mappable flag a8 b8, Mappable flag a9 b9, Mappable flag a10 b10, Mappable flag a11 b11, Mappable flag a12 b12, Mappable flag a13 b13) => Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13) where- mapTuple (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4, mapTuple @flag a5, mapTuple @flag a6, mapTuple @flag a7, mapTuple @flag a8, mapTuple @flag a9, mapTuple @flag a10, mapTuple @flag a11, mapTuple @flag a12, mapTuple @flag a13)--instance {-# OVERLAPPING #-} (Mappable flag a0 b0, Mappable flag a1 b1, Mappable flag a2 b2, Mappable flag a3 b3, Mappable flag a4 b4, Mappable flag a5 b5, Mappable flag a6 b6, Mappable flag a7 b7, Mappable flag a8 b8, Mappable flag a9 b9, Mappable flag a10 b10, Mappable flag a11 b11, Mappable flag a12 b12, Mappable flag a13 b13, Mappable flag a14 b14) => Mappable flag (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) (b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14) where- mapTuple (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) = (mapTuple @flag a0, mapTuple @flag a1, mapTuple @flag a2, mapTuple @flag a3, mapTuple @flag a4, mapTuple @flag a5, mapTuple @flag a6, mapTuple @flag a7, mapTuple @flag a8, mapTuple @flag a9, mapTuple @flag a10, mapTuple @flag a11, mapTuple @flag a12, mapTuple @flag a13, mapTuple @flag a14)
src/Mischief/ECS/Messages.hs view
@@ -9,26 +9,18 @@ where import Control.Monad.IO.Class-import Control.Monad.Reader (MonadReader (..)) import Data.Data (Typeable) import Data.IORef import Data.Kind import Data.Map (Map) import Data.Map qualified as Map-import Mischief.ECS.App import Mischief.ECS.App.SystemDef import Mischief.ECS.App.Systems import Mischief.ECS.Components-import Mischief.ECS.Log import Mischief.ECS.Resources-import Mischief.ECS.Tables-import Mischief.ECS.Utils import Mischief.ECS.World-import Mischief.ECS.World.Insert-import Mischief.ECS.World.Modify-import Mischief.ECS.World.Query (get)+import Mischief.ECS.World.Query (mkGet, single) import Mischief.ECS.World.Query.Markers-import Mischief.ECS.World.Query.Queryable import Prelude hiding (read) -- | Message typeclass.@@ -50,7 +42,7 @@ case Map.lookup world.systemId m.readers of Just r -> return r Nothing -> do- tick <- liftIO $ newIORef $ Tick (0, 0)+ tick <- liftIO $ newIORef $ Tick 0 insertRes $ (\Messages {messages, readers} -> Messages {messages, readers = Map.insert world.systemId (Reader tick) readers}) m return $ Reader tick @@ -59,16 +51,17 @@ -- | Write a message. write :: forall m. (Message m) => m -> System () write !message = do- messages <- resOrInsert $ newMessages @m+ messages <- resOrInsert (newMessages @m) world <- unsafeGetWorld frame <- liftIO $ readIORef world.frame loc <- self- Just currentSystemTick <- get (C @SystemTick) loc+ Just currentSystemTick <- single $ mkGet loc (C @SystemTick) let message' = (frame, currentSystemTick.inner, message)- modify messages (\Messages {messages, readers} -> Messages {messages = message' : messages, readers})- clearOldMessages messages+ let newMessages = (\Messages {messages, readers} -> Messages {messages = message' : messages, readers}) messages+ insertRes newMessages+ clearOldMessages newMessages -- | Read all the messages that haven't been read by the current system. read :: forall m. (Message m) => System [m]@@ -81,7 +74,7 @@ readerTick <- liftIO $ readIORef tick loc <- self- Just currentSystemTick <- get (C @SystemTick) loc+ Just currentSystemTick <- single $ mkGet loc (C @SystemTick) let newMessages = map (\(_, _, x) -> x) $ filter (\(_, tick, _) -> tick < currentSystemTick.inner && tick > readerTick) m.messages liftIO $ writeIORef tick currentSystemTick.inner@@ -92,8 +85,8 @@ add :: forall (m :: Type). (Message m) => System () add = insertRes $ newMessages @m -clearOldMessages :: (Message m) => Result (Messages m) -> System ()+clearOldMessages :: (Message m) => Messages m -> System () clearOldMessages !m = do world <- unsafeGetWorld frame <- liftIO $ readIORef world.frame- modify m (\Messages {messages, readers} -> Messages {messages = filter (\(Frame x, _, _) -> Frame (x + 2) >= frame) messages, readers})+ insertRes $ (\Messages {messages, readers} -> Messages {messages = filter (\(Frame x, _, _) -> Frame (x + 2) >= frame) messages, readers}) m
src/Mischief/ECS/Observer.hs view
@@ -10,11 +10,6 @@ newtype Observer e = Observer (e -> System ()) instance (Typeable e) => Component (Observer e) where- required = require @(EventProxy e, ObserverOrder)--newtype ObserverOrder = ObserverOrder Int- deriving (Show)- deriving anyclass (Component)- deriving newtype (Eq, Ord, Default)+ required = require @(EventProxy e) data EventProxy e = EventProxy deriving (Component, Generic, Default)
− src/Mischief/ECS/Observers.hs
@@ -1,10 +0,0 @@-module Mischief.ECS.Observers where--import Mischief.ECS.Entities-import Mischief.ECS.EventDef-import Mischief.ECS.Observer-import Mischief.ECS.World-import Mischief.ECS.World.Spawn qualified--spawn :: (Event e) => (e -> System ()) -> System Entity-spawn x = Mischief.ECS.World.Spawn.spawn $ Observer x
− src/Mischief/ECS/Prelude.hs
@@ -1,57 +0,0 @@-module Mischief.ECS.Prelude- ( module Mischief.ECS.Components,- module Mischief.ECS.Components.Required,- module Mischief.ECS.Components.Common,- module Mischief.ECS.Entities,- module Mischief.ECS.World.Query,- module Mischief.ECS.World.Query.QueryFilter,- module Mischief.ECS.World.Query.Markers,- module Mischief.ECS.World.Query.TH,- module Mischief.ECS.World.Insert,- module Mischief.ECS.World.Remove,- module Mischief.ECS.World.Modify,- module Mischief.ECS.World.Spawn,- module Mischief.ECS.Resources,- module Mischief.ECS.EventDef,- module Mischief.ECS.World,- module Mischief.ECS.Time,- module Mischief.ECS.World.Defer,- module Mischief.ECS.App,- module Mischief.ECS.App.Plugins,- module Mischief.ECS.App.Schedules,- module Mischief.ECS.App.SystemConfig,- module Mischief.ECS.Log,- module Mischief.ECS.Utils,- module Mischief.ECS.World.Utils,- module Mischief.ECS.Events,- module Mischief.ECS.World.Query.QueryType,- )-where--import Mischief.ECS.App (newApp, runApp)-import Mischief.ECS.App.Plugins (Plugin (..), plug)-import Mischief.ECS.App.Schedules (First (..), PostStartup (..), PostUpdate (..), PreStartup (..), PreUpdate (..), Startup (..), Update (..))-import Mischief.ECS.App.SystemConfig (after, before)-import Mischief.ECS.Components (Component (..), Exclusivity (..), Rel (..))-import Mischief.ECS.Components.Common (Name (..))-import Mischief.ECS.Components.Required (require)-import Mischief.ECS.Entities (Entity)-import Mischief.ECS.EventDef (Event)-import Mischief.ECS.Events (trigger)-import Mischief.ECS.Log-import Mischief.ECS.Resources-import Mischief.ECS.Time (Time, TimePlugin (..), deltaSecs, deltaTime)-import Mischief.ECS.Utils (unwrap)-import Mischief.ECS.World (System)-import Mischief.ECS.World.Defer (defer, delay, runAfter)-import Mischief.ECS.World.Insert (insert, insertIfNeq, insertNew, set, setIfNeq, update)-import Mischief.ECS.World.Modify (modify, modify')-import Mischief.ECS.World.Query (get, get', query, query', single, single')-import Mischief.ECS.World.Query.Markers (Any (..), C (..), E (..), Has (..), HasR (..), M (..), MR (..), Q (..), R (..), Val (..))-import Mischief.ECS.World.Query.QueryFilter (Added (..), Changed (..), Check (..), CheckR (..), Not (..), With (..), Without (..), (|.))-import Mischief.ECS.World.Query.QueryType (QueryType)-import Mischief.ECS.World.Query.Queryable ()-import Mischief.ECS.World.Query.TH (g, q, s)-import Mischief.ECS.World.Remove (remove)-import Mischief.ECS.World.Spawn (despawn, spawn, spawnDefer)-import Mischief.ECS.World.Utils (expect)
− src/Mischief/ECS/Relationships.hs
@@ -1,7 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}--module Mischief.ECS.Relationships where--import Mischief.ECS.Components--data IsExclusiveRelationship = IsExclusiveRelationship deriving (Show, Component)
src/Mischief/ECS/Relationships/ChildOf.hs view
@@ -4,20 +4,18 @@ import Mischief.ECS.Entities import Mischief.ECS.Relationships.Graph import Mischief.ECS.World-import Mischief.ECS.World.Query-import Mischief.ECS.World.Query.Queryable data ChildOf = ChildOf deriving (Show) instance Component ChildOf where- type RelExclusivity ChildOf = Exclusive+ type IsExclusiveRel ChildOf = True --- parent :: forall m w. (MonadSystem w m) => Entity -> m (Maybe Entity)--- parent entity = do--- p <- outgoing @ChildOf entity--- return $ case p of--- [p] -> Just p--- _ -> Nothing+parent :: forall m w. (MonadSystem w m) => Entity -> m (Maybe Entity)+parent !entity = do+ !p <- outgoing' @ChildOf entity+ pure $ case p of+ [!p] -> Just p+ _ -> Nothing --- children :: forall m w. (MonadSystem w m) => Entity -> m [Entity]--- children = outgoing @ChildOf+children :: forall m w. (MonadSystem w m) => Entity -> m [Entity]+children = outgoing' @ChildOf
src/Mischief/ECS/Relationships/Graph.hs view
@@ -3,21 +3,38 @@ module Mischief.ECS.Relationships.Graph where +import Data.List import Mischief.ECS.Components import Mischief.ECS.Components.BundleTypes import Mischief.ECS.Entities-import Mischief.ECS.Tables import Mischief.ECS.World import Mischief.ECS.World.Query-import Mischief.ECS.World.Query.QueryFilter-import Mischief.ECS.World.Query.Queryable+import Mischief.ECS.World.Query.Markers+import Mischief.ECS.World.Query.QueryFilter (QueryFilter (With)) --- outgoing :: forall c m w. (Component c, MonadSystem w m) => Entity -> m [Entity]--- outgoing entity = do--- next <- get (R @c Any) entity--- return $ case next of--- Nothing -> []--- Just next -> map (\x -> x.target) next+type family RelOutgoing b where+ RelOutgoing True = Maybe+ RelOutgoing False = List --- ingoing :: forall c m w. (Component c, BundleTypes c, MonadSystem w m) => Entity -> m [Entity]--- ingoing entity = query' E (With (R @c entity))+class ListToOutgoing b where+ listToOutgoing :: [Entity] -> b++instance ListToOutgoing (Maybe Entity) where+ listToOutgoing [!x] = Just x+ listToOutgoing _ = Nothing++instance ListToOutgoing [Entity] where+ listToOutgoing = id++outgoing' :: forall c m w. (Component c, MonadSystem w m) => Entity -> m [Entity]+outgoing' !entity = do+ !next <- single $ mkGet entity (R' @c Any)+ pure $ case next of+ Nothing -> []+ Just !next -> map (\x -> x.target) next++outgoing :: forall c m w. (Component c, MonadSystem w m, ListToOutgoing (RelOutgoing (IsExclusiveRel c) Entity)) => Entity -> m (RelOutgoing (IsExclusiveRel c) Entity)+outgoing !entity = listToOutgoing <$> outgoing' @c entity++incoming :: forall c m w. (Component c, BundleTypes c, MonadSystem w m) => Entity -> m [Entity]+incoming !entity = query $ mkQuery' E (With (R @c entity))
src/Mischief/ECS/Relationships/Order.hs view
@@ -6,41 +6,36 @@ import Data.Set qualified as Set import Mischief.ECS.Components import Mischief.ECS.Entities-import Mischief.ECS.Log-import Mischief.ECS.Tables import Mischief.ECS.Utils import Mischief.ECS.World import Mischief.ECS.World.Insert import Mischief.ECS.World.Query import Mischief.ECS.World.Query.Markers import Mischief.ECS.World.Query.QueryFilter-import Mischief.ECS.World.Query.Queryable import Mischief.ECS.World.Remove-import Mischief.ECS.World.Spawn-import Mischief.ECS.World.Utils data Before = Before deriving (Component) data Visited = Visited deriving (Component) orderEntities :: [Entity] -> System [Entity]-orderEntities entities = do- res <- orderEntitiesStep (Set.fromList entities)+orderEntities !entities = do+ !res <- orderEntitiesStep (Set.fromList entities) for_ entities $ remove (C @Visited) -- err $ text res- return res+ pure res orderEntitiesStep :: Set Entity -> System [Entity]-orderEntitiesStep entities =+orderEntitiesStep !entities = if null entities then return [] else do- next <- expect "Attempted to Order Cyclic Graph!" =<< findM isAvailable entities+ !next <- expect "Attempted to Order Cyclic Graph!" <$> findM isAvailable entities insert Visited next (next :) <$> orderEntitiesStep (Set.delete next entities) isAvailable :: Entity -> System Bool-isAvailable entity = do- before <- query' E (With (R @Before entity))- isNothing <$> findM ((not . unwrap <$>) . get (Has @Visited)) before+isAvailable !entity = do+ !before <- query $ mkQuery' E (With (R @Before entity))+ isNothing <$> findM (\(!x) -> (not . unwrap <$>) $ single $ mkGet x (Has @Visited)) before
src/Mischief/ECS/Relationships/Tree.hs view
@@ -8,31 +8,34 @@ import Mischief.ECS.Relationships.Graph import Mischief.ECS.World --- descendants :: forall c m w. (Component c, BundleTypes c, MonadSystem w m) => Entity -> m [Entity]--- descendants entity = do--- next <- ingoing @c entity--- next' <- mapM (descendants @c) next--- return $ next ++ concat next'+descendants :: forall c m w. (Component c, BundleTypes c, MonadSystem w m) => Entity -> m [Entity]+descendants !entity = do+ !next <- incoming @c entity+ !next' <- mapM (descendants @c) next+ pure $ next ++ concat next' --- anestors :: forall c m w. (Component c, MonadSystem w m) => Entity -> m [Entity]--- anestors entity = do--- next <- outgoing @c entity--- x <- mapM (anestors @c) next--- return $ concat (next : x)+ancestors' :: forall c m w. (Component c, MonadSystem w m) => Entity -> m [Entity]+ancestors' !entity = do+ !next <- outgoing' @c entity+ !x <- mapM (ancestors' @c) next+ pure $ concat (next : x) --- root :: forall c m w. (Component c, MonadSystem w m) => Entity -> m Entity--- root entity = do--- out <- outgoing @c entity--- case out of--- [] -> return entity--- [p] -> root @c p--- _ -> undefined+ancestors :: forall c m w. (Component c, MonadSystem w m, ListToOutgoing (RelOutgoing (IsExclusiveRel c) Entity)) => Entity -> m (RelOutgoing (IsExclusiveRel c) Entity)+ancestors !e = listToOutgoing <$> ancestors' @c e --- leaves :: forall c m w. (Component c, BundleTypes c, MonadSystem w m) => Entity -> m [Entity]--- leaves entity = do--- ing <- ingoing @c entity--- case ing of--- [] -> return [entity]--- l -> do--- l' <- mapM (leaves @c) l--- return $ concat l'+root :: forall c m w. (Component c, MonadSystem w m) => Entity -> m (Maybe Entity)+root !entity = do+ !out <- outgoing' @c entity+ case out of+ [] -> pure $ Just entity+ [!p] -> root @c p+ _ -> undefined++leaves :: forall c m w. (Component c, BundleTypes c, MonadSystem w m) => Entity -> m [Entity]+leaves !entity = do+ !ing <- incoming @c entity+ case ing of+ [] -> return [entity]+ !l -> do+ !l' <- mapM (leaves @c) l+ pure $ concat l'
+ src/Mischief/ECS/Repeater.hs view
@@ -0,0 +1,47 @@+module Mischief.ECS.Repeater (Repeater (..)) where++import Control.Concurrent (threadDelay)+import Control.Concurrent.Async (async)+import Control.Concurrent.STM+import Control.Monad+import Control.Monad.IO.Class+import Data.IORef+import GHC.Float (float2Int)+import Mischief.ECS.Components+import Mischief.ECS.Components.HooksDef+import Mischief.ECS.Hooks+import Mischief.ECS.World+import Mischief.ECS.World.Insert+import Mischief.ECS.World.Query+import Mischief.ECS.World.Query.Markers++data Repeater = Repeater Float (System ())++newtype RepeaterRef = RepeaterRef (IORef Bool) deriving anyclass (Component)++instance Component Repeater where+ onAdd =+ [ hook $ \(HookContext e) -> do+ i <- liftIO (newIORef False)+ Just (Repeater t s) <- single $ mkGet e (C @Repeater)+ insert (RepeaterRef i) e+ world <- unsafeGetWorld+ void $ liftIO $ async $ run world i (float2Int $ 1000000 * t) s+ ]++ onRemove =+ [ hook $ \(HookContext e) -> do+ break <- single $ mkGet e (C @RepeaterRef)+ case break of+ Nothing -> pure ()+ Just (RepeaterRef break) -> liftIO $ writeIORef break False+ ]++run :: World -> IORef Bool -> Int -> System () -> IO ()+run world break t s = do+ b <- liftIO $ readIORef break+ unless b $ do+ liftIO $ threadDelay t++ liftIO $ atomically $ modifyTVar' world.deferredAsync (++ [s])+ run world break t s
src/Mischief/ECS/Resources.hs view
@@ -1,28 +1,70 @@-module Mischief.ECS.Resources where+module Mischief.ECS.Resources+ ( insertRes,+ res,+ resOrInsert,+ resOrInsertM,+ modifyRes,+ modifyRes',+ )+where +import Data.Maybe import Mischief.ECS.Components import Mischief.ECS.Components.Bundle import Mischief.ECS.Components.Spawn-import Mischief.ECS.Tables import Mischief.ECS.World import Mischief.ECS.World.Insert import Mischief.ECS.World.Query import Mischief.ECS.World.Query.Markers-import Mischief.ECS.World.Query.QueryType-import Mischief.ECS.World.Query.Queryable --- | Insert a resource into this world. If the resource already exists, its value will be overwritten.+-- | Insert a resource into the World. If the resource already exists, its value will be overwritten. insertRes :: forall r. (Component r, Bundle r) => r -> System () insertRes res = do entity <- meta @r insert res entity -res :: forall c. (QueryType c) => System (Maybe c)+-- | Get the value of a resource from the World.+res :: forall c m w. (MonadSystem w m) => (Component c) => m (Maybe c) res = do- meta <- meta @c- get (Val (C @c)) meta+ meta <- tryMeta @c+ case meta of+ Nothing -> pure Nothing+ Just meta -> do+ single $ mkGet meta (C @c) -resOrInsert :: forall r. (Component r, Updateable (Result r), Bundle r) => r -> System (Result r)+-- | Get the value of a resource, or insert a resource if it doesn't exist.+resOrInsert :: forall r. (Component r, Bundle r) => r -> System r resOrInsert r = do meta <- meta @r- getOrInsert r meta+ x <- single $ mkGet meta (C @r)+ case x of+ Nothing -> do+ insertRes r+ pure $! r+ Just x -> pure $! x++-- | Same as 'resOrInsert' but allows the default resource to be produced by a System.+resOrInsertM :: forall r. (Component r, Bundle r) => System r -> System r+resOrInsertM r = do+ meta <- meta @r+ x <- single $ mkGet meta (C @r)+ case x of+ Nothing -> do+ r <- r+ insertRes r+ pure r+ Just x -> pure x++-- | Modify the value of a resource if it exists, otherwise insert the result of the function.+modifyRes :: forall r. (Component r, Bundle r) => (Maybe r -> r) -> System ()+modifyRes f = do+ x <- res @r+ insertRes $ f x++-- | Modify the value of a resource if it exists, otherwise do nothing.+modifyRes' :: forall r. (Component r, Bundle r) => (r -> r) -> System ()+modifyRes' f = do+ x <- res @r+ case x of+ Nothing -> pure ()+ Just x -> insertRes $ f x
src/Mischief/ECS/Schedules.hs view
@@ -1,12 +1,15 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+ module Mischief.ECS.Schedules where +import Language.Haskell.TH (Extension (AllowAmbiguousTypes)) import Mischief.ECS.App import Mischief.ECS.App.Schedules import Mischief.ECS.Entities import Mischief.ECS.World -get :: (Schedule sc) => sc -> System Entity-get = scheduleEntity+get :: forall sc. (Schedule sc) => System Entity+get = scheduleEntity @sc -run :: (Schedule sc) => sc -> System ()-run = runSchedule+run :: forall sc. (Schedule sc) => System ()+run = runSchedule @sc
src/Mischief/ECS/Stdin.hs view
@@ -11,6 +11,7 @@ hSetBuffering stdin NoBuffering hSetEcho stdin False +-- | Gets the characters inputted by the user into the terminal since the lasts call. Only to be used for examples and test applications. Make sure to call 'Mischief.ECS.Stdin.init' first. read :: System String read = do ready <- liftIO $ hReady stdin@@ -21,6 +22,7 @@ else do return [] +-- | Gets the last character inputted by the user into the terminal. Only to be used for examples and test applications. Make sure to call 'Mischief.ECS.Stdin.init' first. readLast :: System (Maybe Char) readLast = do r <- read
src/Mischief/ECS/Systems.hs view
@@ -1,73 +1,103 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+ -- | -- Module with utility functions for creating -- and scheduling systems. module Mischief.ECS.Systems where import Data.Foldable-import Data.Kind-import GHC.Stack.Types import Mischief.ECS.App.Schedules-import Mischief.ECS.App.SystemConfig-import Mischief.ECS.App.Systems (ScheduledIn (ScheduledIn), SystemFunction (SystemFunction), removeSystemFromMap, systemEntity)+import Mischief.ECS.App.Systems (ScheduledIn, SystemFunction (SystemFunction), removeSystemFromMap, systemEntity) import Mischief.ECS.Collectable import Mischief.ECS.Components import Mischief.ECS.Entities-import Mischief.ECS.Mappable import Mischief.ECS.Relationships.Order import Mischief.ECS.World import Mischief.ECS.World.Insert import Mischief.ECS.World.Query import Mischief.ECS.World.Query.Markers import Mischief.ECS.World.Query.QueryFilter-import Mischief.ECS.World.Query.Queryable import Mischief.ECS.World.Remove import Mischief.ECS.World.Spawn import Mischief.ECS.World.Spawn qualified as Spawn-import Mischief.ECS.World.Utils -add :: (HasCallStack, Schedule sc, SystemConfig s) => sc -> s -> System ()-add schedule system = do- let SystemConfigData {systems, edges} = systemConfigData system+data SystemConfig = SystemConfig+ { systems :: [System ()],+ edges :: [(System (), System ())]+ } - for_ systems $ \system -> systemEntity schedule system+newtype Systems = Systems [System ()] deriving newtype (Semigroup) +instance EraseIntoStorage (System ()) Systems where+ erase a = Systems [a]++type ToSystems a = Collectable a Systems++systems :: (ToSystems a) => a -> SystemConfig+systems a =+ let Systems systems = collect a+ in SystemConfig {systems, edges = []}++after :: (ToSystems a) => a -> SystemConfig -> SystemConfig+after a s =+ let Systems systems = collect a+ in SystemConfig {systems = s.systems, edges = s.edges ++ zip systems s.systems}++before :: (ToSystems a) => a -> SystemConfig -> SystemConfig+before a s =+ let Systems systems = collect a+ in SystemConfig {systems = s.systems, edges = s.edges ++ zip s.systems systems}++schedule :: forall sc. (Schedule sc) => SystemConfig -> System ()+schedule SystemConfig {systems, edges} = do+ for_ systems $ \system -> systemEntity @sc system+ for_ edges $ \(s1, s2) -> do- id1 <- systemEntity schedule s1- id2 <- systemEntity schedule s2+ id1 <- systemEntity @sc s1+ id2 <- systemEntity @sc s2 insert (Rel Before id2) id1 -remove :: (Schedule sc, Collectable a [System ()]) => sc -> a -> System ()-remove schedule systems = do- let y :: [System ()] = collect systems- for_ y (removeOne schedule)--removeOne :: (Schedule sc) => sc -> System () -> System ()-removeOne schedule system = do- s <- Mischief.ECS.Systems.get schedule system- sch <- scheduleEntity schedule- removeSystemFromMap (ScheduleId sch) system+order :: forall sc a b. (Schedule sc, ToSystems a, ToSystems b) => (a, b) -> System ()+order (s1, s2) = do+ let Systems systems1 = collect s1+ let Systems systems2 = collect s2 - despawn s+ for_ systems1 $ \s1 -> for_ systems2 $ \s2 -> do+ s1 <- Mischief.ECS.Systems.get @sc s1+ s2 <- Mischief.ECS.Systems.get @sc s2 - query' E (With (R @Before s))- >>= traverse_ (removeRel @Before s)+ insert (Rel Before s2) s1 spawn :: System () -> System Entity spawn = Spawn.spawn . SystemFunction -get :: (Schedule sc) => sc -> System () -> System Entity-get = systemEntity+get :: forall sc. (Schedule sc) => System () -> System Entity+get = systemEntity @sc -schedule :: (Schedule sc) => sc -> System () -> System ()-schedule sch s = do- s' <- Mischief.ECS.Systems.get sch s- sch' <- scheduleEntity sch+-- | Disconnect a system from a schedule. This will keep the system's entity alive, as well as all its other relationships and data,+-- so that if scheduled again it will work the same as before. Use this if you plan to dynamically schedule and unschedule a system. Otherwise check 'unschedule''.+unschedule :: forall sc a. (Schedule sc, ToSystems a) => a -> System ()+unschedule s = do+ sch' <- scheduleEntity @sc - insert (Rel ScheduledIn sch') s'+ let Systems systems = collect s+ for_ systems $ \s -> do+ s' <- Mischief.ECS.Systems.get @sc s -unschedule :: (Schedule sc) => sc -> System () -> System ()-unschedule sch s = do- s' <- Mischief.ECS.Systems.get sch s- sch' <- scheduleEntity sch+ removeRel @ScheduledIn sch' s' - removeRel @ScheduledIn sch' s'+-- | Disconnect a system rom a schedule. This will despawn the system's entity, losing all its data such as ordering with other systems.+-- Use this if you want to disable a scheduled system for good. Otherwise check 'unschedule'.+unschedule' :: forall sc a. (Schedule sc, ToSystems a) => a -> System ()+unschedule' systems = do+ sch <- scheduleEntity @sc++ let Systems y = collect systems+ for_ y $ \system -> do+ s <- Mischief.ECS.Systems.get @sc system+ removeSystemFromMap (ScheduleId sch) system++ despawn s++ query (mkQuery' E (With (R @Before s)))+ >>= traverse_ (removeRel @Before s)
src/Mischief/ECS/Tables.hs view
@@ -3,24 +3,19 @@ module Mischief.ECS.Tables where import Control.Monad (forM, when)-import Data.Foldable (Foldable (toList), find, for_)+import Data.Bifunctor qualified+import Data.Foldable (Foldable (toList), for_) import Data.IORef-import Data.Kind import Data.List (transpose) import Data.Map (Map) import Data.Map qualified as Map-import Data.Maybe (catMaybes, fromMaybe, isJust, isNothing)+import Data.Maybe (catMaybes) import Data.Traversable (for)-import Data.Typeable (Proxy (Proxy), eqT, typeRep, type (:~:) (Refl)) import Data.Vector qualified as Vector-import GHC.Base (Int (..), Word (W#), eqWord#, isTrue#)-import GHC.Records-import GHC.TypeLits+import GHC.Base (Int (..), eqWord#, isTrue#) import Mischief.ECS.Components import Mischief.ECS.Components.Bundle import Mischief.ECS.Entities-import Mischief.ECS.EntityDef (Entity# (Entity#), eqEntity#, liftEntity)-import Mischief.ECS.Utils import Mischief.ECS.Vec (IOVec) import Mischief.ECS.Vec qualified as Vec @@ -29,10 +24,11 @@ data Table = Table { columns :: IORef (Map ComponentId Column), components :: [ComponentId],+ ticks :: IORef (Map ComponentId ComponentTick), entities :: IOVec (Entity, IORef EntityPointer) } -newtype Column = Column (IOVec ComponentData)+newtype Column = Column (IOVec ErasedComponent) baseline_entities_cap :: Int baseline_entities_cap = 64@@ -52,8 +48,9 @@ columns <- newIORef $ Map.fromList (toList cols) entities <- Vec.new baseline_entities_cap+ ticks <- newIORef $ Map.fromList (map (,ComponentTick (Tick 0)) components) - return $ Table {columns = columns, components, entities}+ return $ Table {columns = columns, components, entities, ticks} tableIsEmpty :: Table -> IO Bool tableIsEmpty table = Vec.null table.entities@@ -81,26 +78,22 @@ tapMap map el.id $ \(Column col) -> Vec.pushBack col el.component -insertComponentsIntoTable :: ProcessedBundleData -> Table -> IO ()-insertComponentsIntoTable bundle table = do+insertComponentsIntoTable :: ProcessedBundleData -> Tick -> ChangedComponents -> Table -> IO ()+insertComponentsIntoTable bundle tick changed table = do+ for_ changed $ \a -> modifyIORef' table.ticks (Map.insert a (ComponentTick tick))+ cols <- readIORef table.columns insertComponentsIntoMap bundle cols replaceComponentsIntoMap :: ProcessedBundleData ->- -- | The current tick that will be as the components' change tick.- Maybe Tick -> EntityPointer -> Map ComponentId Column -> IO ()-replaceComponentsIntoMap bundle tick (EntityPointer (# archetypeId, rowIndex #)) tableMap = do+replaceComponentsIntoMap bundle (EntityPointer (# _, rowIndex #)) tableMap = do for_ bundle.elements $ \el -> tapMap tableMap el.id $ \(Column col) ->- case tick of- Just tick ->- Vec.modify_ col (I# rowIndex) (\ComponentData {value, ticks = ComponentTicks {changed, added}} -> ComponentData {value = el.component.value, ticks = ComponentTicks {changed = tick, added}})- Nothing ->- Vec.modify_ col (I# rowIndex) (\ComponentData {value, ticks} -> ComponentData {value = el.component.value, ticks})+ Vec.unsafeWrite col (I# rowIndex) el.component -- foldl' modifyMap tableMap bundle.elements -- where@@ -117,10 +110,15 @@ Table -> IO () replaceComponentsIntoTable bundle tick pointer table = do+ case tick of+ Nothing -> pure ()+ Just tick -> do+ for_ bundle.elements $ \a -> modifyIORef' table.ticks (Map.insert a.id (ComponentTick tick))+ cols <- readIORef table.columns- replaceComponentsIntoMap bundle tick pointer cols+ replaceComponentsIntoMap bundle pointer cols -takeFromColumn :: EntityPointer -> Column -> IO ComponentData+takeFromColumn :: EntityPointer -> Column -> IO ErasedComponent takeFromColumn (EntityPointer (# _, rowIndex #)) (Column col) = Vec.takeSwap col (I# rowIndex) takeComponentsFromTable :: EntityPointer -> Table -> IO ProcessedBundleData@@ -185,8 +183,12 @@ -- insertComponentsIntoTable bundle table -insertEntityIntoTables :: ProcessedBundleData -> Tables -> ArchetypeId -> (Entity, IORef EntityPointer) -> IO ()-insertEntityIntoTables bundle (Tables tables) archetype pointerRef =+type ChangedComponents = [ComponentId]++type AddedComponents = [ComponentId]++insertEntityIntoTables :: ProcessedBundleData -> Tick -> ChangedComponents -> Tables -> ArchetypeId -> (Entity, IORef EntityPointer) -> IO ()+insertEntityIntoTables bundle tick changed (Tables tables) archetype pointerRef = do table <- Vec.unsafeRead tables archetype.id @@ -197,15 +199,15 @@ let !(I# rowIndex') = rowIndex writeIORef (snd pointerRef) $ EntityPointer (# archetype', rowIndex' #) - insertComponentsIntoTable bundle table+ insertComponentsIntoTable bundle tick changed table tryGetComponentFromColumn :: forall c. (Component c) => Column -> EntityPointer -> IO (Maybe c) tryGetComponentFromColumn (Column components) (EntityPointer (# _, rowIndex #)) = do element <- Vec.unsafeRead components (I# rowIndex)- pure $ tryGetComponent element.value+ pure $ tryGetComponent element -tryGetRelCollectionFromTable :: forall c. (Component c) => Table -> Entity -> EntityPointer -> ComponentId -> IO (Maybe [Result (Rel c)])-tryGetRelCollectionFromTable table entity pointer (ComponentId (# id, target #)) =+tryGetRelCollectionFromTable :: forall c. (Component c) => Table -> Entity -> EntityPointer -> ComponentId -> IO (Maybe [(Entity, Rel c)])+tryGetRelCollectionFromTable table entity pointer (ComponentId (# id, _ #)) = do columns <- readIORef table.columns -- TODO: improve lookup performance for partial tuples@@ -224,7 +226,7 @@ then return Nothing else do- return $ Just $ map (\(value, target) -> Result (Rel value target, entity)) $ catMaybes components'+ return $ Just $ map (\(value, target) -> (entity, Rel value target)) $ catMaybes components' tryGetComponentFromTable :: forall c. (Component c) => Table -> EntityPointer -> ComponentId -> IO (Maybe c) tryGetComponentFromTable table pointer componentId =@@ -235,7 +237,7 @@ Nothing -> return Nothing Just column -> tryGetComponentFromColumn column pointer -tryGetRelCollectionFromTables :: forall c. (Component c) => Tables -> Entity -> EntityPointer -> ComponentId -> IO (Maybe ([Result (Rel c)]))+tryGetRelCollectionFromTables :: forall c. (Component c) => Tables -> Entity -> EntityPointer -> ComponentId -> IO (Maybe [(Entity, Rel c)]) tryGetRelCollectionFromTables (Tables tables) entity (EntityPointer (# archetypeId, rowIndex #)) componentId = do table <- Vec.unsafeRead tables (I# archetypeId)@@ -247,63 +249,58 @@ table <- Vec.unsafeRead tables (I# archetypeId) tryGetComponentFromTable table (EntityPointer (# archetypeId, rowIndex #)) componentId -tryGetTicksFromColumn :: Column -> IO [ComponentTicks]-tryGetTicksFromColumn (Column components) = do- frozen <- Vec.freeze components- let x = Vector.map (\x -> x.ticks) frozen- return $ Vector.toList x+-- tryGetEntityTicksFromColumn :: Column -> EntityPointer -> IO ComponentTicks+-- tryGetEntityTicksFromColumn (Column components) (EntityPointer (# _, rowIndex #)) = (\x -> x.ticks) <$> Vec.unsafeRead components (I# rowIndex) -tryGetEntityTicksFromColumn :: Column -> EntityPointer -> IO ComponentTicks-tryGetEntityTicksFromColumn (Column components) (EntityPointer (# _, rowIndex #)) = (\x -> x.ticks) <$> Vec.unsafeRead components (I# rowIndex)+tryGetTicksFromTable :: Table -> ComponentId -> IO (Maybe ComponentTick)+tryGetTicksFromTable table componentId = do+ ticks <- readIORef table.ticks+ pure $ Map.lookup componentId ticks -tryGetTicksFromTable :: Table -> ComponentId -> IO [Maybe ComponentTicks]-tryGetTicksFromTable table componentId =- do- columns <- readIORef table.columns- case Map.lookup componentId columns of- Nothing -> do- l <- Vec.length table.entities- return $ map (const Nothing) [1 .. l]- Just column -> do- map Just <$> tryGetTicksFromColumn column+-- do+-- columns <- readIORef table.columns+-- case Map.lookup componentId columns of+-- Nothing -> do+-- l <- Vec.length table.entities+-- return $ map (const Nothing) [1 .. l]+-- Just column -> do+-- map Just <$> tryGetTicksFromColumn column -tryGetEntityTicksFromTable :: Table -> EntityPointer -> ComponentId -> IO (Maybe ComponentTicks)-tryGetEntityTicksFromTable table pointer componentId =- do- columns <- readIORef table.columns- case Map.lookup componentId columns of- Nothing -> return Nothing- Just column -> Just <$> tryGetEntityTicksFromColumn column pointer+-- tryGetEntityTicksFromTable :: Table -> EntityPointer -> ComponentId -> IO (Maybe ComponentTicks)+-- tryGetEntityTicksFromTable table pointer componentId =+-- do+-- columns <- readIORef table.columns+-- case Map.lookup componentId columns of+-- Nothing -> return Nothing+-- Just column -> Just <$> tryGetEntityTicksFromColumn column pointer -tryGetTicksFromArchetype :: ArchetypeId -> IOVec Table -> ComponentId -> IO [Maybe ComponentTicks]+tryGetTicksFromArchetype :: ArchetypeId -> IOVec Table -> ComponentId -> IO (Maybe ComponentTick) tryGetTicksFromArchetype archetype tables componentId = do table <- Vec.unsafeRead tables archetype.id tryGetTicksFromTable table componentId -tryGetEntityTicksFromArchetype :: ArchetypeId -> IOVec Table -> EntityPointer -> ComponentId -> IO (Maybe ComponentTicks)-tryGetEntityTicksFromArchetype archetype tables pointer componentId = do- table <- Vec.unsafeRead tables archetype.id- tryGetEntityTicksFromTable table pointer componentId+-- tryGetEntityTicksFromArchetype :: ArchetypeId -> IOVec Table -> EntityPointer -> ComponentId -> IO (Maybe ComponentTicks)+-- tryGetEntityTicksFromArchetype archetype tables pointer componentId = do+-- table <- Vec.unsafeRead tables archetype.id+-- tryGetEntityTicksFromTable table pointer componentId -tryGetTicksFromTables :: Tables -> [ArchetypeId] -> ComponentId -> IO [Maybe ComponentTicks]+tryGetTicksFromTables :: Tables -> [ArchetypeId] -> ComponentId -> IO [Maybe ComponentTick] tryGetTicksFromTables (Tables tables) archetypes componentId =- do- results <- mapM (\archetype -> tryGetTicksFromArchetype archetype tables componentId) archetypes- return $ concat results+ mapM (\archetype -> tryGetTicksFromArchetype archetype tables componentId) archetypes -tryGetEntityTicksFromTables :: Tables -> EntityPointer -> ComponentId -> IO (Maybe ComponentTicks)-tryGetEntityTicksFromTables (Tables tables) (EntityPointer (# archetypeId, rowIndex #)) componentId =- do- tryGetEntityTicksFromArchetype (ArchetypeId $ I# archetypeId) tables (EntityPointer (# archetypeId, rowIndex #)) componentId+-- tryGetEntityTicksFromTables :: Tables -> EntityPointer -> ComponentId -> IO (Maybe ComponentTicks)+-- tryGetEntityTicksFromTables (Tables tables) (EntityPointer (# archetypeId, rowIndex #)) componentId =+-- do+-- tryGetEntityTicksFromArchetype (ArchetypeId $ I# archetypeId) tables (EntityPointer (# archetypeId, rowIndex #)) componentId tryGetComponentsFromColumn :: forall c. (Component c) => Column -> IO [c] tryGetComponentsFromColumn (Column components) = do frozen <- Vec.freeze components- let x = Vector.mapM (\x -> tryGetComponent x.value) frozen+ let x = Vector.mapM tryGetComponent frozen pure $ maybe [] Vector.toList x -tryGetRelCollectionsFromTable :: forall c. (Component c) => Table -> ComponentId -> IO [(Entity, [Result (Rel c)])]-tryGetRelCollectionsFromTable table (ComponentId (# id, target #)) =+tryGetRelCollectionsFromTable :: forall c. (Component c) => Table -> ComponentId -> IO [(Entity, [Rel c])]+tryGetRelCollectionsFromTable table (ComponentId (# id, _ #)) = do -- let Just entity = componentId.entity columns <- readIORef table.columns@@ -323,10 +320,10 @@ let components'' = zip (map fst entities) $ transpose $ catMaybes components' return $ map- (\(entity, components) -> (entity, map (\(value, target) -> Result (Rel value target, entity)) components))+ (Data.Bifunctor.second (map (uncurry Rel))) components'' -tryGetComponentsFromTable :: forall c. (Component c) => Table -> ComponentId -> IO [(Entity, Result c)]+tryGetComponentsFromTable :: forall c. (Component c) => Table -> ComponentId -> IO [(Entity, c)] tryGetComponentsFromTable table componentId = do columns <- readIORef table.columns@@ -336,7 +333,7 @@ results <- tryGetComponentsFromColumn @c column entities <- Vec.toList table.entities let zipped = zip (map fst entities) results- return $ map (\(e, r) -> (e, Result (r, e))) zipped+ return $ map (\(e, r) -> (e, r)) zipped tryGetEntitiesFromTable :: Table -> IO [Entity] tryGetEntitiesFromTable table =@@ -344,7 +341,7 @@ entities <- Vec.toList table.entities return $ map fst entities -tryGetComponentsFromTableMaybe :: forall c. (Component c) => Table -> ComponentId -> IO [(Entity, Maybe (Result c))]+tryGetComponentsFromTableMaybe :: forall c. (Component c) => Table -> ComponentId -> IO [(Entity, Maybe c)] tryGetComponentsFromTableMaybe table componentId = do columns <- readIORef table.columns@@ -355,14 +352,14 @@ Just column -> do results <- tryGetComponentsFromColumn @c column entities <- Vec.toList table.entities- return $ zipWith (\x e -> (e, Just $ Result (x, e))) results (map fst entities)+ return $ zipWith (\x e -> (e, Just x)) results (map fst entities) -tryGetRelCollectionsFromArchetype :: forall c. (Component c) => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, [Result (Rel c)])]+tryGetRelCollectionsFromArchetype :: forall c. (Component c) => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, [Rel c])] tryGetRelCollectionsFromArchetype archetype tables componentId = do table <- Vec.unsafeRead tables archetype.id tryGetRelCollectionsFromTable table componentId -tryGetComponentsFromArchetype :: forall c. (Component c) => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, Result c)]+tryGetComponentsFromArchetype :: forall c. (Component c) => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, c)] tryGetComponentsFromArchetype archetype tables componentId = do table <- Vec.unsafeRead tables archetype.id tryGetComponentsFromTable table componentId@@ -372,18 +369,18 @@ table <- Vec.unsafeRead tables archetype.id tryGetEntitiesFromTable table -tryGetComponentsFromArchetypeMaybe :: forall c. (Component c) => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, Maybe (Result c))]+tryGetComponentsFromArchetypeMaybe :: forall c. (Component c) => ArchetypeId -> IOVec Table -> ComponentId -> IO [(Entity, Maybe c)] tryGetComponentsFromArchetypeMaybe archetype tables componentId = do table <- Vec.unsafeRead tables archetype.id tryGetComponentsFromTableMaybe table componentId -tryGetRelCollectionsFromTables :: forall c. (Component c) => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, [Result (Rel c)])]+tryGetRelCollectionsFromTables :: forall c. (Component c) => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, [Rel c])] tryGetRelCollectionsFromTables (Tables tables) archetypes componentId = do results <- mapM (\archetype -> tryGetRelCollectionsFromArchetype archetype tables componentId) archetypes return $ concat results -tryGetComponentsFromTables :: forall c. (Component c) => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, Result c)]+tryGetComponentsFromTables :: forall c. (Component c) => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, c)] tryGetComponentsFromTables (Tables tables) archetypes componentId = do results <- mapM (\archetype -> tryGetComponentsFromArchetype archetype tables componentId) archetypes@@ -395,53 +392,53 @@ results <- mapM (`tryGetEntitiesFromArchetype` tables) archetypes return $ concat results -tryGetComponentsFromTablesMaybe :: forall c. (Component c) => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, Maybe (Result c))]+tryGetComponentsFromTablesMaybe :: forall c. (Component c) => Tables -> [ArchetypeId] -> ComponentId -> IO [(Entity, Maybe c)] tryGetComponentsFromTablesMaybe (Tables tables) archetypes componentId = do results <- mapM (\archetype -> tryGetComponentsFromArchetypeMaybe archetype tables componentId) archetypes return $ concat results -newtype Result c = Result (c, Entity)+-- newtype Result c = Result (c, Entity) -data ErasedResult where- ErasedResult :: Result c -> ErasedResult+-- data ErasedResult where+-- ErasedResult :: Result c -> ErasedResult -value :: Result c -> c-value (Result (c, _)) = c+-- value :: Result c -> c+-- value (Result (c, _)) = c -type family IsComp a where- IsComp (Rel a) = False- IsComp a = True+-- type family IsComp a where+-- IsComp (Rel a) = False+-- IsComp a = True -entityOf :: Result c -> Entity-entityOf (Result (_, e)) = e+-- entityOf :: Result c -> Entity+-- entityOf (Result (_, e)) = e -instance (Show c) => Show (Result c) where- show :: Result c -> String- show = show . value+-- instance (Show c) => Show (Result c) where+-- show :: Result c -> String+-- show = show . value -instance (Eq c) => Eq (Result c) where- (==) :: Result c -> Result c -> Bool- (==) a b = value a == value b+-- instance (Eq c) => Eq (Result c) where+-- (==) :: Result c -> Result c -> Bool+-- (==) a b = value a == value b -instance (Ord c) => Ord (Result c) where- compare :: Result c -> Result c -> Ordering- compare a b = compare (value a) (value b)+-- instance (Ord c) => Ord (Result c) where+-- compare :: Result c -> Result c -> Ordering+-- compare a b = compare (value a) (value b) -instance (HasField a b c) => HasField a (Result b) c where- getField a = getField @a (value a)+-- instance (HasField a b c) => HasField a (Result b) c where+-- getField a = getField @a (value a) -class DeepValue' flag c i | flag c -> i where- deepValue' :: c -> i+-- class DeepValue' flag c i | flag c -> i where+-- deepValue' :: c -> i -instance DeepValue' True (Result c) c where- deepValue' = value+-- instance DeepValue' True (Result c) c where+-- deepValue' = value -instance DeepValue' False (Result (Rel c)) c where- deepValue' x = x.comp+-- instance DeepValue' False (Result (Rel c)) c where+-- deepValue' x = x.comp -class DeepValue c i | c -> i where- deepValue :: c -> i+-- class DeepValue c i | c -> i where+-- deepValue :: c -> i -instance (DeepValue' (IsComp c) (Result c) i) => DeepValue (Result c) i where- deepValue = deepValue' @(IsComp c)+-- instance (DeepValue' (IsComp c) (Result c) i) => DeepValue (Result c) i where+-- deepValue = deepValue' @(IsComp c)
src/Mischief/ECS/Time.hs view
@@ -1,47 +1,98 @@ module Mischief.ECS.Time where import Control.Monad.IO.Class-import GHC.Records (HasField (getField))-import Mischief.ECS.App+import Data.Data+import Data.Function ((&))+import Data.Vector.Internal.Check (HasCallStack) import Mischief.ECS.App.Plugins import Mischief.ECS.App.Schedules import Mischief.ECS.Components import Mischief.ECS.Resources-import Mischief.ECS.Systems qualified as Systems-import Mischief.ECS.Tables+import Mischief.ECS.Systems import Mischief.ECS.Utils import Mischief.ECS.World-import Mischief.ECS.World.Insert-import Mischief.ECS.World.Modify import Mischief.ECS.World.Query-import Mischief.ECS.World.Query.Queryable+import Mischief.ECS.World.Query.Pipe import System.Clock +class ToTime t where+ toTime :: t -> Time+ data Time = Time { delta :: TimeSpec, elapsed :: TimeSpec }++data VirtualTime = VirtualTime+ { virtualDelta :: TimeSpec,+ virtualElapsed :: TimeSpec+ } deriving (Show, Component) -time :: System Time-time = unwrap <$> res @Time+data TimeTypePref -deltaTime :: System Float-deltaTime = deltaSecs <$> time+instance PrefType TimeTypePref TimeTag -deltaSecs :: Time -> Float-deltaSecs Time {delta} = fromIntegral delta.sec + fromIntegral delta.nsec / 1000000000+instance ToTime VirtualTime where+ toTime v = Time {delta = v.virtualDelta, elapsed = v.virtualElapsed} -data TimePlugin = TimePlugin deriving (Eq)+data TimeTag where+ TimeTag :: (Component t, ToTime t) => Proxy t -> TimeTag +time :: (HasCallStack) => (MonadSystem w m) => m Time+time = do+ pref <- getPref @TimeTypePref+ case pref of+ Nothing -> toTime . unwrap <$> res @VirtualTime+ Just (TimeTag (_ :: Proxy t)) -> toTime . unwrap <$> res @t++timeMaybe :: (MonadSystem w m) => m (Maybe Time)+timeMaybe = do+ pref <- getPref @TimeTypePref+ case pref of+ Nothing -> fmap toTime <$> res @VirtualTime+ Just (TimeTag (_ :: Proxy t)) -> fmap toTime <$> res @t++qtime :: (MonadSystem w m) => Query m Time+qtime = pure () & qtraverse (\_ _ -> time)++delta :: (HasCallStack) => (MonadSystem w m) => m Float+delta = do+ time <- time+ pure $ specToSecs time.delta++qdelta :: (MonadSystem w m) => Query m Float+qdelta = pure () & qtraverse (\_ _ -> delta)++elapsed :: (HasCallStack) => (MonadSystem w m) => m Float+elapsed = do+ time <- time+ pure $ specToSecs time.elapsed++qelapsed :: (MonadSystem w m) => Query m Float+qelapsed = pure () & qtraverse (\_ _ -> elapsed)++specToSecs :: TimeSpec -> Float+specToSecs a = fromIntegral a.sec + fromIntegral a.nsec / 1000000000++secsToSpec :: Float -> TimeSpec+secsToSpec s =+ let secs = floor s+ in TimeSpec secs $ floor ((s - fromIntegral secs) * 1000000000)++-- | Plugin that keeps track of time. Enables calling 'delta', 'elapsed', and so on.+--+-- Note that these methods are context-dependent. For instance, if used in a Fixed schedule, 'delta' will always return the time as inferred from the fixed frequency.+data TimePlugin+ instance Plugin TimePlugin where- init _ = do+ init = do currentTime <- liftIO $ getTime Monotonic- insertRes $ Time {delta = TimeSpec {sec = 0, nsec = 0}, elapsed = currentTime}- Systems.add First updateTime+ insertRes $ VirtualTime {virtualDelta = TimeSpec {sec = 0, nsec = 0}, virtualElapsed = currentTime}+ schedule @First (systems updateTime) updateTime :: System () updateTime = do- Just time <- res @Time+ Just time <- res @VirtualTime currentTime <- liftIO $ getTime Monotonic- insertRes Time {delta = currentTime - time.elapsed, elapsed = currentTime}+ insertRes VirtualTime {virtualDelta = currentTime - time.virtualElapsed, virtualElapsed = currentTime}
src/Mischief/ECS/Timer.hs view
@@ -1,12 +1,16 @@ module Mischief.ECS.Timer where +-- | A type that can be used to keep track of time. data Timer = Timer {duration :: Float, elapsed :: Float, mode :: Mode} +-- | A mode for the 'Timer'. Being set to Repeat will mean the timer loops around once it finishes, while being set to Once will make it stop completely after one finish. data Mode = Once | Repeat +-- | Create a new Timer specifying its duration in seconds and its mode. new :: Float -> Mode -> Timer new duration mode = Timer {duration, mode, elapsed = 0} +-- | Tick a timer by a certain duration. Will return the new state of the timer, as well as a bool signaling whether it has just finished or not. tick :: Float -> Timer -> (Timer, Bool) tick _ timer | timer.elapsed >= timer.duration = (timer, False) tick x timer | timer.elapsed + x < timer.duration = (timer {elapsed = timer.elapsed + x}, False)
− src/Mischief/ECS/Tutorial/App.hs
@@ -1,179 +0,0 @@-{-# OPTIONS_GHC -Wno-unused-imports #-}---- |--- Module: Systems Tutorial--- Description: Tutorial on using @Systems@------ This module contains a more in-depth look into the @Mischief App and Plugins@.------ It isn't as technically interesting as the other chapters of the tutorial, focusing more on organization and high-level logic.------ [Previous Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Dungeon")------ [Next Chapter: Components]("Mischief.ECS.Tutorial.Components")------ [Main Page]("Mischief.ECS")-module Mischief.ECS.Tutorial.App- ( -- * Learn You an ECS for Great Mischief! - 3. App and Plugins- -- $intro-- -- * Modular Features- -- $modular_features-- -- * Initialization- -- $init-- -- * Plugin Resolution- -- $resolution-- -- * [Next chapter: Components]("Mischief.ECS.Tutorial.Components")- )-where--import Control.Monad.Reader-import Mischief.ECS---- $intro--- The @'App'@ is a thin wrapper around the @'World'@ that works as an interface for plugging in--- various behavior and features in modular fashion, via @Plugins@.------ A Mischief game / app usually starts by creating an app with a main plugin and then running it.------ @--- main :: 'IO' ()--- main = do--- app <- 'newApp' myPlugin--- 'runApp' app------ data MainPlugin = MainPlugin deriving ('Eq')------ instance 'Plugin' MainPlugin where--- 'Msichief.ECS.App.Plugins.init' _ = 'info' \"Hello!\"--- @------ From that initial plugin you can add other plugins, each adding new features to your game.------ A @Plugin p@ instance has two optional functions:------ 1. An initialization system that will be ran at the very beginning of the app.------ @--- 'Mischief.ECS.App.Plugins.init' :: p -> 'System' ()--- @------ 2. A collection of plugins that will be added along with this plugin.------ @--- 'plugins' :: p -> 'Plugins'--- @------ The collection of plugins can be created using the @plug@ helper function.------ @--- 'plugins' _ = 'plug' (FooPlugin, BarPlugin, BazPlugin)--- @------ Additionally, each @Plugin@ also requires an @Eq@ instance, which we'll talk about later.---- $modular_features--- Mischief is intended to let you cleanly separate and organize your logic.------ For instance, you may have a @PhysicsPlugin@ that adds physics to your game, a @RenderPlugin@ which renders objects, a @LevelPlugin@ that spawns your levels.------ @--- instance 'Plugin' MainPlugin where--- 'plugins' _ = 'plug' (PhysicsPlugin, RenderPlugin, LevelPlugin)--- @------ Ideally, each of these plugins would add their own independent features. So, if you were to remove @physicsPlugin@, your entities simply wouldn't--- move and collide anymore, but the rest of the app would work just fine.------ Note that, internally, @PhsyicsPlugin@ could also be subdivided into its own plugins with separate roles:------ @--- data PhysicsPlugin = PhysicsPlugin deriving ('Eq')------ instance 'Plugin' PhysicsPlugin where--- 'plugins' _ = 'collect' (CollisionPlugin, MovePlugin)--- @------ This also lets the various packages of @Mischief@ be modular, and makes it easy for third party library developers to create plugins that you just plug into your app with ease!---- $init--- So what exactly can you do within the @init@ function of a @Plugin@?------ The answer is, pretty much anything.------ As the name suggests, it is typically used for initializing some sort of logic or behavior within your app. This is--- usually done by scheduling systems, inserting some resource, and spawning important entities, such as observers.------ @--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' $ 'Update' foo '`after`' bar--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' $ 'Startup' baz------ 'insertRes' $ SomeRes 5--- @------ Note that the initialization systems are guaranteed to run before everything else in the app, /but/ there is no guarantee of order between them.--- So, a Plugin should /never/ depend on another Plugin being added before it.------ Additionally, everything that can be done in @init@ can be done the same in a normal system. There's nothing making it unique!---- $resolution--- Have you noticed that the 'Plugin' typeclass requires the type to also derive 'Eq'? Let's dig into that.------ To give a practical example, think of an @SDLPlugin@ which initializes an @SDL@ program, opening--- an actual window and allowing for things such as collecting input and rendering stuff.------ @--- data SDLPlugin = SDLPlugin deriving ('Eq')------ instance 'Plugin' SDLPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = createSDLWindow (Resolution (500, 500))--- @------ Two separate Plugins exist, an @InputPlugin@ which handles reading input events, and a @RenderPlugin@ which handles processing shaders and rendering.--- Both those Plugins need @SDLPlugin@ to function properly.------ @--- data InputPlugin = InputPlugin deriving ('Eq')------ instance 'Plugin' InputPlugin where--- 'plugins' _ = 'plug' SDLPlugin------ data RenderPlugin = RenderPlugin deriving ('Eq')------ instance 'Plugin' RenderPlugin where--- 'plugins' _ = 'plug' SDLPlugin--- @------ So it's a handy feature to have multiple plugins depend on the same plugin. But what if @SDLPlugin@ looked like this:------ @--- data SDLPlugin = SDLPlugin {res :: ('Int', 'Int')} deriving ('Eq')------ instance 'Plugin' SDLPlugin where--- 'Mischief.ECS.App.Plugins.init' (SDLPlugin {res}) = createSDLWindow (Resolution res)--- @------ @SDLPlugin@ now produces different effects based on its value. This means there is a possibility that the following happens:------ @--- data InputPlugin = InputPlugin deriving ('Eq')------ instance 'Plugin' InputPlugin where--- 'plugins' _ = 'plug' (SDLPlugin (300, 300))------ data RenderPlugin = RenderPlugin deriving ('Eq')------ instance 'Plugin' RenderPlugin where--- 'plugins' _ = 'plug' (SDLPlugin (500, 500))--- @------ What if you were to add both @InputPlugin@ and @RenderPlugin@ to your app? Which variant of @SDLPlugin@ would it pick?------ This conflict is why the @Plugin@ typeclass requires @Eq@. When the same plugin is added into the app twice,--- their two values will be compared and, if equal, the plugin will be added, otherwise the app will panic.------ This ensures a level of control over how plugins do their initialization, without forcing a plugin to only be added from one source.
− src/Mischief/ECS/Tutorial/Components.hs
@@ -1,573 +0,0 @@-{-# OPTIONS_GHC -Wno-unused-imports #-}---- |--- Module: Components Tutorial--- Description: Tutorial on using @Components@------ This module contains a more in-depth tutorial on @Mischief Components@.------ [Previous Chapter: App and Plugins]("Mischief.ECS.Tutorial.App")------ [Next Chapter: Relationships]("Mischief.ECS.Tutorial.Relationships")------ [Main Page]("Mischief.ECS")-module Mischief.ECS.Tutorial.Components- ( -- * Learn You an ECS for Great Mischief! - 4. Components- -- $introduction-- -- * The Name Component- -- $name-- -- * Operations- -- $ops-- -- * Query Results- -- $results-- -- * Change Detection- -- $change-- -- * Meta Components- -- $meta-- -- * Resources- -- $resources-- -- * Required Components- -- $required-- -- * Registering Components- -- $reg-- -- * Examples- -- $examples-- -- * [Next Chapter: Relationship]("Mischief.ECS.Tutorial.Relationships")- )-where--import Control.Monad (void)-import Data.Default (Default (def))-import Data.Foldable-import GHC.Generics (Generic)-import GHC.Records (HasField)-import Mischief.ECS---- $introduction--- A component is any type which derives the 'Component' typeclass. They can be both carriers of data or marker components used for querying (Tags from Flecs):------ Component that carries data.------ @--- data Health = Health 'Int' deriving ('Component')--- @------ Marker components.------ @--- data Player = Player deriving ('Component')--- data Enemy = Enemy deriving ('Component')--- @---- $name--- @Name@ is a special component provided by Mischief that is internally added to every spawned entity, based on its @Entity@ index,--- if none is provided on spawn. It can be, of course, changed at any time.------ @--- newtype Name = Name 'String' deriving ('Component')--- @------ Consider this system that prints the name of a given Entity:------ @--- printName :: 'Entity' -> 'System' ()--- printName e = 'info' . 'text' '=<<' 'get' ('C' \@'Name') e--- @------ Notice how the Names behave here:------ @--- foo <- 'spawn' ()--- printName foo------ insert ('Name' \"Foo\") foo--- printName foo------ bar <- 'spawn' ('Name' \"Bar\")--- printName bar--- @------ @--- >> [INFO] Just \"Entity 15v1\"--- >> [INFO] Just \"Foo\"--- >> [INFO] Just \"Bar\"--- @---- $ops------ Mischief offers various @operations@ for inserting and manipulating data into the ECS:------ * You can spawn entities as bundles of components:------ @--- player <- 'spawn' (Name \"Player\", Player)--- @------ * You can insert components on existing entities:------ @--- 'insert' (Name \"New Player Name\", Health 100)--- @------ * You can remove components:------ @--- 'remove' ('C' \@Health, 'C' \@Player) player--- @------ * You can despawn entities:------ @--- 'despawn' player--- @------ Additionally, @insert@ has a couple of variants:------ * @'insertNew'@ only inserts components that aren't already on the entity.--- * @'insertIfNeq'@ only insert components if they aren't on the entity of if their value differs from the current one.---- $results--- A @Result c@ is a wrapper around the component @c@ that's produced by a query. We will discuss querying itself more in the [Query Chapter]("Mischief.ECS.Tutorial.Queries").------ @--- health <- 'get' ('C' \@Health) player--- @------ @--- health :: 'Result' Health--- @------ There are a number of useful operations that can be done on a @Result@:------ * Set a new value for this component.------ @--- 'set' health (Health 100)--- @------ * Modify the value of this component.------ @--- 'modify' health (\(Health x) -> Health (x + 1))--- @------ * Remove the component from the entity.------ @--- 'delete' health--- @------ Note that these functions just call the @insert@, @remove@, etc. operations.------ So these two are equivalent, performance-wise:------ @--- health <- 'query' ('C' \@Health)------ 'for_' health $ \(health) -> do--- 'modify' health $ \(Health x) -> Health (x + 1)--- @------ @--- health <- 'query' ('C' \@Health, 'E')------ 'for_' health $ \((Health x), entity) -> dox--- 'insert' (Health (x + 1)) entity--- @------ Or, if you prefer compact code:------ @--- 'query' ('C' \@Health) '>>=' 'traverse_' ('`modify`' (\(Health x) -> Health (x + 1)))--- @------ You can use the @value@ function to obtain the inner value of a Result.------ @--- 'value' :: 'Result' c -> c--- @------ For instance:------ @--- 'Just' name <- 'get' ('C' \@Name) e--- let name' = 'value' name--- @------ @--- name :: 'Result' Name--- name' :: Name--- @------ You can also use @Val@ to automatically unwrap the value of a Result:------ @--- 'Just' name <- 'get' ('C' \@Name) e--- @------ @--- name :: Name--- @------ If the component has record fields, every field will be inherited by the 'Result' (via a 'HasField' instance)------ @--- data Pos = Pos {x :: 'Float', y :: 'Float'}--- @------ @--- 'Just' pos <- 'get' @Pos e--- let x = pos.x--- let y = pos.y--- @------ @--- pos :: 'Result' Pos--- x :: 'Float'--- y :: 'Float'--- @------ Some typeclasses, namely 'Show', 'Eq', 'Ord' are also implemented for a @Result c@ if they are for the underlying @c@.------ Note that the value of a @Result@ is the value gotten at the time of querying. It could be outdated, in case the live value--- was changed after querying. You can use @update@ to get the live value for a Result:------ @--- name <- 'update' name--- @---- $meta--- Each component has a corresponding entity in the World.--- The components on that entity store information about the component itself. Such as which archetypes it is part of.------ A component's entity can be accessed by using @meta@.------ Getting the entities of the @Name@ and @Player@ components:------ @--- x <- 'meta' \@Name--- y <- 'meta' \@Player--- @------ Most users should avoid tinkering with Meta Components unless they have a good reason to,--- and should absolutely never remove or change any components added to them by the @ECS@.---- $resources--- @Resources@ are singleton components that can be easily accessed and modified from any system.------ Any component can be used as a resource.------ @--- data MyRes = MyRes 'Int' deriving ('Component')--- @------ You can insert a resource into the World using @insertRes@.------ @--- 'insertRes' $ MyRes 5--- @------ And you can query for the value of a resource using @res@:------ @--- 'Just' myRes <- 'res' \@MyRes--- @------ @res r@ returns a @'Maybe' r@ because it's possible for the resource to not have been inserted yet.------ Resources are implemented by inserting a component's value on its own meta entity.------ @'res' \@MyRes@ is the same as doing:------ @--- m <- 'meta' \@MyRes--- 'get' ('C' \@MyRes) m--- @---- $required--- Each component can @require@ a bundle of other components.------ @--- data Player = Player------ instance 'Component' Player where--- 'required' = 'require' \@(Position, Health)--- @------ This means that each time @Player@ is added to an entity, a /default/ @Position@ and @Health@ will also be inserted, if they aren't already present.------ In order for a component to be required by another, it must instance the @Default@ typeclass, either through a @Generic@ derive or a custom instance.------ @--- data Position = Position 'Int' 'Int' deriving ('Component', 'Generic', 'Default')--- @------ @--- data Health = Health 'Int' deriving ('Component')------ instance 'Default' Health where--- 'def' = Health 100--- @------ Requirements are transitive (if @A requires B@ and @B requires C@, then @A requires C@) and /can/ contain cycles.------ A requirement is added to the ECS as a @'RequiredBy'@ \/ @'Requires'@ relationship between the components' entities.---- $reg--- @Registering@ a component involves spawning its meta entity and adding the corresponding data.------ Each component is registered automatically the first time it is inserted on an Entity, so you don't usually--- have to worry about registration.------ Queries are also smart about components; if you query or filter for a component hasn't been registered yet, they will just--- assume that component can't be be on any Entity. Queries can't perform registration themselves, because they're not allowed to mutate--- the world in any way------ However, there may be /extremely/ niche situations where you want to register components earlier than normal, which is where manual registration comes in:------ @--- 'register' \@(Player, Health, Position)--- @------ One such situation could be wanting to check the requirements in-between multiple components.--- If a component hasn't been registered yet, it won't show up when you query for components that require a specific component.---- $change--- @Change detection@ can be done in two ways: @Observers@ and @Filters@.------ === Observers------ Observers can listen to the @'OnInsert'@ and @'OnRemove'@ event:------ * @OnInsert c@ is triggered each time @c@ is inserted on an entity. This event is also triggered when a--- component is re-inserted / changed, meaning this isn't a reliable way to determine if a component was just added.------ @--- onNameInsert :: 'OnInsert' 'Name' -> 'System' ()--- @------ * @OnRemove@ is triggered when a component is removed from an entity.------ @--- onNameRemove :: 'OnRemove' 'Name' -> 'System' ()--- @------ Both events have a @.entity@ field you can use to obtain the entity which it happened on.------ @--- onNameRemove :: 'OnRemove' 'Name' -> 'System' ()--- onNameRemove event = 'info' $ show event.entity <> \" has their name removed!\"--- @------ Don't forget to spawn an Observer to listen to each event.------ @--- import "Mischief.ECS.Observers" qualified as [Observers]("Mischief.ECS.Observers")--- @------ @--- 'void' $ [Observers]("Mischief.ECS.Observers").'Mischief.ECS.Observers.spawn' onNameInsert--- 'void' $ [Observers]("Mischief.ECS.Observers").'Mischief.ECS.Observers.spawn' onNameRemove--- @------ @OnInsert@ is always triggered /after/ a component has been inserted, while @OnRemove@ is triggered /before/. This--- allows you to query for the component and get its value.------ You can find more details on Observers and Events in the corresponding [Chapter]("Mischief.ECS.Tutorial.Events")------ === Filters------ Now for the other way of doing change detection: the @'Changed'@ and @'Added'@ query filters.------ With the following query:------ @--- 'query' ('C' \@'Name') ('Added' ('C' \@Player))--- @------ You will only obtain the name of entities which had the @Player@ component added to them since the current (scheduled) system last ran.------ @Added c@ will catch entities that just had @c@ added to them, while @Changed c@ will catch any insertion, similar to @OnInsert@.--- If you wish to query for entities that have had a component changed but it wasn't just added, you can do:------ @--- 'query' ('C' \@'Name') ('Changed' ('C' \@Player), 'Not' ('Added' ('C' \@Player)))--- @------ === Note on listening to changes------ One essential detail to be aware of here is that insertion (@OnInsert@ or @Changed@) doesn't necessarily mean a component's value has been changed!------ The following @insert@ /will/ trigger change detection:------ @--- p <- 'spawn' (Health 100)--- 'insert' (Health 100) p--- @------ To avoid this, you can derive 'Eq' on your components and use @'insertIfNeq'@ and @'setIfNeq'@, which will only perform insertion if the value of the component is different--- from the current one.---- $hooks--- @Component hooks@ are events associated directly to a Component instance.------ @--- instance Component Foo where--- hooks :: 'Hooks' Foo--- hooks = 'collect' (onInsertFoo, onRemoveFoo, customFoo)------ onInsertFoo :: 'OnInsert' Foo -> 'System' ()--- onInsertFoo = ...------ onRemoveFoo :: 'OnRemove' Foo -> 'System' ()--- onRemoveFoo = ...------ data SomeCustomEvent a deriving ('Event')--- customFoo :: SomeCustomEvent Foo -> 'System' ()--- customFoo = ...--- @------ A Hook on component @c@ can be any function that looks like @('Event' e) => e c -> 'System' ()@.------ Hooks are registered along with their respective component.---- $examples------ This section contains a few chunkier examples that combine the notions from this entire chapter.------ === __Example 1__--------- An example showing different operations that spawn and alter entities.------ @--- import "Mischief.ECS.Prelude"------ data CompA = CompA 'Int' 'Int' deriving ('Component', 'Show')------ data CompB = CompB 'String' deriving ('Component', 'Show')------ data CompC = CompC deriving ('Component', 'Show')------ main :: 'IO' ()--- main = do--- app <- 'newApp' MainPlugin--- 'runApp' app------ data MainPlugin = MainPlugin deriving ('Eq')------ instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- foo <- 'spawn' (Name \"Foo\", CompA 10 10, CompB \"Component B on Foo\", CompC)--- bar <- 'spawn' (Name \"Bar\", CompA 15 3, CompB \"Component B on Bar\")--- baz <- 'spawn' (Name \"Baz\", CompA 0 0, CompB \"Component B on Baz\", CompC)------ 'info' . 'text' '=<<' 'query' ('C' \@Name, 'C' \@CompA, 'M' \@CompB, 'M' \@CompC)------ 'insert' (Name \"Foo2\", CompA 100 100, CompC) foo--- 'remove' ('C' \@CompC, 'C' \@CompB) baz--- 'despawn' bar------ 'info' . 'text' '=<<' 'query' ('C' \@Name, 'C' \@CompA, 'M' \@CompB, 'M' \@CompC)--- @------ @--- >> [Info] [--- (\"Foo\", CompA 10 10, Just CompB \"Component B on Foo\", Just CompC),--- (\"Baz\", CompA 0 0, Just CompB \"Component B on Baz\", Just CompC),--- (\"Bar\", CompA 15 3, Just CompB \"Component B on Bar\", Nothing)--- ]------ >> [Info] [--- ("\Foo2\", CompA 100 100, Just CompB \"Component B on Foo\", Just CompC),--- (\"Baz\", CompA 0 0, Nothing, Nothing)--- ]--- @------ === __Example 2__------ This example shows you how to make a resource that independently tracks the number of Players in the World.------ @--- data PlayerCount = PlayerCount 'Int' deriving ('Component', 'Show')------ changeCount :: 'Int' -> PlayerCount -> PlayerCount--- changeCount n (PlayerCount x) = PlayerCount (x + n)--- @------ We need to write a system that queries all entities that have had a @Player@ component added to them and updates @PlayerCount@ accordingly:------ @--- updateCount :: 'System' ()--- updateCount = do--- x <- 'query'' 'E' ('Added' \@Player)------ count <- 'res' \@PlayerCount--- 'modify' count $ changeCount ('length' x)--- @------ We couldn't have used an observer for this since 'OnInsert' also catches re-insertions.------ We'll also make an Observer that listens to the @OnRemove@ event to update @PlayerCount@:------ @--- handlePlayerRemove :: 'OnRemove' Player -> 'System' ()--- handlePlayerRemove _ = do--- count <- 'res' \@PlayerCount--- 'modify' count $ changecount (-1)--- @------ I also wrote this system that spawns 3 Players and despawns one of them each frame, to make sure both the previous sytems works fine.------ @--- spawnPlayers :: 'System' ()--- spawnPlayers = do--- ('info' . 'text') '=<<' 'res' \@PlayerCount------ p <- 'spawn' Player--- 'void' $ 'spawn' Player--- 'void' $ 'spawn' Player------ 'despawn' p--- @------ Now let's write a a simple app that makes use of these systems:------ @--- import "Mischief.ECS.Systems" qualified as [Systems]("Mischief.ECS.Systems")--- import "Mischief.ECS.Observers" qualified as [Observers]("Mischief.ECS.Observers")------ data Player = Player deriving ('Component')------ main :: 'IO' ()--- main = do--- app <- 'newApp' MainPlugin--- 'runApp' app------ data MainPlugin = MainPlugin deriving ('Eq')------ instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- 'insertRes' $ PlayerCount 0--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' (updateCount, spawnPlayers)--- 'void' $ [Observers]("Mischief.ECS.Observers").'Mischief.ECS.Observers.spawn' handlePlayerRemove--- @------ Running it will result in:------ @--- [INFO] Just (PlayerCount 0)--- [INFO] Just (PlayerCount 2)--- [INFO] Just (PlayerCount 4)--- ...--- @
− src/Mischief/ECS/Tutorial/Dungeon.hs
@@ -1,1229 +0,0 @@-{-# OPTIONS_GHC -Wno-unused-imports #-}---- |--- Module: Components Tutorial--- Description: Introductory Tutorial------ This module walks the user through creating a small game in the terminal.------ [Previous Chapter: Startup Guide]("Mischief.ECS.Tutorial.Startup")------ [Next Chapter: App and Plugins]("Mischief.ECS.Tutorial.App")------ [Main Page]("Mischief.ECS")-module Mischief.ECS.Tutorial.Dungeon- ( -- * Learn You an ECS for Great Mischief! - 2. Coding a Dungeon Game- -- $intro-- -- * Creating an App- -- $creation-- -- * Spawning the Grid- -- $grid-- -- * Traversing the Grid- -- $traversing-- -- * Spawning the Player- -- $player-- -- * Adding Walls- -- $walls-- -- * Moving the Player- -- $move-- -- * Displaying the Grid- -- $display-- -- * Generating Random Positions- -- $rand-- -- * Adding Enemies- -- $enemies-- -- * Moving Enemies- -- $moveE-- -- * Enemy Collision- -- $collision-- -- * Health- -- $health-- -- * Quitting- -- $quit-- -- * Taking Damage- -- $dmg-- -- * Spawning Coins- -- $coins-- -- * Collecting Coins- -- $collect-- -- * Next Steps- -- $next-- -- * [Next Chapter: App and Plugins]("Mischief.ECS.Tutorial.App")- )-where--import Control.Monad (unless, void, when)-import Control.Monad.IO.Class (MonadIO (liftIO))-import Data.Default (Default)-import Data.Foldable (for_)-import Data.List ((!?))-import Data.Traversable (for)-import Mischief.ECS-import System.Exit (exitSuccess)---- $intro--- This module will walk you through creating a simple terminal-based dungeon crawler in Mischief.--- The goal is to have a player which we can freely move on a 2D grid, as well as various objects placed on tiles,--- such as enemies, coins, obstacles, etc.------ If you'd rather go straight to learning about particular features rather than learning by example, feel free to skip to the next chapters.--- You can always come back to this one once you have a better understanding of things.------ Each section in this chapter adds its own isolated mechanics to the game, so there's no harm in reading up to a point and taking a break or--- starting another chapter.---- $creation--- Let's start by creating our App and a Main Plugin which will serve as the starting point of all our logic.------ @--- import "Mischief.ECS.Prelude"------ main :: 'IO' ()--- main = do--- app <- 'newApp' MainPlugin--- 'runApp' app------ data MainPlugin = MainPlugin deriving ('Eq')------ instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = 'info' \"Hello World!\"--- @------ If you run this program you should see \"Hello World!\" logged to the terminal.---- $grid--- The game will play out on a small 2D grid. There are many ways of representing this Mischief, the way I've chosen to do it--- is by having each tile of the grid be an entity, and the full list of entities stored in a global resource.------ The @Tile@ component will be used to mark which entities are tiles:------ @--- data Tile = Tile deriving ('Component')--- @------ Each tile will also have a @Pos@ component containing it's position on the grid:------ @--- data Pos = Pos ('Int', 'Int') deriving ('Component')--- @------ @Grid@ will be the resource containing the (bidimensional) list of all tile entities:------ @--- data Grid = Grid [['Entity']] deriving ('Component')--- @------ I've written two functions returning the width and height of the grid:------ @--- gridH :: 'Int'--- gridH = 10------ gridW :: 'Int'--- gridW = 20--- @------ Let's write a system that spawns the tiles and initializes the grid resource:------ @--- spawnGrid :: 'System' ()--- spawnGrid = do--- tiles \<---- 'for' [0 .. gridH - 1] $ \\i -\>--- 'for' [0 .. gridW - 1] $ \\j -\>--- 'spawn' (Tile, Pos (i, j))------ 'insertRes' $ Grid tiles--- @------ @insertRes@ inserts the component as a singleton resource into the ECS. We can grab its value at any time by using @res \@Grid@.------ Now we just need to modify @MainPlugin@ so that it schedules @spawnGrid@ to happen when the app starts.------ @--- import "Mischief.ECS.Systems" qualified as [Systems]("Mischief.ECS.Systems")------ instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' spawnGrid--- @---- $traversing--- Next, we need to code a way for traversing between adjacent tiles. Having a tile entity, we should have easy access to the entities found above, below, to the left and right of it.------ First, I've written this function which gets an entity by position:------ @--- getTile :: ('Int', 'Int') -> 'System' ('Maybe' 'Entity')--- getTile (x, y) = do--- grid <- 'res' @Grid--- 'pure' $ do--- Grid tiles <- grid--- line <- tiles '!?' x--- line '!?' y--- @------ Now we can write a function that gets the position of a given tile and finds tiles offset by a certain amount:------ @--- moveBy :: ('Int', 'Int') -> 'Entity' -> 'System' ('Maybe' 'Entity')--- moveBy (x, y) entity = do--- 'Just' pos <- 'get' ('C' \@Pos) entity--- let (Pos (x', y')) = 'value' pos--- getTile (x' + x, y' + y)--- @------ Queries will be explained in-depth later, but what happens essentially is that, our query returns a @Result Pos@ for the given entity, and--- we get and unwrap the inner @Pos@ value using the @'value'@ function.------ We can completely bypass this by just using the @Val@ query transformer which unwraps the @Result@ for us:------ @--- moveBy (x, y) entity = do--- 'Just' (Pos (x', y')) <- 'get' ('Val' ('C' \@Pos)) entity--- getTile (x' + x, y' + y)--- @------ We can compress the code even further by using @quasi-queries@. They are macros which allow us to write queries in faster, easier ways.--- They will be explained in a further chapter, but should be pretty easy to understand at an intuitive level. For instance, we can replace--- the above @get@ with the @g@ quasi-query:------ @--- moveBy (x, y) entity = do--- 'Just' (Pos (x', y')) <- ['g'|*Pos|] entity--- getTile (x' + x, y' + y)--- @------ The @*@ is the quasi equivalent of @Val@, although it can also just be written as @Val@ or @val@ if you prefer.---- $player--- Our game needs a player, so we should have a component that uniquely identifies it:------ @--- data Player = Player deriving ('Component')--- @------ We should also have a relationship to associate an entity to a tile, teling us that it's currently placed on that tile.------ @--- data OnTile = OnTile deriving ('Component')--- @------ Additionally, an entity should only be able to be on a single tile at a time. Mischief has a convenient way of doing this hidden in the @Component@ class:------ @--- instance 'Component' OnTile where--- 'isExclusiveRel' = True--- @------ This means that if we insert a new @OnTile@ relationship to the player, the old one will be automatically removed.------ Anyway, it's finally time to spawn our player:------ @--- spawnPlayer :: 'System' ()--- spawnPlayer = do--- 'Just' tile <- getTile (5, 5)--- _ <- 'spawn' (Player, 'Rel' OnTile tile)--- 'pure' ()--- @------ @Rel OnTile tile@ inserts the @(OnTile, tile)@ relationship on the player.------ We can avoid the @pure@ by just using @void@ to consume the value of @spawn@:------ @--- spawnPlayer = do--- 'Just' tile <- getTile (5, 5)--- 'void' $ 'spawn' (Player, 'Rel' OnTile tile)--- @------ Now we can add the player spawning logic to @Startup@:------ @--- instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' spawnPlayer--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' spawnGrid--- @------ Except there's something really wrong in the logic above!--- If we run the app, we will get an error pointing us to the @Just tile <-@ in @spawnPlayer@.--- That system assumes @getTile@ will produce a valid result, but that will only happen if the grid is already initialized. Which means we want--- to guarantee that @spawnPlayer@ happens /after/ @spawnGrid@.------ We can do this by providing an explicit order when scheduling:------ @--- instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' $ spawnPlayer '`after`' spawnGrid--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' spawnGrid--- @------ The app should now run without issues!---- $walls--- Our game will also have Walls which can block the player's movement.------ @--- data Wall = Wall deriving ('Component')--- @------ I've written a system which spawns walls and places them on the tiles around the edge of the grid:------ @--- spawnWall :: ('Int', 'Int') -> 'System' 'Entity'--- spawnWall pos = do--- 'Just' tile <- getTile pos--- 'spawn' (Wall, 'Rel' OnTile tile)------ spawnWalls :: 'System' ()--- spawnWalls = do--- 'for_' [0 .. gridW - 1] $ \i -> spawnWall (0, i)--- 'for_' [0 .. gridW - 1] $ \i -> spawnWall (gridH - 1, i)--- 'for_' [1 .. gridH - 2] $ \i -> spawnWall (i, 0)--- 'for_' [1 .. gridH - 2] $ \i -> spawnWall (i, gridW - 1)--- @------ The system also needs to be scheduled to run, so @MainPlugin@ now looks like this:------ @--- instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' spawnPlayer '`after`' spawnGrid--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' (spawnGrid, spawnWalls)--- @------ Additionally, I wrote a system which checks if a given tile has a wall on it:------ @--- hasWall :: 'Entity' -> 'System' 'Bool'--- hasWall tile = do--- walls <- 'query'' 'E' ('With' ('C' \@Wall, 'R' \@OnTile tile))--- 'pure' $ 'not' $ 'null' walls--- @------ @E@ just grabs the Entity of all queried entities. @With@ is a query filter that makes it so the query only iterates over entities which have those components (in this case,--- they must have @Wall@ and must have a @OnTile@ relationship to this precise tile).--- Note that @query'@ is the filtered version of @query@.------ Here's the same system but in quasi-notation:------ @--- hasWall tile = do--- walls <- ['q'|Entity / With (Wall, OnTile -\> tile)|]--- 'pure' $ 'not' $ 'null' walls--- @------ We can compress it even further using @\<$\>@:------ @--- hasWall tile = 'not' . 'null' '<$>' ['q'|Entity / With (Wall, OnTile -\> tile)|]--- @---- $move--- Next, we should write a system which moves the player from one tile to another.------ First, let's write the actual logic for moving in a certain direction:------ @--- movePlayerBy :: ('Int', 'Int') -> 'System' ()--- movePlayerBy dir = do--- 'Just' player <- 'single'' E ('With' ('C' \@Player))------ 'Just' rel <- 'get' ('R' \@OnTile 'Any') player--- let tile = rel.target------ newTile <- moveBy dir tile------ 'for_' newTile $ \t -> do--- wall <- hasWall t--- 'unless' wall $ 'insert' ('Rel' OnTile t) player--- @------ Let's break it down line-by-line.--------- First, we get the entity of the player:------ @--- 'Just' player <- 'single'' E ('With' ('C' \@Player))--- @------ @single@ is a variant of @query@ which returns a @Maybe@ based on whether there is exactly one entity matching the query or not. We know there is exactly one player, and--- we know by this point it should be spawned, so doing the @Just player <-@ unwrapping is fine.------ The query could also be writtten as:------ @--- ['s'|Entity / With Player|]--- @------ Next, we get the entity of the current tile the player is on:------ @--- 'Just' rel <- 'get' ('R' \@OnTile 'Any') player--- let tile = rel.target--- @------ Querying for @R \OnTile Any@ will give us a list of all @OnTile@ relationships of the player. However, because earlier we set @OnTile@--- to be @Exclusive@, Mischief knows to only return a single relationship.------ We then use @rel.target@ to get the target entity of the relationship, which, in this case, is the tile we are looking for.------ The @get@ could also be written as:------ @--- ['g'|OnTile -\> *|] player--- @------ After, we use the earlier @moveDir@ system to get the new tile the player will be on:------ @--- newTile <- moveBy dir tile--- @------ And finally, we unwrap it (@moveBy@ returns a @Maybe Entity@), check if there is a Wall on it, and if there isn't, move the player to it.------ @--- 'for_' newTile $ \t -> do--- wall <- hasWall t--- 'unless' wall $ 'insert' ('Rel' OnTile t) player--- @------ This could also be written as:------ @--- 'for_' newTile $ \t ->--- hasWall t '>>=' 'flip' 'unless' ('insert' ('Rel' OnTile t) player)--- @------ We also need to somehow get input from the user. Mischief exposes some useful functions for this in the following module:------ @--- import "Mischief.ECS.Stdin" qualified as [Stdin]("Mischief.ECS.Stdin")--- @------ These functions are useful for the purpose of this tutorial but should probably never be used in a released game. Instead, you should import a dedicated--- haskell input library, or use @mischief-input@ which is based on SDL.------ In order to read input we'll need to add @Stdin.init@ to a plugin, which you'll see a bit later.------ For now, we can just use the @Stdin.readLast@ to empty the input buffer and get the last character typed by the user, if any.------ @--- movePlayer :: 'System' ()--- movePlayer = do--- c <- [Stdin]("Mischief.ECS.Stdin").'Mischief.ECS.Stdin.readLast'--- 'for_' c $ \case--- \'w\' -> movePlayerBy (-1, 0)--- \'s\' -> movePlayerBy (1, 0)--- \'a\' -> movePlayerBy (0, -1)--- \'d\' -> movePlayerBy (0, 1)--- _ -> 'pure' ()--- @------ The @for_@ just /'iterates/' over the @Maybe@, applying the function if it has a value, and doing nothing otherwise.------ At this point the codebase is starting to grow, so I have decided to create a new @PlayerPlugin@ which handles all the player logic (including the new movement system),--- and make it a dependency of @MainPlugin@:------ @--- data MainPlugin = MainPlugin deriving ('Eq')------ instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Stdin]("Mischief.ECS.Stdin").'Mischief.ECS.Stdin.init'--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' (spawnGrid, spawnWalls)------ 'plugins' _ = 'plug' PlayerPlugin------ data PlayerPlugin = PlayerPlugin deriving ('Eq')------ instance 'Plugin' PlayerPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' $ spawnPlayer '`after`' spawnGrid--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' movePlayer--- @---- $display--- It's finally time to actually display our game's grid in the terminal!------ You may remember earlier we wrote a system which checks if there is a wall on a given tile.------ @--- hasWall :: 'Entity' -> 'System' 'Bool'--- hasWall tile = 'not' . 'null' '<$>' ['q'|Entity / With (Wall, OnTile -\> tile)|]--- @------ It'd be useful to have something similar but for any arbitrary type of entity. We can write a generic variant of it like this:------ @--- tileHas :: forall c. ('QueryType' c) => 'Entity' -> 'System' 'Bool'--- tileHas tile = 'not' . 'null' '<$>' ['q'|Entity / With (c, OnTile -> tile)|]--- @------ @QueryType@ is a sort of catch-all constraint that makes sure @c@ is a component and can be queried (so nothing weird like it being a tuple).------ Make sure to add @{-# LANGUAGE AllowAmbiguousTypes #-}@ at the top of your .hs file, otherwise the type system will not like that @c@ can not be inferred from the--- function's signature (alternatively you can just pass a @Proxy c@ as a workaround but I personally prefer the other way).------ We can now write @hasWall@ as just:------ @--- hasWall = tileHas \@Wall--- @------ Now it should be easy to write a system that receives a tile and returns a character to represent it:------ @--- showTile :: 'Entity' -> 'System' 'Char'--- showTile tile = do--- player <- tileHas \@Player tile--- wall <- tileHas \@Wall tile------ 'pure' $--- if--- | player -> \'@\'--- | wall -> \'#\'--- | otherwise -> \'.\'--- @------ This requires the @MultiWayIf@ language extension, but there are many ways to write it without it, it's just a personal preference.------ Next, I've written a system which produces a String for the whole grid by calling the previous function on each tile:------ @--- showGrid :: 'System' 'String'--- showGrid = do--- 'Just' (Grid tiles) <- 'res' \@Grid--- lines <- 'for' tiles $ 'traverse' showTile--- 'pure' $ 'unlines' lines--- @------ All that's left is to write a system that prints the string, and schedule it to happen each frame. We'll do the actual--- printing via the @printClear@ function of "Mischief.ECS.Stdout", which automatically clears the terminal.------ @--- printGrid :: 'System' ()--- printGrid = 'Mischief.ECS.Stdout.printClear' '=<<' showGrid--- @------ @--- instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Stdin]("Mischief.ECS.Stdin").'Mischief.ECS.Stdin.init'--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' (spawnGrid, spawnWalls)--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' printGrid--- @------ If you run the app now, you should see the game's grid and we able to use @wasd@ to move the player around!------ @--- ####################--- \#..................#--- \#..................#--- \#..................#--- \#..................#--- \#....\@.............#--- \#..................#--- \#..................#--- \#..................#--- ####################--- @---- $rand--- For some of the next sections, an ability to choose random tiles would be very useful. So let's work on that.------ I've chosen to use the @random@ package, so just add it as a dependency to your project and import it:------ @--- import System.Random--- import System.Random.Stateful--- @------ We need some sort of mutable generator, so I'll create a resource to hold it:------ @--- data Rand = Rand (IOGenM StdGen) deriving ('Component')------ newGen :: 'System' Rand--- newGen = Rand \<$\> (newIOGenM =<< initStdGen)--- @------ Don't forget to insert the resource!------ @--- instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Stdin]("Mischief.ECS.Stdin").'Mischief.ECS.Stdin.init'--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' (spawnGrid, spawnWalls)--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' printGrid------ 'insertRes' '=<<' newGen--- @------ Now it's possible to write a system that generates a random position on the grid:------ @--- randomPos :: 'System' ('Int', 'Int')--- randomPos = do--- 'Just' (Rand gen) <- res @Rand--- i <- applyIOGen (uniformR (0, gridH - 1)) gen--- j <- applyIOGen (uniformR (0, gridW - 1)) gen--- return (i, j)--- @------ And a system that uses it to get the Entity of a random tile:------ @--- randomTile :: 'System' 'Entity'--- randomTile = 'unwrap' \<$\> getTile randomPos--- @------ @unwrap@ is a utility function provided by Mischief that just grabs the value out of a @Maybe@, or panics if there is no value. But in this case,--- we know there will be a value since the provided position is valid.---- $enemies--- It would be a pretty boring game if there were no obstacles. For that reason, we're going to add some enemies.------ Here's the marker component that will be used to identify them:------ @--- data Enemy = Enemy deriving ('Component')--- @------ I'll use this system to spawn an enemy, using the @randomTile@ function defined earlier:------ @--- spawnEnemy :: 'System' Entity--- spawnEnemy = do--- tile <- randomTile--- 'spawn' (Enemy, 'Rel' OnTile tile)--- @------ And this as a driver to handle all enemy spawning (it just spawns 5 enemies):------ @--- spawnEnemies :: 'System' ()--- spawnEnemies = 'for_' [0 .. 4] $ 'const' spawnEnemy--- @------ I've also modified the @showTile@ system to take enemies into account:------ @------ showTile :: 'Entity' -> 'System' 'Char'--- showTile tile = do--- player <- tileHas \@Player tile--- enemy <- tileHas \@Enemy tile--- wall <- tileHas \@Wall tile------ 'pure' $--- if--- | player -> \'@\'--- | wall -> \'#\'--- | enemy -> \'!\'--- | otherwise -> \'.\'--- @------ Finally, we need a to schedule the enemy spawning, so I've created a new @EnemyPlugin@:------ @--- data EnemyPlugin = EnemyPlugin deriving ('Eq')------ instance 'Plugin' EnemyPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' spawnEnemies--- @------ And added it to the list of plugins added by @MainPlugin@:------ @--- instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = ...------ 'plugins' = 'plug' (PlayerPlugin, EnemyPlugin)--- @------ You should now see something like this when running the app:------ @--- ####################--- \#..................#--- \#..!...............#--- \#....!.!...........#--- \#..................#--- \#....\@.............#--- \#..................#--- \#..................#--- \#........!.!.......#--- ####################--- @---- $moveE--- Right now the enemies just sit there. Let's make them move!------ But first, I'd like to present you a new concept: @transitive queries@.------ Up until now, if we wanted the position of the tile of the player we'd do somehing like:------ @--- 'Just' tile <- 'single'' ('R' \@OnTile 'Any') ('With' ('C' \@Player))--- 'Just' pos <- 'get' ('Val' ('C' \@Pos)) tile.target--- @------ We'd do a query to get the player's relationship to the tile, then do another query on the actual tile to get its position.------ But this could also be written as:------ @--- 'Just' pos <- 'single'' ('R' \@OnTile ('Q' ('Val' ('C' \@Pos)))) ('With' ('C' \@Player))--- @------ Instead of getting all relationship, we use the @Q@ marker to run the given query on each target of the relationship. So we transitively--- get the position of the tile through its relationship to the player.------ If you think this is uglier than just the two queries earlier, don't worry, the quasi notation looks much better:------ @--- 'Just' pos \<- ['s'|OnTile -\> (*Pos) / With Player|]--- @------ Don't forget to put the @()@ around @*Pos@!------ You should now be able to understand this system that handles the movement of the enemies:------ @--- moveEnemies :: 'System' ()--- moveEnemies = do--- 'Just' pos \<- ['s'|OnTile -\> (*Pos) / With Player|]------ enemies \<- ['q'|Entity, OnTile -\> (Entity, *Pos) / With Enemy|]--- 'for_' enemies $ \(enemy, (enemyTarget, enemyPos)) -> do------ diff <- decideEnemyDir enemyPos pos------ newTile <- moveBy diff enemyTile--- 'for_' newTile $ \t -> do--- 'insert' ('Rel' OnTile t) enemy--- @------ With this helper function for deciding which direction to move on, based on the player's position.------ @--- decideEnemyDir :: Pos -> Pos -> 'System' ('Int', 'Int')--- decideEnemyDir (Pos (ex, ey)) (Pos (px, py)) = do--- 'pure' $--- if--- | ex > px -> (-1, 0)--- | ey > py -> (0, -1)--- | ex < px -> (1, 0)--- | ey < py -> (0, 1)--- | otherwise -> (0, 0)--- @------ And don't forget to schedule it:------ @--- instance 'Plugin' EnemyPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' spawnEnemies--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' moveEnemies--- @------ Except there's a small problem. If you run the app now, you may notice you don't see any enemies!------ That's because they all already got to the player and are hiding behind it! We've set @moveEnemies@--- to happen every frame, and our frames are happening almost instantly. So we need to add some sort of timing to the enemy's movement.------ There are many cleaner high-level solutions to fix this, some of them even using async systems, but instead, I'll take the opportunity to introduce you to an important notion, Time!------ In order to use @Time@ utilities, you need to add the @'TimePlugin'@ to your app, so I'll add it to our @MainPlugin@:------ @--- 'plugins' = 'plug' (PlayerPlugin, EnemyPlugin, TimePlugin)--- @------ In any system you can use the @deltaTime@ function to get the number of seconds passed since the last frame.------ Mischief also provides a hnady way of keeping track of time via the @Timer@.------ @--- import "Mischief.ECS.Timer" ('Mischief.ECS.Timer.Timer')--- import "Mischief.ECS.Timer" qualified as [Timer]("Mischief.ECS.Timer")--- @------ We can now create a @Cooldown@ component which stores a Timer.------ @--- data Cooldown = Cooldown {timer :: 'Mischief.ECS.Timer'} deriving ('Component')--- @------ We want this to always be on every Enemy, so we can make it a required component of the @Enemy@ component.------ @--- instance 'Component' Enemy where--- 'required' = 'require' \@Cooldown--- @------ In order to have that compile, we also need to provide a @Default@ instance for @Cooldown@:------ @--- instance 'Default' Cooldown where--- 'def' = [Timer]("Mischief.ECS.Timer").'Mischief.ECS.Timer.new' 1 [Timer]("Mischief.ECS.Timer").'Mischief.ECS.Timer.Repeat'--- @------ @Timer.new@ takes a Float (the duration of the timer), and a @Mode@ which is either @Repeat@ or @Once@.------ @Cooldown@ should now automatically be on every enemy.------ We can use the @Timer.tick@ function to advance the state of a timer. It returns the new state, along with a Bool that tells us whether the timer has just finished or not.------ All that's left is to put all of this together:------ @--- moveEnemies :: 'System' ()--- moveEnemies = do--- 'Just' pos \<- ['s'|OnTile -\> (*Pos) / With Player|]--- delta <- 'deltaTime'------ enemies \<- ['q'|Entity, OnTile -\> (Entity, *Pos), Cooldown / With Enemy|]--- 'for_' enemies $ \(enemy, (enemyTile, enemyPos), cooldown) -> do--- let (timer, finished) = [Timer]("Mischief.ECS.Timer").'Mischief.ECS.Timer.tick' delta cooldown.timer--- 'set' cooldown $ Cooldown timer------ 'when' finished $ do--- diff <- decideEnemyDir enemyPos pos------ newTile <- moveBy diff enemyTile--- 'for_' newTile $ \t -> do--- 'insert' ('Rel' OnTile t) enemy--- @------ Each frame, we tick the cooldown timer of each enemy, and only move them if that timer has just finished. This means every enemy will now move only once per 0.5 seconds.------ Don't forget to use the @set@ to pass the new timer back into the ECS! In Mischief, all variables you use are @immutable@, so you need to explicitly order mutations.------ You can now run your app and see the enemies chasing you!---- $collision--- Right now the enemies just kinda overlap each other and go under the player. We can fix that by preventing them to move.------ Now, let's write a function that tells us whether a certain tile is free to move on or not:------ @--- tileIsFree :: 'Entity' -> 'System' Bool--- tileIsFree tile = do--- wall <- tileHas \@Wall tile--- enemy <- tileHas \@Enemy tile--- player <- tileHas \@Player tile--- pure $ not (wall || enemy || player)--- @------ Plus, an extra one which takes the tile's position directly rather than the entity:------ @--- tileAtPosIsFree :: ('Int', 'Int') -> 'System' 'Bool'--- tileAtPosIsFree pos = do--- tile <- getTile pos--- 'maybe' ('pure' False) tileIsFree tile--- @------ And let's integrate it into the system which decides the enemy's movement direction:------ @--- decideEnemyDir :: Pos -> Pos -> 'System' ('Int', 'Int')--- decideEnemyDir (Pos (ex, ey)) (Pos (px, py)) = do--- left <- tileAtPosIsFree (ex - 1, ey)--- up <- tileAtPosIsFree (ex, ey - 1)--- right <- tileAtPosIsFree (ex + 1, ey)--- down <- tileAtPosIsFree (ex, ey + 1)------ 'pure' $--- if--- | ex \> px && left -\> (-1, 0)--- | ey \> py && up -\> (0, -1)--- | ex \< px && right -\> (1, 0)--- | ey \< py && down -\> (0, 1)--- | otherwise -> (0, 0)--- @------ (There are definitely /much/ better ways to write this but I can't really be bothered, feel free to make it cleaner at home)------ I've also replaced the @hasWall@ in the @movePlayerBy@ function with @tileIsFree@, so the player can collide with enemies as well--- (make sure to also replace the @'unless'@ with @'when'@!).------ The game should now have fullly working collision and feel much more solid!---- $health--- Here's a simple one: let's add a @Health@ component to the player and have it be displayed under the grid each frame.------ I'll also give it a @Default@ instance so it can be required by the @Player@ component.------ @--- data Health = Health {hp :: 'Int'} deriving ('Component')------ instance 'Default' Health where--- 'def' = Health 100--- @------ @--- instance 'Component' Player where--- 'required' = 'require' \@Health--- @------ I've written a system that returns a string for the health:------ @--- showHealth :: 'System' 'String'--- showHealth = do--- 'Just' health <- ['s'|Health / With Player|]--- 'pure' $ "Health: " ++ 'show' health.hp--- @------ And I added it to the printing system:------ @--- printGrid :: 'System' ()--- printGrid = do--- grid <- showGrid--- health <- showHealth--- 'Mischief.ECS.Stdout.printClear' $ health ++ \"\\n\" ++ grid--- @------ In case you're thinking about it, yes, Health could just be a resource, I just decided to make it a component.------ Your game should now print the health at the top:------ @--- Health: 100--- ####################--- \#............!.....#--- \#.....!............#--- \#..................#--- \#..................#--- \#....\@!......!..!..#--- \#..................#--- \#..................#--- \#..................#--- ####################--- @---- $dmg--- Let's add behavior for enemies damaging the player. I'll also take this opportunity to introduce you to Events.------ We can create a damage event like this:------ @--- data Damage = Damage deriving ('Event')--- @------ We also need an observer system for it:------ @--- onDamage :: Damage -> 'System' ()--- onDamage dmg = do--- 'Just' health <- ['s'|Health / With Player|]--- 'modify' health $ \(Health x) -> Health $ 'max' (x - dmg.amount) 0--- @------ In order to activate the system when the event is triggered, we need to spawn an observer for it. I'll do it in the @PlayerPlugin@:------ @--- import "Mischief.ECS.Observers" as [Observers]("Mischief.ECS.Observers")--- @------ @--- instance 'Plugin' PlayerPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' $ spawnPlayer '`after`' spawnGrid--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' movePlayer--- 'void' $ [Observers]("Mischief.ECS.Observers").'Mischief.ECS.Observers.spawn' onDamage--- @------ The last thing we need is a way for enemies to trigger the event. I made a system which checks if an enemy is adjacent to the player and triggers the event:------ @--- tryDamage :: 'System' ()--- tryDamage = do--- 'Just' player \<- ['s'|OnTile -\> (*Pos) / With Player|]--- enemies \<- ['q'|OnTile -\> (*Pos) / With Enemy|]------ 'for_' enemies $ \pos -> do--- 'when' (isAdjacent pos player) $ do--- 'trigger' (Damage 5)--- @------ With this helper function:------ @--- isAdjacent :: Pos -> Pos -> 'Bool--- isAdjacent (Pos (x1, y1)) (Pos (x2, y2)) =--- let dx = abs (x1 - x2)--- dy = abs (y1 - y2)--- in (dx == 1 && dy == 0) || (dx == 0 && dy == 1)--- @------ I've scheduled @tryDamage@ to happen every frame, after both the player and enemies have moved:------ @--- instance 'Plugin' EnemyPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' spawnEnemies--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' moveEnemies--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' $ tryDamage '`after`' moveEnemies '`after`' movePlayer--- @------ This now /technically/ works, except that the enemies almost instantly kill the player on contact. That's because they deal damage every frame,--- the same issue we had when they were moving each frame.------ I'll show you a different way to solve this problem. We can add an @Invincible@ component on the player after being hit once,--- which causes it to not receive damage, and which is removed after a delay.------ @--- data Invincible = Invincible deriving ('Component')--- @------ I'll modify the @onDamage@ observer like so:------ @--- onDamage :: Damage -> 'System' ()--- onDamage dmg = do--- player <- ['s'|(Entity, Health) / With Player, Without Invincible|]------ 'for_' player $ \(entity, health) -> do--- 'modify' health $ \(Health x) -> Health $ max (x - dmg.amount) 0------ 'insert' Invincible entity--- 'delay' 1000000 $ 'remove' ('C' \@Invincible) entity--- @------ Let's analyze it.------ This line queries the player's Entity and Health, but only if they don't have the @Invincible@ component.------ @--- player <- ['s'|(Entity, Health) / With Player, Without Invincible|]--- @------ If the query returned something (if the player isn't invincible), it will take damage.------ @--- 'modify' health $ \(Health x) -\> Health $ max (x - dmg.amount) 0--- @------ Then the @Invincible@ component will be inserted on the player.------ @--- 'insert' Invincible entity--- @------ And finally, we use the @delay@ async function to tell Mischief to run remove the component after a delay (in miliseconds):------ @--- 'delay' 1000000 $ 'remove' ('C' \@Invincible) entity--- @------ Now, the player will only be able to take damage once per second!---- $quit--- It feels weird that the player can reach 0 health but the game just keeps running. So let's add some logic for quitting:--------- @--- onDamage :: Damage -> 'System' ()--- onDamage dmg = do--- player <- ['s'|(Entity, Health) / With Player, Without Invincible|]------ 'for_' player $ \(entity, health) -> do--- 'modify' health $ \(Health x) -> Health $ max (x - dmg.amount) 0------ 'insert' Invincible entity--- 'delay' 1000000 $ 'remove' ('C' \@Invincible) entity------ 'when' (health.hp == 0) $ 'liftIO' 'exitSuccess'--- @------ But you may notice, the player actually takes an extra hit before that condition is triggered. That's because the @health@ variable is immutable.--- When we call @modify@, we update the live value of the component, but our local variable stays as it is.------ We can use @update@ to get the live value:------ @--- 'Just' health <- 'update' health--- 'when' (health.hp == 0) $ 'liftIO' 'exitSuccess'--- @------ The logic should now work as expected.---- $coins--- Now, for our last bit of logic, we should add a reason for the player to not die. Let's spawn a bunch of coins!------ First, we need a marker component for the coins:------ @--- data Coin = Coin deriving ('Component')--- @------ Second, here's a system that spawns a coin on a random free tile:------ @--- spawnCoin :: 'System' ()--- spawnCoin = do--- tile <- randomTile--- free <- tileIsFree tile--- if free--- then--- 'void' $ 'spawn' (Coin, 'Rel' OnTile tile)--- else--- spawnCoin--- @------ (If the tile is not free, it will just keep looping and generating tiles until it finds one that is).------ Third, we can use intervals to make the system repeat every two seconds.------ @--- import "Mischief.ECS.Interval" qualified as [Interval]("Mischief.ECS.Interval")--- @--------- @--- instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Stdin]("Mischief.ECS.Stdin").'Mischief.ECS.Stdin.init'--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' (spawnGrid, spawnWalls)--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' printGrid------ interval <- [Interval]("Mischief.ECS.Interval").'Mischief.ECS.Interval.start' 2000000 spawnCoin------ 'insertRes' '=<<' newGen--- @------ You can also use @Interval.stop@ on the returned value to stop the interval at any point, but I won't be doing that here.------ Finally, we should display the coins:------ @--- showTile :: 'Entity' -> 'System' 'Char'--- showTile tile = do--- player <- tileHas \@Player tile--- enemy <- tileHas \@Enemy tile--- wall <- tileHas \@Wall tile--- coin <- tileHas \@Coin tile------ 'pure' $--- if--- | player -> \'@\'--- | wall -> \'#\'--- | enemy -> \'!\'--- | coin -> \'$\'--- | otherwise -> \'.\'--- @---- $collect--- All that's left is letting the player collect coins and keeping track of how many they got.------ I'll do this via a resource this time.------ @--- data Coins = Coins 'Int' deriving ('Component')--- @------ I'll insert the resource in @MainPlugin@:------ @--- instance 'Plugin' MainPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Stdin]("Mischief.ECS.Stdin").'Mischief.ECS.Stdin.init'--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' (spawnGrid, spawnWalls)--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' printGrid------ interval <- [Interval]("Mischief.ECS.Interval").'Mischief.ECS.Interval.start' 2000000 spawnCoin------ 'insertRes' '=<<' newGen--- 'insertRes' $ Coins 0--- @------ And I'll update the display to also show the number of coins:------ @--- showCoins :: 'System' 'String'--- showCoins = do--- 'Just' (Coins c) <- 'res' \@Coins--- 'pure' $ \"Coins: \" ++ show c--- @------ @--- printGrid :: 'System' ()--- printGrid = do--- grid <- showGrid--- health <- showHealth--- coins <- showCoins--- 'Mischief.ECS.Stdout.printClear' $ health ++ \"\\n\" ++ grid ++ \"\\n\" ++ coins ++ \"\\n\"--- @------ And now finally, a system that checks if there are any coins on the same tile as the player, despawns them, and increments the resource:------ @--- collectCoins :: 'System' ()--- collectCoins = do--- 'Just' playerTile <- ['s'|OnTile -> (Entity) / With Player|]--- coins <- ['q'|Entity / With OnTile -> playerTile, With Coin|]------ 'Just' (Coins c) <- 'res' \@Coins--- 'insertRes' $ Coins $ c + 'length' coins------ 'for_' coins despawn--- @------ And I'll schedule it:------ @--- instance 'Plugin' PlayerPlugin where--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' $ spawnPlayer '`after`' spawnGrid--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' movePlayer--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' $ collectCoins '`after`' movePlayer------ 'void' $ [Observers]("Mischief.ECS.Observers").'Mischief.ECS.Observers.spawn' onDamage--- @------ And that's it! Out player should now be able to collect coins!------ @--- Health: 5--- ####################--- \#..................#--- \#..................#--- \#$....!!!!!\@...$...#--- \#$.................#--- \#..................#--- \#......$...........#--- \#.$................#--- \#..................#--- ####################--- Coins: 16--- @---- $nextf--- Don't worry if there are various details that you haven't fully understood yet. The next chapters will go into detail over the many aspects of the ECS. This chapter was just meant--- to give you a general idea of working with Mischief. You can find the whole code for this example [here](https://github.com/PVDoriginal/mischief/blob/main/mischief-ecs/examples/Dungeon.hs).
− src/Mischief/ECS/Tutorial/Events.hs
@@ -1,99 +0,0 @@-{-# OPTIONS_GHC -Wno-unused-imports #-}---- |--- Module: Events and Messages Tutorial--- Description: Tutorial on using @Events and Messages@------ This module contains a more in-depth tutorial on using @Events and Messages@.------ [Previous Chapter: Systems]("Mischief.ECS.Tutorial.Systems")------ [Main Page]("Mischief.ECS")-module Mischief.ECS.Tutorial.Events- ( -- * Learn You an ECS for Great Mischief! - 8. Events and Messages- -- $intro-- -- * Events- -- $events-- -- * Messages- -- $msg- )-where--import Mischief.ECS---- $intro--- This chapter presents two ways of communicating between systems. @Events@ and @Messages@!.---- $events--- You should already be pretty familiar with @Events@ if you've read the rest of this tutorial. But let's get into them again just in case.------ An event is any type deriving the @Event@ typeclass.------ @--- data Foo = Foo 'Int' deriving ('Event')--- @------ Events can be triggered using @trigger@:------ @--- 'trigger' (Foo 5)--- @------ Events can be listened to by spawning an observer. Observers will be called immediately when an event is triggered.------ @--- listenToFoo :: Foo -> 'System' ()--- listenToFoo foo = ...--- @------ @--- _ <- [Observers]("Mischief.ECS.Observers").'Mischief.ECS.Observers.spawn' listenToFoo--- @------ There are also pre-defined systems called by Mischief:------ * @'OnInsert' c@ - called after @c@ has been inserted on an entity.--- * @'OnRemove' c@ - called before @c@ is removed from an entity.--- * @'OnInsertRel' c@ / @'OnRemoveRel' c@ - same as the above but for relationships.------ Events are really handy, but they can be become inefficient if called many times in a frame, since there will be an individual system ran per triggered event.--- That's where messages come in!---- $msg--- @Messages@ are just convenient wrappers around resources that are used for inter-system communication.------ A message is a type that derives the @Message@ typeclass:------ @--- data Foo = Foo 'Int' deriving ('Message')--- @------ Tools for working with messages are found provided by the @Messages@ module:------ @--- import "Mischief.ECS.Messages" qualified as [Messages]("Mischief.ECS.Messages")--- @------ There are two main functions used to deal with messages, @write@ and @read@.------ You can use @write@ to write a new message into the buffer:------ @--- [Messages]("Mischief.ECS.Messages").'Mischief.ECS.Messages.write' (Foo 5)--- @------ And you can use @read@ to drain the buffer of a certain type of message:------ @--- messages <- [Messages]("Mischief.ECS.Messages").'Mischief.ECS.Messages.read' \@Foo--- @------ @--- messages :: [Foo]--- @------ @read@ will get all messages that haven't yet been read by the current system, erasing them from the buffer.------ Messages are better than events for higher throughput, since they function in batches so a system can process multiple messages at a time.
+ src/Mischief/ECS/Tutorial/Guides/Basics.hs view
@@ -0,0 +1,281 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- [Previous Chapter: Organizing your Project]("Mischief.ECS.Tutorial.Guides.Organizing")+--+-- [Next Chapter: Query Basics]("Mischief.ECS.Tutorial.Guides.QueryBasics")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.Basics+ ( -- * Learn You an ECS for Great Mischief! - 2.2. Entities and Components Basics+ -- $intro++ -- * What's in a Bundle?+ -- $bundle++ -- * Manipulating Data+ -- $manipulating++ -- * Reading Data+ -- $reading++ -- * Required Components+ -- $required++ -- * Metadata+ -- $metadata++ -- * Resources+ -- $resources++ -- * From+ -- $from++ -- * Registering Components+ -- $reg++ -- * [Next Chapter: Query Basics]("Mischief.ECS.Tutorial.Guides.QueryBasics")+ )+where++import Data.Default (Default)+import Mischief.ECS++-- $intro+-- Data in an ECS is manipulated (stored, mutated, destroyed), by performing operations on entities. These operations usually involve bundles of components.++-- $bundle+-- A bundle is a tuple formed of /"things/" that can be /attached/ (or, inserted) to an Entity.+--+-- A bundle can contain:+--+-- * Component Values: Will attach the component.+--+-- @+-- (Name \"A\", Health 100) bob+-- @+--+-- * Relationships: Will attach a relationship with the given entity, containing the given component.+--+-- @+-- ('Rel' Likes charlie) bob+-- @+--+-- * Resources: Will insert the given resource into the world. Is agnostic to the entity it's inserted on.+--+-- @+-- ('Res' (FooRes \"A\"))+-- @+--+-- * From: Will insert the given component on the given entity. Is agnostic to the entity it's inserted on.+--+-- @+-- ('From' alice (Name \"Alice\"))+-- @++-- $manipulating+-- In order to create new data, you spawn an entity with the given data stored in components (through a bundle):+--+-- @+-- bob <- 'spawn' (Name \"Bob\", Player)+-- @+--+-- You can also attach new data to an entity:+--+-- @+-- 'insert' (Health 100, 'Rel' Likes alice) bob+-- @+--+-- To change data, you re-insert the relevant components to the entity:+--+-- @+-- 'insert' (Name \"Bob's New Name\") bob+-- @+--+-- To erase data, you remove the components from the entity:+--+-- @+-- 'remove' ('C' \@Health, 'R' Likes alice) bob+-- @+--+-- You can also @despawn@ an entity, erasing all its data:+--+-- @+-- 'despawn' bob+-- @++-- $reading+-- Data can be retrieved through queries.+--+-- You can get Bob's name and health like this:+--+-- @+-- 'Just' (name, health) \<- 'single' ['q'|bob. Name, Health|]+-- @+--+-- You can grab each entity's name and health like this:+--+-- @+-- x \<- 'query' ['q'|Name, Health|]+-- @+--+-- More on queries in [the next chapter]("Mischief.ECS.Tutorial.Guides.QueryBasics").++-- $required+-- A component @A@ can require any number of other components, as long as they all instance the 'Default' typeclass.+--+-- @+-- data Player+--+-- instance 'Component' Player where+-- required = 'require' \@Health+--+-- data Health = Health 'Int' deriving ('Component')+--+-- instance 'Default' Health where+-- def = Health 100+-- @+--+-- When inserting @Player@ on an entity, @Health 100@ will be immediately inserted as well, as long as the @Health@ component+-- wasn't already inserted.++-- $resources+-- @Resources@ are singleton components that can be easily accessed and modified from any system.+--+-- Any component can be used as a resource.+--+-- @+-- data MyRes = MyRes 'Int' deriving ('Component')+-- @+--+-- You can insert a resource into the World using @insertRes@.+--+-- @+-- 'insertRes' $ MyRes 5+-- @+--+-- And you can query for the value of a resource using @res@:+--+-- @+-- 'Just' myRes <- 'res' \@MyRes+-- @+--+-- Resources are implemented by inserting a component's value on its own meta entity. Which means:+--+-- @+-- 'res' \@MyRes+-- @+--+-- Is equivalent to:+--+-- @+-- m <- 'meta' \@MyRes+-- 'single' $ [q|m. MyRes|]+-- @+--+-- Besides using @insertRes@, resources can be inserted as part of a bundle through the @Res@ type.+--+-- The following will spawn an entity, insert a @Name@ on it, and additionally insert a resource into the world.+--+-- @+-- a <- 'spawn' ()+-- 'insert' (Name "A", 'Res' $ SomeRes 5) a+-- @+--+-- It's equivalent to:+--+-- @+-- a <- 'spawn' ()+-- 'insert' (Name \"A\") a+-- 'insertRes' (SomeRes 5)+-- @+--+-- @Res@ can also be used in queries to grab a resource:+--+-- @+-- 'mkQuery' ('C' \@Name, 'Res' \@SomeRes)+-- @+--+-- Or+--+-- @+-- ['q'|Name, Res SomeRes|]+-- @+--+-- This will query the name of all entities, and attach @Res SomeRes@ to all of them.++-- $from+-- From is a special type in Mischief:+--+-- @+-- data From c = From {entity :: 'Entity', comp :: c}+-- @+--+-- It symbolizes the idea of a foreign component. A component belonging to an external entity that we store alongside it.+--+-- From can be inserted in any bundle, inserting the component @c@ on the entity stored /inside/ it. For instance, the following code+-- will insert the name \"B2\" on @b@ and the name \"A2\" on @a@:+--+-- @+-- a \<- 'spawn' (Name \"A\")+-- b \<- 'spawn' (Name \"B\")+--+-- 'insert' (Name \"B2\", 'From' a (Name \"A2\")) b+-- @+--+-- Equivalent to:+--+-- @+-- a \<- 'spawn' (Name \"A\")+-- b \<- 'spawn' (Name \"B\")+--+-- 'insert' (Name \"B2\") b+-- 'insert' (Name \"A2\") a+-- @+--+-- From is generally returned from traversal queries such as:+--+-- @+-- x \<- 'query' ['q'|Name, ChildOf -\> (Name)|]+-- @+--+-- @+-- x :: [Name, From Name]+-- @+--+-- Where @Name@ will be the name of each entity and @From Name@ will be the name of their parent.++-- $metadata+-- Each component has a corresponding entity in the World.+-- The components on that entity store information about the component itself. Such as which archetypes it is part of.+--+-- A component's entity can be accessed by using @meta@.+--+-- Getting the entities of the @Name@ and @Player@ components:+--+-- @+-- x <- 'meta' \@Name+-- y <- 'meta' \@Player+-- @+--+-- Most users should avoid tinkering with Meta Components unless they have a good reason to,+-- and should absolutely never remove or change any components added to them by the @ECS@.++-- $reg+-- @Registering@ a component involves spawning its meta entity and adding the corresponding data.+--+-- Each component is registered automatically the first time it is inserted on an Entity, so you don't usually+-- have to worry about registration.+--+-- Queries are also smart about components; if you query or filter for a component that hasn't been registered yet, they will just+-- assume that component can't be be on any Entity. Queries can't perform registration themselves, because they're not allowed to mutate+-- the world in any way.+--+-- However, there may be /extremely/ niche situations where you want to register components earlier than normal, which is where manual registration comes in:+--+-- @+-- 'register' \@(Player, Health, Position)+-- @+--+-- One such situation could be wanting to check the requirements between multiple components on runtime.+-- If a component hasn't been registered yet, it won't show up when you query for components that require a specific component.
+ src/Mischief/ECS/Tutorial/Guides/Change.hs view
@@ -0,0 +1,140 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- Module: Queries Tutorial+-- Description: How To Mischief.+--+-- [Previous Chapter: Events and Messages]("Mischief.ECS.Tutorial.Guides.Events")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.Change+ ( -- * Learn You an ECS for Great Mischief! - 2.11. Change Detection+ -- $intro++ -- * Events+ -- $events++ -- * Query Filters+ -- $filters++ -- * Avoiding False Changes+ -- $false++ -- * Component Hooks+ -- $hooks+ )+where++import Mischief.ECS++-- $intro+-- This chapter will show you ways of listening and reacting to changes in the World.++-- $events+-- There are various pre-defined events that can be used to listen to when a component is added, inserted, or removed:+--+-- @+-- onAddName :: 'OnAdd' Name -> 'System' ()+-- onAddName OnAdd {entity} = do+-- Just name <- 'single' ['q'|entity. Name|]+-- 'info' ['i'|#{entity} was just added the name #{name}|]+-- @+--+-- For insertion and removal you can use @OnSet@ and @OnRemove@ respectively.+--+-- There are also @OnAddRel@, @OnSetRel@, @OnRemoveRel@ which act the same but can listen to relationship changes.+--+-- For instance:+--+-- @+-- onRemoveChildOf :: 'OnRemoveRel' ChildOf -> 'System' ()+-- onRemoveChildOf OnRemoveRel {entity, target} =+-- 'info' ['i'|#{entiy} is no longer the child of #{target}|]+-- @++-- $filters+-- You can use the @Changed@ filter in a query to select the entities that have had a component changed.+--+-- With the following query we can get the name of all entities that have had the @Health@ component changed in the last frame:+--+-- @+-- ['q'|Name / Changed Health|]+-- & 'query'+-- @+--+-- Note however, that the changed marker is set per-archetype table. This means that you might get extra entities, not just those which have had that component changed.+-- This is intended to be used for optimization purposes, and not for reacting to individual changes. Use events for that instead.++-- $false+-- One essential detail to be aware of here is that listening to insertion (through @OnSet@ or @Changed@) doesn't necessarily mean a component's value has been changed!+--+-- The following @insert@ /will/ trigger change detection:+--+-- @+-- p <- 'spawn' (Health 100)+-- 'insert' (Health 100) p+-- @+--+-- To avoid this, you can derive 'Eq' on your components and use @'insertIfNeq'@, which will only perform insertion if the value of the component is different+-- from the current one.++-- $hooks+-- Hooks are specialized events associated directly to a Component instance.+--+-- @+-- instance 'Component' Foo where+-- onAdd = [hook onAddFoo]+-- onSet = [hook onSetFoo]+-- onRemove = [hook onRemoveFoo]+--+-- onAddFoo :: 'HookContext' -> 'System' ()+-- onAddFoo = ...+--+-- onSetFoo :: 'HookContext' -> 'System' ()+-- onSetFoo = ...+--+-- onRemoveFoo :: 'HookContext' -> 'System' ()+-- onRemoveFoo = ...+-- @+--+-- Similar to change events, @HookContext@ has an @.entity@ field you can use to access the entity the hook was triggered on.+--+-- Relationship support dedicated hooks, named @onAddRel@, @onSetRel@, @onRemoveRel@, following the same rules as the normal hooks.+--+-- This is how we can log a message each time @Likes@ is added between two entities:+--+-- @+-- instance 'Component' Likes where+-- onAddRel = ['hookRel' onLikesAdd]+--+-- onLikesAdd :: 'HookContextRel' -> 'System' ()+-- onLikesAdd HookContextRel {entity, target} = 'info' ['i'|#{entity} now likes #{target}!|]+-- @+--+-- There are a number of predefined hooks that are useful when working with relationships, which can be found in "Mischief.ECS.HooksRel".+-- For instance, @addOther@ can be used to automate adding a complementary relationship on the target of a relationship.+--+-- As a quick example of why this is useful, this is how you'd create a @Before@/@After@ relationship between entities:+--+-- @+-- data Before = Before+-- data After = After+--+-- instance 'Component' Before where+-- onAddRel = [HooksRel.'Mischief.ECS.HooksRel.addOther' ('const' After)]+--+-- instance 'Component' After where+-- 'hooks' = [HooksRel.'Mischief.ECS.HooksRel.addOther' ('const' Before)]+-- @+--+-- Now, when you do:+--+-- @+-- 'insert' ('Rel' Before a) b+-- @+--+-- A @Rel After b@ will be inserted automatically on @a@.+--+-- And vice versa.+--+-- There are other interesting hooks, such as ones for automatically cleaning up a relationship when an entity is despawned. Check out "Mischief.ECS.HooksRel" for details!
+ src/Mischief/ECS/Tutorial/Guides/Events.hs view
@@ -0,0 +1,91 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- Module: Queries Tutorial+-- Description: How To Mischief.+--+-- [Previous Chapter: Parallelism and Asynchronicity]("Mischief.ECS.Tutorial.Guides.Parallelism")+--+-- [Next Chapter: Change Detection]("Mischief.ECS.Tutorial.Guides.Change")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.Events+ ( -- * Learn You an ECS for Great Mischief! - 2.10. Events and Messages+ -- $intro++ -- * Events+ -- $events++ -- * Messages+ -- $msg++ -- * [Next Chapter: Change Detection]("Mischief.ECS.Tutorial.Guides.Change")+ )+where++import Mischief.ECS++-- $intro+-- This chapter presents two ways of communicating between systems. @Events@ and @Messages@!.++-- $events+-- An event is any type deriving the @Event@ typeclass.+--+-- @+-- data Foo = Foo 'Int' deriving ('Event')+-- @+--+-- Events can be triggered using @trigger@:+--+-- @+-- 'trigger' (Foo 5)+-- @+--+-- Events can be listened to by spawning an observer. Observers will be called immediately when an event is triggered.+--+-- @+-- listenToFoo :: Foo -> 'System' ()+-- listenToFoo foo = ...+-- @+--+-- @+-- _ <- 'spawn' ('Observer' listenToFoo)+-- @++-- $msg+-- @Messages@ are just convenient wrappers around resources that are used for inter-system communication.+--+-- A message is a type that derives the @Message@ typeclass:+--+-- @+-- data Foo = Foo 'Int' deriving ('Message')+-- @+--+-- Tools for working with messages are found provided by the @Messages@ module:+--+-- @+-- import "Mischief.ECS.Messages" qualified as Messages+-- @+--+-- There are two main functions used to work with messages, @write@ and @read@.+--+-- You can use @write@ to write a new message into the buffer:+--+-- @+-- Messages.'Mischief.ECS.Messages.write' (Foo 5)+-- @+--+-- And you can use @read@ to drain the buffer of a certain type of message:+--+-- @+-- messages <- Messages.'Mischief.ECS.Messages.read' \@Foo+-- @+--+-- @+-- messages :: [Foo]+-- @+--+-- @read@ will get all messages that haven't yet been read by the current system, erasing them from the buffer.+--+-- Messages are better than events when dealing with higher throughput, since they natively function in batches, letting you process multiple messages at a time from+-- within the same system.
+ src/Mischief/ECS/Tutorial/Guides/MonadicQueries.hs view
@@ -0,0 +1,263 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- Module: Queries Tutorial+-- Description: How To Mischief.+--+-- [Previous Chapter: Query Piping]("Mischief.ECS.Tutorial.Guides.QueryPiping")+--+-- [Next Chapter: Scheduling and Running Systems]("Mischief.ECS.Tutorial.Guides.Scheduling")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.MonadicQueries+ ( -- * Learn You an ECS for Great Mischief! - 2.6. Monadic Queries+ -- $intro++ -- * Binding+ -- $bind++ -- * Sources+ -- $source++ -- * Accumulating and Aggregating Queries+ -- $accumulation++ -- * Grabbing New Components and Resources+ -- $extend++ -- * Lifting+ -- $lifting++ -- * [Next Chapter: Scheduling and Running Systems]("Mischief.ECS.Tutorial.Guides.Scheduling")+ )+where++import Control.Monad (void)+import Data.Foldable (for_)+import Data.Traversable (for)+import Mischief.ECS++-- $intro+-- Finally we get to the most powerful and complete ability of Queries: monadic operations!++-- $bind+-- Monadic queries are usually written by using one of the query runners (@query_@, @query@, @single@), followed by a @do@:+--+-- @+-- 'query_' $ do+-- playerName <- ['q'|Name / With Player|]+-- 'pure' ()+-- @+--+-- When you bind a value out of a query (the @<-@ above), you are immediately iterating over all elements of a query and applying operations to them.+-- It is in a way similar to the @List@ monad if you are familiar with it (if you are not, don't worry!).+--+-- Essentially, in pseudo-imperative, python-eseque code, the above is equivalent to:+--+-- @+-- for playerName in [q|Name / With Player|]:+-- pure ()+-- @+--+-- There are many great uses to this, which you'll see in this chapter.+-- The most naive one is that it naturally allows you to write sql-like joins:+--+-- @+-- 'query_' $ do+-- playerName <- ['q'|Name / With Player|]+-- ['q'|Name / With Enemy|]+-- & 'qinfo' (\\enemyName -> ['i'|#{playerName}, #{enemyName}|])+-- @+--+-- Which is, in essence:+--+-- @+-- for playerName in [q|Name / With Player|]:+-- for enemyName in [q|Name / With Enemy|]:+-- print (playerName + \", \" + enemyName)+-- @++-- $source+-- There is one detail about the section above that may not make much sense at first. Essentially, you may be wondering what happens if you do this:+--+-- @+-- 'query_' $ do+-- Health health <- ['q'|Health / With Player|]+-- ['q'|Name / With Enemy|]+-- & 'qinsert' (\_ -> (Health $ health - 1))+-- @+--+-- It does, in fact, insert the player's health (minus 1) to each enemy! But why, and how can we do an insertion on the player?+--+-- The answer are Sources. Each time you start a new query (e.g. the @[q|Name / With Enemy|]@ above), you turn your \"attention\" to the+-- new entiites. The source of the query are the enemies.+--+-- You can change the source of a query using @qrefocus@; this code will iterate over all enemies but insert the Health component on the player:+--+-- @+-- 'query_' $ do+-- (player, Health health) <- ['q'|Entity, Health / With Player|]+-- ['q'|Name / With Enemy|]+-- & 'qrefocus' player+-- & 'qinsert' (\_ -> (Health $ health - 1))+-- @+--+-- Keeping track of sources can be a significant footgun and one of the main disadvantages to writing monadic queries.+--+-- One other way to bring the \"attention\" back to the players is by using @qpure@ (or @pure ()@ followed by @qrefocus@):+--+-- @+-- 'query_' $ do+-- (player, Health health) <- ['q'|Entity, Health / With Player|]+-- enemyName <- ['q'|Name / With Enemy|]+-- 'qpure' player+-- & 'qinsert' (\_ -> (Health $ health - 1))+-- @+--+-- @qpure@ essentially creates a one-element query with the current player as the source, you can think of this as:+--+-- @+-- for (player, Health health) in ['q'|Entity, Health / With Player|]:+-- for enemyName in ['q'|Name / With Enemy|]+-- for _ in [()]:+-- insert (Health $ health - 1) player+-- @+--+-- Note that using just @pure ()@ without refocusing is completely valid. But keep in mind that the source of the computation you're+-- writing will be the \"null entity\". If you want to perform any sourced operations such as @qinsert@, you'll need to refocus first.++-- $accumulation+-- To re-use the context from the previous chapter, consider that you have players marked by a @Player@ component, each with+-- a @Coins Int@ component storing how many coins they have collected. You have coins in the world, marked by @Coin Int@, and want to+-- iterate over all players, then over all coins, and, if the coin is in range of the player, despawn the entity and add its value to the+-- total collected by the player.+--+-- This \"nested query\" is a problem you may encounter in a lot of places. You've already seen how it's solved imperatively, by actually nesting the queries,+-- but that solution isn't great. It's highly imperative and mutable, and the indentation gets bad as you need to nest more and more queries.+--+-- This is where the monadic part of the queries comes in. The goal is to perform all those operations /inside/ the same query, smartly chaining them using /do/.+--+-- As a first step it's often helpful to start with a code like this:+--+-- @+-- 'query_' $ do+-- (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]+-- 'pure' ()+-- @+--+-- Just copy the main query that you're iterating over. And /bind/ the data out of it using @<-@.+-- Wrap it in a @do@ folllwing a @query_@ or @query@.+--+-- At this point, you've put your hand on a slice of the data flowing through the query. What you do with this data+-- dictates what will happen with all data.+--+-- Next, try replacing the @pure@ with another query, typically the second query that you're iterating over:+--+-- @+-- 'query_' $ do+-- (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]+--+-- ['q'|Coin / With OnTile -\> tile|]+-- & 'qtap' (\\e _ -> 'despawn' e)+-- @+--+-- This is already a valid query. You're iterating over all the coins that are on each player's tile. You've changed the source, or subject,+-- from being the players to being the coins. But in this case that's not really what you want. You want to somehow collect or aggregate+-- the coins to use them in the larger query.+--+-- Let's try collecting them first:+--+-- @+-- 'query_' $ do+-- (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]+--+-- collectedCoins <- 'qcollect' $ do+-- ['q'|Coin / With OnTile -\> tile|]+-- & 'qtap' (\\e _ -> 'despawn' e)+--+-- pure ()+-- @+--+-- The @qcollect@ function is, in a way, the opposite of the bind operation. It returns from a loop by aggregating all its results into a list.+-- You may see that @collectedCoins@ has type @[From Coin]@.+--+-- Now we need to calculate the actual sum of all coins. We can use a fold for that:+--+-- @+-- let totalCoins = 'foldr' (\\('From' _ (Coin x)) -> (+ x)) coins collectedCoins+-- @+--+-- In order to update the Coins component you have a few options.+--+-- Firstly, you can use @qpure entity@ (or @pure () & qrefocus entity@) to bring the attention back to the player and run a @qinsert@ on it:+--+-- @+-- 'query_' $ do+-- (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]+--+-- collectedCoins <- 'qcollect' $ do+-- ['q'|Coin / With OnTile -\> tile|]+-- & 'qtap' (\\e _ -> 'despawn' e)+--+-- pure ()+-- @+--+-- So this is what you want to be doing instead:+--+-- @+-- 'query_' $ do+-- (entity, 'From' _ tile, Coins coins) \<- ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]+--+-- ['q'|Coin / With OnTile -\> tile|]+-- & 'qtap' (\\e _ -> 'despawn' e)+-- & 'qcollect' entity+-- & 'qinsert' (\\collectedCoins -> Coins $ 'foldr' (\\('From' _ (Coin x)) -> (+ x)) coins collectedCoins)+--+-- let totalCoins = 'foldr' (\\('From' _ (Coin x)) -> (+ x)) coins collectedCoins+--+-- 'qpure' entity+-- & qinsert (const $ Coins totalCoins)+-- @+--+-- That's it! The coins are now folded and added to the total held by the player.+--+-- You can also iterate over the \"null entity\" and perform an insertion manually:+--+-- @+-- 'pure' ()+-- & 'qtap' (\\_ _ -> 'insert' (Coins totalCoins) entity)+-- @+--+-- Or you can use @qlift@ which does exactly this!+--+-- @+-- 'qlift' $ 'insert' (Coins totalCoins) entity+-- @++-- $extend+-- One powerup monadic queries give you is that they let you seamlessly read components and resources without unwrapping Maybes.+--+-- @+-- 'query_' $ do+-- (entity, health) <- ['q'|Entity, Health|]+-- (names, player) <- 'qget' entity ['qd'|Likes -> (Names), Player|]+-- someRes <- 'qres' \@SomeRes+-- 'pure' ()+-- @+--+-- @qget@ will grab the respective Query Data from the given entity. Due to the nature of the monad, your query will be nullified+-- if any of these componetns don't exist. You would be iterating over an empty list!+--+-- @qres@ works the same way but grabs a resource.++-- $lifting+-- You can lift systems into the query monad by using @qlift@:+--+-- @+-- 'query_' $ do+-- (player, Health x) <- ['q'|Entity, Health / With Player|]+-- 'qlift' $ 'insert' (Health $ x + 1) player+-- @+--+-- This, essentially, allows you to switch between the two /access modes/. One where you can easily query and grab new data, and one+-- where you can perform fine-grained changes on said data.
+ src/Mischief/ECS/Tutorial/Guides/Organizing.hs view
@@ -0,0 +1,76 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- [Previous Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Introdution.Dungeon")+--+-- [Next Chapter: Entities and Components Basics]("Mischief.ECS.Tutorial.Guides.Basics")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.Organizing+ ( -- * Learn You an ECS for Great Mischief! - 2.1. Organizing your Project+ -- $intro++ -- * Modular Plugins and Features+ -- $plugins++ -- * Files+ -- $files++ -- * Be Consistent!+ -- $consistency++ -- * [Next Chapter: Entities and Components Basics]("Mischief.ECS.Tutorial.Guides.Basics")+ )+where++import Mischief.ECS++-- $intro+-- It's a common problem when working with a game framework to wonder where and how to organize your logic.+-- Luckily, Mischief comes with a few tips and guidelines on the subject.++-- $plugins+-- Modularily is highly encouraged in everything you do. You should strive to design your codebase so that each+-- part of it can be easily plugged in and out without affecting the functionality of anything outside of it.+--+-- For instance, you may have a @PhysicsPlugin@ that adds physics to your game, a @RenderPlugin@ which renders objects, a @LevelPlugin@ that spawns your levels.+--+-- @+-- main :: 'IO' ()+-- main = do+-- app <- 'newApp'+-- 'addPlugin' \@PhysicsPlugin+-- 'addPlugin' \@RenderPlugin+-- 'addPlugin' \@LevelPlugin+-- @+--+-- Ideally, each of these plugins add their own independent features. So if you were to remove @physicsPlugin@, your entities simply wouldn't+-- move and collide anymore, but the rest of the app would work just fine. Dependencies between plugins at the same level should be avoided.+-- For instance, @PhysicsPlugin@ should not ever depend on @RenderPlugin@.+--+-- Internally, @PhsyicsPlugin@ could also be subdivided into its own plugins with separate roles:+--+-- @+-- data PhysicsPlugin+--+-- instance 'Plugin' PhysicsPlugin where+-- deps = ['dep' \@CollisionPlugin, 'dep' \@MovePlugin]+-- @+--+-- And these should follow the same principle of independence.+--+-- Of course, complete modulariy may just not be possible or desireable at points, but we consider it to be a great standard to look up to.++-- $files+-- We encourage placing each plugin in a separate module / file. It's also desireable for the module hierarchy to+-- follow the follow the plugin dependencies. If the @Player@ plugin in @MyGame.Player@ depends on the @Health@ plugin, the latter+-- is ideally placed in a @MyGame.Player.Health@ module.+--+-- We also encourage defining components locally in the module that uses them most. If multiple modules at the same level use the+-- same component, consider placing it in a separete @Common@ module.++-- $consistency+-- Following some of these guidelines may be incompatible with your type of project or the way you prefer to organize things. That's fine!+--+-- The most important thing is to try and keep it all consistent throughout your project. Set clear+-- guidelines and rules for yourself and stick to them!
+ src/Mischief/ECS/Tutorial/Guides/Parallelism.hs view
@@ -0,0 +1,118 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- Module: Queries Tutorial+-- Description: How To Mischief.+--+-- [Previous Chapter: Keeping Track of Time]("Mischief.ECS.Tutorial.Guides.Time")+--+-- [Next Chapter: Events and Messages]("Mischief.ECS.Tutorial.Guides.Events")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.Parallelism+ ( -- * Learn You an ECS for Great Mischief! - 2.9. Parallelism and Asynchronicity+ -- $intro++ -- * The System Monads+ -- $monad++ -- * Running Systems in Parallel+ -- $par++ -- * Iterating in Parallel+ -- $parFor++ -- * Querying in Parallel+ -- $qpar++ -- * Asynchronicity+ -- $async++ -- * [Next Chapter: Events and Messages]("Mischief.ECS.Tutorial.Guides.Events")+ )+where++import Mischief.ECS++-- $intro+-- Mischief offers a few primitives for making systems run in parallel or asynchronously.++-- $monad+-- There is another monad that acts similary to @System@. It is called @ParSystem@, and+-- it doesn't allow for immediate mutations of the World.+--+-- Both @ParSystem@ and @System@ instance the @MonadSystem@ class, which you can use to write functions that work with both:+--+-- @+-- f :: (MonadSystem w m) => m ()+-- @+--+-- Functions such as @query@, @query_@, @single@ work with both monads, since they only read data.+--+-- Functions that mutate the world only work with @System@, requiring you to use @defer@ (see previous chapter) when used in @ParSystem@:+--+-- @+-- 'defer' $ 'insert' (Health 100) bob+-- @++-- $par+-- You can run a list of @ParSystems@ in parallel using the @par@ function:+--+-- @+-- par [foo, bar]+-- @++-- $parFor+-- You can iterate over Foldables in parallel using @parFor@:+--+-- @+-- names <- 'query' ['q'|Entity, Name|]+-- 'parFor' names $ \\(entity, Name name) -> do+-- 'defer' $ 'insert' (Name $ name ++ \"2\") entity+-- @++-- $qpar+-- You can parallelize any @Query ParSystem a@ using @qpar@:+--+-- @+-- names <-+-- ['q'|Name / With Player|]+-- & 'qpar'+-- & 'query'+-- @++-- $async+-- Mischief has a couple of primitived that allow running systems asynchronously.+--+-- === runAfter+--+-- Asynchonicity in Mischief can be achieved using the @runAfter@ primtitive.+-- You provide it an 'IO' action that returns an @a@, and a system which consumes that @a@.+--+-- The 'IO' will be ran fully asychrnously and then will add the system to a special async-friendly deferred list that+-- will be applied at the first available sync point.+--+-- We can look at @delay@ as an example of how this may be useful:+--+-- @+-- 'delay' d system = 'runAfter' ('threadDelay' d) ('const' system)+-- @+--+-- Whcih allows delaying any system by an amount of time:+--+-- @+-- 'delay' 0.5 $ 'insert' (Health 100) player+-- @+--+-- === Repeaters+--+-- Repeaters are another way of running async systems.+--+-- A Repeater will run the given system at the given interval of time. This Repeater will+-- print \"Hello!\" once per second:+--+-- @+-- 'spawn_' 'Repeater' 1 ('info' \"Hello!\")+-- @+--+-- Repeaters can be despawned to stop them from running.
+ src/Mischief/ECS/Tutorial/Guides/QuasiConversion.hs view
@@ -0,0 +1,125 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- [Previous Chapter: Query Basics]("Mischief.ECS.Tutorial.Guides.QueryBasics")+--+-- [Next Chapter: Query Piping]("Mischief.ECS.Tutorial.Guides.QueryPiping")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.QuasiConversion+ ( -- * Learn You an ECS for Great Mischief! - 2.4. Quasi-Query Conversion+ -- $intro++ -- * Query Markers+ -- $markers++ -- * Converting @q@+ -- $q++ -- * [Next Chapter: Query Piping]("Mischief.ECS.Tutorial.Guides.QueryPiping")+ )+where++import Mischief.ECS++-- $intro+-- This chapter will teach you to write queries without quasi-notation (such as @[q|..|]@).+-- It is extremely optional and you may never want or need to use the normal notation,+-- so feel free to skip past it.++-- $markers+-- To write a query without the quasi-quoter, you ned to use markers. They are+-- simple abstract types that define the sort of thing you're querying for.+--+-- You can query for a component using @'C'@.+--+-- @+-- ['qd'|Name, Health|]+-- @+--+-- @+-- ('C' \@Name, 'C' \@Health)+-- @+--+-- You can query for a relationship using @'R'@.+--+-- @+-- ['qd'|Likes -> *, ChildOf -> bob|]+-- @+--+-- @+-- ('R' \@Likes 'Any', 'R' \@ChildOf bob)+-- @+--+-- You can run transitive queries using @'Q'@ (or @'Q''@ to also provide a filter):+--+-- @+-- ['qd'|Likes -> (Name), ChildOf -> (Health / With Player)|]+-- @+--+-- @+-- ('R' \@Likes ('Q' ('C' \@Name)), 'R' \@ChildOf ('Q'' ('C' \@Health) ('With' ('C' \@Player))))+-- @+--+-- You can query for optional components using @M@. And for optional relationships using @MR@.+--+-- @+-- ['qd'|Maybe Name, Maybe Likes -> *|]+-- @+--+-- @+-- ('M' \@Name, 'MR' \@Likes 'Any')+-- @+--+-- Each filter takes a single @C@ or @R@ type. They must be chained with @And@, @Or@, @Not@.+--+-- @+-- ['qf'|With Player, Without Likes -> * || !Changed Health|]+-- @+--+-- @+-- 'With' ('C' \@Player) '`And`' ('Without' ('R' \@Likes 'Any') '`Or`' 'Not' ('Changed' ('C' \@Health)))+-- @++-- $q+-- The @q@ quasi-quoter will generate one of four functions, based on the following context:+--+-- If you query for components with no filter:+--+-- @+-- ['q'|Name, Health|]+-- @+--+-- @+-- 'mkQuery' ('C' \@Name, 'C' \@Health)+-- @+--+-- If you query for components with a filter:+--+-- @+-- ['q'|Name / With Player|]+-- @+--+-- @+-- 'mkQuery'' ('C' \@Name) ('With' ('C' \@Player))+-- @+--+-- If you query for components of a specific entity with no filter:+--+-- @+-- ['q'|bob. Name, Health|]+-- @+--+-- @+-- 'mkGet' bob ('C' \@Name, 'C' \@Health)+-- @+--+-- If you query for components of a specific entity with a filter:+--+-- @+-- ['q'|bob. Name / With Player|]+-- @+--+-- @+-- 'mkGet'' bob ('C' \@Name) ('With' ('C' \@Player))+-- @
+ src/Mischief/ECS/Tutorial/Guides/QueryBasics.hs view
@@ -0,0 +1,150 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- [Previous Chapter: Entities and Components Basics]("Mischief.ECS.Tutorial.Guides.Basics")+--+-- [Next Chapter: Quasi-Query Conversion]("Mischief.ECS.Tutorial.Guides.QuasiConversion")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.QueryBasics+ ( -- * Learn You an ECS for Great Mischief! - 2.3. Query Basics+ -- $intro++ -- * Query Data+ -- $qd++ -- * Query Filters+ -- $qf++ -- * What about @q@?+ -- $q++ -- * What was that about a Query?+ -- $query++ -- * [Next Chapter: Quasi-Query Conversion]("Mischief.ECS.Tutorial.Guides.QuasiConversion")+ )+where++import Mischief.ECS++-- $intro+-- In an ECS, queries are a mechanism used to read data based on a certain user-defined format. In Mischief, they're a bit more complex than that;+-- queries are pure functions chained together which can trigger effects in the World, as well as retrieve data.+--+-- This chapter is an introduction to the @q@ quasi-quoter, and a short how-to guide on reading data from entities. The next chapters+-- delve into more complex queries.++-- $qd+-- Query Data is a tuple of types that are queryable. Query Data can be written using the @qd@ macro. Here are the things you can query for:+--+-- * Components: get a component's value.+--+-- @+-- ['qd'|Name, Health|]+-- @+--+-- * Relationships: get relationships with a specific component. Either all relationships, or just with a specific entity. This will return either a+-- single @Rel@ or a list of them.+--+-- @+-- ['qd'|Likes -> *|]+-- @+--+-- @+-- ['qd'|Likes -> bob|]+-- @+--+-- * Transitive Components: similar to the previous but you give it an entire query to grab from each of the targets. Will return a @From@ or a list of them.+--+-- @+-- ['qd'|Likes -> (Name, Health)|]+-- @+--+-- * Resource: Grabs a resource from the world.+--+-- @+-- ['qd'|Res SomeRes]+-- @+--+-- When you query for each of these types, the query will filter out any entities that don't have them.+-- For instance, if you're querying for @Health@, you're leaving out entities that don't have @Health@, and if+-- you're querying for @Likes -> *@ you're leaving out entities that like no one.+--+-- In order to include optional components, resources, relationships, you can use the @Maybe@ keyword:+--+-- @+-- ['qd'|Name, Maybe Health, Maybe Likes -> (Name)]+-- @++-- $qf+-- Query Filters can be used to filter the entities that a query is looking at. They can be written with the @qf@ quasi-quoter.+--+-- There are three such filters:+--+-- * With: filters out entities that do not have this component.+--+-- @+-- ['qf'|With Player|]+-- @+--+-- * Without: filters out entities that have this component.+--+-- @+-- ['qf'|Without Enemy|]+-- @+--+-- * Changed: filters out /most/ entities that haven't had this component be re-inserted since the current system last ran:+--+-- @+-- ['qf'|Changed Name|]+-- @+--+-- Filters can be combined with @&&@ and @||@. Separating them by @,@ will result in an implicit @&&@:+--+-- @+-- ['qf'|With Health, Changed Name|]+-- @+--+-- Is the same as:+--+-- @+-- ['qf'|With Health && Changed Name|]+-- @++-- $q+-- @q@ combines the previous two quasi-quoters, and produces a @Query@.+--+-- For instance, the following will result in a @Query (Name, Player)@:+--+-- @+-- ['q'|Name, Player / Without Enemy|]+-- @+--+-- The first part (left of the @/@) is the same as @qd@, while the other is the same as @qf@. The @/@ along with the filter can be omitted. Note that+-- the filter in @q@ must be an Archetype Filter!+--+-- Additionally, @q@ accepts an Entity at the start, filling the Query with just the components from that Entity:+--+-- @+-- ['q'|bob. Name, Health|]+-- @+--+-- Grabbing the @Name@ and @Health@ of /just/ bob is an @O(1)@ operation.++-- $query+-- @Query (Name, Player)@ is a Query through which the @Name@ and @Player@ components flow. You can think of it+-- as a wrapper around @[(Name, Player)]@, although it's a bit more complicated than that.+--+-- The list of components can be extracted from a Query using @query@ or @single@:+--+-- @+-- names \<- 'query' ['q'|Name|]+-- @+--+-- @+-- 'Just' bobName \<- 'single' ['q'|bob. Name|]+-- @+--+-- @single@ is the same as @query@ but instead of returning a list, it returns a @Maybe@ depending on if that list had exactly one element or not. It is useful+-- if you're retrieving the components of a specific entity or if you know there's only one entity that could fit your query (for instance, there is only one Player spawned).
+ src/Mischief/ECS/Tutorial/Guides/QueryPiping.hs view
@@ -0,0 +1,205 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- [Previous Chapter: Quasi-Query Conversion]("Mischief.ECS.Tutorial.Guides.QuasiConversion")+--+-- [Next Chapter: Monadic Queries]("Mischief.ECS.Tutorial.Guides.MonadicQueries")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.QueryPiping+ ( -- * Learn You an ECS for Great Mischief! - 2.5. Query Piping+ -- $intro++ -- * Map, Filters, and Insertion+ -- $map++ -- * Extending Queries+ -- $extend++ -- * Hierarchy Traversal+ -- $trav++ -- * Impure Piping+ -- $impure++ -- * Making Your Own Pipes!+ -- $custom++ -- * [Next Chapter: Monadic Queries]("Mischief.ECS.Tutorial.Guides.MonadicQueries")+ )+where++import Mischief.ECS++-- $intro+-- Query piping is the process of chaining multiple query-returning functions together to produce a+-- computation that can be ran in a system.+--+-- All of these functions are technically pure. Only the @query_@, @query@ and @single@ functions are impure. They take a query computation and run it in the World.+--+-- A piping functions looks something like:+--+-- @+-- qmap :: (a -> b) -> 'Query' m a -> 'Query' m b+-- @+--+-- And they are idiomatically chained using @&@.+--+-- There are many piping functions, all of which you can find in "Mischief.ECS.World.Query.Pipe". This chapter will go over the most important ones.++-- $map+-- @qmap@ is a function which maps each set of components flowing through the query to something else. For instance, this is+-- how you get the health of all players, with @1@ added to it:+--+-- @+-- healths \<-+-- ['q'|Health / With Player|]+-- & 'qmap' (\\(Health x) -\> Health (x + 1))+-- & 'query'+-- @+--+-- Note that @qmap@ only changed the local values of the components returned from the query. It does not automatically update their values in the ECS!+--+-- In order to insert them, you can use @qinsert@:+--+-- @+-- healths \<-+-- ['q'|Health / With Player|]+-- & 'qinsert' (\\(Health x) -\> Health (x + 1))+-- & 'query'+-- @+--+-- You can also use @query_@ to only perform the insertion and discard the results:+--+-- @+-- ['q'|Health / With Player|]+-- & 'qinsert' (\\(Health x) -\> Health (x + 1))+-- & 'query_'+-- @+--+-- @qfilter@ can filter entities. This is how you can update the position of all players that have a velocity higher than 0:+--+-- @+-- ['q'|Position, Velocity / With Player|]+-- & 'qfilter' (\\(_, Velocity v) -\> v \> 0)+-- & 'qinsert' (\\(Position p, Velocity v) -\> Position (p + v))+-- & 'query_'+-- @+--+-- @qcheck@ is a special function which takes an Entity Filter. It lets you easily filter out entities that haven't had a specific component be changed or added:+--+-- @+-- ['q'|Name|]+-- & 'qcheck' ['qf'|Changed Name, !Added Name|]+-- & 'qinfo' (\\name -> ['i'|#{name} has changed their name!|])+-- & 'query_'+-- @+--+-- Note that all filters should be moved to @[q|...|]@ where possible. There they act as Archetype Filters and are much faster.+--+-- @qmapMaybe@ is also worth mentioning, as it combines @qmap@ and @qfilter@ and can be very useful.++-- $impure+-- There are query pipes that accept impure functions. They typically receive both a set of the components in the query, and the Entity associated with+-- that data, to allow for easier mutation.+--+-- @qtap@ simply receives the data and attaches an impure computation:+--+-- @+-- ['q'|Health|]+-- & 'qtap'+-- (\\entity (Health h) -\> do+-- 'info' ['i'|#{entity} has #{h} HP!|]+-- 'insert' (Health $ h + 1) entity+-- )+-- & 'query_'+-- @+--+-- @qtraverse@ is similar, but also maps the data.+--+-- @+-- ['q'|Health|]+-- & 'qtraverse'+-- (\\entity (Health h) -\> do+-- takeDamage \<- someImpureFunction entity+-- if takeDamage then (Health $ h - 1) else Health h+-- )+-- & 'query_'+-- @+--+-- Some of the pure functions also have impure variants. Such as @qmap@ has @qmapM@ (equivalent to @qtraverse@), and @qfilter@ has @qfilterM@.++-- $extend+-- Extensions are a particular category of pipes that extend each set of components in the Query.+--+-- @qentity@ pairs the components with their source entity:+--+-- @+-- ['q'|Name|]+-- & 'qentity'+-- & 'qinfo' (\\(entity, name) -> ['i'|The name of #{entity} is #{name}|])+-- & 'query_'+-- @+--+-- @qextend@ takes a Query Data and a function to map it into the existing query:+--+-- @+-- ['q'|Name, Player|]+-- & 'qextend' ['qd'|Health|] (\\(name, player) health -> (name, health))+-- & 'qinfo' (\\(name, health) -> ['i'|#{name} has #{health}|])+-- & 'query_'+-- @++-- $trav+-- There are two built-in pipes to help you traverse entity hierarchies: @qrelateOne@ and @qrelateMany@.+--+-- First, take a moment to familiarize yourself with the functions in @Mischief.ECS.Relationships.Graph@ and+-- @Mischief.ECS.Relationships.Tree@. They all look either like @Entity -> m (Maybe Entity)@, or like+-- @Entity -> m [Entity]@ (sometimes depending on the exclusivity of the relationship).+--+-- In @Graph@ you have generic graph traversals, going from a node to all nodes that go into it, or to all nodes that+-- it goes into (@incoming@ and @outgoing@ respectively). In @Tree@, there are functions to traverse a tree-like hierarchy,+-- such as getting the @root@ of the tree, or getting all the @leaves@ that are connected to the current node. It is your+-- responsibility to ensure the relationship is a tree! (i.e. has no cycles).+--+-- Both traversal pipes take a traversal function, a Query Data, and a function to map the new components into the current one. One such traversal may look like this:+--+-- @+-- ['q'|Name|]+-- & 'qrelateOne' (Tree.'Mischief.ECS.Relationships.Tree.root' \@Likes) \['qd'|Name|] (,)+-- & 'qinfo' (\\(a, b) -> ['i'|The root of #{a} is #{b}|])+-- & 'query_'+-- @+--+-- @qrelateMany@ works the same but expects a @Entity -> m [Entity]@ traversal instead. In both cases, if the traversal results in no elements+-- (the function returns either Nothing or an empty list), the query will be nulled.+--+-- Take note that the components returned from the targets of the traversal will be wrapped in a @From@.++-- $custom+-- Writing your own pipes is easy!+--+-- For instance, let's say you want a function that damages an entity, and returns their new health.+-- Obviously this is trivial and not worth its own separate function, but try to consider it:+--+-- @+-- qdamage :: 'Int' -> 'Query' 'System' Health -> 'Query' 'System' Health+-- qdamage dmg x =+-- x+-- & 'qmap' (\\(Health x) -\> Health (x - dmg))+-- & 'qinsert' id+-- @+--+-- Which you can then use like so:+--+-- @+-- healths <-+-- ['q'|Health / With Player|]+-- & qdamage 5+-- & 'query'+-- @+--+-- The names of these should generally start with @q@. And they should+-- contain as few side-effects as possible. A good rule to follow+-- is to try to have each pipe apply a single meaningful change to the data,+-- which is obvious from the name.
+ src/Mischief/ECS/Tutorial/Guides/Scheduling.hs view
@@ -0,0 +1,187 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- [Previous Chapter: Monadic Queries]("Mischief.ECS.Tutorial.Guides.MonadicQueries")+--+-- [Next Chapter: Keeping Track of Time]("Mischief.ECS.Tutorial.Guides.Time")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.Scheduling+ ( -- * Learn You an ECS for Great Mischief! - 2.7. Scheduling and Running Systems+ -- $intro++ -- * Automatic Scheduling+ -- $auto_scheduling++ -- * Manual Scheduling+ -- $manual_scheduling++ -- * Creating and Running Schedules+ -- $schedules++ -- * Deferring Systems+ -- $defer++ -- * Fixed Schedules+ -- $fixed++ -- * [Next Chapter: Keeping Track of Time]("Mischief.ECS.Tutorial.Guides.Time")+ )+where++import Control.Monad (void)+import Data.Foldable (for_)+import Data.Traversable (for)+import Mischief.ECS+import Mischief.ECS.App.Schedules (FixedSchedule)+import Mischief.ECS.App.Systems (ScheduledIn, SystemFunction)+import Mischief.ECS.Fixed qualified as Fixed+import Mischief.ECS.Schedules qualified as Schedules+import Mischief.ECS.Systems qualified as Systems++-- $intro+-- A @System@ in Mischief is a Monad that executes operations on a World.+--+-- Unlike other ECS's, systems here are fully composable, In fact, most of the functions discussed in this tutorial so far were systems.+-- For instance, the type of @spawn@ is:+--+-- @+-- 'spawn' :: ('Bundle' b) => b -> 'System' ()@+-- @+--+-- Systems can either be ran directly, or they can be added scheduled.++-- $auto_scheduling+-- In order to schedule a system (or a tuple of systems) you can just use @systems@ to put your hand on them, and @schedule@ to add it to a given Schedule.+--+-- @+-- 'systems' (foo, bar)+-- & 'schedule' \@SomeSchedule+-- @+--+-- Systems scheduled this way are stores as a combination of the actual system and the schedule.+-- If we were to add @foo@ to /another/ schedule, it would be considered a different system.+--+-- The @aftr@ and @before@ functions can be used to explicitly add ordering with other systems when registering them:+--+-- @+-- 'systems' foo+-- & 'before' foo+-- & 'after' baz+-- & 'schedule' \@SomeSchedule+-- @+--+--+-- A system that's been scheduled this way can be unscheduled with @unschedule@ or @unschedule'@.+--+-- @unschedule@ will keep the system alive but disconnect it from the given schedule. If re-scheduled later,+-- the system will keep its previous ordering and other configuration:+--+-- @+-- 'unschedule' \@SomeSchedule foo+-- @+--+-- @unschedule'@ will completely wipe a system, erasing all of its configuration. If re-scheduled later,+-- the system will be completely new and it won't have its previous ordering.+--+-- @+-- 'Systems.unschedule'' \@SomeSchedule foo+-- @++-- $manual_scheduling+-- In order to manually create a schedules system, you spawn an entity with the @SystemFunction@ component:+--+-- @+-- foo :: 'System' ()+-- @+--+-- @+-- fooEntity <- 'spawn' $ 'SystemFunction' foo+-- @+--+-- You can get a schedule's entity using the following function:+--+-- @+-- scheduleEntity <- Schedules.'Schedules.get' \@SomeSchedule+-- @+--+-- Where:+--+-- @+-- import "Mischief.ECS.Schedules" qualified as Schedules+-- @+--+-- To add the system to the schedule you insert a @ScheduledIn@ relationship between the two:+--+-- @+-- 'insert' ('Rel' 'ScheduledIn' scheduleEntity) fooEntity+-- @+--+-- This will make make your system run along with @SomeSchedule@. Compared to using @Systems.add@, this method will not do any sort of bookkeeping for you.+-- Is is your job to keep track of the spawned system's entity.+--+-- Two systems can be ordered by using the @Before@ relationship:+--+-- @+-- fooEntity <- 'spawn' $ 'SystemFunction' foo+-- barEntity <- 'spawn' $ 'SystemFunction' bar+--+-- 'insert' ('Rel' 'Before' fooEntity) barEntity+-- @+--+-- The above will order @bar@ to happen before @foo@.++-- $schedules+-- Same as systems, @Schedules@ are entities. Each schedule has an associated type:+--+-- @+-- data Update deriving ('Schedule')+-- @+--+-- You can both create and get the the entity of a schedule using @Schedules.get@:+--+-- @+-- update <- Schedules.'Schedules.get' \@Update+-- @+--+-- You can run a schedule using @Schedules.run@:+--+-- @+-- Schedules.'Schedules.run' \@Update+-- @+--+-- This will run all systems currently linked to that Schedule, respecting their ordering.+--+-- Mischief has two components: @'StartupSchedule'@ and @'UpdateSchedule'@ which you can add to a schedule to make it automatically run on app startup, respectively each frame.+-- Additionally, 'FixedPlugin' allows you to use 'FixedSchedule'.+--+-- Schedules can also be ordered via @'Before'@ (same relationship used for ordering systems).++-- $defer+-- @defer@ is a very important primitive.+--+-- Normally every systems has its effect applied immediately. When you write @insert (Name \"Bob\") bob@,+-- you are /immediately/ inserting the respective component. When you do @e <- 'spawn' ()@, you are /immediately/ spawning that entity into the World.+--+-- @'defer'@ takes a system and stores it in an internal queue instead of directly applying it.+--+-- @+-- 'defer' $ 'spawn' ()+-- @+--+-- @+-- 'defer' $ do+-- e <- 'spawn' ()+-- 'insert' ('Name' \"Name\") e+-- @+--+-- The queue of deferred systems will be flushed (running all the systems in it) at a sync point, usually in-between systems.+--+-- You can also flush the queue manually using @'flush'@.++-- $fixed+-- Mischief offers Fixed schedules mirroring the normal Update schedules. They are called @'FixedUpdate'@, @'FixedPostUpdate'@ and so on.+--+-- Systems added to them will be ran at the fixed frequency set in the @'FixedFrequency'@ resource. By default 0.03 (30fps).+--+-- Make sure to add the @'FixedPlugin'@ to your App if you wish to use fixed systems.
+ src/Mischief/ECS/Tutorial/Guides/Time.hs view
@@ -0,0 +1,83 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- Module: Queries Tutorial+-- Description: How To Mischief.+--+-- [Previous Chapter: Scheduling and Running Systems]("Mischief.ECS.Tutorial.Guides.Scheduling")+--+-- [Next Chapter: Parallelism and Asynchronicity]("Mischief.ECS.Tutorial.Guides.Parallelism")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Guides.Time+ ( -- * Learn You an ECS for Great Mischief! - 2.8. Keeping Track of Time+ -- $intro++ -- * Time+ -- $time++ -- * Timer+ -- $timer++ -- * [Next Chapter: Parallelism and Asynchronicity]("Mischief.ECS.Tutorial.Guides.Parallelism")+ )+where++import Control.Monad (void)+import Data.Foldable (for_)+import Data.Traversable (for)+import Mischief.ECS+import System.Clock (TimeSpec)++-- $intro+-- This chapter shows you how to get the Time and use Timers.++-- $time+-- To read time you can use the functions from:+--+-- @+-- import "Mischief.ECS.Time" qualified as Time+-- @+--+-- The most important one is @delta@, which gets you the time that has passed since the last frame, in seconds.+--+-- @+-- d <- Time.'Mischief.ECS.Time.delta'+-- @+--+-- You can also use @Time.time@ to get the actual Time:+--+-- @+-- t <- Time.'Mischief.ECS.Time.time'+-- @+--+-- @Time@ has two fields: @.elapsed@ and @.delta@. Both contain a 'TimeSpec'.+--+-- You can convert a TimeSpec to seconds using @specToSecs@:+--+-- @+-- let elapsed = Time.'Mischief.ECS.Time.specToSecs' t.elapsed+-- let delta = Time.'Mischief.ECS.Time.specToSecs' t.delta+-- @++-- $timer+-- A Timer is a data type that can keep track of time and be ticked down each frame.+--+-- @+-- import "Mischief.ECS.Timer" qualified as Timer+-- @+--+-- There are two modes for a timer. A @Repeat@ timer will infinitely loop around once it finished, while an @Once@ timer stops after finishing once.+--+-- To create a timer you must give it a duration and a mode:+--+-- @+-- let timer = Timer.'Mischief.ECS.Timer.new' 0.5 Timer.'Mischief.ECS.Timer.Repeat'+-- @+--+-- The @tick@ function takes an amount of time (typically obtained from @Time.delta@) and returns a new timer, along with a bool specifying whether it just finished or not:+--+-- @+-- delta <- Time.'Mischief.ECS.Time.delta'+-- let (timer', justFinished) <- Timer.'Mischief.ECS.Timer.tick' delta timer+-- @
+ src/Mischief/ECS/Tutorial/Introduction/Dungeon.hs view
@@ -0,0 +1,1194 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- Module: Components Tutorial+-- Description: Introductory Tutorial+--+-- This module walks the user through creating a small game in the terminal.+--+-- [Previous Chapter: Startup Guide]("Mischief.ECS.Tutorial.Introduction.Startup")+--+-- [Next Chapter: Organizing your Project]("Mischief.ECS.Tutorial.Guides.Organizing")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Introduction.Dungeon+ ( -- * Learn You an ECS for Great Mischief! - 1.2. Coding a Dungeon Game+ -- $intro++ -- * Creating an App+ -- $creation++ -- * Spawning the Grid+ -- $grid++ -- * Traversing the Grid+ -- $traversing++ -- * Spawning the Player+ -- $player++ -- * Adding Walls+ -- $walls++ -- * Displaying the Grid+ -- $display++ -- * Moving the Player+ -- $move++ -- * Receiving Input+ -- $input++ -- * Generating Random Positions+ -- $rand++ -- * Adding Enemies+ -- $enemies++ -- * Moving Enemies+ -- $moveE++ -- * Enemy Collision+ -- $collision++ -- * Health+ -- $health++ -- * Taking Damage+ -- $dmg++ -- * Quitting+ -- $quit++ -- * Spawning Coins+ -- $coins++ -- * Collecting Coins+ -- $collect++ -- * Next Steps+ -- $next++ -- * [Next Chapter: Organizing your Project]("Mischief.ECS.Tutorial.Guides.Organizing")+ )+where++import Control.Monad (unless, void, when)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.Default (Default)+import Data.Foldable (for_)+import Data.List ((!?))+import Data.Traversable (for)+import Mischief.ECS+import System.Exit (exitSuccess)++-- $intro+-- This module will walk you through creating a simple terminal-based dungeon crawler in Mischief.+-- The goal is to have a player which we can freely move on a 2D grid, as well as various objects placed on tiles,+-- such as enemies, coins, obstacles, etc.+--+-- If you'd rather go straight to learning about particular solutions or technical details rather than learning by example, feel free to skip to the next chapters.+-- You can always come back to this one once you have a better understanding of things.+--+-- Each section in this chapter adds its own isolated mechanics to the game, so there's no harm in reading up to a point and taking a break or+-- starting another chapter.+--+-- You can see the entire code from this chapter [here](https://codeberg.org/mischief/mischief/src/branch/main/mischief-ecs/examples/Dungeon.hs).++-- $creation+-- Let's start by creating our App and a main Plugin which will serve as the starting point of all our logic.+--+-- @+-- import "Mischief.ECS"+--+-- main :: 'IO' ()+-- main = do+-- app <- 'newApp'+-- 'addPlugin' \@MainPlugin app+-- 'runApp' app+--+-- data MainPlugin+--+-- instance 'Plugin' MainPlugin where+-- init = 'pure' ()+-- @+--+-- You can already run your program and it should work! Although it doesn't do or print anything.++-- $grid+-- The game will play out on a small 2D grid. There are many ways of representing this Mischief, the way I've chosen to do it+-- is by having each tile of the grid be an entity, and the full list of entities stored in a global resource.+--+-- We'll use this @Tile@ component to mark which entities are tiles:+--+-- @+-- data Tile = Tile deriving ('Component')+-- @+--+-- Each tile will also have a @Pos@ component containing it's position on the grid:+--+-- @+-- data Pos = Pos ('Int', 'Int') deriving ('Component')+-- @+--+-- Let's make a resource that stores the list of all tile entities, and call it @Grid@:+--+-- @+-- data Grid = Grid [['Entity']] deriving ('Component')+-- @+--+-- We're also writing these two helper functions returning the width and height of the grid:+--+-- @+-- gridH :: 'Int'+-- gridH = 10+--+-- gridW :: 'Int'+-- gridW = 20+-- @+--+-- Let's write a system that spawns the tiles and initializes the grid resource:+--+-- @+-- spawnGrid :: 'System' ()+-- spawnGrid = do+-- tiles \<-+-- 'for' [0 .. gridH - 1] $ \\i -\>+-- 'for' [0 .. gridW - 1] $ \\j -\>+-- 'spawn' (Tile, Pos (i, j))+--+-- 'insertRes' $ Grid tiles+-- @+--+-- Now we just need to modify @MainPlugin@ so that it schedules @spawnGrid@ to happen when the app starts.+--+-- @+-- instance 'Plugin' MainPlugin where+-- init = do+-- 'systems' spawnGrid+-- & 'schedule' \@Startup+-- @++-- $traversing+-- Next, we need to code a way for traversing between adjacent tiles. Having a tile entity, we should have easy access to the entities found above, below, to the left and right of it.+--+-- First, I've written this function which gets an entity by position, given the Grid:+--+-- @+-- getTile :: ('Int', 'Int') -> Grid -> 'Maybe' 'Entity'+-- getTile (x, y) (Grid tiles) = do+-- line <- tiles !? x+-- line !? y+-- @+--+-- Now we can write a function that receives the position of a given tile, the grid, and finds tiles offset by a certain amount:+--+-- @+-- moveBy :: ('Int', 'Int') -> Pos -> Grid -> 'Maybe' 'Entity'+-- moveBy (x, y) (Pos (x', y')) = getTile (x' + x, y' + y)+-- @++-- $player+-- Our game needs a player, so we should have a component that uniquely identifies it:+--+-- @+-- data Player = Player deriving ('Component')+-- @+--+-- We should also have a relationship to associate an entity to a tile, teling us that it's currently placed on that tile.+--+-- @+-- data OnTile = OnTile+--+-- instance 'Component' OnTile where+-- type 'IsExclusiveRel' OnTile = 'True'+-- @+--+-- Setting @IsExclusiveRel@ to True on the component instance will make it so there can only be one such relationship on an entity at a time.+-- In our case, this means the player can only be on one tile at a time, and moving them will remove the previous relationship.+--+-- Now it's finally time to spawn our player:+--+-- @+-- spawnPlayer :: 'System' ()+-- spawnPlayer = do+-- 'Just' grid \<- res \@Grid+-- let 'Just' tile = getTile (5, 5) grid+-- 'void' $ 'spawn' (Player, 'Rel' OnTile tile)+-- @+--+-- @Rel OnTile tile@ inserts the @(OnTile, tile)@ relationship on the player. Tile @(5, 5)@ is just an arbitrary tile I chose to place the player on.+--+-- Now we can schedule the player spawning logic to @Startup@:+--+-- @+-- instance 'Plugin' MainPlugin where+-- init = do+-- 'systems' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' spawnPlayer+-- & 'schedule' \@Startup+-- @+--+-- Except there's something really wrong in the logic above!+-- If we run the app, we will get an error pointing us to the @Just grid <-@ in @spawnPlayer@.+-- We are assuming the grid already exists when that system runs, Which means we want+-- to guarantee that @spawnPlayer@ happens /after/ @spawnGrid@.+--+-- We can do this by providing an explicit order when scheduling:+--+-- @+-- instance 'Plugin' MainPlugin where+-- init = do+-- 'systems' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' spawnPlayer+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+-- @+--+-- The app should now run without issues!++-- $walls+-- Our game will also have Walls which can block the player's movement. We'll make yet another marker component to represent them:+--+-- @+-- data Wall = Wall deriving ('Component')+-- @+--+-- Let's write a system which spawns a wall on a specific position:+--+-- @+-- spawnWall :: ('Int', 'Int') -> 'System' ('Maybe' 'Entity')+-- spawnWall pos = do+-- 'Just' grid \<- 'res' \@Grid+-- 'for' (getTile pos grid) $ \\tile ->+-- 'spawn' (Wall, Rel OnTile tile)+-- @+--+-- And another system that spawns walls on all the tiles at the edge of the grid:+--+-- @+-- spawnWalls :: 'System' ()+-- spawnWalls = do+-- 'for_' [0 .. gridW - 1] $ \\i -> spawnWall (0, i)+-- 'for_' [0 .. gridW - 1] $ \\i -> spawnWall (gridH - 1, i)+-- 'for_' [1 .. gridH - 2] $ \\i -> spawnWall (i, 0)+-- 'for_' [1 .. gridH - 2] $ \\i -> spawnWall (i, gridW - 1)+-- @+--+-- The system also needs to be scheduled to run, so @MainPlugin@ now looks like this:+--+-- @+-- instance 'Plugin' MainPlugin where+-- init = do+-- 'systems' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' (spawnPlayer, spawnWalls)+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+-- @+--+-- Additionally, it would be helpful to write a system which checks if a given tile has a wall on it:+--+-- @+-- hasWall :: 'Entity' -> 'System' 'Bool'+-- hasWall tile = 'not' . 'null' \<$\> 'query' ['q'|/With Wall, With OnTile -> tile|]+-- @+--+-- We are querying for all entities which have a @Wall@ component and a @OnTile@ relationship to the given entity, and checking whether the list is not null or not.+--+-- But it would be even more useful to have something similar but for any arbitrary component. We can write a generic variant of it like this:+--+-- @+-- tileHas :: forall c. ('Component' c) => 'Entity' -> 'System' 'Bool'+-- tileHas tile = 'not' . 'null' \<$\> 'query' ['q'|Entity / With (c, OnTile -> tile)|]+-- @+--+-- Make sure to add @{-# LANGUAGE AllowAmbiguousTypes #-}@ at the top of your .hs file, otherwise the type checker will not like that @c@ can not be inferred from the+-- function's input.+--+-- We can now write @hasWall@ as just:+--+-- @+-- hasWall :: 'Entity' -> 'System' 'Bool'+-- hasWall = tileHas \@Wall+-- @++-- $display+-- It's finally time to display our game's grid in the terminal!+--+-- Let's start by writing a system that receives a tile and returns a character to represent it based on what's placed on it:+--+-- @+-- showTile :: 'Entity' -> 'System' 'Char'+-- showTile tile = do+-- player <- tileHas \@Player tile+-- wall <- tileHas \@Wall tile+--+-- 'pure' $+-- if+-- | player -> \'@\'+-- | wall -> \'#\'+-- | otherwise -> \'.\'+-- @+--+-- This requires the @MultiWayIf@ language extension, but there are many alternate ways to write it; it's just a personal preference.+--+-- Next, let's write a system which produces a String to represent the whole grid by calling the previous function on each tile:+--+-- @+-- showGrid :: 'System' 'String'+-- showGrid = do+-- 'Just' (Grid tiles) <- 'res' \@Grid+-- lines <- 'for' tiles $ 'traverse' showTile+-- 'pure' $ 'unlines' lines+-- @+--+-- All that's left is to write a system that prints the string, and schedule it to happen each frame. We'll do the actual+-- printing via the @printClear@ function of "Mischief.ECS.Stdout", which automatically clears the terminal, after each print.+--+-- @+-- printGrid :: 'System' ()+-- printGrid = 'Mischief.ECS.Stdout.printClear' '=<<' showGrid+-- @+--+-- And we need to schedule if, of course.+--+-- @+-- instance 'Plugin' MainPlugin where+-- init = do+-- Stdin.'Mischief.ECS.Stdin.init'+--+-- 'systems' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' (spawnPlayer, spawnWalls)+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' printGrid+-- & 'schedule' \@Update+-- @+--+-- If you run the app now, you should see the game's grid in your terminal!+--+-- @+-- ####################+-- \#..................#+-- \#..................#+-- \#..................#+-- \#..................#+-- \#....\@.............#+-- \#..................#+-- \#..................#+-- \#..................#+-- ####################+-- @++-- $move+-- Next, we should write a system which moves the player from one tile to another.+--+-- First, let's write the actual logic for moving in a certain direction:+--+-- @+-- movePlayerBy :: ('Int', 'Int') -> 'System' ()+-- movePlayerBy dir = do+-- 'Just' grid <- 'res' \@Grid+-- ['q'|OnTile -> (Pos) / With Player|]+-- & 'qmapMaybe' (\\pos -> moveBy dir pos.comp grid)+-- & 'qfilterM' (\\_ newTile -> 'not' \<$\> hasWall newTile)+-- & 'qinsert' (\\newTile -> 'Rel' OnTile newTile)+-- & 'query_'+-- @+--+-- @qmapMaybe@ /tries/ to get the new tile, after which we run a filter to check if we the tile has a wall on it. Finally we @qmap@ the tile to the new relationship.+-- As you've seen before, @qmap@ automatically runs an insertion of the returned components.++-- $input+-- We also need to somehow get input from the user. Mischief exposes some useful functions for this in the following module:+--+-- @+-- import "Mischief.ECS.Stdin" qualified as Stdin+-- @+--+-- These functions are useful for the purpose of this tutorial but should probably never be used in a released game. Instead, you should import a dedicated+-- haskell input library, or use @mischief-input@ which is based on SDL.+--+-- In order to read input we'll need to add @Stdin.init@ to a plugin, which you'll see a bit later.+--+-- For now, we can just use the @Stdin.readLast@ to empty the input buffer and get the last character typed by the user, if any.+--+-- @+-- movePlayer :: 'System' ()+-- movePlayer = do+-- c <- Stdin.'Mischief.ECS.Stdin.readLast'+-- 'for_' c $ \\case+-- \'w\' -> movePlayerBy (-1, 0)+-- \'s\' -> movePlayerBy (1, 0)+-- \'a\' -> movePlayerBy (0, -1)+-- \'d\' -> movePlayerBy (0, 1)+-- _ -> 'pure' ()+-- @+--+-- The @for_@ just \'iterates\' over the @Maybe@, applying the function if it has a value, and doing nothing otherwise.+--+-- At this point the codebase is starting to grow, so I have decided to create a new @PlayerPlugin@ which handles all the player logic (including the new movement system),+-- and make it a dependency of @MainPlugin@:+--+-- @+-- data MainPlugin+--+-- instance 'Plugin' MainPlugin where+-- init = do+-- Stdin.'Mischief.ECS.Stdin.init'+--+-- 'systems' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' spawnWalls+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' printGrid+-- & 'schedule' \@Update+--+-- deps = ['dep' \@PlayerPlugin]+--+-- data PlayerPlugin+--+-- instance 'Plugin' PlayerPlugin where+-- init = do+-- 'systems' spawnPlayer+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' movePlayer+-- & 'schedule' \@Update+-- @+--+-- You should now be able to move the player around when running the game!++-- $rand+-- For some of the next sections, an ability to choose random tiles would be very useful. So let's work on that.+--+-- I've chosen to use the @random@ package, so just add it as a dependency to your project and import it:+--+-- @+-- import System.Random+-- import System.Random.Stateful+-- @+--+-- We need some sort of mutable generator, so I'll create a resource to hold it:+--+-- @+-- data Rand = Rand (IOGenM StdGen) deriving ('Component')+--+-- newGen :: 'System' Rand+-- newGen = Rand \<$\> (newIOGenM =<< initStdGen)+-- @+--+-- Don't forget to insert the resource!+--+-- @+-- instance 'Plugin' MainPlugin where+-- init = do+-- Stdin.'Mischief.ECS.Stdin.init'+--+-- 'systems' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' spawnWalls+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' printGrid+-- & 'schedule' \@Update+--+-- 'insertRes' =<< newGen+-- @+--+--+-- Now it's possible to write a system that generates a random position on the grid:+--+-- @+-- randomPos :: 'System' ('Int', 'Int')+-- randomPos = do+-- 'Just' (Rand gen) <- res @Rand+-- i <- applyIOGen (uniformR (0, gridH - 1)) gen+-- j <- applyIOGen (uniformR (0, gridW - 1)) gen+-- return (i, j)+-- @+--+-- And a system that uses it to get the Entity of a random tile:+--+-- @+-- randomTile :: 'System' 'Entity'+-- randomTile = do+-- 'Just' grid <- 'res' \@Grid+-- randomPos <- randomPos+--+-- 'pure' . 'unwrap' $ getTile randomPos grid+-- @+--+-- @unwrap@ is a utility function provided by Mischief that just grabs the value out of a @Maybe@, or panics if there is no value. In this case,+-- we know there will be a value since the provided position is valid.++-- $enemies+-- It would be a pretty boring game if there were no obstacles. For that reason, we're going to add some enemies.+--+-- Here's the marker component that will be used to identify them:+--+-- @+-- data Enemy = Enemy deriving ('Component')+-- @+--+-- I'll use this system to spawn an enemy, using the @randomTile@ function defined earlier:+--+-- @+-- spawnEnemy :: 'System' Entity+-- spawnEnemy = do+-- tile <- randomTile+-- 'spawn' (Enemy, 'Rel' OnTile tile)+-- @+--+-- And this as a driver to handle all enemy spawning (it just spawns 5 enemies):+--+-- @+-- spawnEnemies :: 'System' ()+-- spawnEnemies = 'for_' [0 .. 4] $ 'const' spawnEnemy+-- @+--+-- I've also modified the @showTile@ system to take enemies into account:+--+-- @+--+-- showTile :: 'Entity' -> 'System' 'Char'+-- showTile tile = do+-- player <- tileHas \@Player tile+-- enemy <- tileHas \@Enemy tile+-- wall <- tileHas \@Wall tile+--+-- 'pure' $+-- if+-- | player -> \'@\'+-- | wall -> \'#\'+-- | enemy -> \'!\'+-- | otherwise -> \'.\'+-- @+--+-- Finally, we need a to schedule the enemy spawning, so I've created a new @EnemyPlugin@:+--+-- @+-- data EnemyPlugin+--+-- instance 'Plugin' EnemyPlugin where+-- init = do+-- 'systems' spawnEnemies+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+-- @+--+-- And added it to the list of plugins added by @MainPlugin@:+--+-- @+-- instance 'Plugin' MainPlugin where+-- init = ...+--+-- deps = ['dep' \@PlayerPlugin, 'dep' \@EnemyPlugin]+-- @+--+-- You should now see something like this when running the app:+--+-- @+-- ####################+-- \#..................#+-- \#..!...............#+-- \#....!.!...........#+-- \#..................#+-- \#....\@.............#+-- \#..................#+-- \#..................#+-- \#........!.!.......#+-- ####################+-- @++-- $moveE+-- Right now the enemies just sit there. Let's make them move!+--+-- Fist, I've written this helper @System@ that receives an enemy's position, the player's position, and decides the direction the enemy will move in.+-- (yes, it technically could be a pure function but you'll see why it's a System a bit later).+--+-- @+-- decideEnemyDir :: Pos -> Pos -> 'System' ('Int', 'Int')+-- decideEnemyDir (Pos (ex, ey)) (Pos (px, py)) = do+-- 'pure' $+-- if+-- \| ex \> px -\> (-1, 0)+-- \| ey \> py -\> (0, -1)+-- \| ex \< px -\> (1, 0)+-- \| ey \< py -\> (0, 1)+-- \| otherwise -\> (0, 0)+-- @+--+-- Then, I wrote this system that grabs the player's position and then iterates over all enemies to move them:+--+-- @+-- moveEnemies :: 'System' ()+-- moveEnemies = do+-- 'Just' ('From' _ playerPos) \<- 'single' ['q'|OnTile -\> (Pos) / With Player|]+-- 'Just' grid <- 'res' \@Grid+--+-- ['q'|OnTile -> (Pos) / With Enemy|]+-- & 'qtraverse' (\\_ pos -> (,pos) \<$\> decideEnemyDir pos.comp playerPos)+-- & 'qmapMaybe' (\\(diff, pos) -\> moveBy diff pos.comp grid)+-- & 'qinsert' ('Rel' OnTile)+-- & 'query_'+-- @+--+-- If the lambdas get overwhelming you can always create intermediary functions that work on queries:+--+-- @+-- qdecideEnemyTile :: Grid -> Pos -> 'Query' 'System' ('From' Pos) -> 'Query' 'System' 'Entity'+-- qdecideEnemyTile grid playerPos x =+-- x+-- & 'qtraverse' (\\_ pos -> (,pos) \<$\> decideEnemyDir pos.comp playerPos)+-- & 'qmapMaybe' (\\(diff, pos) -> moveBy diff pos.comp grid)+-- @+--+-- So our system is now just:+--+-- @+-- moveEnemies :: 'System' ()+-- moveEnemies = do+-- 'Just' ('From' _ playerPos) \<- 'single' ['q'|OnTile -\> (Pos) / With Player|]+-- 'Just' grid <- 'res' \@Grid+--+-- ['q'|OnTile -\> (Pos) / With Enemy|]+-- & qdecideEnemyTile grid playerPos+-- & 'qinsert' ('Rel' OnTile)+-- & 'query_'+-- @+--+-- And now to schedule our enemy movement:+--+-- @+-- data EnemyPlugin+--+-- instance 'Plugin' EnemyPlugin where+-- init = do+-- 'systems' spawnEnemies+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' moveEnemies+-- & 'schedule' \@Update+-- @+--+-- Except there's a small problem. If you run the app now, you may notice you don't see any enemies!+-- That's because they all already got to the player and are hiding behind it! We've set @moveEnemies@+-- to happen every frame, and our frames are happening almost instantly. So we need to add some sort of timing to the enemy's movement.+--+-- There are many cleaner high-level solutions to fix this, some of them even using async systems, but instead, I'll take the opportunity to introduce you to an important notion, Time!+--+-- In order to use @Time@ utilities, you need to add the @'TimePlugin'@ to your app, so I'll add it to our @MainPlugin@:+--+-- @+-- deps = ['dep' \@PlayerPlugin, 'dep' \@EnemyPlugin, 'dep' \@TimePlugin]+-- @+--+-- We'll now import the @Time@ module which contains various utlities for keeping track of time, including @Time.delta@ which returns the time, in seconds,+-- that has passed between frames.+--+-- @+-- import "Mischief.ECS.Time" qualified as Time+-- @+--+-- Mischief also provides a handy way of keeping track of time via the @Timer@. This module contains functions to work with it.+--+-- @+-- import "Mischief.ECS.Timer" qualified as Timer+-- @+--+-- A Timer is an object that can be ticked down each frame, to trigger certain conditions only once a cerain amount of time has passed.+--+-- Let's create a @Cooldown@ component which stores a Timer.+--+-- @+-- data Cooldown = Cooldown {timer :: 'Mischief.ECS.Timer.Timer'} deriving ('Component')+-- @+--+-- We want this to always be on every Enemy, so we can make it a required component of the @Enemy@ component.+--+-- @+-- instance 'Component' Enemy where+-- 'required' = 'require' \@Cooldown+-- @+--+-- In order to have that compile, we also need to provide a @Default@ instance for @Cooldown@:+--+-- @+-- instance 'Default' Cooldown where+-- 'def' = Cooldown $ Timer.'Mischief.ECS.Timer.new' 1 Timer.'Mischief.ECS.Timer.Repeat'+-- @+--+-- @Timer.new@ takes a Float (the duration of the timer), and a @Mode@ which is either @Repeat@ or @Once@.+--+-- @Cooldown@ should now automatically be added on every enemy with the default value.+--+-- We can use the @Timer.tick@ function to advance the state of a timer. It returns the new state, along with a Bool that tells us whether the timer has just finished or not.+--+-- All that's left is to put all of this together. I've written this intermediary function to use with queries that will filter the query so that each enemy can only attack when their cooldown has just finished:+--+-- @+-- qfilterCooldown :: 'Query' 'System' a -> 'Query' 'System' a+-- qfilterCooldown x = do+-- x+-- & 'qextend' \['qd'|Cooldown|] (,)+-- & 'qfilterM'+-- (\\entity (_, Cooldown timer) -> do+-- delta <- Time.'Mischief.ECS.Time.delta'+-- let (timer', justFinished) = Timer.'Mischief.ECS.Timer.tick' delta timer+-- 'insert' (Cooldown timer') entity+-- 'pure' justFinished+-- )+-- & 'qmap' 'fst'+-- @+--+-- First we use @qextend@ to also query for the @Cooldown@ of the current entity. Then we run a small impure filter that gets the delta,+-- updates the Timer, modifies the value of Cooldown by re-inserting it on the entity, and filters based on whether the timer had just finished or not.+--+-- Notice that this time we're using @qd@ instead of @q@. That's because @qextend@ expects a /query data/.+--+-- We also use a final @qmap@ to get the query back to its original data. This makes it extremely generic!+--+-- Here's the new @moveEnemies@ function, with the timer-based filter:+--+-- @+-- moveEnemies :: 'System' ()+-- moveEnemies = do+-- 'Just' ('From' _ playerPos) \<- 'single' ['q'|OnTile -\> (Pos) / With Player|]+-- 'Just' grid <- 'res' \@Grid+--+-- ['q'|OnTile -\> (Pos) / With Enemy|]+-- & qfilterCooldown+-- & qdecideEnemyTile grid playerPos+-- & 'qinsert' ('Rel' OnTile)+-- & 'query_'+-- @+--+-- You can now run the app and see the enemies chasing you!++-- $collision+-- Right now the enemies just kind of overlap each other and go under the player. We can fix that by preventing them to move.+--+-- Now, let's write a function that tells us whether a certain tile is free to move on or not:+--+-- @+-- tileIsFree :: 'Entity' -> 'System' Bool+-- tileIsFree tile = do+-- wall <- tileHas \@Wall tile+-- enemy <- tileHas \@Enemy tile+-- player <- tileHas \@Player tile+-- pure $ not (wall || enemy || player)+-- @+--+-- Plus, an extra one which takes the tile's position directly rather than the entity:+--+-- @+-- tileAtPosIsFree :: ('Int', 'Int') -> 'System' 'Bool'+-- tileAtPosIsFree pos = do+-- 'Just' grid \<- 'res' \@Grid+-- 'maybe' ('pure' False) tileIsFree (getTile pos grid)+-- @+--+-- And let's integrate it into the system which decides the enemy's movement direction:+--+-- @+-- decideEnemyDir :: Pos -> Pos -> 'System' ('Int', 'Int')+-- decideEnemyDir (Pos (ex, ey)) (Pos (px, py)) = do+-- left <- tileAtPosIsFree (ex - 1, ey)+-- up <- tileAtPosIsFree (ex, ey - 1)+-- right <- tileAtPosIsFree (ex + 1, ey)+-- down <- tileAtPosIsFree (ex, ey + 1)+--+-- 'pure' $+-- if+-- | ex \> px && left -\> (-1, 0)+-- | ey \> py && up -\> (0, -1)+-- | ex \< px && right -\> (1, 0)+-- | ey \< py && down -\> (0, 1)+-- | otherwise -> (0, 0)+-- @+--+-- (There are definitely /much/ better ways to write this, feel free to experiment and make it cleaner at home!)+--+-- I've also replaced the @hasWall@ in the @movePlayerBy@ function with @tileIsFree@, so the player can collide with enemies as well.+--+-- @+-- movePlayerBy :: ('Int', 'Int') -> 'System' ()+-- movePlayerBy dir = do+-- 'Just' grid \<- 'res' \@Grid+-- ['q'|OnTile -\> (Pos) / With Player|]+-- & 'qmapMaybe' (\\pos -> moveBy dir pos.comp grid)+-- & 'qfilterM' ('const' tileIsFree)+-- & 'qinsert' ('Rel' OnTile)+-- & 'query_'+-- @+--+-- The game should now have fully working collision and feel much more solid!++-- $health+-- Here's a simple one: let's add a @Health@ component to the player and have it be displayed under the grid each frame.+--+-- I'll also give it a @Default@ instance so it can be required by the @Player@ component.+--+-- @+-- data Health = Health {hp :: 'Int'} deriving ('Component')+--+-- instance 'Default' Health where+-- 'def' = Health 100+-- @+--+-- @+-- instance 'Component' Player where+-- 'required' = 'require' \@Health+-- @+--+-- I've written a system that returns a string for the health:+--+-- @+-- showHealth :: 'System' 'String'+-- showHealth = do+-- 'Just' health <- 'single' ['q'|Health / With Player|]+-- 'pure' $ "Health: " ++ 'show' health.hp+-- @+--+-- And I added it to the printing system:+--+-- @+-- printGrid :: 'System' ()+-- printGrid = do+-- grid <- showGrid+-- health <- showHealth+-- 'Mischief.ECS.Stdout.printClear' $ health ++ \"\\n\" ++ grid+-- @+--+-- Your game should now print the health at the top:+--+-- @+-- Health: 100+-- ####################+-- \#............!.....#+-- \#.....!............#+-- \#..................#+-- \#..................#+-- \#....\@!......!..!..#+-- \#..................#+-- \#..................#+-- \#..................#+-- ####################+-- @++-- $dmg+-- Let's add behavior for enemies damaging the player. I'll also take this opportunity to introduce you to Events.+--+-- We can create a damage event like this:+--+-- @+-- data Damage = Damage {amount :: 'Int'} deriving ('Event')+-- @+--+-- We also need an observer system for it:+--+-- @+-- onDamage :: Damage -> 'System' ()+-- onDamage dmg = do+-- ['q'|Health / With Player|]+-- & 'qinsert' (\\(Health x) -> Health $ 'max' (x - dmg.amount) 0)+-- & 'query_'+-- @+--+-- We also need to spawn an observer that listens to the world and triggers the event. I'll do so from the player plugin:+--+-- @+-- instance 'Plugin' PlayerPlugin where+-- init = do+-- 'systems' spawnPlayer+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' movePlayer+-- & 'schedule' \@Update+--+-- 'void' $ 'spawn' ('Observer' onDamage)+-- @+--+-- The last thing we need is a way for enemies to trigger the event. Let us make a system which checks if an enemy is adjacent to the player and triggers the event:+--+-- @+-- tryDamage :: 'System' ()+-- tryDamage = do+-- adjacentEnemies <-+-- ['q'|OnTile -\> (Pos) / With Player|]+-- & 'qjoin' (\\pos -\> ['q'|OnTile -\> (Pos) / With Enemy|] & 'qfilter' (\\a -> isAdjacent a.comp pos.comp)) (,)+-- & 'query'+--+-- 'unless' ('null' adjacentEnemies) $ do+-- 'trigger' $ Damage 5+-- @+--+-- With this helper function:+--+-- @+-- isAdjacent :: Pos -> Pos -> 'Bool'+-- isAdjacent (Pos (x1, y1)) (Pos (x2, y2)) =+-- let dx = abs (x1 - x2)+-- dy = abs (y1 - y2)+-- in (dx == 1 && dy == 0) || (dx == 0 && dy == 1)+-- @+--+-- The @qjoin@ function maps each of our positions into an entirely new query, filtering the entities of that query+-- based on the position. This is actually a less powerful bind operation, as we'll see in the next chapter.+--+-- I've scheduled @tryDamage@ to happen every frame, after both the player and enemies have moved:+--+-- @+-- instance 'Plugin' EnemyPlugin where+-- init = do+-- 'systems' spawnEnemies+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' moveEnemies+-- & 'schedule' \@Update+--+-- 'systems' tryDamage+-- & 'after' moveEnemies+-- & 'after' movePlayer+-- & 'schedule' \@Update+-- @+--+-- This now /technically/ works, except that the enemies almost instantly defeat the player on contact. That's because they deal damage every frame,+-- the same issue we had when they were moving each frame.+--+-- I'll show you a new way to solve this problem. We'll add an @Invincible@ component on the player after being hit once,+-- which causes it to not receive damage, and which is removed after a delay.+--+-- @+-- data Invincible = Invincible deriving ('Component')+-- @+--+-- I'll modify the @onDamage@ observer like so:+--+-- @+-- onDamage :: Damage -> 'System' ()+-- onDamage dmg = do+-- ['q'|Health / With Player, Without Invincible|]+-- & 'qinsert' (\\(Health x) -> (Health $ 'max' (x - dmg.amount) 0, Invincible))+-- & 'qtap' (\\e _ -> 'delay' 1000000 $ 'remove' ('C' \@Invincible) e)+-- & 'query_'+-- @+--+-- We are now only querying the player only if they are not invincible. We are then inserting the @Invincible@ components on them, and+-- removing it after a fixed delay of 1 second. @qtap@ is the function used to apply an arbitrary side effect over the query's elements.+--+-- Now, the player will only be able to take damage once per second!++-- $quit+-- It feels weird that the player can reach 0 health but the game just keeps running. So I've added an+-- extra instruction into our @qtap@ exits the program when the hp reaches 0.+--+-- @+-- onDamage :: Damage -> System ()+-- onDamage dmg = do+-- ['q'|Health / With Player, Without Invincible|]+-- & 'qinsert' (\\(Health x) -> (Health $ 'max' (x - dmg.amount) 0, Invincible))+-- & 'qtap'+-- ( \\e (Health hp) -> do+-- 'delay' 1 $ 'remove' ('C' \@Invincible) e+-- 'when' (hp == 0) $ 'liftIO' 'exitSuccess'+-- )+-- & 'query_'+-- @+--+-- But you may notice, the player actually takes an extra hit before that condition is triggered. That's because the result of the function+-- from @qinsert@ is not actually propagated further in the query. If you look at the signature of @qinsert@ you'll notice it actually ends in:+-- @Query m a -> Query m a@. We can instead use the @qmodify@ function which maps the values, inserts them, and propagates them:+--+-- @+-- onDamage :: Damage -> System ()+-- onDamage dmg = do+-- ['q'|Health / With Player, Without Invincible|]+-- & 'qmodify' (\\(Health x) -> (Health $ 'max' (x - dmg.amount) 0, Invincible))+-- & 'qtap'+-- ( \\e (Health hp, _) -> do+-- 'delay' 1000000 $ 'remove' ('C' \@Invincible) e+-- 'when' (hp == 0) $ 'liftIO' 'exitSuccess'+-- )+-- & 'query_'+-- @+--+-- The logic should now work as expected.++-- $coins+-- Now, for our last bit of logic, we should add a reason for the player to not die. Let's spawn a bunch of coins!+--+-- First, we need a marker component for the coins. Each containing a value:+--+-- @+-- data Coin = Coin 'Int' deriving ('Component')+-- @+--+-- Second, here's a system that spawns a coin on a random free tile:+--+-- @+-- spawnCoin :: 'System' ()+-- spawnCoin = do+-- tile <- randomTile+-- free <- tileIsFree tile+-- if free+-- then+-- 'void' $ 'spawn' (Coin 5, 'Rel' OnTile tile)+-- else+-- spawnCoin+-- @+--+-- (If the tile is not free, it will just keep looping and generating tiles until it finds one that is).+--+-- Third, we can spawn a Repeater which will run our system every 2 seconds:+--+-- @+-- instance 'Plugin' MainPlugin where+-- init = do+-- Stdin.'Mischief.ECS.Stdin.init'+--+-- 'systems' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' spawnWalls+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' printGrid+-- & 'schedule' \@Update+--+-- 'spawn_' $ 'Repeater' 2 spawnCoin+--+-- 'insertRes' =<< newGen+-- @+--+-- Finally, we should display the coins:+--+-- @+-- showTile :: 'Entity' -> 'System' 'Char'+-- showTile tile = do+-- player <- tileHas \@Player tile+-- enemy <- tileHas \@Enemy tile+-- wall <- tileHas \@Wall tile+-- coin <- tileHas \@Coin tile+--+-- 'pure' $+-- if+-- | player -> \'@\'+-- | wall -> \'#\'+-- | enemy -> \'!\'+-- | coin -> \'$\'+-- | otherwise -> \'.\'+-- @++-- $collect+-- All that's left is letting the player collect coins and keeping track of how many they have collected.+--+-- Let's make another component which will be held on the player, containing the total amount of coins they've collected:+--+-- @+-- data Coins = Coins 'Int' deriving ('Component', 'Generic', 'Default')+-- @+--+-- And make it another required component of @Player@:+--+-- @+-- instance 'Component' Player where+-- required = 'require' \@(Health, Coins)+-- @+--+-- And then update the display to also show the number of coins:+--+-- @+-- showCoins :: 'System' 'String'+-- showCoins = do+-- 'Just' (Coins c) <- 'single' $ ['q'|Coins|]+-- 'pure' $ \"Coins: \" ++ show c+-- @+--+-- @+-- printGrid :: 'System' ()+-- printGrid = do+-- grid <- showGrid+-- health <- showHealth+-- coins <- showCoins+-- 'Mischief.ECS.Stdout.printClear' $ health ++ \"\\n\" ++ grid ++ \"\\n\" ++ coins ++ \"\\n\"+-- @+--+-- And now finally, a system that checks if there are any coins on the same tile as the player, despawns them, and increments the resource:+--+-- @+-- collectCoins :: 'System' ()+-- collectCoins = do+-- players \<- 'query' ['q'|Entity, OnTile -\> (Entity), Coins / With Player|]+-- 'for_' players $ \\(player, 'From' _ tile, Coins coins) -> do+-- collected <-+-- ['q'|Coin / With OnTile -> tile|]+-- & 'qtap' (\\e _ -> despawn e)+-- & 'query'+--+-- 'insert' (Coins $ 'foldr' (\\(Coin x) -> (+ x)) coins collected) player+-- @+--+-- We iterate over all players, then query all coins that share the same tile as the current player, while despawning them. We+-- then do a @foldr@ to sum the value of all coins over the @Coins@ of the player and re-insert it. There are much cleaner,+-- less imperative, ways of writing this, some of which will be covered in the next chapter.+--+-- Now to schedule it:+--+-- @+-- instance 'Plugin' PlayerPlugin where+-- init = do+-- 'systems' spawnPlayer+-- & 'after' spawnGrid+-- & 'schedule' \@Startup+--+-- 'systems' movePlayer+-- & 'schedule' \@Update+--+-- 'systems' collectCoins+-- & 'after' movePlayer+-- & 'schedule' \@Update+--+-- 'void' $ 'spawn' ('Observer' onDamage)+-- @+--+-- And that's it! Out player should now be able to collect coins!+--+-- @+-- Health: 5+-- ####################+-- \#..................#+-- \#..................#+-- \#$....!!!!!\@...$...#+-- \#$.................#+-- \#..................#+-- \#......$...........#+-- \#.$................#+-- \#..................#+-- ####################+-- Coins: 16+-- @++-- $next+-- Don't worry if there are various details that you haven't fully understood yet. The next chapters will go into detail over the many aspects of the ECS. This chapter was just meant+-- to give you a general idea of working with Mischief. Don't forget that you can view the whole code for this example [here](https://codeberg.org/mischief/mischief/src/branch/main/mischief-ecs/examples/Dungeon.hs).
+ src/Mischief/ECS/Tutorial/Introduction/Startup.hs view
@@ -0,0 +1,426 @@+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- |+-- Module: Components Tutorial+-- Description: Introductory Tutorial+--+-- This module walks the user through setting up Mischief and creating a simple app.+--+-- [Next Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Introduction.Dungeon")+--+-- [Main Page]("Mischief.ECS")+module Mischief.ECS.Tutorial.Introduction.Startup+ ( -- * Learn You an ECS for Great Mischief! - 1.1. Startup Guide+ -- $intro++ -- * The ECS+ -- $ecs++ -- * The App+ -- $app++ -- * Your First System+ -- $firstSystem++ -- * Your First Component+ -- $firstComp++ -- * Your First Query+ -- $firstQuery++ -- * Your First Mutation+ -- $firstMut++ -- * Your First Resource+ -- $res++ -- * Your First Relationship+ -- $rel++ -- * Your First Transitive Query+ -- $trans++ -- * Explanation: From+ -- $from++ -- * What's Next?+ -- $next++ -- * [Next Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Introduction.Dungeon")+ )+where++import Control.Monad (when)+import Data.Foldable (for_)+import Data.Traversable (for)+import Mischief.ECS++-- $intro+-- This chapter will guide you through setting up a working Mischief app and performing some simple operations on data.++-- $ecs+-- Mischief's ECS logic is designed to be very approachable and simple to write.+--+-- @Components@ are just types deriving the @Component@ typeclass.+--+-- @+-- data Position = Position {x :: 'Float', y :: 'Float'} deriving ('Component')+-- @+--+-- @Systems@ are functions in the @System@ monad.+--+-- @+-- updatePositions :: 'System' ()+-- updatePosition =+-- ['q'|Position, Velocity|]+-- & 'qinsert' (\\(Position p, Velocity v) -> (Position $ p + v))+-- & 'query_'+-- @+--+-- @Entities@ are opaque ids used to represent and manipulate data.+--+-- @+-- data Entity = Entity 'Int'+-- @++-- $app+-- A Mischief program usually starts with creating an App and adding a plugin to it. So let's do that!+--+-- @+-- import "Mischief.ECS"+--+-- main :: 'IO' ()+-- main = do+-- app <- 'newApp'+-- 'addPlugin' \@MyPlugin app+-- 'runApp' app+--+-- data MyPlugin+--+-- instance 'Plugin' MyPlugin+-- @++-- $firstSystem+-- Copy the following function into your file:+--+-- @+-- helloWorld :: 'System' ()+-- helloWorld = 'info' "Hello World!"+-- @+--+-- This will be our first system. It just logs a message saying /"Hello World!"/. The only remaining step is to schedule it to run!+--+-- @+-- instance 'Plugin' MyPlugin where+-- 'Mischief.ECS.App.Plugins.init' :: 'System' ()+-- 'Mischief.ECS.App.Plugins.init' = do+-- 'systems' helloWorld+-- & 'schedule' \@Update+-- @+--+-- The @systems@ function will grab the system for us, and @schedule \@Update@ will add it to the Update schedule,+-- making it run once per frame. If you run your app again, you will see \"Hello World!\" printed to your terminal many, many times.++-- $firstComp+-- Let's do a little more than greeting the whole world, let's greet some individual people!+--+-- In ECS, you would generally model people as entities with a set of components that define them. Let's start with a simple @Person@ component:+--+-- @+-- data Person = Person deriving ('Component')+-- @+--+-- So how can we give people names? In a more traditional design you could just add a @name :: String@ field to @Person@. But the ECS makes you think of it differently!+-- A @Name@ is just a piece of data that can be attached to anything. A dog could also have a name. So why not just make a @Name@ component?+--+-- @+-- data Name = Name 'String' deriving ('Component')+-- @+--+-- No need to write this one though, since this exact @Name@ is already defined internally by Mischief and exported by the Prelude.+--+-- Now that we can represent people with names, let's make a system that spawns some:+--+-- @+-- addPeople :: 'System' ()+-- addPeople = do+-- kim <- 'spawn' (Person, Name \"Kimberly\")+-- nick <- 'spawn' (Person, Name \"Nicholas\")+-- flo <- 'spawn' (Person, Name \"Florian\")+-- 'pure' ()+-- @+--+-- You can register it to run on the app's Startup schedule, making it run only once, at the start:+--+-- @+-- instance 'Plugin' MyPlugin where+-- init = do+-- 'systems' helloWorld+-- & 'schedule' \@Update+--+-- 'systems' addPeople+-- & 'schedule' \@Startup+-- @++-- $firstQuery+-- If you run your app, the people will be spawned but we aren't doing anything with them yet! Let's make a system that greets them:+--+-- @+-- greetPeople :: 'System' ()+-- greetPeople = do+-- people <- 'query' [q|Name, Person|]+-- 'for_' people $ \\(name, _) -> do+-- 'info' ['i'|Hello #{name}!|]+-- @+--+-- The above @query@ function will grab the @Name@ and @Person@ of every entity. We then iterate over them in order to greet them.+--+-- The @Person@ component however, is only queried to ensure we are querying the right entities. We don't care about its value at all! So we can instead write it+-- as a filter to limit the types of entities selected by the query and save us the trouble of carrying an extra variable around.+--+-- @+-- greetPeople :: 'System' ()+-- greetPeople = do+-- people <- 'query' [q|Name / With Person|]+-- 'for_' people $ \\name ->+-- 'info' ['i'|Hello #{name}!|]+-- @+--+-- @With Person@ is a filter, telling our query builder to only select entities with the @Person@ component. We use @/@ to separate+-- the data that we're querying from the filters.+--+-- Additionally, Mischief lets you write and process queries by piping dedicated functions into each other. For instance, our earlier function is equivalent to:+--+-- @+-- greetPeople :: 'System' ()+-- greetPeople = do+-- ['q'|Name / With Person|]+-- & 'qinfo' (\\name -> ['i'|Hello #{name}!|])+-- & 'query_'+-- @+--+-- The quasi-query (@[q|..|]@) produces our query, we then use @qinfo@ to display a message to the terminal for each element in the query,+-- and finally we use @query_@ to run all the commands and discard the results (the normal @query@ returns the results).+--+-- Now we can schedule this system to also run:+--+-- @+-- instance 'Plugin' MyPlugin where+-- init = do+-- 'systems' (helloWorld, greePeople)+-- & 'schedule' \@Update+--+-- 'systems' addPeople+-- & 'schedule' \@Startup+-- @+--+-- Running our app will result in the following output:+--+-- @+-- [INFO] Hello World!+-- [INFO] Hello Kimberly!+-- [INFO] Hello Nicholas!+-- [INFO] Hello Florian!+-- @+--+-- Note that \"Hello World\" might show above or beneath the others. That's because systems in the same schedule can run in any order unless they are explicitly ordered.++-- $firstMut+-- If we want to change the name of some people, we can apply a mutation to a value obtained from the query:+--+-- @+-- updateFlo :: 'System' ()+-- updateFlo = do+-- people <- 'query' ['q'|Entity, Name / With Person|]+-- 'for_' people $ \\(entity, name) -> do+-- 'when' (name == Name \"Florian\") $+-- 'insert' (Name \"Florianne\") entity+-- @+--+-- We are querying the name of each entity (@Name@), along with its actual id (@Entity@). We then iterate over all the names, and once we see \"Florian\", we+-- re-insert the component, changing its value to \"Florianne\". Re-insertion is the main way to mutate data in Mischief.+--+-- Although.. that feels awfully imperative doesn't it? Let's rewrite the same system, this time using piping:+--+-- @+-- updateFlo :: 'System' ()+-- updateFlo = do+-- ['q'|Name / With Person|]+-- & 'qfilter' (== Name \"Florian\")+-- & 'qinsert' (\\_ -> Name \"Florianne\")+-- & 'query_'+-- @+--+-- This time we apply a filter over our Query, leaving only those entities with their name set to \"Florian\". We then use @qinsert@ to+-- map the old name to the new one and insert it on the entity.+--+-- Let's add the new system to a schedule:+--+-- @+-- 'Mischief.ECS.App.Plugins.init' = do+-- 'systems' (helloWorld, greetPeople)+-- & 'schedule' \@Update+--+-- 'systems' addPeople+-- & 'schedule' \@Startup+--+-- 'systems' updateFlo+-- & 'before' greetPeople+-- & 'schedule' \@Update+-- @+--+-- Note that we have explicitly ordered @updateFlo@ to happen /before/ @greetPeople@. We want to only greet Flo after their name has changed! Running the app+-- should now show \"Hello Florianne!\" instead of \"Florian\".++-- $res+-- Resources are a great way to store global information that can be easily written to and read in any system.+--+-- Let's say we want to have a custom greeting that we can change at runtime. We could store that in a resource:+--+-- @+-- data Greeting = Greeting 'String' deriving ('Component')+-- @+--+-- Yes, resources are just normal components!+--+-- We'll also give it a @Show@ instance to make printing it easier:+--+-- @+-- instance 'Show' Greeting where+-- 'show' (Greeting a) = a+-- @+--+-- Let's insert a greeting from our init system:+--+-- @+-- 'Mischief.ECS.App.Plugins.init' = do+-- 'systems' (helloWorld, greetPeople)+-- & 'schedule' \@Update+--+-- 'systems' addPeople+-- & 'schedule' \@Startup+--+-- 'systems' updateFlo+-- & 'before' greetPeople+-- & 'schedule' \@Update+--+-- 'insertRes' (Greeting \"Hey\")+-- @+--+-- @insertRes@ inserts the corresponding resource into the World.+--+-- And let's modify @greetPeople@ so that it uses the current greeting from the resource:+--+-- @+-- greetPeople :: 'System' ()+-- greetPeople = do+-- 'Just' greeting <- 'res' \@Greeting+--+-- ['q'|Name / With Person|]+-- & 'qinfo' (\\name -> ['i'|#{greeting} #{name}!|])+-- & 'query_'+-- @+--+-- You should now see this when running the app:+--+-- @+-- [INFO] Hello World!+-- [INFO] Hey Kimberly!+-- [INFO] Hey Nicholas!+-- [INFO] Hey Florianne!+-- @++-- $rel+-- Relationships in Mischief are pairs made up of a Component and an Entity. Let's implement a simple relationship between our entities that says which like which.+--+-- We'll start by defining a component:+--+-- @+-- data Likes = Likes deriving ('Component')+-- @+--+-- Let's now modify our spawning system to also insert relationships between our three entities. We can insert a relationship using the @Rel@ keyword.+--+-- @+-- addPeople :: 'System' ()+-- addPeople = do+-- kim <- 'spawn' (Person, Name \"Kimberly\")+-- nick <- 'spawn' (Person, Name \"Nicholas\")+-- flo <- 'spawn' (Person, Name \"Florian\")+--+-- 'insert' ('Rel' Likes kim) flo+-- 'insert' ('Rel' Likes nick, 'Rel' Likes flo) kim+-- @+--+-- We've now made @flo@ like @kim@, and we've made @kim@ like both @nick@ and @flo@!.++-- $trans+-- We now have relationships but we aren't doing much with them. What about having a system that displays the name of each entity, along with the name of all entities they like?+--+-- We can make use of a mechanism called a @transitive query@, which look like this:+--+-- @+-- showLikes :: 'System' ()+-- showLikes = do+-- ['q'|Name, Likes -\> (Name)|]+-- & 'qinfo' (\\(name, likes) -\> [i|#{name} likes #{likes}|])+-- & 'query_'+-- @+--+-- Pretty cool, huh?+--+-- Now let's schedule our new system to run:+--+-- @+-- 'Mischief.ECS.App.Plugins.init' = do+-- 'systems' (helloWorld, greetPeople, showLikes)+-- & 'schedule' \@Update+--+-- 'systems' addPeople+-- & 'schedule' \@Startup+--+-- 'systems' updateFlo+-- & 'before' (greetPeople, showLikes)+-- & 'schedule' \@Update+--+-- 'insertRes' (Greeting \"Hey\")+-- @+--+-- We should now see these additional lines printed to the terminal:+--+-- @+-- [INFO] Florianne likes [From (42v1, Kimberly)]+-- [INFO] Kimberly likes [From (44v1, Nicholas),From (45v1, Florianne)]+-- @++-- $from+-- You may have noticed earlier, when we query for @-> (Name)@ and then print the names, we don't get the actual names, but instead+-- something that looks like @From (42v1, Kimberly)@.+--+-- That's because, when doing transitive queries, the components come wrapped in this:+--+-- @+-- data From c = From {comp :: c, entity :: Entity}+-- @+--+-- They have a different origin entity than the other components in the query, and this is our main way of keeping track of that.+--+-- To get rid of the wrapper we can just change the query to this:+--+-- @+-- showLikes :: 'System' ()+-- showLikes = do+-- ['q'|Name, Likes -\> (Name)|]+-- & 'qinfo' (\\(name, likes) -\> [i|#{name} likes #{'map' (.comp) likes}|])+-- & 'query_'+-- @++-- $next+-- What you learn next is up to you.+--+-- The [next chapter]("Mischief.ECS.Tutorial.Introduction.Dungeon") will have you working on a little dungeon game in the terminal and introduce you to more notions. If you prefer to learn by example I would recommended+-- checking that out.+--+-- Then there's a [chapter]("Mischief.ECS.Tutorial.HowTo") which goes over different situations and problems you may encounter and describes various solutions to them.+--+-- After that, there are chapters giving you a technical overview for various parts of the ECS (Components, Queries, Systems, and so on).
− src/Mischief/ECS/Tutorial/Queries.hs
@@ -1,322 +0,0 @@-{-# OPTIONS_GHC -Wno-unused-imports #-}---- |--- Module: Queries Tutorial--- Description: Tutorial on using @Queries@------ This module contains a more in-depth tutorial on @Mischief Queries@.------ [Previous Chapter: Relationships]("Mischief.ECS.Tutorial.Relationships")------ [Next Chapter: Systems]("Mischief.ECS.Tutorial.Systems")------ [Main Page]("Mischief.ECS")-module Mischief.ECS.Tutorial.Queries- ( -- * Learn You an ECS for Great Mischief! - 6. Queries- -- $intro-- -- * Outputs- -- $outputs-- -- * Val- -- $val-- -- * Filters- -- $filters-- -- * The Check Filter- -- $check-- -- * Quasi-Queries- -- $quasi-- -- * [Next Chapter: Systems]("Mischief.ECS.Tutorial.Systems")- )-where--import Mischief.ECS---- $intro--- Queries are the main way we read component data. They are fully type-checked and based on archetypes, making them quite fast.------ Each query associates an output type to the types you put into it.------ For instance,------ @--- x <- 'query' ('C' \@A, 'M' \@B)--- @------ Will have an output type of:------ @--- ('Result' A, 'Maybe' ('Result' B))--- @------ With an actual return type of:------ @--- x :: ['Result' A, 'Maybe' ('Result' B)]--- @------ Because @query@ actually returns @[Output]@.------ @single@ on the other hand, returns a @Maybe Output@, only outputting something if there is exactly one entity which matches the query.------ @--- x <- 'single' ('C' \@A, 'M' \@B)--- @------ @--- x :: 'Maybe' ('Result' A, 'Maybe' ('Result' B))--- @------ @get@ queries the components of a specific entity:------ @--- x <- 'get' ('C' \@A) e--- @------ @--- x :: 'Maybe' ('Result' A)--- @------ @get@ returns @'Nothing'@ if the entity doesn't exist or if it doesn't contain any of the components.---- $val--- Working with @Result c@ can be annoying, especially if you only want to read the value of a component and not mutate anything.------ That's why all query types can be wrapped in a @Val@ in order to extract their values out of the @Result@:------ @--- 'query' ('C' \@Foo, 'Val' ('C' \@Bar, 'M' \@Baz))--- @------ Will return:------ @--- ['Result' Foo, (Bar, 'Maybe' Baz)]--- @------ @Val@ maps anything that looks like a @Result c@ to just @c@, while leaving most other things be as they are.---- $outputs--- Here's a list of the the various markers you can use in queries and each of their output types:------ Entity:------ * @'E'@ -> @'Entity'@------ Components:------ * @'C' c@ -> @'Result' c@--- * @'M' c@ -> @'Maybe' ('Result' c)@--- * @'Has' c@ -> @'Bool'@------ Relationships:------ * @'R' c e@ -> @'Result' ('Rel' c)@--- * @'R' c 'Any'@ -> @['Result' ('Rel' c)]@--- * @'MR' c e@ -> @'Maybe' ('Result' ('Rel' c))@--- * @'MR' c 'Any'@ -> @'Maybe' ['Result' ('Rel' c)@--- * @'HasR' c e@ -> @'Bool'@--- * @'HasR' c 'Any'@ -> @'Bool'@------ Transitive:------ * @'R' c ('Q' q)@ -> @[Result of q]@--- * @'MR' c ('Q' q)@ -> @'Maybe' [Result of q]@--- * @'HasR' c ('Q' q)@ -> @'Bool'@------ Note that the relational queries using @Any@ and the transitive ones will return a single element--- instead of a list, in the case of that relation being exclusive.---- $filters--- Filters can be passed to @'@ variants of query functions, such as @'query''@ and @'single''@.------ There is an implicit @and@ between filters. @(A, B)@ means @A and B@. If you wish to express @A or B@, you can write it as @A |. B@. @Not@ can be used to--- negate filters.------ @(A |. Not (B, C))@ means @A or (not (B and C))@.------ Most filters expect a tuple of @'C'@ and @'R'@ types. These are all valid filters:------ * @'With' ('C' \@Foo)@--- * @'With' ('R' \@Foo e, 'C' \@Bar)@--- * @'With' ('R' \@Foo 'Any', 'C' \@Bar, 'C' \@Baz)@------ These filters are:------ * @'With'@--- * @'Without'@--- * @'Changed'@--- * @'Added'@---- $check--- @Check@ is a special filter which takes a @f :: c -> Bool@ function and only accepts entities for which @f@ applied over the @c@ component is True.--- Naturally, all entities that don't contain the @c@ component will fail.------ For instance, here's how we can select all entities named \"Bob\":------ @--- 'query'' 'E' ('Check' (== 'Name' "\Bob\"))--- @------ For relationships, you must use the dedicated @'CheckR'@ variant which also expects an entity or @Any@.------ Selecting all entities which Like alice more than 5:------ @--- 'query'' 'E' ('CheckR' alice (> Likes 5))--- @------ Selecting all entities which like any other entity more than 10:------ @--- 'query'' 'E' ('CheckR' 'Any' (> Like 10))--- @---- $quasi--- @Quasi-Queries@ are queries written via a special quasi-quoter. Make sure to have the @QuasiQuotes@ and @TemplateHaskell@ langauge extensions enabled in order to use them.------ === Components------ Here's how we can rewrite a simple componnet query in quasi form:------ @--- 'query' ('C' \@Foo, 'C' \@Bar)--- @------ @--- ['q'|Foo, Bar|]--- @------ As you can see, a @'C' \@c@ becomes @c@.------ === Relationships------ What about relationships?------ @--- 'query' ('R' \@Foo e, 'R' \@Bar 'Any')--- @------ @--- ['q'|Foo -> e, Bar -> *|]--- @------ @'R' \@c a@ is translated to @c -> a@, and @Any@ becomes @*@.------ == Transitive------ Transitive queries are written the same as relationship ones, but with @()@ around their target:------ @--- 'query' ('R' \@Foo ('Q' ('C' \@Bar)))--- @------ @--- ['q'|Foo -> (Bar)]--- @------ === Modifiers------ Quasi-Queries also accept @Maybe@ and @Has@ modifiers:------ @--- 'query' ('M' \@Foo, 'HasR' \@Bar 'Any')--- @------ @--- ['q'|Maybe Foo, Has Bar -> *|]--- @------ Since Quasi-Queries are parsed internally by Mischief, alternative symbols are allowed:------ * @Maybe@ | @maybe@ | @M@ | @m@--- * @Has@ | @has@ | @H@ | @h@------ So the above query can also be written as:------ @--- ['q'|M Foo, H Bar -> *|]--- @------ You don't need to worry about the distinction between @M@ and @MR@ and so on, the parer will infer which to use.------ === Val------ @Val@ is accepted in quasi notation too:------ @--- 'query' ('Val' ('C' \@Foo, 'C' \@Bar))--- @------ @--- ['q'|Val (Foo, Bar)|]--- @------ @Val@ can be written as: @Val@, @val@, @V@, @v@, @*@.------ So an equivalent way to write the above would be:------ @--- ['q'|*(Foo, Bar)|]--- @------ === Entity------ @'E'@ can be written as: @Entity@, @entity@, @E@, @e@.------ === Filters------ In order to add a filter to a quasi-query, we must separate it with a @\/@ from the rest of the query:------ @--- 'query'' ('C' \@Name) ('With' ('C' @\Foo, R @\Bar 'Any'), 'Without'('C' @\Baz))--- @------ @--- ['q'|Name \/ With (Foo, Bar -> *), Without Baz|]--- @------ @Added@ and @Changed@ also exists for quasi-queries. All filters can be written either starting with a lower or uppercase letter. For instance, both @with@ and @With@ are correct.--- @Not@ can also be written as @!@ and @|.@ can be written as @|.@, @||@, @or@, @Or@.------ === Check------ In quasi notation, @Check@ is unified for both components and relationships. Simply put @-> a@ after it if it's a relationship!------ Getting all entities named \"Bob\" which like alice more than 5:------ @--- ['q'|Entity / Check (== Name \"Bob\"), Check (> Likes 5) -> alice|]--- @------ The argument for @Check@ can be any arbitrary lambda function or a function defined outside the quasi-quote.------ === Generics------ In order to use a quasi-query for a type with generic parameters, such as:------ @--- data A a b = A deriving ('Component')--- @------ The entire type must be put in @()@. For instance:------ @--- ['q'|Maybe (A Int Float), Likes -> *|]--- @------ === Other Quasies------ There is also the @g@ Quasi-Query for @get@, and @s@ for @single@:------ @--- ['g'|Name|] alice--- @------ @--- ['s'|Name / with Player|]--- @
− src/Mischief/ECS/Tutorial/Relationships.hs
@@ -1,402 +0,0 @@-{-# OPTIONS_GHC -Wno-unused-imports #-}---- |--- Module: Relationships Tutorial--- Description: Tutorial on using @Relationships@------ This module contains a more in-depth tutorial on @Mischief Relationships@.------ [Previous Chapter: Components]("Mischief.ECS.Tutorial.Components")------ [Next Chapter: Queries]("Mischief.ECS.Tutorial.Queries")------ [Main Page]("Mischief.ECS")-module Mischief.ECS.Tutorial.Relationships- ( -- * Learn You an ECS for Great Mischief! - 5. Relationships- -- $intro-- -- * Insertion- -- $insertion-- -- * Removal- -- $removal-- -- * Querying- -- $query-- -- * Transitive Querying- -- $trans-- -- * Hooks- -- $hooks-- -- * Examples- -- $examples-- -- * [Next Chapter: Queries]("Mischief.ECS.Tutorial.Queries")- )-where--import Mischief.ECS---- $intro--- Mischief implement @Relationships@ in a similar way to @Flecs@.------ When a component is added to an entity, it is actually indexed by a @ComponentId@:------ @--- data 'ComponentId' = ComponentId {id :: 'Entity', entity :: 'Maybe' 'Entity'}--- @------ The first field, @id@, is the entity corresponding to the component, while the second field, @entity@, is an optional reference to another entity.------ This means that each @ComponentId@ can either be a simple component, or a pair between a component and an entity (technically even between--- two components or two entities but that's not directly allowed by the API).------ So a relationship in Mischief is a pair between a component and an entity. It can be inserted on entities using @Rel@:------ @--- data 'Rel' c = Rel {comp :: c, target :: 'Entity'}--- @------ For instance, this is how we spawn an entity @b@ that's a child of @a@:------ @--- a <- 'spawn' ()--- b <- 'spawn' ('Rel' 'ChildOf' a)--- @---- $insertion--- Let's consider the following component, which will symbolize that an entity likes another, and by how much:------ @--- data Likes = Likes 'Int' deriving ('Component')--- @------ And three spawned entities: @alice@, @bob@, @charlie@.------ As mentioned before, we can insert a relationship using the 'Rel' type.------ @--- 'insert' ('Rel' (Likes 3) alice, 'Rel' (Likes 5) charlie) bob--- 'insert' ('Rel' (Likes 2) bob) alice--- @------ If we insert a second relationship with the same component and the same target, its value will overwrite the other. For instance, the following code--- will make @alice@ like @bob@ by 3 instead of 2:------ @--- 'insert' ('Rel' (Likes 3) bob) alice--- @---- $removal--- Removing relationships can be done through the @remove@ function, same as normal components. But instead of using the @'C'@ marker, we will use the @'R'@ marker.------ Making @bob@ stop liking @alice@.------ @--- 'remove' ('R' \@Likes alice) bob--- @------ The @'R'@ marker takes a type hint of the relationship's type (@\@Likes@), and a target Entity (@alice@). But it can also be given the @Any@ wildcard instead:------ @--- 'remove' ('R' \@Likes 'Any') bob--- @------ This will remove all @Likes@ relationships from @bob@, making him not like anyone.---- $query--- Relationships can be queried using the @R@ marker.------ @--- x <- 'query' ('R' \@Likes alice)--- @------ @--- x :: ['Result' ('Rel' Likes)]--- @------ In quasi-queries, this becomes:------ @--- x \<- ['q'|Likes -\> alice|]--- @------ @Result (Rel c)@ is the return type of @R \@c e@. It can be used in most Result-based operation discussed in the previous chapter, such as @'set'@ and @'delete'@.------ Querying can also be done using the @Any@ wildcard:------ @--- x <- 'query' ('R' \@Likes Any)--- @------ @--- x :: [['Result' ('Rel' Likes)]]--- @------ In quasi-queries, @Any@ is symbolized by @*@:------ @--- x \<- ['q'|Likes -\> *|]--- @------ As you can see, the return type of @'R' \@c Any@ is @[Result (Rel c)]@. It's returning a list of relationships, rather than a single relationship (unless the--- relationship is exclusive, but more on that in a bit).------ Remember that the fields of the inner type of a Result are inherited by the Result itself. So we can just use @.comp@ and @.target@ to get the component and target of a @Result (Rel c)@.------ @--- 'Just' x <- 'get' ('R' \@Likes alice) bob--- @------ @--- x.target :: Entity--- x.comp :: Likes--- @------ In the case of querying for @R c Any@, the query will only match entities that have at least one such relationship. The resulting @[Result (Rel c)]@ should never be empty.------ If you wish to also include entities that do not have those relationships, you can use @`MR`@ (short for @Maybe Relationships@), the relational equivalent of @'M'@.------ @--- x <- 'query' ('MR' \@Likes alice)--- @------ @--- x :: ['Maybe' ('Result' ('Rel' c))]--- @------ Which is this in quasi form:------ @--- x <- ['q'|Maybe Likes -> alice|]--- @---- $exclusive--- A relationship can be made exclusive by setting the following associated type on its component instance:------ @--- instance 'Component' FooRel where--- type 'RelExclusivity' FooRel = 'Exclusive'--- @------ This will make it so only one instance of a relationship can exist on an entity at once.------ @--- insert (Rel (FooRel, a)) c--- insert (Rel (FooRel, b)) c--- @------ Will result in just @Rel (FooRel, b)@ being on @c@.------ It also changes the result of @R \@FooRel Any@ queries to be of the form:------ @--- 'Result ('Rel' FooRel)--- @------ Instead of:------ @--- ['Result ('Rel' FooRel)]--- @---- $trans--- Transitive queries are a powerful primitive which allow us to easily query components based on relational connections.------ For instance, this is how we can get the name of each entity, along with the names of all entities they like:------ @--- x <- 'query' ('C' \@Name, 'R' \@Likes ('Q' ('C' \@Name)))--- @------ @--- x :: [(Result Name, [Result Name])]--- @------ They /tend/ to look much better when written as quasi-queries (don't forget the @()@!):------ @--- x \<- ['q'|Name, Likes -\> (Name)]--- @------ Note that transitive queries can be nested as much as you want:------ @--- x \<- ['q'|Name, Likes -\> (Name, Likes -\> (Name))|]--- @------ @--- x :: [(Result Name, [(Result Name, [Result Name])])]--- @---- $hooks--- There are a number of predefined hooks that are useful when working with relationships, which can be found in "Mischief.ECS.Hooks".------ For instance, @relComplementary@ can be used to automate adding a complementary relationship on the target of a relationship.------ As a quick example of why this is useful, let's create a @Before@/@After@ relationship between entities:------ @--- data Before = Before--- data After = After------ instance 'Component' Before where--- 'hooks' = 'relComplementary' ('const' After)------ instance 'Component' After where--- 'hooks' = 'relComplementary' ('const' Before)--- @------ Now, when we do:------ @--- 'insert' ('Rel' Before a) b--- @------ A @Rel After b@ will be inserted automatically on @a@.------ And when we do:------ @--- 'remove' ('R' \@Before) b--- @------ @Rel After b@ will be removed from @a@.------ And vice versa.------ There are also @'relCleanupRemove'@ and @'relCleanupDespawn'@ for automatically removing a relationship (or ,respectively, despawning its entity) when its target has been despawned. And a--- more generic @'relCleanup'@ which allows adding custom cleanup behavior.---- The 'WithR' query filter lets us easily query for components of entities that have a certain relationship with a certain entity.------ Getting a list of all entities that like bob.------ @--- x <- 'query'' 'E' ('WithR' @Likes bob)--- @------ Getting a list of all entities that like anyone.------ @--- x <- 'query'' 'E' ('WithR' @Likes Any)--- @------ We can also modify @Likes@ to have an @Int@ as well, representing how much an entity likes another:------ @--- data Likes = Likes 'Int' deriving ('Component')--- @------ @--- 'insert' ('Rel' (Likes 5, alice), 'Rel' (Likes 8, charlie)) bob--- @------ The 'R' marker type can be used in a query to get a @['Result' ('Rel' c)]@ for each entity.------ Getting the name and all the Likes relationships of all entities.------ @--- x <- query (C \@Name, 'R' \@Likes Any)--- @------ @--- x :: [('Result' Name, ['Result' ('Rel' Likes)])]--- @--------- Getting the name and the Like relationship with bob for all entities.------ @--- x <- query (C \@Name, 'R' \@Likes bob)--- @------ @--- x :: [('Result' Name, 'Result' ('Rel' Likes))]--- @------ Note that @'R' \@Likes@ will limit the query to only the archetypes that contain any relation with @Likes@.--- You can also use @'MR'@ (Maybe relationship) to also include the entities that don't contain such relationships.------ A component can be made @exclusive@ by setting the following 'Bool' in the 'Component' instance:------ @--- instance 'Component' Likes where--- 'isExclusiveRel' = 'True'--- @------ If a component is exclusive, there can only be one relationship containing it on an entity at once.------ For instance, if we do:------ @--- 'insert' ('Rel' (Likes, alice) bob--- 'insert' ('Rel' (Likes, charlie)) bob--- @------ @(Likes, charlie)@ will overwrite @(Likes, alice)@.------ This is useful for relationships such as 'ChildOf', since an entity can only have one parent at a time.---- $examples------ === __Example 1__--------- An example showing different operations on relationships, and the @relCleanupRemove@ hook.------ @--- import "Mischief.ECS.Prelude"--- import "Mischief.ECS.Hooks" qualified as [Hooks]("Mischief.ECS.Hooks")--- import "Mischief.ECS.Systems" qualified as [Systems]("Mischief.ECS.Systems")------ data Likes = Likes 'Int' deriving ('Show')------ instance 'Component' Likes where--- 'hooks' = [Hooks]("Mischief.ECS.Hooks").'Mischief.ECS.Hooks.relCleanupRemove'------ main :: 'IO' ()--- main = do--- app <- 'newApp' MainPlugin--- 'runApp' app------ data MainPlugin = MainPlugin deriving ('Eq')------ instance 'Plugin' MainPlugin where--- init _ = [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' test------ test :: 'System' ()--- test = do--- alice <- 'spawn' ('Name' \"Alice\")--- bob <- 'spawn' ('Name' \"Bob\")--- charlie <- 'spawn' ('Name' \"Charlie\")------ 'insert' ('Rel' (Likes 5) alice, 'Rel' (Likes 10) bob) charlie--- 'insert' ('Rel' (Likes 7) alice) bob--- 'insert' ('Rel' (Likes 9) bob, 'Rel' (Likes 5) charlie) alice------ ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes alice)--- ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes bob)--- ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes charlie)------ 'remove' ('R' \@Likes 'Any') alice--- 'despawn' bob------ ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes alice)--- ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes bob)--- ('info' . 'text') =<< 'query' ('C' \@Name, 'R' \@Likes charlie)--- @------ @--- > [INFO] [(\"Bob\",Rel {comp = Likes 7, target = 28v1}),(\"Charlie\",Rel {comp = Likes 5, target = 28v1})]--- > [INFO] [(\"Alice\",Rel {comp = Likes 9, target = 29v1}),(\"Charlie\",Rel {comp = Likes 10, target = 29v1})]--- > [INFO] [(\"Alice\",Rel {comp = Likes 5, target = 30v1})]------ > [INFO] [(\"Charlie\",Rel {comp = Likes 5, target = 28v1})]--- > [INFO] []--- > [INFO] []--- @------ You can see in the second set of prints that nobody likes Bob anymore, since he "died", and the cleanup hook made it so any "Likes -> Bob" relationships were automatically removed.
− src/Mischief/ECS/Tutorial/Startup.hs
@@ -1,431 +0,0 @@-{-# OPTIONS_GHC -Wno-unused-imports #-}---- |--- Module: Components Tutorial--- Description: Introductory Tutorial------ This module walks the user through setting up Mischief and creating a simple app.------ [Next Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Dungeon")------ [Main Page]("Mischief.ECS")-module Mischief.ECS.Tutorial.Startup- ( -- * Learn You an ECS for Great Mischief! - 1. Startup Guide-- -- * What do I need to know?- -- $know-- -- * Setup- -- $setup-- -- * The App- -- $app-- -- * The ECS- -- $ecs-- -- * Your First System- -- $firstSystem-- -- * Your First Component- -- $firstComp-- -- * Your First Query- -- $firstQuery-- -- * Your First Mutation- -- $firstMut-- -- * Your First Resource- -- $res-- -- * Your First Relationship- -- $rel-- -- * Your First Transitive Query- -- $trans-- -- * What's Next?- -- $next-- -- * [Next Chapter: Coding a Dungeon Game]("Mischief.ECS.Tutorial.Dungeon")- )-where--import Control.Monad (when)-import Data.Foldable (for_)-import Data.Traversable (for)-import Mischief.ECS---- $know--- This book doesn't assume any knowledge of other game engines or programming paradigms, but it does expect some Haskell knowledge.------ While it is possible to read this book and get a pretty good idea of what Mischief is and how it works, you'll have a much better time--- if you have at least a very basic understanding of Haskell syntax.---- $setup--- In order to use Mischief, you'll first need to install @GHC@ and @cabal@. You can follow [this](https://www.haskell.org/cabal/) quick-start guide in order to do that.------ After you have a new project set up, you need to install @mischief-ecs@:------ @--- cabal add mischief-ecs--- @------ We recommend using @GHC2024@ as the language standard (set in your @.cabal@ file).------ == Language Extensions--- We generally recommend using the following language extensions in a Mischief project:------ @--- DeriveAnyClass--- DuplicateRecordFields--- NoFieldSelectors--- DerivingStrategies--- OverloadedRecordDot--- OverloadedStrings--- QuasiQuotes--- RequiredTypeArguments--- TypeFamilyDependencies--- @------ @QuasiQuotes@ and @OverloadedStrings@ are especially important because some Mischief features are not available without them (namely quasi-queries and logging).--- The rest of the extensions are highly optional.------ You can paste these extensions in the @default-extensions@ field of your @.cabal@ file.---- $app--- A Mischief program usually starts with an app and a plugin.------ @--- import "Mischief.ECS.Prelude"------ main :: 'IO' ()--- main = do--- app <- 'newApp' MyPlugin--- 'runApp' app------ data MyPlugin = MyPlugin deriving ('Eq', 'Plugin')--- @------ If you copy this code into your project and run it using @cabal run@, your app will start! Although we haven't told it to do anything yet.------ The @App@ is a wrapper around our @World@, which is the structure containing all data stored by the ECS. It allows us to add--- initializition instructions and to plug additional logic into our game through @Plugins@.---- $ecs--- Mischief's ECS logic is designed to be very approachable and simple to write.------ @Components@ are just types deriving the @Component@ typeclass.------ @--- data Position = Position {x :: 'Float', y :: 'Float'} deriving ('Component')--- @------ @Systems@ are functions in the @System@ monad.------ @--- printPositions :: 'System' ()--- printPositions = do--- 'info' . 'text' =<< 'query' ('C' \@Position)--- @------ @Entities@ are ids used to represent and manipulate data.------ @--- data Entity = Entity 'Int'--- @---- $firstSystem--- Paste the following function into your module:------ @--- helloWorld :: 'System' ()--- helloWorld = 'info' "Hello World!"--- @------ This will be our first system. The only remaining step is to schedule it to run!------ @--- import "Mischief.ECS.Systems" qualified as [Systems]("Mischief.ECS.Systems")--- @------ @--- instance 'Plugin' MyPlugin where------ 'Mischief.ECS.App.Plugins.init' :: MyPlugin -> 'System' ()--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' helloWorld--- @------ The @Systems.add@ function adds the system to your App's update schedule, making it run once per frame. If you run your app again,--- you will see \"Hello World!\" printed to your terminal many, many times.------ As you may have noticed, the @init@ we give to the Plugin is, in itself, a system! There's nothing differentiating--- the logic you write here from the logic ran at any point in your app's runtime. @init@ is just a convenient way of--- adding some initialization that happens before anything else, but we'll get into that later.---- $firstComp--- Let's do a little more than greeting the whole world, let's greet some individual people!------ In ECS, you would generally model people as entities with a set of components that define them. Let's start with a simple @Person@ component:------ @--- data Person = Person deriving ('Component')--- @------ So how can we give people names? In a more traditional design you could just add a @name :: String@ field to @Person@. But the ECS makes you think of it differently!--- A @Name@ is just a piece of data that can be attached to anything. A dog could also have a name. So why not just make a @Name@ component?------ @--- data Name = Name 'String' deriving ('Component')--- @------ No need to define it though, since this exact @Name@ is already defined internally by Mischief, you can just use it directly.------ Now that we can represent people with names, let's make a system that spawns some:------ @--- addPeople :: 'System' ()--- addPeople = do--- kim <- 'spawn' (Person, Name \"Kimberly\")--- nick <- 'spawn' (Person, Name \"Nicholas\")--- flo <- 'spawn' (Person, Name \"Florian\")--- 'pure' ()--- @------ You can register it to run on the app's startup like this:------ @--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' addPeople--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' helloWorld--- @---- $firstQuery--- If you run your app, the people will be spawned but we aren't doing anything with them yet! Let's make a system that greets them:------ @--- greetPeople :: 'System' ()--- greetPeople = do--- people <- 'query' ('C' \@Name, 'C' \@Person)--- 'for_' people $ \(name, _) ->--- 'info' $ \"Hello \" <> 'text' name--- @------ The above query will grab the @Name@ and @Person@ of every entity. Then it iterates over them in order to greet them.------ The @Person@ component however, is only queried to ensure we are querying the right entities. We don't care about its value at all! So we can instead write it--- as a filter to limit the types of entities selected by the query and save us the trouble of carrying an extra variable around.------ @--- greetPeople :: 'System' ()--- greetPeople = do--- people <- 'query'' ('C' \@Name) ('With' ('C' \@Person))--- 'for_' people $ \name ->--- 'info' $ \"Hello \" <> 'text' name--- @------ Do note the use of @query'@ here instad of @query@. The former is a variant of the same function but which also expects a filter.------ Mischief has two equivalent ways of writing queries. The normal way that you've seen above, and the quasi way:------ @--- people <- ['q'|Name / With Person|]--- @------ Query-queries are macros meant to simplify writing queries. They'll become especially helpful once we start dealing--- with relationships and transitive queries.------ Now we can schedule this system to also run:------ @--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' addPeople--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' (helloWorld, greetPeople)--- @------ Running our app will result in the following output:------ @--- [INFO] Hello World!--- [INFO] Hello Kimberly--- [INFO] Hello Nicholas--- [INFO] Hello Florian--- @------ Note that \"Hello World\" might show above or beneath the other people, since systems in the same schedule can run in any order unless they are explicitly ordered.---- $firstMut--- If we want to change the name of some people, we can apply a mutation to a value obtained from the query:------ @--- updateFlo :: 'System'--- updateFlo = do--- people <- ['q'|Name / With Person|]--- 'for_' people $ \name -> do--- 'when' (name == Name \"Florian\") $--- 'set' name (Name \"Florianne\")--- @------ Although.. that feels awfully imperative doesn't it? We can also write the same system as:------ @--- updateFlo = do--- florians <- ['q'|Name / With Person, Check (== Name \"Florian\")|]--- 'for_' florians $ '`set`' Name \"Florianne\"--- @------ The above query can also be written like this, in non-quasi notation:------ @--- florians <- 'query'' ('C' \@Name) ('With' ('C' \@Person), 'Check' (== Name \"Florian\"))--- @------ Let's add the new system to a schedule:------ @--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' addPeople--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' (helloWorld, greetPeople)--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' $ updateFlo '`before`' greetPeople--- @------ Note that we have explicitly ordered @updateFlo@ to happen before @greetPeople@. We want to only greet Flo after their name has changed!---- $res--- Resources are a great way to store global information that can be easily written to and read in any system.------ Let's say we want to have a custom greeting that we can change at runtime. We could store that in a resource:------ @--- data Greeting = Greeting 'String' deriving ('Component')--- @------ Yes, resources are just normal components! Any component can be stored and retrieved as a resource by using @insertRes@ and @res@.------ Let's insert a greeting from our init system:------ @--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' addPeople--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' (helloWorld, greetPeople)--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' $ updateFlo '`before`' greetPeople------ 'insertRes' (Greeting \"Hey\")--- @------ And let's modify @greetPeople@ so that it uses the current greeting from the resource:------ @--- greetPeople :: 'System' ()--- greetPeople = do--- 'Just' (Greeting greeting) <- 'res' \@Greeting------ people <- ['q'|Name / With Person]--- 'for_' people $ \name ->--- 'info' $ 'text' greeting <> \" \" <> 'text' name--- @------ You should now see this when running the app:------ @--- [INFO] Hello World!--- [INFO] Hey Kimberly--- [INFO] Hey Nicholas--- [INFO] Hey Florianne--- @---- $rel--- Relationships in Mischief are pairs made up of a Component and an Entity. Let's implement a simple relationship between our entities that says which like which.------ We'll start by defining a component:------ @--- data Likes = Likes deriving ('Component')--- @------ Let's now modify our spawning system to also insert relationships between our three entities. We can insert a relationship using the @Rel@ keyword.------ @--- addPeople :: 'System' ()--- addPeople = do--- kim <- 'spawn' (Person, Name \"Kimberly\")--- nick <- 'spawn' (Person, Name \"Nicholas\")--- flo <- 'spawn' (Person, Name \"Florian\")------ 'insert' ('Rel' Likes kim) flo--- 'insert' ('Rel' Likes nick, 'Rel' Likes flo) kim--- @------ We've now made @flo@ like @kim@, and we've made @kim@ like both @nick@ and @flo@!.---- $trans--- We now have relationships but we aren't doing much with them. What about having a system that displays the name of each entity, along with the name of all entities they like?------ There are a few different ways to get the names of entities that a given entity likes, for instance we could do:------ @--- people <- 'query' ('C' \@Name, 'R' \@Likes Any)------ 'for_' people $ \(name, likes) -> do--- names \<- 'for' likes $ \\l -\> 'get' ('C' \@Name) l.target--- 'info' $ 'text' name <> " likes " <> 'text' names--- @------ We get the name of each entity, along with all their @Likes@ relationships (using the @R@ marker). Then, for each entity, we iterate over all their relationships and get the names of the targets.--- (@get@ is just like @query@ but it queries the components of a specific entity).------ But we'll have a way easier time getting there by just using @transitive queries@!--- Rather than querying for the relationships themselves, Mischief allows us to query for the components of the relationship targets from within the same query:------ @--- people <- 'query' ('C' \@Name, 'R' \@Likes ('Q' ('C' \@Name)))------ 'for_' people $ \(name, names) -> do--- 'info' $ 'text' name <> " likes " <> 'text' names--- @------ There's no need for a second query to grab the names. The equivalent quasi-query looks like this:------ @--- people \<- ['q'|Name, Likes -\> (Name)|]--- @------ Pretty nice, huh?------ Let's now put this logic in a proper system and schedule it to run:------ @--- showLikes :: 'System' ()--- showLikes = do--- people \<- ['q'|Name, Likes -\> (Name)|]------ 'for_' people $ \(name, names) -> do--- 'info' $ 'text' name <> " likes " <> 'text' names--- @------ @--- 'Mischief.ECS.App.Plugins.init' _ = do--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Startup' addPeople--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' (helloWorld, greetPeople, showLikes)--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' 'Update' $ updateFlo '`before`' greetPeople------ 'insertRes' (Greeting \"Hey\")--- @------ We should now see these additional likes printed to the terminal:------ @--- [INFO] \"Florianne\" likes [\"Kimberly\"]--- [INFO] \"Kimberly\" likes [\"Nicholas\", \"Florianne\"]--- @---- $next--- What you learn next is up to you.------ The next chapter will have you working on a little dungeon game in the terminal and introduce you to more notions. If you prefer to learn by example it's recommended to--- go check that out.------ After that, the next chapters go into detail on various topics (Components, Queries, Systems, etc.), so you may choose to just read those directly, and maybe come--- back to the game later.
− src/Mischief/ECS/Tutorial/Systems.hs
@@ -1,397 +0,0 @@-{-# OPTIONS_GHC -Wno-unused-imports #-}---- |--- Module: Systems Tutorial--- Description: Tutorial on using @Systems@------ This module contains a more in-depth tutorial on using @Mischief Systems@.------ [Previous Chapter: Queries]("Mischief.ECS.Tutorial.Queries")------ [Next Chapter: Events and Messages]("Mischief.ECS.Tutorial.Events")------ [Main Page]("Mischief.ECS")-module Mischief.ECS.Tutorial.Systems- ( -- * Learn You an ECS for Great Mischief! - 7. Systems- -- $intro-- -- * Scheduling- -- $scheduling-- -- * Schedules- -- $schedules-- -- * Deferring- -- $deferring-- -- * Parallelism- -- $par-- -- * Asynchronicity- -- $async-- -- * [Next Chapter: Events and Messages]("Mischief.ECS.Tutorial.Events")- )-where--import Control.Concurrent (threadDelay)-import Control.Monad.Reader-import Data.Foldable (for_)-import Mischief.ECS---- $intro--- A @System@ in Mischief is a Monad that executes operations on a World.------ Unlike other ECS's, systems here are fully composable, In fact, most of the functions discussed in this tutorial so far were systems.--- For instance, the type of @spawn@ is:------ @--- 'spawn' :: ('Bundle' b) => b -> 'System' ()@--- @------ Systems can either be ran directly, or they can be added scheduled.---- $scheduling--- Any @System ()@ can be added to a @Schedule@. This will make the system run when that schedule is ran.------ There are two ways of scheduling systems, an automatic and a manual way. Tools for using both are found in "Mischief.ECS.Systems".------ == Automatic------ In order to schedule a system we just use the @add@ function:--------- @--- data SomeSchedule = SomeSchedule deriving ('Schedule')--- systemA :: 'System' ()--- @------ @--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' SomeSchedule systemA--- @------ This will register @systemA@ and schedule it to run in @SomeSchedule@.------ Systems scheduled this way are considered a unique combination of the actual system and the schedule.--- If we were to add @systemA@ to /another/ schedule, it would be considered a different system.------ You can also add a tuple of systems directly:------ @--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' SomeSchedule (systemA, systemB)--- @------ This function also allows custom ordering between systems. For instance, if we want to schedule a new @systemC@ that happens /before/ @systemA@ and /after/ @SystemB@:------ @--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' SomeSchedule $ systemC '`before`' systemA '`after`' systemB--- @------ One very important thing to keep in mind is that a scheduled system is only unique as long as the type you're registering is @System ()@.------ For instance, scheduling this system:------ @--- systemA :: 'Int' -> 'System' ()--- @------ @--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' SomeSchedule (systemA 5)--- @------ And then another system which we want to run after:------ @--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.add' SomeSchedule $ systemB '`after`' (systemA 4)--- @------ Will compile fine, but since @systemA 5@ and @systemA 4@ are different systems, we've basically told @systemB@ to happen after a system that's not even running.------ Both @systemB@ and @systemA 5@ will run, but there won't be any explicit ordering between them.------ A system that's been added with @Systems.add@ can be removed using @Systems.remove@:------ @--- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.remove' SomeSchedule systemA--- @------ Note however, that this will also erase all orderings @systemA@ had with other systems at that point.------ If you wish to just temporarily disable a system while keeping its configuration, you can use--- @[Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.unschedule'@ instead. And then use @[Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.schedule'@ to--- re-enable it.------ == Manual------ In order to schedule a system manually you just spawn an entity for it:------ @--- foo :: 'System' ()--- @------ @--- fooEntity <- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.spawn' foo--- @------ And then link that entity to a schedule via the @ScheduledIn@ relationship.------ @--- someSchedule <- [Schedules]("Mischief.ECS.Schedules").'Mischief.ECS.Schedules.get' SomeSchedule--- 'insert' ('Rel' 'ScheduledIn' someSchedule) fooEntity--- @------ This will make make your system run along with @SomeSchedule@. Compared to using @Systems.add@, this method will not do any sort of bookkeeping for you.--- Is is your job to keep track of the spawned system's entity.------ Two systems can be ordered by using the @Before@ relationship:------ @--- fooEntity <- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.spawn' foo--- barEntity <- [Systems]("Mischief.ECS.Systems").'Mischief.ECS.Systems.spawn' bar------ 'insert' ('Rel' 'Before' fooEntity) barEntity--- @------ The above will order @bar@ to happen before @foo@.---- $schedules--- Same as systems, @Schedules@ are entities. Each schedule has an associated type, usually empty:------ @--- data Update = Update deriving ('Schedule')--- @------ You can both register and get the the entity of a schedule using @Schedules.get@:------ @--- update <- [Schedules]("Mischief.ECS.Schedules").'Mischief.ECS.Schedules.get' Update--- @------ You can run a schedule using @Schedules.run@:------ @--- [Schedules]("Mischief.ECS.Schedules").'Mischief.ECS.Schedules.run' Update--- @------ This will run all systems currently linked to that Schedule, respecting their ordering.------ Mischief has two components: @'StartupSchedule'@ and @'UpdateSchedule'@ which you can add to a schedule to make it automatically run on app startup, respectively each frame.------ These schedules can also be ordered via @'Before'@ (same relationship used for ordering systems).------ The systems Mischief has by default in Startup:------ * 'PreStartup'--- * 'Startup'--- * 'PostStartup'------ And in Update:------ * 'First'--- * 'PreUpdate'--- * 'Update'--- * 'PostUpdate'------ @First@ is usually reserved for internal systems (such as updating time).---- @The startup schedules@ run once at the start of the app, before any Update, in this order:---- * 'PreStartup'---- * 'Startup'---- * 'PostStartup------- @The update shchedules@ run once every frame, in this order:------- * 'First'---- * 'PreUpdate'---- * 'Update'---- * 'PostUpdate'---- 'Last'------ Note that @First@ and @Last@ are reserved mostly for ECS internal logic and should generally be avoided.------ In order to add a system @s :: 'System' ()@ to a schedule @sc@, you can use this 'Plugin':------ @--- 'addSystems' :: ('Schedule' sc, 'SystemConfig' s) => sc -> s -> 'Plugin' ()--- @------ @'SystemConfig'@ is a typeclass that allows you to provide stuff like a @'System' ()@, a tuple of systems, or a system ordered to happen after / before another system.------ @--- 'addSystems' 'Update' a--- @------ @--- 'addSystems' 'Update' (a, b)--- @------ @--- 'addSystems' 'Update' $ a '`after`' b--- @------ @--- 'addSystems' 'Update' $ (a, b) '`after`' c '`before`' d--- @------ Keep in mind that when you add @a '`after`' b@, this will just add @a@ to the schedule; @b@ needs to be added separately, and doesn't need--- to be in the same plugin. It's even possible to create and order a system to run before a system defined internally by Mischief, as long as--- that system is exported and visible to you.------ Additionally, these are equivalent:------ @--- 'addSystems' b--- 'addSystems' 'Update' $ a `'after'` b--- @------ @--- 'addSystems' 'Update' $ a `'after'` b--- 'addSystems' b--- @------ Note that when you order systems, they will be ordered only in the context of the current schedule.--- So @'addSystems' 'Update' $ a '`after`' b@ will order @a@ after the system @b@ that's in Update, if there is one at the point of running the app.------ Also, a system can be added to any number of schedules. Adding a system twice to the same schedule will have no effect.---- $deferring--- Time to learn a very powerful and important primitive:------ @--- 'defer' :: 'System' a -> 'System' ()--- @------ All the systems presented so far in this tutorial had their effect applied immediately. When you write @set Name $ Name \"Player\"@,--- you are /immediately/ mutating the respective component. When you do @e <- 'spawn' ()@, you are /immediately/ spawning that entity into the World.------ @'defer'@ takes a system and adds it to an internal list instead of applying it.------ @--- 'defer' $ 'spawn' ()--- @------ @--- 'defer' $ do--- e <- 'spawn' ()--- 'insert' ('Name' \"Name\") e--- @------ You can then use @'flush'@ to empty the list of deferred systems, applying all of them.--- Mischief automatically runs @'flush'@ at each @sync point@, usually at the end of each scheduled system.------ @forkDeref@ is a useful function that temporarily restricts @flush@ to just the current context:------ @--- 'defer' $ a------ 'forkDefer' $ do--- 'defer' $ do--- b--- c--- 'flush'--- @------ The above @flush@ will just run @b@ and @c@. @forkDeref@ will drain all non-flushed systems into the outer context.------ There is also a special @'deferSpawn'@ primitive that immediately returns an @Entity@ you can use but defers the actual spawn.---- $par--- @Parallelism@ in Mischief happens through the @'ParSystem'@ monad.------ @ParSystem@ is a special variant of @System@ that forbids any mutations to the World.------ This will throw a compilation error:------ @--- s :: 'ParSystem' ()--- s = 'void' $ 'spawn' ()--- @------ There are generally 2 types of operations allowed in a @ParSystem@:------ * Queries--- * Deferred Systems------ So for instance, if we want to read and change the name of the player in a @ParSystem@:------ @--- changeName :: 'ParSystem' ()--- changeName = do--- 'Just' name <- 'single'' ('C' \@Name) ('With' ('C' \@Player))--- 'defer' $ 'set' name (Name "New Name")--- @------ so how can we actually run systems in parallel? There are two main primitives used for it: @par@ and @parIter@:------ @par@ is given a list of @'ParSystem' ()@ and will run each of them in parallel:------ @--- 'par' [foo, bar, baz]--- @------ @parIter@ (and @parIter_@ which ignores the result) applies a @ParSystem@ over the elements of a list. Given a list of Entities, this is how we can get their names in parallel:------ @--- entities :: ['Entity']--- @------ @--- names <- 'parIter' entities $ 'get' ('C' \@Name)--- @------ These primtiives should only be used in performance which are at the risk of bottlenecking performance.---- $async--- Mischief has a couple of primitived that allow running systems asynchronously.------ === runAfter------ Asynchonicity in Mischief can be achieved using the @runAfter@ primtitive.--- You provide it an 'IO' action that returns an @a@, and a system which consumes that @a@.------ The 'IO' will be ran fully asychrnously and then will add the system to a special async-friendly deferred list that--- will be applied at the first available sync point.------ We can look at @delay@ as an example of how this may be useful:------ @--- 'delay' d system = 'runAfter' ('threadDelay' d) ('const' system)--- @------ Whcih allows delaying any system by an amount of time:------ @--- 'delay' 500 $ 'insert' (Health 100) player--- @------ === Intervals------ Intervals are another way of running async systems.------ @--- import "Mischief.ECS.Interval" qualified as [Interval]("Mischief.ECS.Interval")--- @------ This allows you to set a system to repeatedly run on a fixed interval:--------- This will print \"Hello!\" once per second:------ @--- hello <- [Interval]("Mischief.ECS.Interval").'Mischief.ECS.Interval.start' 1000 ('info' \"Hello!\")--- @------ The interval can be stopped at any time by calling @Interval.stop@ on the returned object:------ @--- [Interval]("Mischief.ECS.Interval").'Mischief.ECS.Interval.stop' hello--- @
src/Mischief/ECS/Utils.hs view
@@ -1,10 +1,14 @@ module Mischief.ECS.Utils where import Data.Data+import Data.Either (fromRight) import Data.Foldable import Data.Kind import Data.Maybe+import Data.Text (Text)+import Data.Text qualified as T import GHC.Stack+import Unsafe.Coerce (unsafeCoerce) class GetRep t where getRep :: t -> TypeRep@@ -21,8 +25,52 @@ else findM f xs -unwrap :: (HasCallStack) => Maybe a -> a-unwrap = withFrozenCallStack $ fromMaybe undefined+class (Functor f) => Unwrappable f where+ unwrap' :: (HasCallStack) => Text -> f a -> a -(>>+) :: forall (m :: Type -> Type) a b. (Monad m) => m a -> (a -> m b) -> m a-(>>+) a f = a >>= f >>= const a+instance Unwrappable Maybe where+ unwrap' t a = withFrozenCallStack $ fromMaybe (error $ T.unpack t) a++instance Unwrappable (Either a) where+ unwrap' t a = withFrozenCallStack $ fromRight (error $ T.unpack t) a++-- | Unwrap a 'Maybe' or 'Either', panicking if it doesn't contain a value.+unwrap :: (HasCallStack, Unwrappable f) => f a -> a+unwrap = withFrozenCallStack $ unwrap' ""++-- | Unwrap a 'Maybe' or 'Either', throwing the given error if it doesn't contain a value.+expect :: (HasCallStack, Unwrappable f) => Text -> f a -> a+expect t = withFrozenCallStack $ unwrap' t++andM :: (Traversable t, Applicative f) => t (f Bool) -> f Bool+andM t = and <$> sequenceA t++orM :: (Traversable t, Applicative f) => t (f Bool) -> f Bool+orM t = or <$> sequenceA t++anyM :: (Traversable t, Monad f) => (a -> f Bool) -> t a -> f Bool+anyM f t = do+ bools <- traverse f t+ pure $ or bools++mapLeft :: (a -> c) -> Either a b -> Either c b+mapLeft _ (Right a) = Right a+mapLeft f (Left a) = Left $ f a++data ErasedValue where+ ErasedValue :: forall a. (Typeable a) => a -> ErasedValue++eraseValue :: forall a. (Typeable a) => a -> ErasedValue+eraseValue = ErasedValue++uneraseValue :: forall a. (Typeable a) => ErasedValue -> Maybe a+uneraseValue (ErasedValue (a :: b)) =+ case eqT @a @b of+ Nothing -> Nothing+ Just Refl -> Just a++unsafeUneraseValue :: forall a. ErasedValue -> a+unsafeUneraseValue (ErasedValue a) = unsafeCoerce a++fromMaybeM :: (Applicative m) => m a -> Maybe a -> m a+fromMaybeM m = maybe m pure
src/Mischief/ECS/Vec.hs view
@@ -3,6 +3,7 @@ -- adds new elements at the end of vector. -- -- We reallocate vector with 1.5x length to get amortized append.+{- HLINT ignore "Use camelCase" -} module Mischief.ECS.Vec ( Vec (..), IOVec,@@ -66,7 +67,6 @@ import Data.Vector qualified as Vector import Data.Vector.Mutable (MVector) import Data.Vector.Mutable qualified as MVector-import Debug.Trace (traceShow) import GHC.Generics import GHC.Stack (HasCallStack) import Prelude hiding (length, null, read)@@ -82,18 +82,18 @@ -- | Return current capacity of the vector (amount of elements that it can fit without realloc) capacity :: (PrimMonad m) => Vec (PrimState m) a -> m Int-capacity v =+capacity !v = MVector.length <$> readMutVar v.buffer {-# INLINE capacity #-} -- | Return current amount of elements in the vector length :: (PrimMonad m) => Vec (PrimState m) a -> m Int-length v = readMutVar v.len+length !v = readMutVar v.len {-# INLINE length #-} -- | Return 'True' if there is no elements inside the vector null :: (PrimMonad m) => Vec (PrimState m) a -> m Bool-null v = (== 0) <$> length v+null !v = (== 0) <$> length v -- | Allocation of new growable vector with given capacity. new :: (PrimMonad m) => Int -> m (Vec (PrimState m) a)@@ -103,10 +103,10 @@ -- | Allocation of new growable vector with given filled size and capacity. -- Elements is not initialized. Capacity must be greater than filled size. newSized :: (PrimMonad m) => Int -> Int -> m (Vec (PrimState m) a)-newSized n cap = do- buffer <- MVector.new cap- buffer <- newMutVar buffer- len <- newMutVar n+newSized !n !cap = do+ !buffer <- MVector.new cap+ !buffer <- newMutVar buffer+ !len <- newMutVar n pure Vec {buffer, len} {-# INLINEABLE newSized #-} @@ -119,10 +119,10 @@ Int -> Vec (PrimState m) a -> m (Vec (PrimState m) a)-slice i n v = do- len <- newMutVar n- mv <- readMutVar v.buffer- buffer <- newMutVar $! MVector.slice i n mv+slice !i !n !v = do+ !len <- newMutVar n+ !mv <- readMutVar v.buffer+ !buffer <- newMutVar $! MVector.slice i n mv pure $! Vec {len, buffer} {-# INLINEABLE slice #-} @@ -132,9 +132,9 @@ (PrimMonad m) => Vector a -> m (Vec (PrimState m) a)-thaw u = do- buffer <- newMutVar =<< Vector.thaw u- len <- newMutVar $! Vector.length u+thaw !u = do+ !buffer <- newMutVar =<< Vector.thaw u+ !len <- newMutVar $! Vector.length u pure Vec {buffer, len} {-# INLINEABLE thaw #-} @@ -145,9 +145,9 @@ (PrimMonad m) => Vec (PrimState m) a -> m (Vector a)-freeze v = do- n <- length v- mv <- readMutVar v.buffer+freeze !v = do+ !n <- length v+ !mv <- readMutVar v.buffer Vector.freeze $ MVector.take n mv {-# INLINEABLE freeze #-} @@ -155,7 +155,7 @@ (PrimMonad m) => Vec (PrimState m) a -> m [a]-toList vec = Vector.toList <$> freeze vec+toList !vec = Vector.toList <$> freeze vec ensure_not_oob :: (HasCallStack, PrimMonad m) =>@@ -166,8 +166,8 @@ Vec (PrimState m) a -> -- | The length of the vec m ()-ensure_not_oob fname i vec = do- len <- length vec+ensure_not_oob !fname !i !vec = do+ !len <- length vec when (i < 0 || i >= len) $ do error $ mconcat [fname, ": index ", show i, " is out bounds ", show len] @@ -177,11 +177,11 @@ Vec (PrimState m) a -> Int -> m ()-ensure v cap = do- current_cap <- capacity v+ensure !v !cap = do+ !current_cap <- capacity v unless (current_cap >= cap) $ do- buffer <- readMutVar v.buffer- grown <- MVector.grow buffer (cap - current_cap)+ !buffer <- readMutVar v.buffer+ !grown <- MVector.grow buffer (cap - current_cap) writeMutVar v.buffer grown {-# INLINEABLE ensure #-} @@ -193,14 +193,14 @@ -- | Additional n elements Int -> m ()-ensureAppend vec i = do- len <- length vec- buf <- readMutVar vec.buffer- let cap = MVector.length buf+ensureAppend !vec !i = do+ !len <- length vec+ !buf <- readMutVar vec.buffer+ let !cap = MVector.length buf unless (cap >= len + i) $ do -- ugly as shit code would like to fix- let newCap = ceiling $ max (growFactor * fromIntegral cap) (fromIntegral cap + growFactor * fromIntegral (len + i - cap))- new_buf <- MVector.grow buf (newCap - cap)+ let !newCap = ceiling $ max (growFactor * fromIntegral cap) (fromIntegral cap + growFactor * fromIntegral (len + i - cap))+ !new_buf <- MVector.grow buf (newCap - cap) writeMutVar vec.buffer new_buf where growFactor :: Double@@ -214,9 +214,9 @@ -- | Index of element. Must be in [0 .. length) range Int -> m a-read vec i = do+read !vec !i = do ensure_not_oob "Vec.read" i vec- buf <- readMutVar vec.buffer+ !buf <- readMutVar vec.buffer MVector.unsafeRead buf i {-# INLINEABLE read #-} @@ -227,8 +227,8 @@ -- | Index of element. Must be in [0 .. length) range Int -> m a-unsafeRead vec i = do- buf <- readMutVar vec.buffer+unsafeRead !vec !i = do+ !buf <- readMutVar vec.buffer MVector.unsafeRead buf i {-# INLINEABLE unsafeRead #-} @@ -240,9 +240,9 @@ Int -> a -> m ()-write vec i value = do+write !vec !i !value = do ensure_not_oob "Vec.write" i vec- buf <- readMutVar vec.buffer+ !buf <- readMutVar vec.buffer MVector.unsafeWrite buf i value {-# INLINEABLE write #-} @@ -254,8 +254,8 @@ Int -> a -> m ()-unsafeWrite vec i value = do- buf <- readMutVar vec.buffer+unsafeWrite !vec !i !value = do+ !buf <- readMutVar vec.buffer MVector.unsafeWrite buf i value {-# INLINEABLE unsafeWrite #-} @@ -265,9 +265,9 @@ Int -> (a -> a) -> m a-modify vec i f = do+modify !vec !i !f = do ensure_not_oob "Vec.modify" i vec- old_val <- read vec i+ !old_val <- read vec i write vec i (f old_val) pure old_val {-# INLINEABLE modify #-}@@ -278,9 +278,9 @@ Int -> (a -> a) -> m ()-modify_ vec i f = do+modify_ !vec !i !f = do ensure_not_oob "Vec.modify_" i vec- old_val <- read vec i+ !old_val <- read vec i write vec i (f old_val) {-# INLINEABLE modify_ #-} @@ -290,9 +290,9 @@ Int -> (a -> m a) -> m a-modifyM vec i f = do+modifyM !vec !i !f = do ensure_not_oob "Vec.modifyM" i vec- old_val <- read vec i+ !old_val <- read vec i write vec i =<< f old_val pure old_val {-# INLINEABLE modifyM #-}@@ -303,9 +303,9 @@ Int -> (a -> m a) -> m ()-modifyM_ vec i f = do+modifyM_ !vec !i !f = do ensure_not_oob "Vec.modifyM_" i vec- old_val <- read vec i+ !old_val <- read vec i write vec i =<< f old_val {-# INLINEABLE modifyM_ #-} @@ -315,7 +315,7 @@ Int -> (a -> m ()) -> m ()-tap vec i act = do+tap !vec !i !act = do ensure_not_oob "Vec.tap" i vec act =<< read vec i {-# INLINE tap #-}@@ -326,7 +326,7 @@ Vec (PrimState m) a -> a -> m ()-pushBack vec value = do+pushBack !vec !value = do ensureAppend vec 1 unsafePushBack vec value {-# INLINEABLE pushBack #-}@@ -338,9 +338,9 @@ Vec (PrimState m) a -> a -> m ()-unsafePushBack vec a = do- len <- length vec- buf <- readMutVar vec.buffer+unsafePushBack !vec !a = do+ !len <- length vec+ !buf <- readMutVar vec.buffer MVector.write buf len a writeMutVar vec.len (len + 1) {-# INLINEABLE unsafePushBack #-}@@ -353,11 +353,11 @@ Int -> -- | index of element #2 to swap m ()-swap vec i j = do+swap !vec !i !j = do when (i /= j) $ do ensure_not_oob "Vec.swap" i vec ensure_not_oob "Vec.swap" j vec- buffer <- readMutVar vec.buffer+ !buffer <- readMutVar vec.buffer MVector.swap buffer i j shrink ::@@ -365,8 +365,8 @@ Vec (PrimState m) a -> Int -> m ()-shrink vec amount = do- old_len <- length vec+shrink !vec !amount = do+ !old_len <- length vec writeMutVar vec.len (old_len - amount) takeSwap ::@@ -374,8 +374,8 @@ Vec (PrimState m) a -> Int -> m a-takeSwap vec i = do- value <- read vec i+takeSwap !vec !i = do+ !value <- read vec i removeSwap vec i pure value @@ -385,15 +385,15 @@ -- | index of element to remove Int -> m ()-removeSwap vec i = do- len <- length vec+removeSwap !vec !i = do+ !len <- length vec swap vec i (len - 1) shrink vec 1 clone :: (PrimMonad m) => Vec (PrimState m) a -> m (Vec (PrimState m) a)-clone vec = do- list <- toList vec- len <- length vec- newVec <- new len+clone !vec = do+ !list <- toList vec+ !len <- length vec+ !newVec <- new len for_ list $ pushBack newVec- return newVec+ pure newVec
src/Mischief/ECS/World.hs view
@@ -1,11 +1,12 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+ module Mischief.ECS.World ( -- * World World (..), newWorld, setSystemId, setDeferred,- setPrefs,- forkPrefs, Frame (..), SystemTools (..), worldGet,@@ -13,21 +14,33 @@ worldSet, worldSpawnByInsert, + -- * Prefs+ PrefType,+ setPref,+ getPref,+ SupressEventsPref,+ -- * Systems System (..), SystemId (..),+ runWhen, runSystem, MonadSystem, unsafeGetWorld,+ sysUnliftIO,+ SystemAny (..), -- * Parallel ParSystem (..), ParWorld (..),+ parLift, ) where import Colog qualified import Control.Concurrent.STM (TVar, newTVarIO)+import Control.Monad+import Control.Monad.Catch import Control.Monad.IO.Class (MonadIO (liftIO)) import Control.Monad.Primitive (PrimMonad (..), RealWorld) import Control.Monad.Reader.Class (MonadReader (..), asks)@@ -35,12 +48,13 @@ import Control.Monad.Trans.Reader (ReaderT (runReaderT)) import Data.Data import Data.IORef (IORef, modifyIORef', newIORef, readIORef)+import Data.Map (Map)+import Data.Map qualified as Map import Mischief.ECS.Archetypes (Archetypes, emptyArchetypes) import Mischief.ECS.Collectable import Mischief.ECS.Components- ( Component (RelExclusivity),+ ( Component (IsExclusiveRel), Components,- Exclusivity (..), Rel, Tick (Tick), emptyComponents,@@ -51,12 +65,9 @@ Entity (Entity), emptyEntities, )-import Mischief.ECS.EventDef import Mischief.ECS.Hidden-import Mischief.ECS.Mappable import Mischief.ECS.Tables (Tables, emptyTables)-import Mischief.ECS.World.Prefs (WorldPrefs, newPrefs)-import Mischief.ECS.World.Query.QueryType+import Mischief.ECS.Utils (ErasedValue (..), eraseValue, unsafeUneraseValue) -- | @The World@ is the main data structure storing the entities, components, archetypes, and everything else that lives in our app. data World = World@@ -68,13 +79,14 @@ entities :: Entities, -- | Archetype tables. Storage for the actual data of components. tables :: Tables,- -- | List of events to be ran at the next sync point.- events :: IORef [ErasedEvent], -- | List of deferred system to be ran at the next sync point. They can also be flushed manually using 'flush'. deferred :: IORef [System ()], -- | Secondary list of deferred systems stored in a TVar, meant to be used when running systems asynchronously. -- They are ran at the first available sync point, and can also be flushed manually with 'flushAsync'. deferredAsync :: TVar [System ()],+ -- | Systems to be tried at the next sync point. If the first system returns a Just, it is removed from the list and the second+ -- system is ran with the returned value.+ trySystems :: IORef [(System (Maybe ErasedValue), ErasedValue -> System ())], -- | The current tick, incremented each time a system is ran, used for change detection. tick :: IORef Tick, -- | Id of the current system.@@ -82,11 +94,46 @@ -- | The current frame. frame :: IORef Frame, -- | Certain toggleable settings.- prefs :: WorldPrefs,+ prefs :: Prefs, logger :: Colog.LogAction IO Colog.Message,+ loggerErr :: Colog.LogAction IO Colog.Message, tools :: SystemTools } +newtype Prefs = Prefs (Map TypeRep Pref)++data Pref where+ Pref :: (Typeable a) => a -> Pref++class (Typeable a, Typeable b) => PrefType a b | a -> b++data SupressEventsPref++instance PrefType SupressEventsPref Bool++worldSetPref :: forall a b. (PrefType a b) => b -> World -> World+worldSetPref !value !world =+ let Prefs !prefs = world.prefs+ in world {prefs = Prefs $ Map.insert (typeRep $ Proxy @a) (Pref value) prefs}++worldGetPref :: forall a b. (PrefType a b) => World -> Maybe b+worldGetPref World {prefs = Prefs !prefs} = worldGetPref' @a =<< Map.lookup (typeRep $ Proxy @a) prefs++worldGetPref' :: forall a b. (PrefType a b) => Pref -> Maybe b+worldGetPref' (Pref (!s :: a')) =+ case eqT @a' @b of+ Just Refl -> Just s+ Nothing -> Nothing++setPref :: forall a b c. (PrefType a b) => b -> System c -> System c+setPref !value !s = do+ !world <- unsafeGetWorld+ let world' = worldSetPref @a value world+ liftIO $ runSystem s world'++getPref :: forall a m b w. (PrefType a b, MonadSystem w m) => m (Maybe b)+getPref = worldGetPref @a <$> unsafeGetWorld+ newtype Frame = Frame Int deriving (Show, Eq, Ord) -- | Unique id assigned to each system that's added to a schedule. Just a wrapper around Entity.@@ -94,27 +141,27 @@ -- | Create a new World in IO. newWorld :: SystemTools -> IO World-newWorld tools = do- archetypes <- emptyArchetypes- components <- emptyComponents- entities <- emptyEntities- tables <- emptyTables- deferred <- newIORef []- deferredAsync <- newTVarIO []- events <- newIORef []- tick <- newIORef (Tick (0, 0))- frame <- newIORef (Frame 0)- let prefs = newPrefs+newWorld !tools = do+ !archetypes <- emptyArchetypes+ !components <- emptyComponents+ !entities <- emptyEntities+ !tables <- emptyTables+ !deferred <- newIORef []+ !deferredAsync <- newTVarIO []+ !tick <- newIORef (Tick 0)+ !frame <- newIORef (Frame 0)+ !trySystems <- newIORef []+ let !prefs = Prefs Map.empty - let logger = Colog.cmap Colog.fmtMessage Colog.logTextStdout+ let !logger = Colog.cmap Colog.fmtMessage Colog.logTextStdout+ let !loggerErr = Colog.cmap Colog.fmtMessage Colog.logTextStderr - return+ pure World { archetypes, components, entities, tables,- events, deferred, deferredAsync, tick,@@ -122,44 +169,39 @@ frame, prefs, logger,+ loggerErr,+ trySystems, tools } -- | Change the current SystemId of the World. setSystemId :: SystemId -> World -> World-setSystemId systemId world = world {systemId}+setSystemId !systemId !world = world {systemId} -- | Set the list of deferred systems of the World. setDeferred :: IORef [System ()] -> World -> World-setDeferred deferred world = world {deferred}---- | Set new WorldPrefs for the World.-setPrefs :: WorldPrefs -> World -> World-setPrefs prefs world = world {prefs}+setDeferred !deferred !world = world {deferred} --- | A System is a set of instructions applied over a World.--- It can be added to the App to be ran on a certain Schedule.+-- | A System is a monad that mutates the World. ----- A system is actually a wrapper around @'ReaderT' 'World' 'IO'@, meaning you can 'ask' for the World,--- or do IO operations by using 'liftIO'.+-- It can be added to a certain schedule and ordered with other systems.+-- It also offers IO access via 'liftIO'.+--+-- Read the [tutorial]("Mischief.ECS.Tutorial.Systems") on systems for more details. newtype System a = System (ReaderT (Hidden World) IO a)- deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader (Hidden World), MonadFail)+ deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader (Hidden World), MonadFail, MonadThrow) -- | Run a 'System' with the given 'World' inside 'IO' runSystem :: System a -> World -> IO a-runSystem (System !r) w = runReaderT r (hide w)+runSystem (System !r) !w = runReaderT r (hide w) +-- runSystem' :: (MonadSystem w m) => m a -> World -> IO a+-- runSystem' (System !r) w =+ instance PrimMonad System where type PrimState System = PrimState IO primitive = System . lift . primitive --- | Run a 'System' with changed 'WorldPrefs'.-forkPrefs :: (WorldPrefs -> WorldPrefs) -> System a -> System a-forkPrefs f s = do- world <- unsafeGetWorld- let world' = setPrefs (f world.prefs) world- liftIO $ runSystem s world'- -- | Special wrapper around World given to 'ParSystem's. data ParWorld = ParWorld { -- | @Hidden@ ensures users cannot access and mutate the World.@@ -176,7 +218,7 @@ -- -- To run a normal @System ()@, you need to 'Mischief.ECS.World.Defer.defer'! newtype ParSystem a = ParSystem (ReaderT ParWorld IO a)- deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader ParWorld, MonadFail, PrimMonad)+ deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader ParWorld, MonadFail, PrimMonad, MonadThrow) class GetWorld a where getWorld :: a -> Hidden World@@ -185,48 +227,89 @@ getWorld = id instance (GetWorld ParWorld) where- getWorld x = x.world+ getWorld !x = x.world +class SystemAny m where+ systemAny :: System a -> ParSystem a -> m a++instance SystemAny ParSystem where+ systemAny _ a = a++instance SystemAny System where+ systemAny a _ = a+ -- | Typeclass that can be used to generalize systems to both @'System'@ and @'ParSystem'@. -- -- Used mainly by queries and other operations which don't mutate the World.-class (GetWorld w, MonadReader w a, Applicative a, MonadFail a, Functor a, Monad a, MonadIO a, PrimMonad a, PrimState a ~ RealWorld) => MonadSystem w a+class (GetWorld w, MonadReader w a, Applicative a, MonadFail a, Functor a, Monad a, MonadIO a, PrimMonad a, PrimState a ~ RealWorld, MonadThrow a, SystemAny a) => MonadSystem w a instance MonadSystem (Hidden World) System instance MonadSystem ParWorld ParSystem +-- Grab the World. DO NOT USE THIS. unsafeGetWorld :: (MonadSystem w m) => m World unsafeGetWorld = do asks (unhide . getWorld) instance EraseIntoStorage (System ()) [System ()] where erase :: System () -> [System ()]- erase x = [x]+ erase !x = [x] data SystemTools = SystemTools- { get :: forall c m w. (MonadSystem w m, QueryType c) => Proxy c -> Entity -> m (Maybe c),- getRAny :: forall c m w. (Component c, MonadSystem w m, RelExclusivity c ~ Inclusive) => Proxy c -> Entity -> m (Maybe [Rel c]),+ { get :: forall c m w. (MonadSystem w m, Component c) => Proxy c -> Entity -> m (Maybe c),+ getRAny :: forall c m w. (Component c, MonadSystem w m, IsExclusiveRel c ~ False) => Proxy c -> Entity -> m (Maybe [Rel c]), set :: forall c. (Bundle c) => c -> Entity -> System (), spawnByInsert :: forall b. (Bundle b) => Entity -> b -> System () } -worldGet :: forall c m w. (MonadSystem w m, QueryType c) => Proxy c -> Entity -> m (Maybe c)-worldGet p e = do- World {tools = SystemTools {get}} <- unsafeGetWorld+worldGet :: forall c m w. (MonadSystem w m, Component c) => Proxy c -> Entity -> m (Maybe c)+worldGet !p !e = do+ World {tools = SystemTools {get = !get}} <- unsafeGetWorld get p e worldSet :: forall c. (Bundle c) => c -> Entity -> System ()-worldSet c e = do- World {tools = SystemTools {set}} <- unsafeGetWorld+worldSet !c !e = do+ World {tools = SystemTools {set = !set}} <- unsafeGetWorld set c e -worldGetRAny :: forall c m w. (Component c, MonadSystem w m, RelExclusivity c ~ Inclusive) => Proxy c -> Entity -> m (Maybe [Rel c])-worldGetRAny p e = do- World {tools = SystemTools {getRAny}} <- unsafeGetWorld+worldGetRAny :: forall c m w. (Component c, MonadSystem w m, IsExclusiveRel c ~ False) => Proxy c -> Entity -> m (Maybe [Rel c])+worldGetRAny !p !e = do+ World {tools = SystemTools {getRAny = !getRAny}} <- unsafeGetWorld getRAny p e worldSpawnByInsert :: forall b. (Bundle b) => Entity -> b -> System ()-worldSpawnByInsert e b = do- World {tools = SystemTools {spawnByInsert}} <- unsafeGetWorld+worldSpawnByInsert !e !b = do+ World {tools = SystemTools {spawnByInsert = !spawnByInsert}} <- unsafeGetWorld spawnByInsert e b++sysUnliftIO :: System (System a -> IO a)+sysUnliftIO = do+ !world <- hide <$> unsafeGetWorld+ pure $ \(System !a) -> runReaderT a world++-- | Primitive that allows you to set a system to run at every sync point until it returns a @Just a@, at which point+-- it will run the second system with the obtained @a@ and stop running.+--+-- It will run once on the spot, meaning it has no overhead if the first system immediately succeed.+--+-- You should only use this in one-time logic, such as Events or the Startup schedule. You should not use it+-- in logic that runs continuously, such as in Update, as it will keep queueing up a new system to check without removing the old one.+runWhen :: (Typeable a) => System (Maybe a) -> (a -> System b) -> System ()+runWhen !s1 !s2 = do+ b <- s1+ case b of+ Just a -> void $ s2 a+ Nothing -> do+ World {trySystems = !trySystems} <- unsafeGetWorld+ liftIO $ modifyIORef' trySystems (++ [(fmap eraseValue <$> s1, void . s2 . unsafeUneraseValue)])++-- | Lift a ParSystem into a System. Doesn't flush the deferred queue!+parLift :: ParSystem a -> System a+parLift (ParSystem !p) = do+ world <- unsafeGetWorld+ systems <- liftIO $ newIORef []+ a <- liftIO $ runReaderT p ParWorld {world = Hidden world, parDeferred = systems}+ systems <- liftIO $ readIORef systems+ liftIO $ modifyIORef' world.deferred (++ systems)+ pure a
src/Mischief/ECS/World/Change.hs view
@@ -1,11 +1,11 @@ module Mischief.ECS.World.Change where -import Control.Monad.Reader (MonadIO (liftIO), ask)+import Control.Monad.Reader (MonadIO (liftIO)) import Data.Foldable import Data.IORef-import Data.Map qualified as Map import Data.Maybe import Data.Set qualified as Set+import Data.Text qualified as T import GHC.Base (Int (..)) import GHC.Stack (HasCallStack) import Mischief.ECS.App.SystemDef@@ -13,15 +13,12 @@ import Mischief.ECS.Components import Mischief.ECS.Components.Bundle import Mischief.ECS.Entities-import Mischief.ECS.EntityDef-import Mischief.ECS.Events import Mischief.ECS.Log import Mischief.ECS.Tables import Mischief.ECS.Vec qualified as Vec import Mischief.ECS.World import Mischief.ECS.World.Query import Mischief.ECS.World.Query.Markers-import Mischief.ECS.World.Query.Queryable import Mischief.ECS.World.Utils data ChangeResult = ChangeResult@@ -30,65 +27,75 @@ } changeArchetype :: (HasCallStack) => Entity -> ArchetypeData -> Maybe ProcessedBundleData -> System ChangeResult-changeArchetype entity newArchetype insertedBundle = do- world <- unsafeGetWorld- currentTick <- liftIO $ readIORef world.tick+changeArchetype !entity !newArchetype !insertedBundle = do+ !world <- unsafeGetWorld+ !currentTick <- liftIO $ readIORef world.tick - reqAdded <- liftIO $ newIORef []+ !reqAdded <- liftIO $ newIORef []+ !newAdded <- liftIO $ newIORef [] - Just pointer <- liftIO $ getPointer entity world.entities- (EntityPointer (# archetypeId', rowIndex' #)) <- liftIO $ readIORef pointer+ Just !pointer <- liftIO $ getPointer entity world.entities+ (EntityPointer (# !archetypeId', !rowIndex' #)) <- liftIO $ readIORef pointer - table <- Vec.read world.tables.inner (I# archetypeId')+ !table <- Vec.read world.tables.inner (I# archetypeId') - collected <- liftIO $ takeComponentsFromTable (EntityPointer (# archetypeId', rowIndex' #)) table+ !collected <- liftIO $ takeComponentsFromTable (EntityPointer (# archetypeId', rowIndex' #)) table - newElements'' <-+ -- TODO OPT: this whole thing can be greatly optimized+ !newElements'' <- mapM- ( \component -> do+ ( \(!component) -> do case insertedBundle of- Nothing -> maybe undefined return (find (\x -> x.id == component) collected.elements)- Just bundle ->- case find (\x -> x.id == component) bundle.elements of- Just x -> return x+ Nothing -> maybe undefined return (find (\(!x) -> x.id == component) collected.elements)+ Just !bundle ->+ case find (\(!x) -> x.id == component) bundle.elements of+ Just !x -> do+ case find (\(!x) -> x.id == component) collected.elements of+ Nothing -> do+ liftIO $ modifyIORef' newAdded (++ [x])+ Just _ -> pure ()+ pure x Nothing ->- case find (\x -> x.id == component) collected.elements of- Just x -> return x+ case find (\(!x) -> x.id == component) collected.elements of+ Just !x -> return x Nothing -> do- r <- getDefault component+ !r <- getDefault component liftIO $ modifyIORef' reqAdded (++ [r])- return r+ pure r ) $ Set.toList newArchetype.components - let newElements = case insertedBundle of- Nothing -> ProcessedBundleData newElements''- Just bundle ->- let newElements' = setChangedTickOfComponents (ProcessedBundleData newElements'') (\id -> isInProcessedBundle bundle id && isInProcessedBundle collected id) currentTick- in setAddedTickOfComponents newElements' (\id -> isInProcessedBundle bundle id && not (isInProcessedBundle collected id)) currentTick+ -- let newElements = case insertedBundle of+ -- Nothing -> ProcessedBundleData newElements''+ -- Just bundle ->+ -- let !newElements' = setChangedTickOfComponents (ProcessedBundleData newElements'') (\(!id) -> isInProcessedBundle bundle id && isInProcessedBundle collected id) currentTick+ -- in setAddedTickOfComponents newElements' (\(!id) -> isInProcessedBundle bundle id && not (isInProcessedBundle collected id)) currentTick - liftIO $ insertEntityIntoTables newElements world.tables newArchetype.id (entity, pointer)+ let newElements = ProcessedBundleData newElements'' - requiredComponentsAdded <- liftIO $ readIORef reqAdded- return ChangeResult {requiredComponentsAdded}+ !newComponents <- liftIO $ readIORef newAdded+ let changed = maybe [] ((map (.id)) . (.elements)) insertedBundle ++ map (.id) newComponents + -- warn $ T.show newArchetype.id+ -- warn $ T.show $ changed++ liftIO $ insertEntityIntoTables newElements currentTick changed world.tables newArchetype.id (entity, pointer)++ !requiredComponentsAdded <- liftIO $ readIORef reqAdded+ pure ChangeResult {requiredComponentsAdded, newComponents}+ getDefault :: ComponentId -> System ProcessedBundleElement-getDefault (ComponentId (# id, e #)) = do- world <- unsafeGetWorld+getDefault (ComponentId (# !id, !e #)) = do+ !world <- unsafeGetWorld - Just x <- get (C @DefaultValue) (Entity (# id, 0## #))- let dv = value x- let (DefaultValue value) = dv+ Just !x <- single $ mkGet (Entity (# id, 0## #)) (C @DefaultValue)+ let (DefaultValue !value) = x - let (SystemId sys) = world.systemId- currentSystemTick <- fromMaybe (SystemTick $ Tick (0, 0)) <$> get (Val $ C @SystemTick) sys+ let (SystemId !sys) = world.systemId+ !currentSystemTick <- fromMaybe (SystemTick $ Tick 0) <$> single (mkGet sys (C @SystemTick)) - return+ pure ProcessedBundleElement { id = ComponentId (# id, e #),- component =- ComponentData- { value,- ticks = ComponentTicks {changed = currentSystemTick.inner, added = currentSystemTick.inner}- }+ component = value }
src/Mischief/ECS/World/Defer.hs view
@@ -1,110 +1,88 @@ module Mischief.ECS.World.Defer where import Control.Concurrent-import Control.Concurrent.Async import Control.Concurrent.STM-import Control.Exception import Control.Monad.IO.Class import Control.Monad.Reader import Data.Foldable import Data.Functor import Data.IORef-import Mischief.ECS.Hidden+import GHC.Float (float2Int) import Mischief.ECS.World --- class Defer s where--- -- | Defer a command to be ran after the current 'System' is finished,--- -- or when 'flush' is called.--- defer :: System a -> s---- instance Defer (System ()) where--- defer :: System a -> System ()--- defer !system = do--- world <- ask--- liftIO $ modifyIORef' world.deferred (++ [system $> ()])---- instance Defer (ParSystem ()) where--- defer :: System a -> ParSystem ()--- defer !system = do--- ParWorld {deferred} <- ask--- liftIO $ modifyIORef' deferred (++ [system $> ()])--class Defer m where- defer :: System a -> m ()--instance Defer System where- defer :: System a -> System ()- defer !s = do- world <- unsafeGetWorld- liftIO $ modifyIORef' world.deferred (++ [s $> ()])--instance Defer ParSystem where- defer :: System a -> ParSystem ()- defer !s = do- ParWorld {parDeferred} <- ask- liftIO $ modifyIORef' parDeferred (++ [s $> ()])+-- | Defer a System. This will put it in the current deferred queue which is typically flushed in-between scheduled systems.+--+-- This is useful for executing mutations in parallel systems.+--+-- You can manually flush the queue with 'flush'.+defer :: (MonadSystem w m) => System a -> m ()+defer s =+ systemAny+ ( do+ world <- unsafeGetWorld+ liftIO $ modifyIORef' world.deferred (++ [s $> ()])+ )+ ( do+ ParWorld {parDeferred = !parDeferred} <- ask+ liftIO $ modifyIORef' parDeferred (++ [s $> ()])+ ) -- | Flush the current list of deferred commands. flush :: System () flush = do- world <- unsafeGetWorld- systems <- liftIO $ readIORef world.deferred+ !world <- unsafeGetWorld+ !systems <- liftIO $ readIORef world.deferred - for_ systems $ \s -> do+ for_ systems $ \(!s) -> do forkDefer $ do s flush liftIO $ writeIORef world.deferred [] +-- | Flush the current list of deferred async commands. flushAsync :: System () flushAsync = do- world <- unsafeGetWorld+ !world <- unsafeGetWorld - systems <- liftIO $ atomically $ do- systems <- readTVar world.deferredAsync+ !systems <- liftIO $ atomically $ do+ !systems <- readTVar world.deferredAsync writeTVar world.deferredAsync []- return systems+ pure systems - for_ systems $ \s -> do+ for_ systems $ \(!s) -> do forkDefer $ do s flush forkDefer :: System a -> System a-forkDefer s = do- world <- unsafeGetWorld- deferred <- liftIO $ newIORef []+forkDefer !s = do+ !world <- unsafeGetWorld+ !deferred <- liftIO $ newIORef [] - let world' = setDeferred deferred world- a <- liftIO $ runSystem s world'+ let !world' = setDeferred deferred world+ !a <- liftIO $ runSystem s world' - deferred <- liftIO $ readIORef deferred+ !deferred <- liftIO $ readIORef deferred liftIO $ modifyIORef' world.deferred (++ deferred)- return a---- let world' =---- forkSystem :: ParSystem () -> System ()--- forkSystem (ParSystem !x) = do--- world <- ask--- _ <- liftIO $ forkIO $ do--- deferred <- newIORef []--- runReaderT x ParWorld {world, deferred}--- deferred' <- readIORef deferred---- atomically $ modifyTVar' world.deferredAsync (++ deferred')---- return ()+ pure a +-- | Run an IO action asynchronously before running a System with its output. Useful when performing significant IO such as reading / loading files. runAfter :: (MonadSystem w m) => IO a -> (a -> System ()) -> m () runAfter !function !system = do- world <- unsafeGetWorld+ !world <- unsafeGetWorld _ <- liftIO $ forkIO $ do- a <- function+ !a <- function atomically $ modifyTVar' world.deferredAsync (++ [system a]) - return ()+ pure () -delay :: (MonadSystem w m) => Int -> System () -> m ()-delay !d system = runAfter (threadDelay d) (const system)+-- | Delay a system by a given amount of seconds.+--+-- Example:+--+-- @+-- delay 1.5 $ insert (Health 100) player+-- @+delay :: (MonadSystem w m) => Float -> System a -> m ()+delay !d !system = runAfter (threadDelay (float2Int $ 1000000 * d)) (void . const system)
src/Mischief/ECS/World/Insert.hs view
@@ -6,36 +6,30 @@ import Control.Exception import Control.Monad import Control.Monad.IO.Class-import Control.Monad.Reader (MonadReader (..))-import Data.Data import Data.Foldable (for_) import Data.IORef import Data.List hiding (insert)-import Data.Map qualified as Map-import Data.Maybe (fromMaybe)+import Data.Maybe import Data.Set qualified as Set-import Data.Text qualified as Text+import Data.Text qualified as T import GHC.Base (Int (..)) import GHC.Stack-import Mischief.ECS.Archetypes import Mischief.ECS.Archetypes.Graph ( getArchetypeOnInsert, ) import Mischief.ECS.Components import Mischief.ECS.Components.Bundle+import Mischief.ECS.Components.HooksDef (HookContext (..), HookContextRel (..))+import Mischief.ECS.Components.Spawn (ComponentAddHooks (ComponentAddHooks), ComponentAddHooksRel (ComponentAddHooksRel), ComponentSetHooks (ComponentSetHooks), ComponentSetHooksRel (ComponentSetHooksRel), meta) import Mischief.ECS.Entities-import Mischief.ECS.EntityDef-import Mischief.ECS.EventDef import Mischief.ECS.Events import Mischief.ECS.Log import Mischief.ECS.Tables import Mischief.ECS.Vec qualified as Vec import Mischief.ECS.World import Mischief.ECS.World.Change-import Mischief.ECS.World.Prefs import Mischief.ECS.World.Query import Mischief.ECS.World.Query.Markers-import Mischief.ECS.World.Query.Queryable import Mischief.ECS.World.Utils data Exception' = Exception' deriving (Show)@@ -47,183 +41,180 @@ -- If the entity already contains these components, their values will be -- updated in-place instead of causing an archetype change. insert :: (HasCallStack) => forall b. (Bundle b) => b -> Entity -> System ()-insert bundle entity =+insert !bundle !entity = do- world <- unsafeGetWorld- pointer <- liftIO $ getPointer entity world.entities+ !world <- unsafeGetWorld+ !pointer <- liftIO $ getPointer entity world.entities case pointer of- Nothing -> warn $ "Insertion failed: Entity " <> text entity <> " is not alive."- Just currentPointer -> do- let BundleData {elements} = bundleData bundle+ Nothing -> warn $ "Insertion failed: Entity " <> T.show entity <> " is not alive."+ Just !currentPointer -> do+ let BundleData {elements = !elements, resources = !resources, external = !external} = bundleData bundle - currentTick <- liftIO $ readIORef world.tick+ for_ external $ \(!e, !x) -> do+ insert x e - bundleData <- liftIO $ processBundleElements world ComponentTicks {changed = currentTick, added = currentTick} elements- let newComponents = sort $ map (\x -> x.id) bundleData.elements+ for_ resources $ \BundleElement {component = ErasedComponent (!val :: c)} -> do+ !m <- meta @c+ insert val m - (EntityPointer (# archetypeId, rowIndex #)) <- liftIO $ readIORef currentPointer+ !currentTick <- liftIO $ readIORef world.tick - currentTable <- Vec.read world.tables.inner (I# archetypeId)+ !bundleData <- liftIO $ processBundleElements world elements+ let !newComponents = sort $ map (\(!x) -> x.id) bundleData.elements - -- Simple case, no archetype change.- if newComponents `isSubsequenceOf` currentTable.components- then- liftIO $ replaceComponentsIntoTable bundleData (Just currentTick) (EntityPointer (# archetypeId, rowIndex #)) currentTable- -- Complex case, archetype change.- else do- newArchetype <- getArchetypeOnInsert (ArchetypeId $ I# archetypeId) newComponents- ChangeResult {requiredComponentsAdded} <- changeArchetype entity newArchetype (Just bundleData)+ (EntityPointer (# !archetypeId, !rowIndex #)) <- liftIO $ readIORef currentPointer - unless world.prefs.supressEvents $- triggerInsertEvent (ProcessedBundleData requiredComponentsAdded) entity+ !currentTable <- Vec.read world.tables.inner (I# archetypeId) - unless world.prefs.supressEvents $- triggerInsertEvent bundleData entity+ ChangeResult {requiredComponentsAdded = !requiredComponentsAdded, newComponents = !newComponents} <-+ if newComponents `isSubsequenceOf` currentTable.components+ -- Simple case, no archetype change.+ then do+ liftIO $ replaceComponentsIntoTable bundleData (Just currentTick) (EntityPointer (# archetypeId, rowIndex #)) currentTable+ pure $ ChangeResult [] []+ -- Complex case, archetype change.+ else do+ !newArchetype <- getArchetypeOnInsert (ArchetypeId $ I# archetypeId) newComponents+ changeArchetype entity newArchetype (Just bundleData) -getOrInsert :: forall qd. (Updateable (Result qd), Bundle qd) => qd -> Entity -> System (Result qd)-getOrInsert val entity = do- g <- update (Result (val, entity))- case g of- Just g -> return g- Nothing -> do- insert val entity- return $ Result (val, entity)+ !supressEvents <- fromMaybe False <$> getPref @SupressEventsPref+ unless supressEvents $ do+ triggerAddEvent (ProcessedBundleData newComponents) entity+ triggerAddEvent (ProcessedBundleData requiredComponentsAdded) entity+ triggerSetEvent bundleData entity+ triggerSetEvent (ProcessedBundleData requiredComponentsAdded) entity -- | Insert a bundle of components on an Entity. -- -- Only the components that the entity doesn't already have will be inserted, and the rest ignored. insertNew :: forall b. (Bundle b) => b -> Entity -> System ()-insertNew bundle entity =+insertNew !bundle !entity = do- world <- unsafeGetWorld- pointer <- liftIO $ getPointer entity world.entities+ let BundleData {elements = !elements, resources = !resources, external = !external} = bundleData bundle+ for_ external $ \(!e, !s) -> do+ insertNew s e - case pointer of- Nothing -> warn $ "Insertion failed: Entity " <> text entity <> " is not alive."- Just currentPointer -> do- let BundleData {elements} = bundleData bundle+ !world <- unsafeGetWorld+ !pointer <- liftIO $ getPointer entity world.entities - currentTick <- liftIO $ readIORef world.tick+ case pointer of+ Nothing -> warn $ "Insertion failed: Entity " <> T.show entity <> " is not alive."+ Just !currentPointer -> do+ for_ resources $ \BundleElement {component = ErasedComponent (!val :: c)} -> do+ !m <- meta @c+ !r <- single $ mkGet m (C @c)+ case r of+ Just _ -> pure ()+ Nothing -> insert val m - bundleData <- liftIO $ processBundleElements world ComponentTicks {changed = currentTick, added = currentTick} elements+ !bundleData <- liftIO $ processBundleElements world elements - (EntityPointer (# archetypeId, _ #)) <- liftIO $ readIORef currentPointer+ (EntityPointer (# !archetypeId, _ #)) <- liftIO $ readIORef currentPointer - currentTable <- Vec.read world.tables.inner (I# archetypeId)+ !currentTable <- Vec.read world.tables.inner (I# archetypeId) - let newComponents = ProcessedBundleData $ filter (\c -> c.id `notElem` currentTable.components) bundleData.elements+ let newComponents = ProcessedBundleData $ filter (\(!c) -> c.id `notElem` currentTable.components) bundleData.elements unless (null newComponents.elements) $ do- newArchetype <- getArchetypeOnInsert (ArchetypeId $ I# archetypeId) $ map (\x -> x.id) newComponents.elements+ !newArchetype <- getArchetypeOnInsert (ArchetypeId $ I# archetypeId) $ map (\(!x) -> x.id) newComponents.elements ChangeResult {requiredComponentsAdded} <- changeArchetype entity newArchetype (Just bundleData) - unless world.prefs.supressEvents $- triggerInsertEvent (ProcessedBundleData requiredComponentsAdded) entity-- unless world.prefs.supressEvents $- triggerInsertEvent newComponents entity+ !supressEvents <- fromMaybe False <$> getPref @SupressEventsPref+ unless supressEvents $ do+ triggerAddEvent (ProcessedBundleData requiredComponentsAdded) entity+ triggerAddEvent newComponents entity+ triggerSetEvent (ProcessedBundleData requiredComponentsAdded) entity+ triggerSetEvent newComponents entity -insertIfNeq :: (BundleEq b) => b -> Entity -> System ()-insertIfNeq b entity = do- let BundleData {elements} = bundleDataEq b+insertIfNeq :: forall b. (BundleEq b) => b -> Entity -> System ()+insertIfNeq !b !entity = do+ let BundleData {elements = !elements, resources = !resources, external = !external} = bundleDataEq b - comps <- flip filterM (Set.toList elements) $ \BundleElement {rep, component = ErasedComponentEq (val :: c)} -> do- val' <- case rep of- PairRep (_, target) -> fmap (\x -> x.comp) <$> get (R @c target) entity- _ -> fmap value <$> get (C @c) entity+ !comps <- flip filterM (Set.toList elements) $ \BundleElement {rep, component = ErasedComponentEq (!val :: c)} -> do+ !val' <- case rep of+ PairRep (_, target) -> fmap (\(!x) -> x.comp) <$> single (mkGet entity (R @c target))+ _ -> single (mkGet entity (C @c)) case val' of- Nothing -> return True- Just val' -> return (val /= val')-- insert (bundleEqToSimple $ BundleData (Set.fromList comps)) entity--class Settable c i | c -> i where- setInner :: c -> i -> System ()+ Nothing -> pure True+ Just !val' -> pure (val /= val') - setIfNeqInner :: (Eq i) => c -> i -> System ()+ resources <- flip filterM (Set.toList resources) $ \BundleElement {rep, component = ErasedComponentEq (!val :: c)} -> do+ !e <- meta @c+ val' <- case rep of+ ComponentRep _ -> single $ mkGet e (C @c)+ _ -> undefined -class Settable' isRel c i | isRel c -> i where- setInner' :: c -> i -> System ()- setIfNeqInner' :: (Eq i) => c -> i -> System ()+ case val' of+ Nothing -> pure True+ Just !val' -> pure (val /= val') -instance (Component c) => Settable' False (Result (Rel c)) c where- setInner' :: Result (Rel c) -> c -> System ()- setInner' !result !newValue = Mischief.ECS.World.Insert.insert (Rel newValue result.target) (entityOf result)+ for_ external $ \(!e, !external) -> do+ insertIfNeq external e - setIfNeqInner' :: (Component c, Eq c) => Result (Rel c) -> c -> System ()- setIfNeqInner' !result !newValue = do- curr <- get (R @c result.target) (entityOf result)- case curr of- Nothing -> warn $ "SetIfNeq failed: Entity " <> text (entityOf result) <> " is not alive."- Just curr ->- when (curr.comp /= newValue) $- setInner' @False result newValue+ insert (bundleEqToSimple $ BundleData (Set.fromList comps) (Set.fromList resources) Set.empty) entity -instance (Component c, IsComponentC c ~ HTrue) => Settable' True (Result c) c where- setInner' :: Result c -> c -> System ()- setInner' !result !newValue = Mischief.ECS.World.Insert.insert newValue (entityOf result)+triggerAddEvent :: ProcessedBundleData -> Entity -> System ()+triggerAddEvent !bundle !entity =+ for_ bundle.elements $ \(!x) -> do+ let !(ComponentId (# _, !target #)) = x.id+ case target of+ Nothing ->+ triggerAddEventC x.component entity+ Just !target ->+ triggerAddEventR x.component target entity - setIfNeqInner' :: (Component c, Eq c) => Result c -> c -> System ()- setIfNeqInner' !result !newValue = do- curr <- get (C @c) (entityOf result)- case curr of- Nothing -> warn $ "SetIfNeq failed: Entity " <> text (entityOf result) <> " is not alive."- Just curr ->- when (value curr /= newValue) $- setInner' @True result newValue+triggerAddEventC :: ErasedComponent -> Entity -> System ()+triggerAddEventC (ErasedComponent (_ :: c)) !entity = do+ let !context = HookContext {entity} -instance (Settable' (IsComp c) (Result c) i) => Settable (Result c) i where- setInner = setInner' @(IsComp c)- setIfNeqInner = setIfNeqInner' @(IsComp c)+ !m <- meta @c+ !hooks <- single $ mkGet m (C @ComponentAddHooks)+ for_ hooks $ \(ComponentAddHooks !h) -> do+ for_ h $ \(!h) -> h context --- | Set the value of a component obtained as query result.------ Note that the local 'Result' won't be mutated.--- You'll need to query the component again or use 'update' to update the current result.-set :: (Settable c i) => c -> i -> System ()-set = setInner+ trigger $ OnAdd @c entity -setIfNeq :: (Eq i, Settable c i) => c -> i -> System ()-setIfNeq = setIfNeqInner+triggerAddEventR :: ErasedComponent -> Entity -> Entity -> System ()+triggerAddEventR (ErasedComponent (_ :: c)) !target !entity = do+ let !context = HookContextRel {entity, target} -class Updateable' flag r where- updateInner' :: r -> System (Maybe r)+ !m <- meta @c+ Just !hooks <- single $ mkGet m (M @ComponentAddHooksRel)+ for_ hooks $ \(ComponentAddHooksRel !h) -> do+ for_ h $ \(!h) -> h context -instance (Component c, IsComponentC c ~ HTrue) => Updateable' True (Result c) where- updateInner' r = get (C @c) (entityOf r)+ trigger $ OnAddRel @c entity target -instance (Component c) => Updateable' False (Result (Rel c)) where- updateInner' r = get (R @c r.target) (entityOf r)+triggerSetEvent :: ProcessedBundleData -> Entity -> System ()+triggerSetEvent !bundle !entity =+ for_ bundle.elements $ \(!x) -> do+ let !(ComponentId (# _, !target #)) = x.id+ case target of+ Nothing ->+ triggerSetEventC x.component entity+ Just !target ->+ triggerSetEventR x.component target entity -class Updateable r where- updateInner :: r -> System (Maybe r)+triggerSetEventC :: ErasedComponent -> Entity -> System ()+triggerSetEventC (ErasedComponent (_ :: c)) !entity = do+ let !context = HookContext {entity} -instance (Updateable' (IsComp c) (Result c)) => Updateable (Result c) where- updateInner = updateInner' @(IsComp c)+ !m <- meta @c+ Just !hooks <- single $ mkGet m (M @ComponentSetHooks)+ for_ hooks $ \(ComponentSetHooks !h) -> do+ for_ h $ \(!h) -> h context --- | Update the value of a 'Result'.------ Useful if you've done changed to the component and want to grab the live value--- without re-querying.-update :: forall c. (Updateable (Result c)) => Result c -> System (Maybe (Result c))-update = updateInner+ trigger $ OnSet @c entity -triggerInsertEvent :: ProcessedBundleData -> Entity -> System ()-triggerInsertEvent bundle entity =- for_ bundle.elements $ \x -> do- let !(ComponentId (# id, target #)) = x.id- case target of- Nothing ->- triggerInsertEventC x.component.value entity- Just target ->- triggerInsertEventR x.component.value target entity+triggerSetEventR :: ErasedComponent -> Entity -> Entity -> System ()+triggerSetEventR (ErasedComponent (_ :: c)) !target !entity = do+ let !context = HookContextRel {entity, target} -triggerInsertEventC :: ErasedComponent -> Entity -> System ()-triggerInsertEventC (ErasedComponent (_ :: c)) entity =- runEvent $ eraseEvent $ OnInsert @c entity+ !m <- meta @c+ Just !hooks <- single $ mkGet m (M @ComponentSetHooksRel)+ for_ hooks $ \(ComponentSetHooksRel !h) -> do+ for_ h $ \(!h) -> h context -triggerInsertEventR :: ErasedComponent -> Entity -> Entity -> System ()-triggerInsertEventR (ErasedComponent (_ :: c)) target entity = do- runEvent $ eraseEvent $ OnInsertRel @c entity target+ trigger $ OnSetRel @c entity target
− src/Mischief/ECS/World/Modify.hs
@@ -1,70 +0,0 @@-module Mischief.ECS.World.Modify where--import Data.Maybe-import Mischief.ECS.Components-import Mischief.ECS.Components.Bundle-import Mischief.ECS.Components.Common-import Mischief.ECS.Entities-import Mischief.ECS.Log-import Mischief.ECS.Tables-import Mischief.ECS.World-import Mischief.ECS.World.Insert-import Mischief.ECS.World.Query-import Mischief.ECS.World.Query.Markers-import Mischief.ECS.World.Query.Queryable-import Mischief.ECS.World.Remove---- | Modifies the value of the component with the given function.------ The function will be applied over the @live value@ of the component, adding some overhead.------ If you are confident the value in the 'Result' is the live one, or otherwise do not care of updating the live value,--- you are encouraged to use 'modify'' instead.------ Note that this will trigger change detection even if the provided function is 'id'.-modify :: forall c i. (Updateable (Result c), Settable (Result c) i, DeepValue (Result c) i) => Result c -> (i -> i) -> System ()-modify !result !f = do- res <- update result- case res of- Nothing -> warn $ "Modify failed: Entity " <> text (entityOf result) <> " is not alive."- Just res -> do- let v = deepValue res- set result (f v)---- | Modifies the value of the component with the given function.------ The function will be applied over the value contained within the 'Result', which is not guaranteed--- to be the live value of the component.------ If you wish to apply the function over the live value, use 'modify' instead.------ Note that this will trigger change detection even if the provided function is 'id'.-modify' :: forall c i. (Settable (Result c) i, DeepValue (Result c) i) => Result c -> (i -> i) -> System ()-modify' !result !f = do- let v = deepValue result- set result (f v)---- | The most generic function for modifying a component on a given entity.------ This can do removals, insertions, and modify the value.------ Example:------ @--- data Counter = Counter 'Int' deriving ('Component', 'Queryable')------ incrementCounter :: 'Entity' -> 'System' ()--- incrementCounter = 'alter' (\case 'Nothing' -> 'Just' $ Counter 0; 'Just' (Counter x) -> 'Just' $ Counter (x + 1))--- @-alter :: forall c. (Queryable (C c) (Result c), Bundle c, Component c) => (Maybe c -> Maybe c) -> Entity -> System ()-alter !f !entity = do- val <- get (C @c) entity- let r = f (fmap value val)-- if isNothing r && isJust val- then- remove (C @c) entity- else case r of- Just r ->- insert r entity- Nothing -> return ()
src/Mischief/ECS/World/Par.hs view
@@ -1,6 +1,14 @@-module Mischief.ECS.World.Par where+module Mischief.ECS.World.Par+ ( par,+ par',+ parIterList,+ parFor,+ parFor_,+ parFor',+ parFor'_,+ )+where -import Control.Concurrent (forkIO) import Control.Concurrent.Async (async, wait) import Control.Monad import Control.Monad.IO.Class@@ -11,49 +19,80 @@ import GHC.Conc (numCapabilities) import Mischief.ECS.Hidden import Mischief.ECS.World+import Mischief.ECS.World.Defer +-- | Receives a list of @ParSystems@ and runs them in parallel. If ran in 'System', flushes the deferred queue when done! par :: (MonadSystem w m) => [ParSystem ()] -> m ()-par !parSystems = do- world <- unsafeGetWorld+par !x =+ systemAny+ ( do+ par' x+ flush+ )+ (par' x) - x <- forM parSystems $ \(ParSystem p) -> do- systems <- liftIO $ newIORef []- id <- liftIO $ async $ runReaderT p ParWorld {world = Hidden world, parDeferred = systems}- return (id, systems)+-- | Receives a list of @ParSystems@ and runs them in parallel. Doesn't flush the deferred queue when done!+par' :: (MonadSystem w m) => [ParSystem ()] -> m ()+par' !parSystems = do+ !world <- unsafeGetWorld - for_ x $ \(id, systems) -> do+ !x <- forM parSystems $ \(ParSystem !p) -> do+ !systems <- liftIO $ newIORef []+ !id <- liftIO $ async $ runReaderT p ParWorld {world = Hidden world, parDeferred = systems}+ pure (id, systems)++ for_ x $ \(!id, !systems) -> do liftIO $ wait id- systems <- liftIO $ readIORef systems+ !systems <- liftIO $ readIORef systems liftIO $ modifyIORef' world.deferred (++ systems) parIterList :: (MonadSystem w m, Foldable t) => t a -> ([a] -> ParSystem b) -> m [b] parIterList !list !s = do- world <- unsafeGetWorld+ !world <- unsafeGetWorld - let n = numCapabilities- let len = length list+ let !n = numCapabilities+ let !len = length list - let chunks = group (len `div` n) (toList list)+ let !chunks = group (len `div` n) (toList list) - x <- forM chunks $ \chunk -> do- systems <- liftIO $ newIORef []- let ParSystem p = s chunk- id <- liftIO $ async $ runReaderT p ParWorld {world = Hidden world, parDeferred = systems}- return (id, systems)+ !x <- forM chunks $ \(!chunk) -> do+ !systems <- liftIO $ newIORef []+ let ParSystem !p = s chunk+ !id <- liftIO $ async $ runReaderT p ParWorld {world = Hidden world, parDeferred = systems}+ pure (id, systems) - for x $ \(id, systems) -> do- a <- liftIO $ wait id- systems <- liftIO $ readIORef systems+ for x $ \(!id, !systems) -> do+ !a <- liftIO $ wait id+ !systems <- liftIO $ readIORef systems liftIO $ modifyIORef' world.deferred (++ systems)- return a+ pure a group :: Int -> [a] -> [[a]] group _ [] = []-group 0 l = [l]+group 0 !l = [l] group !n !l = take n l : group n (drop n l) -parIter :: (MonadSystem w m, Foldable t) => t a -> (a -> ParSystem b) -> m [b]-parIter x s = concat <$> parIterList x (`for` s)+-- | Iterates over a list in parallel. If ran from 'System', flushes the deferred queue when done.+parFor :: (Foldable t, MonadSystem w m) => t a -> (a -> ParSystem b) -> m [b]+parFor !x !s =+ systemAny+ ( do+ !a <- parFor' x s+ flush+ pure a+ )+ (parFor' x s) -parIter_ :: (MonadSystem w m, Foldable t) => t a -> (a -> ParSystem b) -> m ()-parIter_ x s = void (parIterList x (`for_` s))+-- | Iterates over a list in parallel and discards the results. If ran from 'System', flushes the deffered queue when done.+parFor_ :: (Foldable t) => t a -> (a -> ParSystem b) -> System ()+parFor_ !x !s = do+ void $ parFor' x s+ flush++-- | Iterates over a list in parallel. Doesn't flush the deferred queue when done.+parFor' :: (MonadSystem w m, Foldable t) => t a -> (a -> ParSystem b) -> m [b]+parFor' !x !s = concat <$> parIterList x (`for` s)++-- | Iterates over a list in parallel and discards the results. Doesn't flush the deffered queue when done.+parFor'_ :: (MonadSystem w m, Foldable t) => t a -> (a -> ParSystem b) -> m ()+parFor'_ !x !s = void (parIterList x (`for_` s))
− src/Mischief/ECS/World/Prefs.hs
@@ -1,14 +0,0 @@-module Mischief.ECS.World.Prefs where--newtype WorldPrefs = WorldPrefs- { supressEvents :: Bool- }--newPrefs :: WorldPrefs-newPrefs =- WorldPrefs- { supressEvents = False- }--supressEvents :: Bool -> WorldPrefs -> WorldPrefs-supressEvents b WorldPrefs {} = WorldPrefs {supressEvents = b}
src/Mischief/ECS/World/Query.hs view
@@ -7,98 +7,96 @@ import Control.Monad.IO.Class import Data.Data import Data.Foldable-import Data.Foldable hiding (and)-import Data.IORef-import Data.Map qualified as Map import Data.Maybe import Data.Set qualified as Set-import GHC.Base (Int (..), eqWord#, isTrue#)-import Mischief.ECS.App.SystemDef+import Data.Traversable import Mischief.ECS.Archetypes.Graph-import Mischief.ECS.Collectable import Mischief.ECS.Components-import Mischief.ECS.Components.BundleTypes-import Mischief.ECS.Components.Common import Mischief.ECS.Entities-import Mischief.ECS.EntityDef-import Mischief.ECS.Log-import Mischief.ECS.Tables+import Mischief.ECS.Tables (Tables (Tables)) import Mischief.ECS.Vec qualified as Vec import Mischief.ECS.World+import Mischief.ECS.World.Par (parFor') import Mischief.ECS.World.Query.Markers import Mischief.ECS.World.Query.QueryFilter import Mischief.ECS.World.Query.Queryable-import Mischief.ECS.World.Utils import Prelude hiding (and) -runQuery :: forall qd m w output. (Queryable qd output, MonadSystem w m) => qd -> QueryFilter -> World -> m [output]-runQuery query filter world =- do- components <-- liftIO $- mapM- ( \(c, t) -> do- c <- getComponentId c world.components- return $- fmap- ( \c ->- case t of- CompQ -> (c, ComponentQuery)- RelQ -> (c, RelationshipQueryAny)- RelQ' entity -> (setCompIdTarget (Just entity) c, RelationshipQuery)- )- c- )- (Set.toList (queryTypes query))- archetypes <- findMatchingArchetypes (catMaybes components) world.archetypes- let (otherFilter, archetypeFilter) = extractArchetypeFilters $ preprocessFilter filter+findArchetypes :: forall qd m w output. (Queryable qd output, MonadSystem w m) => qd -> m [ArchetypeId]+findArchetypes !query = do+ !world <- unsafeGetWorld+ !components <-+ liftIO $+ mapM+ ( \(!c, !t) -> do+ !c <- getComponentId c world.components+ pure $+ fmap+ ( \(!c) ->+ case t of+ CompQ -> (c, ComponentQuery)+ RelQ -> (c, RelationshipQueryAny)+ RelQ' !entity -> (setCompIdTarget (Just entity) c, RelationshipQuery)+ )+ c+ )+ (Set.toList (queryTypes query)) - archetypes' <- filterM (\(components, _) -> liftIO $ (filterArchetype . preprocessFilter) archetypeFilter components world) archetypes+ findMatchingArchetypes (catMaybes components) world.archetypes - outputs <- liftIO $ runQueryInternal query (map snd archetypes') world- outputs' <- filterM (\(e, b, _) -> (&& b) <$> filterQuery (preprocessFilter otherFilter) e) outputs- return $ map (\(_, _, o) -> o) outputs'+-- runQuery :: forall qd m w output a. (Queryable qd output, MonadSystem w m) => qd -> QueryFilter -> World -> m [output]+-- runQuery query filter world =+-- do+-- archetypes <- findArchetypes query+-- -- let (otherFilter, archetypeFilter) = extractArchetypeFilters $ preprocessFilter filter -query :: forall qd output m w. (Queryable qd output, MonadSystem w m) => qd -> m [output]-query qd = do- world <- unsafeGetWorld- runQuery qd NoFilter world+-- -- archetypes' <- filterM (\(components, _) -> liftIO $ (filterArchetype . preprocessFilter) archetypeFilter components world) archetypes +-- -- outputs <- liftIO $ runQueryInternal query (map snd archetypes') world+-- -- outputs' <- filterM (\(e, b, _) -> (&& b) <$> filterQuery (preprocessFilter otherFilter) e) outputs+-- -- return $ map (\(_, _, o) -> o) outputs'+-- undefined++-- query :: forall qd output m w. (Queryable qd output, MonadSystem w m) => qd -> m [output]+-- query qd = do+-- world <- unsafeGetWorld+-- runQuery qd NoFilter world+ entityQuery :: forall qd output m w. (Queryable qd output, MonadSystem w m) => qd -> Entity -> m (Maybe output)-entityQuery qd entity = do- world <- unsafeGetWorld+entityQuery !qd !entity = do+ !world <- unsafeGetWorld liftIO $ runQueryEntity qd world entity -get :: forall qd m w out. (Queryable qd out, MonadSystem w m) => qd -> Entity -> m (Maybe out)-get = entityQuery+-- get :: forall qd m w out. (Queryable qd out, MonadSystem w m) => qd -> Entity -> m (Maybe out)+-- get = entityQuery -get' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> Entity -> m (Maybe out)-get' qd qf entity = do- b <- filterQuery (preprocessFilter $ collect qf) entity- if b- then- entityQuery qd entity- else- pure Nothing+-- get' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> Entity -> m (Maybe out)+-- get' qd qf entity = do+-- b <- filterQuery (preprocessFilter $ collect qf) entity+-- if b+-- then+-- entityQuery qd entity+-- else+-- pure Nothing -query' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> m [out]-query' qd filter = do- world <- unsafeGetWorld- runQuery qd (collect filter) world+-- query' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> m [out]+-- query' qd filter = do+-- world <- unsafeGetWorld+-- runQuery qd (collect filter) world -single :: forall qd m w out. (Queryable qd out, MonadSystem w m) => qd -> m (Maybe out)-single qd = do- res <- query qd- case res of- [x] -> return $ Just x- _ -> return Nothing+-- single :: forall qd m w out. (Queryable qd out, MonadSystem w m) => qd -> m (Maybe out)+-- single qd = do+-- res <- query qd+-- case res of+-- [x] -> return $ Just x+-- _ -> return Nothing -single' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> m (Maybe out)-single' qd filter = do- res <- query' qd filter- case res of- [x] -> return $ Just x- _ -> return Nothing+-- single' :: forall qd m w out qf. (Queryable qd out, MonadSystem w m, Collectable qf QueryFilter) => qd -> qf -> m (Maybe out)+-- single' qd filter = do+-- res <- query' qd filter+-- case res of+-- [x] -> return $ Just x+-- _ -> return Nothing -- iter :: forall qd m w. (Queryable qd, MonadSystem w m) => (QueryOutput qd -> m ()) -> m () -- iter system = do@@ -115,43 +113,293 @@ -- res <- query @qd -- parIterList res $ \chunk -> for_ chunk system -class GetResultComponentId c where- getResultComponentId :: (MonadSystem w m) => c -> m (Maybe ComponentId)+class GetComponentId c where+ getComponentId' :: (MonadSystem w m) => c -> FilterComponent -class GetResultComponentId' flag c where- getResultComponentId' :: (MonadSystem w m) => c -> m (Maybe ComponentId)+tryMetaLocal :: forall c m w. (Component c, MonadSystem w m) => m (Maybe Entity)+tryMetaLocal = do+ !world <- unsafeGetWorld+ !component <- liftIO $ getComponentId (typeRep $ Proxy @c) world.components+ pure $ fmap (\(ComponentId (# !id, _ #)) -> Entity (# id, 0## #)) component -instance (Component c) => GetResultComponentId' True (Result c) where- getResultComponentId' _ = fmap (\(Entity (# id, _ #)) -> ComponentId (# id, Nothing #)) <$> tryMetaLocal @c+-- getComponentId' _ = -instance (Component c) => GetResultComponentId' False (Result (Rel c)) where- getResultComponentId' r = fmap (\(Entity (# id, _ #)) -> ComponentId (# id, Just r.target #)) <$> tryMetaLocal @c+-- instance (Component c) => GetComponentId (R c Entity) where+-- getComponentId' (R e) = fmap (\(Entity (# id, _ #)) -> ComponentId (# id, Just e #)) <$+-- instance {-# OVERLAPPING #-} (Component c) => GetComponentId (C c) where> tryMetaLocal @c -tryMetaLocal :: forall c m w. (Component c, MonadSystem w m) => m (Maybe Entity)-tryMetaLocal = do- world <- unsafeGetWorld- component <- liftIO $ getComponentId (typeRep $ Proxy @c) world.components- return $ fmap (\(ComponentId (# id, _ #)) -> Entity (# id, 0## #)) component+-- instance (GetResultComponentId' (IsComp c) (Result c)) => GetResultComponentId (Result c) where+-- getResultComponentId = getResultComponentId' @(IsComp c) -instance (GetResultComponentId' (IsComp c) (Result c)) => GetResultComponentId (Result c) where- getResultComponentId = getResultComponentId' @(IsComp c)+-- addedChanged :: forall m w. (MonadSystem w m) => (ComponentTicks -> Tick -> Tick -> Bool) -> FilterComponent -> Entity -> m Bool+-- addedChanged !f (FilterComponent (!c, Nothing, Nothing)) e = do+-- !world <- unsafeGetWorld+-- !id <- liftIO $ getComponentId c world.components+-- case id of+-- Nothing -> pure False+-- Just !id -> do+-- !ticks <- liftIO $ tryGetEntityTicks e id world+-- case ticks of+-- Nothing -> pure False+-- Just !ticks -> do+-- (!lastSystemTick, !currentSystemTick) <- liftIO $ getSystemTicksInternal world+-- pure $ f ticks lastSystemTick currentSystemTick+-- addedChanged !f (FilterComponent (!c, Just !entity, Nothing)) !e = do+-- world <- unsafeGetWorld+-- !id <- liftIO $ getComponentId c world.components+-- case id of+-- Nothing -> pure False+-- Just (ComponentId (# !id, _ #)) -> do+-- !ticks <- liftIO $ tryGetEntityTicks e (ComponentId (# id, Just entity #)) world+-- case ticks of+-- Nothing -> pure False+-- Just !ticks -> do+-- (!lastSystemTick, !currentSystemTick) <- liftIO $ getSystemTicksInternal world+-- pure $ f ticks lastSystemTick currentSystemTick+-- addedChanged !f (FilterComponent (!c, _, Just _)) e = do+-- world <- unsafeGetWorld+-- !id <- liftIO $ getComponentId c world.components+-- case id of+-- Nothing -> return False+-- Just (ComponentId (# !id, _ #)) -> do+-- Just (ComponentType (_ :: Proxy a)) <- liftIO $ runQueryEntity (C @ComponentType) world (Entity (# id, 0## #))+-- !rels <- single $ mkGet e (R' @a Any)+-- case rels of+-- Nothing -> pure False+-- Just !rels -> do+-- and+-- <$> for+-- rels+-- ( \(Rel _ !target) -> do+-- addedChanged f (FilterComponent (c, Just target, Nothing)) e+-- ) -addedChanged :: forall c m w. (MonadSystem w m, GetResultComponentId (Result c)) => (ComponentTicks -> Tick -> Tick -> Bool) -> Result c -> m Bool-addedChanged f r = do- id <- getResultComponentId r- case id of- Nothing -> return False- Just id -> do- world <- unsafeGetWorld- ticks <- liftIO $ tryGetEntityTicks (entityOf r) id world- case ticks of- Nothing -> return False- Just ticks -> do- (lastSystemTick, currentSystemTick) <- liftIO $ getSystemTicksInternal world- return $ f ticks lastSystemTick currentSystemTick+-- ticks <- liftIO $ tryGetEntityTicks e (ComponentId (# id, Just entity #)) world+-- case ticks of+-- Nothing -> return False+-- Just ticks -> do+-- (lastSystemTick, currentSystemTick) <- liftIO $ getSystemTicksInternal world+-- return $ f ticks lastSystemTick currentSystemTick -added :: forall c m w. (MonadSystem w m, GetResultComponentId (Result c)) => Result c -> m Bool-added = addedChanged qfAddedF+-- added :: forall c m w. (MonadSystem w m, ToFilterComponent c) => c -> Entity -> m Bool+-- added !x = addedChanged qfAddedF (toFilterComponent x) -changed :: forall c m w. (MonadSystem w m, GetResultComponentId (Result c)) => Result c -> m Bool-changed = addedChanged qfChangedF+-- changed :: forall c m w. (MonadSystem w m, ToFilterComponent c) => c -> Entity -> m Bool+-- changed !x = addedChanged qfChangedF (toFilterComponent x)++has :: forall c m w. (MonadSystem w m, ToFilterComponent c) => c -> Entity -> m Bool+has !c !e = do+ !world <- unsafeGetWorld+ liftIO $ filterEntity (With c) world e++-- | A Query is a chain of operations which return a list of values when ran.+data Query m a where+ BuildQuery :: (Queryable qd out) => qd -> QueryFilter -> Maybe [Entity] -> Query m out+ MapQuery :: Query m b -> (Entity -> b -> m a) -> Query m a+ FilterQuery :: Query m a -> (Entity -> a -> m Bool) -> Query m a+ PairEntityQuery :: Query m a -> Query m (Entity, a)+ DoQuery :: Query m a -> (Entity -> a -> m b) -> Query m a+ CollectQuery :: Query m a -> Query m [From a]+ PureQuery :: a -> Query m a+ AppQuery :: Query m (a -> b) -> Query m a -> Query m b+ BindQuery :: Query m a -> (a -> Query m b) -> Query m b+ EmptyQuery :: Query m a+ RefocusQuery :: Query m a -> Entity -> Query m a+ StopQuery :: Query m a+ IterQuery :: [From a] -> Query m a+ ParQuery :: Query ParSystem a -> Query m a++instance (MonadSystem w m) => Functor (Query m) where+ fmap :: (a -> b) -> Query m a -> Query m b+ fmap !f !a = MapQuery a (\_ x -> pure $ f x)++instance (MonadSystem w m) => Applicative (Query m) where+ pure :: a -> Query m a+ pure = PureQuery+ (<*>) :: Query m (a -> b) -> Query m a -> Query m b+ (<*>) = AppQuery++instance (MonadSystem w m) => Monad (Query m) where+ (>>=) :: (MonadSystem w m) => Query m a -> (a -> Query m b) -> Query m b+ (>>=) = BindQuery++qrun :: (MonadSystem w m) => Query m out -> m [(Entity, out)]+qrun (BuildQuery !qd !qf Nothing) = do+ !world <- unsafeGetWorld+ !archetypes' <- findArchetypes qd++ !archetypes <-+ liftIO+ ( filterM+ ( \a -> do+ let Tables tables = world.tables+ table <- Vec.unsafeRead tables a.id+ filterArchetype qf world table+ )+ archetypes'+ )++ !x <- liftIO $ runQueryInternal (E, qd) archetypes world+ pure $! mapMaybe (\case (_, False, _) -> Nothing; (_, True, x) -> Just x) x+qrun (BuildQuery !qd !qf (Just [!entity])) = do+ !world <- unsafeGetWorld+ !b <- liftIO $ filterEntity qf world entity+ if not b+ then pure []+ else do+ !e <- entityQuery qd entity+ case e of+ Nothing -> pure []+ Just !e -> pure [(entity, e)]+qrun (BuildQuery !qd !qf (Just !entities)) = concat <$> mapM (\e -> qrun (BuildQuery qd qf (Just [e]))) entities+qrun (MapQuery !a !f) = do+ !x <- qrun a+ mapM (\(!e, !x) -> (e,) <$> f e x) x+qrun (FilterQuery !a !f) = do+ !x <- qrun a+ filterM (uncurry f) x+qrun (PairEntityQuery !a) = do+ !x <- qrun a+ pure $ map (\(!e, !a) -> (e, (e, a))) x+qrun (DoQuery !a !f) = do+ !x <- qrun a+ for_ x (uncurry f)+ pure x+qrun (CollectQuery !a) = do+ !a <- map (uncurry From) <$> qrun a+ pure [(Entity (# 0##, 0## #), a)]+qrun (PureQuery !a) = pure [(Entity (# 0##, 0## #), a)]+qrun (AppQuery !f !a) = do+ !f <- qrun f+ !a <- qrun a+ pure $ catMaybes $ [tryJoin a f | a <- a, f <- f]+qrun (BindQuery !a !f) = do+ !x <- qrun a+ concat+ <$> traverse+ ( \(_, !x) -> do+ qrun (f x)+ )+ x+qrun EmptyQuery = pure []+qrun (RefocusQuery !a !e) = do+ !x <- qrun a+ pure $ map (\(_, !a') -> (e, a')) x+qrun StopQuery = pure []+qrun (IterQuery a) = pure $ map (\(From e a) -> (e, a)) a+qrun (ParQuery a) = systemAny (parLift $ qrunPar a) (qrunPar a)++qrunPar :: Query ParSystem a -> ParSystem [(Entity, a)]+qrunPar (BuildQuery a b c) = qrun (BuildQuery a b c)+qrunPar (MapQuery !a !f) = do+ !x <- qrunPar a+ parFor' x (\(!e, !x) -> (e,) <$> f e x)+qrunPar (FilterQuery !a !f) = do+ !x <- qrunPar a+ catMaybes+ <$> parFor'+ x+ ( \(e, x) -> do+ a <- f e x+ pure $ if a then Just (e, x) else Nothing+ )+qrunPar (PairEntityQuery !a) = do+ !x <- qrunPar a+ pure $ map (\(!e, !a) -> (e, (e, a))) x+qrunPar (DoQuery !a !f) = do+ !x <- qrunPar a+ for_ x (uncurry f)+ pure x+qrunPar (CollectQuery !a) = do+ !a <- map (uncurry From) <$> qrunPar a+ pure [(Entity (# 0##, 0## #), a)]+qrunPar (PureQuery !a) = pure [(Entity (# 0##, 0## #), a)]+qrunPar (AppQuery !f !a) = do+ !f <- qrunPar f+ !a <- qrunPar a+ pure $ catMaybes $ [tryJoin a f | a <- a, f <- f]+qrunPar (BindQuery !a !f) = do+ !x <- qrunPar a+ concat+ <$> traverse+ ( \(_, !x) -> do+ qrunPar (f x)+ )+ x+qrunPar EmptyQuery = pure []+qrunPar (RefocusQuery !a !e) = do+ !x <- qrunPar a+ pure $ map (\(_, !a') -> (e, a')) x+qrunPar StopQuery = pure []+qrunPar (IterQuery a) = pure $ map (\(From e a) -> (e, a)) a+qrunPar (ParQuery a) = qrunPar a++-- qrunPar x = qrun x++tryJoin :: (Entity, a) -> (Entity, a -> b) -> Maybe (Entity, b)+tryJoin (Entity (# 0##, 0## #), !a) (!e, !f) = Just (e, f a)+tryJoin (!e, !a) (Entity (# 0##, 0## #), !f) = Just (e, f a)+tryJoin (!e, !a) (!e', !f)+ | e == e' = Just (e, f a)+ | otherwise = Nothing++-- | Run a 'Query', returning a list of values.+query :: (MonadSystem w m) => Query m out -> m [out]+query !a = map snd <$> qrun a++-- | Run a 'Query'.+query_ :: (MonadSystem w m) => Query m out -> m ()+query_ !a = void $ qrun a++-- | Run a 'Query', returning an element only if it was the only result.+single :: (MonadSystem w m) => Query m out -> m (Maybe out)+single !a = do+ !x <- query a+ case x of+ [!x] -> pure (Just $! x)+ _ -> pure Nothing++-- | Run a 'Query', returning an arbitrary element produced by it, or nothing if the query was null.+queryOne :: (MonadSystem w m) => Query m out -> m (Maybe out)+queryOne !a = do+ x <- query a+ case x of+ (!x : _) -> pure (Just $! x)+ _ -> pure Nothing++mkQuery :: (Queryable qd out) => qd -> Query m out+mkQuery !x = mkQuery' x NoFilter++mkQuery' :: (Queryable qd out) => qd -> QueryFilter -> Query m out+mkQuery' !a !b = BuildQuery a b Nothing++mkGet :: (Queryable qd out) => Entity -> qd -> Query m out+mkGet !x !a = mkGet' x a NoFilter++mkGet' :: (Queryable qd out) => Entity -> qd -> QueryFilter -> Query m out+mkGet' !a !b !c = BuildQuery b c (Just [a])++check :: forall m w. (MonadSystem w m) => QueryFilter -> Entity -> m Bool+check f e = isJust <$> single (mkGet' e () f)++-- | Same as 'runWhen', but runs when the provided query is not empty.+runWhenQ :: (Typeable a) => Query System a -> ([a] -> System b) -> System ()+runWhenQ q =+ runWhen+ ( do+ x <- query q+ pure $ case x of+ [] -> Nothing+ a -> Just a+ )++-- | Same as 'runWhen', but runs when the provided query is not empty. Picks an arbitrary element to pass forward.+runWhenOne :: (Typeable a) => Query System a -> (a -> System b) -> System ()+runWhenOne q =+ runWhen+ ( do+ x <- query q+ pure $ case x of+ [] -> Nothing+ (a : _) -> Just a+ )
src/Mischief/ECS/World/Query/Markers.hs view
@@ -271,3 +271,7 @@ newtype Val a = Val a newtype R' a b = R' b++data MRes a = MRes++data HasRes a = HasRes
+ src/Mischief/ECS/World/Query/Pipe.hs view
@@ -0,0 +1,359 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# OPTIONS_GHC -Wno-partial-fields #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}++module Mischief.ECS.World.Query.Pipe+ ( -- * Impure+ qtraverse,+ qtap,++ -- * Mapping+ qmap,+ qmapM,+ qmapMaybe,+ qmapMaybeM,++ -- * Filtering+ qfilter,+ qfilterM,++ -- * Insertion+ qinsert,+ qinsertNew,+ qinsertIfNeq,+ qmodify,+ qmodifyNew,+ qmodifyIfNeq,++ -- * Expansion+ qextend,+ qentity,++ -- * Traversal+ qrelateOne,+ qrelateMany,+ qjoin,++ -- * Monad+ qpure,+ qrefocus,+ qres,+ qget,+ qgetAll,+ qcollect,+ qmaybe,+ qstopIf,+ qstop,+ qiter,+ qiterE,+ qlift,+ qlift_,++ -- * Logging+ qinfo,+ qwarn,+ qerr,++ -- * Other+ qdespawn,++ -- * Parallelism+ qpar,+ )+where++import Control.Monad (void)+import Control.Monad qualified+import Data.Function+import Data.Maybe+import Data.Text (Text)+import GHC.Stack+import Mischief.ECS.Components+import Mischief.ECS.Components.Bundle+import Mischief.ECS.Entities+import Mischief.ECS.Log+import Mischief.ECS.World+import Mischief.ECS.World.Defer+import Mischief.ECS.World.Insert+import Mischief.ECS.World.Query+import Mischief.ECS.World.Query.Markers+import Mischief.ECS.World.Query.QueryFilter+import Mischief.ECS.World.Query.Queryable+import Mischief.ECS.World.Spawn++{-# RULES+"qmap/qmap" forall f g xs. qmap f (qmap g xs) = qmap (f . g) xs+ #-}++qcollect :: (MonadSystem w m) => Query m a -> Query m [From a]+qcollect = CollectQuery++-- | Maps @Query m a@ to @Query m b@. Same as 'fmap'.+--+-- __Example__+--+-- @+-- [q|Name, Position|]+-- & qmap (\\(name, _) -> name)+-- & query+-- @+qmap :: (MonadSystem w m) => (a -> b) -> Query m a -> Query m b+qmap = fmap+{-# INLINE [1] qmap #-}++-- | 'qmap' with side effects. Same as 'qtraverse'.+qmapM :: (MonadSystem w m) => (Entity -> a -> m b) -> Query m a -> Query m b+qmapM = qtraverse++-- | 'qmapMaybe' with side effects.+qmapMaybeM :: (MonadSystem w m) => (Entity -> a -> m (Maybe b)) -> Query m a -> Query m b+qmapMaybeM !f !b = MapQuery (FilterQuery (MapQuery b f) (\_ !x -> pure (isJust x))) (\_ !x -> pure (fromMaybe undefined x))++-- | Possibly maps each element from @Query m a@ to an element from @Query m b@. If Nothing, will filter this Entity out of the Query.+--+-- __Example__+--+-- @+-- f :: Name -> Maybe Name+-- @+--+-- @+-- [q|Name|]+-- & qmapMaybe f+-- & query+-- @+qmapMaybe :: (MonadSystem w m) => (a -> Maybe b) -> Query m a -> Query m b+qmapMaybe !f !x = qmap (fromMaybe undefined) $ qfilter isJust (qmap f x)++-- | Map @Query m a@ to @Query m b@ with side effects.+--+-- __Example__+--+-- @+-- [q|Name, Position|]+-- & qtraverse (\\e (name, pos) -> do+-- [i|The name of entity #{e} is #{name}|]+-- pure pos+-- )+-- & query+-- @+qtraverse :: (Entity -> a -> m b) -> Query m a -> Query m b+qtraverse !f !x = MapQuery x f++qlift :: (MonadSystem w m) => m a -> Query m a+qlift !s = pure () & qtraverse (\_ _ -> s)++qlift_ :: (MonadSystem w m) => m a -> Query m ()+qlift_ = void . qlift++-- | Apply a side effect over a query.+--+-- __Example__+--+-- @+-- [q|Name, Position|]+-- & qtraverse (\\e (name, pos) -> do+-- [i|The name of entity #{e} is #{name}|]+-- )+-- & query+-- @+qtap :: (Entity -> a -> m b) -> Query m a -> Query m a+qtap = flip DoQuery++-- | Insert the values obtained from a mapping function.+--+-- __Example__+--+-- @+-- q[|Position, Velocity|]+-- & qinsert (\\(Position p, Velocity v) -> Position (p + v))+-- & query+-- @+qinsert :: (Bundle b) => (a -> b) -> Query System a -> Query System a+qinsert !f = qtap (\(!e) !a -> insert (f a) e)++-- | Same as 'qinsert' but only inserts components that aren't already on the entity.+qinsertNew :: (Bundle b) => (a -> b) -> Query System a -> Query System a+qinsertNew !f = qtap (\(!e) !a -> insertNew (f a) e)++-- | Same as 'qinsert' but only inserts components that are new or different than the ones already on the entity. Avoids triggering false change detection.+qinsertIfNeq :: (BundleEq b) => (a -> b) -> Query System a -> Query System a+qinsertIfNeq !f = qtap (\(!e) !a -> insertIfNeq (f a) e)++-- | Same as 'qinsert' but will also propagate the mapped elements. Equivalent to @qinsert id . qmap f@.+qmodify :: (Bundle b) => (a -> b) -> Query System a -> Query System b+qmodify !f = qinsert id . qmap f++-- | Same as 'qinsertNew' but will also propagate the mapped elements. Equivalent to @qinsertNew id . qmap f@.+qmodifyNew :: (Bundle b) => (a -> b) -> Query System a -> Query System b+qmodifyNew !f = qinsertNew id . qmap f++-- | Same as 'qinsertIfNeq' but will also propagate the mapped elements. Equivalent to @qinsertIfNeq id . qmap f@.+qmodifyIfNeq :: (BundleEq b) => (a -> b) -> Query System a -> Query System b+qmodifyIfNeq !f = qinsertIfNeq id . qmap f++{-# RULES+"qfilter/qfilter" forall f g xs. qfilter f (qfilter g xs) = qfilter (\x -> f x && g x) xs+ #-}++-- | Filter the entities of a query.+--+-- __Example__+--+-- @+-- [q|Name|]+-- & qfilter (== Name "Florian")+-- & query+-- @+qfilter :: (MonadSystem w m) => (a -> Bool) -> Query m a -> Query m a+qfilter !f !x = FilterQuery x (\_ !x -> pure (f x))+{-# INLINE [1] qfilter #-}++-- | Filter the entities of a query with possible side effects.+--+-- __Example__+--+-- @+-- [q|Position|]+-- & qfilterM (\\e _ -> 'changed' \@Position e)+-- & query+-- @+qfilterM :: (Entity -> a -> m Bool) -> Query m a -> Query m a+qfilterM !f !x = FilterQuery x f++-- check :: (MonadSystem w m) => QueryFilter EntityFilter -> Entity -> m Bool+-- check NoFilter _ = pure True+-- check (With !x) !e = do+-- !world <- unsafeGetWorld+-- liftIO $ filterEntity (With x) world e+-- check (Without !x) !e = do+-- !world <- unsafeGetWorld+-- liftIO $ filterEntity (Without x) world e+-- check (Added !x) !e = added x e+-- check (Changed !x) !e = changed x e+-- check (And !a !b) !e = (&&) <$> check a e <*> check b e+-- check (Or !a !b) !e = (||) <$> check a e <*> check b e+-- check (Not !a) !e = not <$> check a e++-- {-# RULES+-- "qcheck/qcheck" forall f g xs. qcheck f (qcheck g xs) = qcheck (f `And` g) xs+-- #-}++-- | Filter the entities of a query with a dedicated query filter.+--+-- __Example__+--+-- @+-- [q|Position|]+-- & qcheck [qf|Changed Position, !Added Position|]+-- & query+-- @+-- qcheck :: (MonadSystem w m) => QueryFilter EntityFilter -> Query m a -> Query m a+-- qcheck !f = qfilterM (\(!e) _ -> check f e)+-- {-# INLINE [1] qcheck #-}++-- | Extends the query with new elements.+--+-- __Example__+--+-- @+-- [q|Name|]+-- & qextend [qd|Position|] (,)+-- & query+-- @+qextend :: (MonadSystem w m, Queryable qd out) => qd -> (a -> out -> c) -> Query m a -> Query m c+qextend !qd !f !a = do+ (!e, !a) <- qentity a+ a' <- qget e qd+ qpure e & qmap (const $ f a a')++-- | Grab a Query Data from an entity resulting from a traversal and map it into the current data wrapped in a @From@:+--+-- __Example__+--+-- @+-- [q|Name|]+-- & qrelateOne (Tree.'Mischief.ECS.Relationships.Tree.root' \@Like) \[qd|Name|] (,)+-- & query_+-- @+qrelateOne :: (MonadSystem w m, Queryable qd out) => (Entity -> m (Maybe Entity)) -> qd -> (a -> From out -> c) -> Query m a -> Query m c+qrelateOne !f !qd !f' !a = do+ (!a, !e) <- a & qtraverse (\e a -> (a,) <$> f e)+ case e of+ Nothing -> EmptyQuery+ Just !e -> do+ !out <- qget e qd+ pure $ f' a (From e out)++-- | Same as 'qrelateOne' but takes a traversal that returns a list of entities.+qrelateMany :: (MonadSystem w m, Queryable (E, qd) (Entity, out)) => (Entity -> m [Entity]) -> qd -> (a -> [From out] -> c) -> Query m a -> Query m c+qrelateMany !f !qd !f' !a = do+ ((_, !a), !e) <- qentity a & qtraverse (\(!e) !a -> (a,) <$> f e)+ case e of+ [] -> EmptyQuery+ e -> do+ !as <-+ qgetAll e (E, qd)+ & qmap (uncurry From)+ & qcollect+ & qmap (map (.comp))+ pure $ f' a as++qres :: forall c m w. (MonadSystem w m, Component c) => Query m c+qres = (\(Res c) -> c) <$> qget (Entity (# 0##, 0## #)) (Res @c)++qget :: (MonadSystem w m, Queryable qd out) => Entity -> qd -> Query m out+qget !entity !qd = BuildQuery qd NoFilter (Just [entity])++qgetAll :: (MonadSystem w m, Queryable qd out) => [Entity] -> qd -> Query m out+qgetAll !entity !qd = BuildQuery qd NoFilter (Just entity)++qmaybe :: (MonadSystem w m) => Maybe a -> Query m a+qmaybe = qmapMaybe id . pure++qrefocus :: Entity -> Query m a -> Query m a+qrefocus = flip RefocusQuery++qpure :: (MonadSystem w m) => Entity -> Query m ()+qpure !e = qrefocus e $ pure ()++qstopIf :: (MonadSystem w m) => Bool -> Query m ()+qstopIf b = Control.Monad.when b qstop++qstop :: (MonadSystem w m) => Query m a+qstop = StopQuery++qiter :: (MonadSystem w m) => [From a] -> Query m a+qiter = IterQuery++qiterE :: (MonadSystem w m) => [Entity] -> Query m Entity+qiterE l = qiter (map (\x -> From x x) l)++-- | Pairs the query's elements with their entity.+qentity :: (MonadSystem w m) => Query m a -> Query m (Entity, a)+qentity = PairEntityQuery++qjoin :: (MonadSystem w m) => (a -> Query m b) -> (a -> [From b] -> c) -> Query m a -> Query m c+qjoin !f !f' !a = do+ (_, !a) <- qentity a+ f a & qcollect & qfilter (not . null) & qmap (f' a)++-- | Logs an INFO message.+qinfo :: (HasCallStack, MonadSystem w m) => (a -> Text) -> Query m a -> Query m a+qinfo !f !a = withFrozenCallStack $ qtap (\_ !x -> info (f x)) a++-- | Logs a WARNING message.+qwarn :: (HasCallStack, MonadSystem w m) => (a -> Text) -> Query m a -> Query m a+qwarn !f !a = withFrozenCallStack $ qtap (\_ !x -> warn (f x)) a++-- | Logs an ERROR message.+qerr :: (HasCallStack, MonadSystem w m) => (a -> Text) -> Query m a -> Query m a+qerr !f !a = withFrozenCallStack $ qtap (\_ !x -> err (f x)) a++qdespawn :: Query System a -> Query System a+qdespawn a = do+ void $ qtap (\(!e) _ -> despawn e) a+ qstop++qpar :: (MonadSystem w m) => Query ParSystem a -> Query m a+qpar = ParQuery
src/Mischief/ECS/World/Query/QueryFilter.hs view
@@ -4,241 +4,54 @@ import Data.Data import Data.Maybe-import Data.Set qualified as Set-import GHC.Base (eqWord#, isTrue#, (/=#))-import Mischief.ECS.Collectable+import GHC.Base (eqWord#, isTrue#) import Mischief.ECS.Components-import Mischief.ECS.Components.BundleTypes import Mischief.ECS.Entities-import Mischief.ECS.EntityDef import Mischief.ECS.World import Mischief.ECS.World.Query.Markers -- import Mischief.ECS.World.Query.Queryable --- newtype QueryFilters = QueryFilters [QueryFilter] deriving newtype (Semigroup)--qfChangedF :: ComponentTicks -> Tick -> Tick -> Bool-qfChangedF ticks lastSystemTick currentSystemTick = ticks.changed >= lastSystemTick && ticks.changed < currentSystemTick+qfChangedF :: (Tick -> Tick -> Bool) -> (Tick -> Tick -> Bool) -> ComponentTick -> Tick -> Tick -> Bool+qfChangedF f1 f2 (ComponentTick !tick) !lastSystemTick !currentSystemTick = f1 tick lastSystemTick && f2 tick currentSystemTick -qfAddedF :: ComponentTicks -> Tick -> Tick -> Bool-qfAddedF ticks lastSystemTick currentSystemTick = ticks.added >= lastSystemTick && ticks.added < currentSystemTick+data FilterType = ArchetypeFilter | EntityFilter -data QueryFilter- = NoFilter- | QFWith (TypeRep, Maybe Entity)- | QFWithRelAny TypeRep- | QFChanged (TypeRep, Maybe Entity) (ComponentTicks -> Tick -> Tick -> Bool)- | QFChangedRelAny TypeRep (ComponentTicks -> Tick -> Tick -> Bool)- | QFCheckRaw (TypeRep, Maybe Entity, ErasedCheck)- | QFCheckRawRelAny (TypeRep, ErasedCheck)- | QFNot QueryFilter- | QFAnd QueryFilter QueryFilter- | QFOr QueryFilter QueryFilter+data QueryFilter where+ NoFilter :: QueryFilter+ With :: (ToFilterComponent a) => a -> QueryFilter+ Without :: (ToFilterComponent a) => a -> QueryFilter+ Changed :: (ToFilterComponent a) => a -> (Tick -> Tick -> Bool) -> (Tick -> Tick -> Bool) -> QueryFilter+ And :: QueryFilter -> QueryFilter -> QueryFilter+ Or :: QueryFilter -> QueryFilter -> QueryFilter -instance Semigroup QueryFilter where- (<>) :: QueryFilter -> QueryFilter -> QueryFilter- (<>) = QFAnd+newtype FilterComponent = FilterComponent {inner :: (TypeRep, Maybe Entity, Maybe Any)} -instance {-# OVERLAPPING #-} EraseIntoStorage QueryFilter QueryFilter where- erase = id+class ToFilterComponent a where+ toFilterComponent :: a -> FilterComponent -instance (IntoQueryFilter q) => EraseIntoStorage q QueryFilter where- erase = intoQueryFilter+instance (Component c) => ToFilterComponent (C c) where+ toFilterComponent _ = FilterComponent (typeRep $ Proxy @c, Nothing, Nothing) -data ErasedCheck where- ErasedCheck :: (Component c) => (c -> Bool) -> ErasedCheck+instance (Component c) => ToFilterComponent (R c Entity) where+ toFilterComponent (R !entity) = FilterComponent (typeRep $ Proxy @c, Just entity, Nothing) -instance Show ErasedCheck where- show _ = "erased check"+instance (Component c) => ToFilterComponent (R c Any) where+ toFilterComponent _ = FilterComponent (typeRep $ Proxy @c, Nothing, Just Any) -and' :: [QueryFilter] -> QueryFilter-and' [x] = x-and' x = foldr QFAnd NoFilter x+instance Semigroup QueryFilter where+ (<>) :: QueryFilter -> QueryFilter -> QueryFilter+ (<>) = And -filterArchetype :: QueryFilter -> [ComponentId] -> World -> IO Bool-filterArchetype NoFilter _ _ = return True-filterArchetype (QFWith (x, entity)) components world = do- component <- fmap (setCompIdTarget entity) <$> getComponentId x world.components- return $ case component of+filterArchetype' :: FilterComponent -> World -> [ComponentId] -> IO Bool+filterArchetype' (FilterComponent (!c, !entity, Nothing)) !world !components = do+ !component <- fmap (setCompIdTarget entity) <$> getComponentId c world.components+ pure $ case component of Nothing -> False- Just component -> component `elem` components-filterArchetype (QFWithRelAny x) components world = do- component <- getComponentId x world.components+ Just !component -> component `elem` components+filterArchetype' (FilterComponent (!c, _, Just _)) world components = do+ !component <- getComponentId c world.components case component of Nothing -> pure False- Just (ComponentId (# id, _ #)) -> do- return $ any (\(ComponentId (# id', a #)) -> isJust a && isTrue# (eqWord# id' id)) components-filterArchetype (a `QFAnd` b) c w = do- x <- filterArchetype a c w- y <- filterArchetype b c w- return $ x && y-filterArchetype (a `QFOr` b) c w = do- x <- filterArchetype a c w- y <- filterArchetype b c w- return $ x || y-filterArchetype (QFNot a) c w = not <$> filterArchetype a c w-filterArchetype _ _ _ = pure True---- | Extracts archetype-level filters from the bigger filter where possible, to be applied at the start of querying for better performance.-extractArchetypeFilters :: QueryFilter -> (QueryFilter, QueryFilter)-extractArchetypeFilters NoFilter = (NoFilter, NoFilter)-extractArchetypeFilters (QFWith x) = (NoFilter, QFWith x)-extractArchetypeFilters (QFWithRelAny x) = (NoFilter, QFWithRelAny x)-extractArchetypeFilters (QFChanged x f) = (QFChanged x f, NoFilter)-extractArchetypeFilters (QFChangedRelAny x f) = (QFChangedRelAny x f, NoFilter)-extractArchetypeFilters (QFCheckRaw x) = (QFCheckRaw x, NoFilter)-extractArchetypeFilters (QFCheckRawRelAny x) = (QFCheckRawRelAny x, NoFilter)-extractArchetypeFilters (a `QFAnd` b) = (filter1 `QFAnd` filter2, res1 `QFAnd` res2)- where- (filter1, res1) = extractArchetypeFilters a- (filter2, res2) = extractArchetypeFilters b-extractArchetypeFilters (a `QFOr` b) =- if isArchetypeFilter a && isArchetypeFilter b- then- (filter1 `QFOr` filter2, res1 `QFOr` res2)- else- (a `QFOr` b, NoFilter)- where- (filter1, res1) = extractArchetypeFilters a- (filter2, res2) = extractArchetypeFilters b-extractArchetypeFilters (QFNot a) = (QFNot x, QFNot y)- where- (x, y) = extractArchetypeFilters a--isArchetypeFilter :: QueryFilter -> Bool-isArchetypeFilter NoFilter = True-isArchetypeFilter (QFChanged _ _) = False-isArchetypeFilter (QFChangedRelAny _ _) = False-isArchetypeFilter (QFWith _) = True-isArchetypeFilter (QFWithRelAny _) = True-isArchetypeFilter (a `QFAnd` b) = isArchetypeFilter a || isArchetypeFilter b-isArchetypeFilter (a `QFOr` b) = isArchetypeFilter a && isArchetypeFilter b-isArchetypeFilter (QFCheckRaw _) = False-isArchetypeFilter (QFCheckRawRelAny _) = False-isArchetypeFilter (QFNot a) = isArchetypeFilter a--preprocessFilter :: QueryFilter -> QueryFilter-preprocessFilter = propagateQFNot--propagateQFNot :: QueryFilter -> QueryFilter-propagateQFNot (QFNot NoFilter) = NoFilter-propagateQFNot (QFNot (QFNot a)) = propagateQFNot a-propagateQFNot (QFNot (a `QFAnd` b)) = propagateQFNot (QFNot a) `QFOr` propagateQFNot (QFNot b)-propagateQFNot (QFNot (a `QFOr` b)) = propagateQFNot (QFNot a) `QFAnd` propagateQFNot (QFNot b)-propagateQFNot (a `QFAnd` b) = propagateQFNot a `QFAnd` propagateQFNot b-propagateQFNot (a `QFOr` b) = propagateQFNot a `QFOr` propagateQFNot b-propagateQFNot x = x--class IntoQueryFilter qf where- intoQueryFilter :: qf -> QueryFilter--newtype With c = With c--instance (Collectable c FilterType) => IntoQueryFilter (With c) where- intoQueryFilter (With c) =- let l :: FilterType = collect c- in and' $ map withF l.inner--newtype Without c = Without c--instance (Collectable c FilterType) => IntoQueryFilter (Without c) where- intoQueryFilter (Without c) =- let l :: FilterType = collect c- in and' $ map (QFNot . withF) l.inner--newtype Not c = Not c--instance (IntoQueryFilter q) => IntoQueryFilter (Not q) where- intoQueryFilter (Not a) = QFNot $ intoQueryFilter a--data Or a b = Or a b--(|.) :: a -> b -> Or a b-(|.) = Or--data And a b = And a b--(&.) :: a -> b -> And a b-(&.) = And--infix 8 &.--infix 9 |.--instance (IntoQueryFilter a, IntoQueryFilter b) => IntoQueryFilter (a `Or` b) where- intoQueryFilter (Or a b) = QFOr (intoQueryFilter a) (intoQueryFilter b)--instance (IntoQueryFilter a, IntoQueryFilter b) => IntoQueryFilter (a `And` b) where- intoQueryFilter (And a b) = QFAnd (intoQueryFilter a) (intoQueryFilter b)--newtype Changed c = Changed c--instance (Collectable c FilterType) => IntoQueryFilter (Changed c) where- intoQueryFilter (Changed c) =- let l :: FilterType = collect c- in and' $ map changedF l.inner--newtype Added c = Added c--instance (Collectable c FilterType) => IntoQueryFilter (Added c) where- intoQueryFilter (Added c) =- let l :: FilterType = collect c- in and' $ map addedF l.inner--newtype Check c = Check (c -> Bool)--instance (Component c) => IntoQueryFilter (Check c) where- intoQueryFilter (Check f) = checkF (typeRep $ Proxy @c, Nothing, Nothing, ErasedCheck f)--data CheckR e c = CheckR e (c -> Bool)--instance (Component c) => IntoQueryFilter (CheckR Entity c) where- intoQueryFilter (CheckR e f) = checkF (typeRep $ Proxy @c, Just e, Nothing, ErasedCheck f)--instance (Component c) => IntoQueryFilter (CheckR Any c) where- intoQueryFilter (CheckR _ f) = checkF (typeRep $ Proxy @c, Nothing, Just Any, ErasedCheck f)--withF :: (TypeRep, Maybe Entity, Maybe Any) -> QueryFilter-withF (c, e, Nothing) = QFWith (c, e)-withF (c, _, _) = QFWithRelAny c--addedF :: (TypeRep, Maybe Entity, Maybe Any) -> QueryFilter-addedF (c, e, Nothing) = QFWith (c, e) `QFAnd` QFChanged (c, e) qfAddedF-addedF (c, _, _) = QFWithRelAny c `QFAnd` QFChangedRelAny c qfAddedF--changedF :: (TypeRep, Maybe Entity, Maybe Any) -> QueryFilter-changedF (c, e, Nothing) = QFWith (c, e) `QFAnd` QFChanged (c, e) qfChangedF-changedF (c, _, _) = QFWithRelAny c `QFAnd` QFChangedRelAny c qfChangedF--checkF :: (TypeRep, Maybe Entity, Maybe Any, ErasedCheck) -> QueryFilter-checkF (c, e, Nothing, f) = QFWith (c, e) `QFAnd` QFCheckRaw (c, e, f)-checkF (c, _, _, f) = QFWithRelAny c `QFAnd` QFCheckRawRelAny (c, f)--newtype FilterType = FilterType {inner :: [(TypeRep, Maybe Entity, Maybe Any)]} deriving newtype (Semigroup)--instance (Component c) => EraseIntoStorage (C c) FilterType where- erase _ = FilterType [(typeRep $ Proxy @c, Nothing, Nothing)]--instance (Component c) => EraseIntoStorage (R c Entity) FilterType where- erase (R e) = FilterType [(typeRep $ Proxy @c, Just e, Nothing)]--instance (Component c) => EraseIntoStorage (R c Any) FilterType where- erase _ = FilterType [(typeRep $ Proxy @c, Nothing, Just Any)]--newtype CheckFilterType = CheckFilterType {inner :: [(TypeRep, Maybe Entity, Maybe Any, ErasedCheck)]} deriving newtype (Semigroup)--instance (Component c) => EraseIntoStorage (C c, c -> Bool) CheckFilterType where- erase (_, f) = CheckFilterType [(typeRep $ Proxy @c, Nothing, Nothing, ErasedCheck f)]--instance (Component c) => EraseIntoStorage (R c Entity, c -> Bool) CheckFilterType where- erase :: (Component c) => (R c Entity, c -> Bool) -> CheckFilterType- erase (R e, f) = CheckFilterType [(typeRep $ Proxy @c, Just e, Nothing, ErasedCheck f)]--instance (Component c) => EraseIntoStorage (R c Any, c -> Bool) CheckFilterType where- erase :: (Component c) => (R c Any, c -> Bool) -> CheckFilterType- erase (_, f) = CheckFilterType [(typeRep $ Proxy @c, Nothing, Just Any, ErasedCheck f)]--type family InnerC a where- InnerC (C (Rel a)) = a- InnerC (C a) = a+ Just (ComponentId (# !id, _ #)) -> do+ pure $ any (\(ComponentId (# !id', !a #)) -> isJust a && isTrue# (eqWord# id' id)) components
− src/Mischief/ECS/World/Query/QueryType.hs
@@ -1,6 +0,0 @@-module Mischief.ECS.World.Query.QueryType where--import Mischief.ECS.Components-import Mischief.ECS.Mappable--type QueryType a = (Mappable MapQueryVal a a, Mappable MapQueryValidity a a, Component a)
src/Mischief/ECS/World/Query/Queryable.hs view
@@ -8,17 +8,18 @@ import Data.Bifunctor qualified import Data.Data import Data.IORef+import Data.Map (Map)+import Data.Map qualified as Map import Data.Maybe import Data.Set (Set) import Data.Set qualified as Set import Data.Traversable import GHC.Base (Int (I#), eqWord#, isTrue#) import Mischief.ECS.App.SystemDef-import Mischief.ECS.Collectable import Mischief.ECS.Components import Mischief.ECS.Entities-import Mischief.ECS.Mappable import Mischief.ECS.Tables+import Mischief.ECS.Utils import Mischief.ECS.Vec qualified as Vec import Mischief.ECS.World import Mischief.ECS.World.Query.Markers@@ -29,9 +30,6 @@ data RelTarget = AnyTarget | RelTargets [Entities] -filterQueryIO :: QueryFilter -> World -> Entity -> IO Bool-filterQueryIO a w e = runSystem (filterQuery a e) w- class Queryable qd output | qd -> output where runQueryEntity :: qd -> World -> Entity -> IO (Maybe output) runQueryInternal :: qd -> [ArchetypeId] -> World -> IO [(Entity, Bool, output)]@@ -45,17 +43,84 @@ IsComponentC Entity = HFalse IsComponentC a = HTrue -instance {-# OVERLAPPABLE #-} (Component c) => Queryable (C c) (Result c) where- runQueryEntity _ world entity = do- result <- tryGetEntityComponent @c world entity+instance Queryable () () where+ runQueryEntity _ _ _ = pure $ Just ()+ runQueryInternal _ archetypes world = do+ !e <- tryGetEntities world archetypes+ pure $ map (\(!e) -> (e, True, ())) e+ queryTypes _ = Set.empty++instance {-# OVERLAPPABLE #-} (Component c) => Queryable (C c) c where+ runQueryEntity _ !world !entity = do+ !result <- tryGetEntityComponent @c world entity return $ case result of- Just (Just res) -> Just $ Result (res, entity)+ Just (Just !res) -> Just res _ -> Nothing - runQueryInternal _ archetypes world = map (\(a, b) -> (a, True, b)) <$> tryGetComponents @c world archetypes+ runQueryInternal _ !archetypes !world = map (\(!a, !b) -> (a, True, b)) <$> tryGetComponents @c world archetypes queryTypes _ = Set.singleton (typeRep (Proxy @c), CompQ) +instance (Component c) => Queryable (c -> Res c) (Res c) where+ runQueryEntity _ !world _ = do+ !x <- getComponentId (typeRep $ Proxy @c) world.components+ case x of+ Nothing -> pure Nothing+ Just (ComponentId (# !id, _ #)) -> do+ let !entity = Entity (# id, 0## #)+ !y <- runQueryEntity (C @c) world entity+ pure $ fmap Res y++ runQueryInternal _ !archetypes !world = do+ !res <- runQueryEntity (Res @c) world (undefined :: Entity)+ !x <- tryGetEntities world archetypes++ pure $ case res of+ Nothing -> map (,False,undefined) x+ Just !res -> map (,True,res) x++ queryTypes _ = Set.empty++instance (Component c) => Queryable (MRes c) (Maybe (Res c)) where+ runQueryEntity _ !world _ = do+ !x <- getComponentId (typeRep $ Proxy @c) world.components+ case x of+ Nothing -> pure (Just Nothing)+ Just (ComponentId (# !id, _ #)) -> do+ let !entity = Entity (# id, 0## #)+ !y <- runQueryEntity (C @c) world entity+ pure $ Just $ fmap Res y++ runQueryInternal _ archetypes world = do+ !res <- runQueryEntity (MRes @c) world (undefined :: Entity)+ !x <- tryGetEntities world archetypes++ pure $ case res of+ Nothing -> map (,False,undefined) x+ Just !res -> map (,True,res) x++ queryTypes _ = Set.empty++instance (Component c) => Queryable (HasRes c) Bool where+ runQueryEntity _ !world _ = do+ !x <- getComponentId (typeRep $ Proxy @c) world.components+ case x of+ Nothing -> pure (Just False)+ Just (ComponentId (# !id, _ #)) -> do+ let !entity = Entity (# id, 0## #)+ !y <- runQueryEntity (C @c) world entity+ pure $ Just $ isJust y++ runQueryInternal _ !archetypes !world = do+ !res <- runQueryEntity (HasRes @c) world (undefined :: Entity)+ !x <- tryGetEntities world archetypes++ pure $ case res of+ Nothing -> map (,False,undefined) x+ Just !res -> map (,True,res) x++ queryTypes _ = Set.empty+ instance Queryable E Entity where runQueryEntity _ _ entity = return $ Just entity @@ -63,465 +128,457 @@ queryTypes _ = Set.empty -class RelQuery (exclusive :: Exclusivity) qd output | qd exclusive -> output where+class RelQuery (exclusive :: Bool) qd output | qd exclusive -> output where relRunQueryEntity :: qd -> World -> Entity -> IO (Maybe output) relRunQueryInternal :: qd -> [ArchetypeId] -> World -> IO [(Entity, Bool, output)] relQueryTypes :: qd -> Set (TypeRep, TypeQuery) -instance (Component c) => RelQuery Inclusive (R c Any) [Result (Rel c)] where- relRunQueryEntity _ world entity = do- res <- tryGetEntityRelCollection @c world entity+instance (Component c) => RelQuery False (R c Any) [Rel c] where+ relRunQueryEntity _ !world !entity = do+ !res <- tryGetEntityRelCollection @c world entity return $ case res of- Just (Just x) -> Just x+ Just (Just !x) -> Just x _ -> Nothing - relRunQueryInternal _ archetypes world = map (\(a, b) -> (a, True, b)) <$> tryGetRelCollections @c world archetypes+ relRunQueryInternal _ !archetypes !world = map (\(!a, !b) -> (a, True, b)) <$> tryGetRelCollections @c world archetypes relQueryTypes _ = Set.singleton (typeRep $ Proxy @c, RelQ) -instance (Component c) => RelQuery Exclusive (R c Any) (Result (Rel c)) where- relRunQueryEntity _ world entity = do- res <- tryGetEntityRelCollection @c world entity+instance (Component c) => RelQuery True (R c Any) (Rel c) where+ relRunQueryEntity _ !world !entity = do+ !res <- tryGetEntityRelCollection @c world entity return $ case res of- Just (Just [x]) -> Just x+ Just (Just [!x]) -> Just x _ -> Nothing - relRunQueryInternal _ archetypes world = do- rels <- tryGetRelCollections @c world archetypes- return $ map (\(e, x : _) -> (e, True, x)) rels+ relRunQueryInternal _ !archetypes !world = do+ !rels <- tryGetRelCollections @c world archetypes+ return $ map (\(!e, !x : _) -> (e, True, x)) rels relQueryTypes _ = Set.singleton (typeRep $ Proxy @c, RelQ) -instance (Component c, Queryable q out) => RelQuery Inclusive (R c (Q q)) [out] where- relRunQueryEntity (R (Q q)) world entity = do- res <- relRunQueryEntity @Inclusive (R @c Any) world entity+instance (Component c, Queryable q out) => RelQuery False (R c (Q q)) [From out] where+ relRunQueryEntity (R (Q !q)) !world !entity = do+ !res <- relRunQueryEntity @False (R @c Any) world entity - case fmap (traverse $ \r -> runQueryEntity q world r.target) res of+ case fmap (traverse $ \(!r) -> fmap (From r.target) <$> runQueryEntity q world r.target) res of Nothing -> pure Nothing- Just x -> do- x <- x+ Just !x -> do+ !x <- x pure $ case catMaybes x of [] -> Nothing- x -> Just x+ !x -> Just x - relRunQueryInternal (R (Q q)) archetypes world = do- res <- relRunQueryInternal @Inclusive (R @c Any) archetypes world- for res $ \(e, b, rels) -> do- a <- catMaybes <$> (traverse $ \r -> runQueryEntity q world r.target) rels+ relRunQueryInternal (R (Q !q)) !archetypes !world = do+ !res <- relRunQueryInternal @False (R @c Any) archetypes world+ for res $ \(!e, !b, !rels) -> do+ !a <- catMaybes <$> (traverse $ \(!r) -> fmap (From r.target) <$> runQueryEntity q world r.target) rels pure $ case a of [] -> (e, False, undefined)- x -> (e, b, x)+ !x -> (e, b, x) relQueryTypes _ = Set.singleton (typeRep $ Proxy @c, RelQ) -instance (Component c, Queryable q out, Collectable f QueryFilter) => RelQuery Inclusive (R c (Q' q f)) [out] where- relRunQueryEntity (R (Q' q f')) world entity = do- let f :: QueryFilter = collect f'- res <- relRunQueryEntity @Inclusive (R @c Any) world entity+instance (Component c, Queryable q out) => RelQuery False (R c (Q' q QueryFilter)) [From out] where+ relRunQueryEntity (R (Q' !q !f)) !world !entity = do+ !res <- relRunQueryEntity @False (R @c Any) world entity case fmap- ( traverse $ \r -> do- t <- filterQueryIO f world r.target+ ( traverse $ \(!r) -> do+ !t <- filterEntity f world r.target if t then- runQueryEntity q world r.target+ fmap (From r.target) <$> runQueryEntity q world r.target else pure Nothing ) res of Nothing -> pure Nothing- Just x -> do- x <- x+ Just !x -> do+ !x <- x pure $ case catMaybes x of [] -> Nothing- x -> Just x+ !x -> Just x - relRunQueryInternal (R (Q' q f')) archetypes world = do- let f :: QueryFilter = collect f'- res <- relRunQueryInternal @Inclusive (R @c Any) archetypes world- for res $ \(e, b, rels) -> do- a <-+ relRunQueryInternal (R (Q' !q !f)) !archetypes !world = do+ !res <- relRunQueryInternal @False (R @c Any) archetypes world+ for res $ \(!e, !b, !rels) -> do+ !a <- catMaybes- <$> ( traverse $ \r -> do- t <- filterQueryIO f world r.target+ <$> ( traverse $ \(!r) -> do+ !t <- filterEntity f world r.target if t then- runQueryEntity q world r.target+ fmap (From r.target) <$> runQueryEntity q world r.target else pure Nothing ) rels pure $ case a of [] -> (e, False, undefined)- x -> (e, b, x)+ !x -> (e, b, x) relQueryTypes _ = Set.singleton (typeRep $ Proxy @c, RelQ) -instance (Component c, Queryable q out) => RelQuery Exclusive (R c (Q q)) out where- relRunQueryEntity (R (Q q)) world entity = do- res <- relRunQueryEntity @Exclusive (R @c Any) world entity+instance (Component c, Queryable q out) => RelQuery True (R c (Q q)) (From out) where+ relRunQueryEntity (R (Q !q)) !world !entity = do+ !res <- relRunQueryEntity @True (R @c Any) world entity - case fmap (\r -> runQueryEntity q world r.target) res of+ case fmap (\(!r) -> (r.target, runQueryEntity q world r.target)) res of Nothing -> pure Nothing- Just x -> x- relRunQueryInternal (R (Q q)) archetypes world = do- res <- relRunQueryInternal @Exclusive (R @c Any) archetypes world- for res $ \(e, b, rels) -> do- r <- (\r -> runQueryEntity q world r.target) rels+ Just (!e, !x) -> fmap (From e) <$> x+ relRunQueryInternal (R (Q !q)) !archetypes !world = do+ !res <- relRunQueryInternal @True (R @c Any) archetypes world+ for res $ \(!e, !b, !rels) -> do+ !r <- (\(!r) -> (,r.target) <$> runQueryEntity q world r.target) rels+ pure $ case r of- Nothing -> (e, False, undefined)- Just x -> (e, b, x)+ (Nothing, _) -> (e, False, undefined)+ (Just !x, !e') -> (e, b, From e' x) relQueryTypes _ = Set.singleton (typeRep $ Proxy @c, RelQ) -instance (Component c, Queryable q out, Collectable f QueryFilter) => RelQuery Exclusive (R c (Q' q f)) out where- relRunQueryEntity (R (Q' q f')) world entity = do- let f :: QueryFilter = collect f'- res <- relRunQueryEntity @Exclusive (R @c Any) world entity+instance (Component c, Queryable q out) => RelQuery True (R c (Q' q QueryFilter)) (From out) where+ relRunQueryEntity (R (Q' !q !f)) !world !entity = do+ !res <- relRunQueryEntity @True (R @c Any) world entity case fmap- ( \r -> do- t <- filterQueryIO f world r.target+ ( \(!r) -> do+ !t <- filterEntity f world r.target if t then- runQueryEntity q world r.target+ fmap (From r.target) <$> runQueryEntity q world r.target else pure Nothing ) res of Nothing -> pure Nothing- Just x -> x- relRunQueryInternal (R (Q' q f')) archetypes world = do- let f :: QueryFilter = collect f'- res <- relRunQueryInternal @Exclusive (R @c Any) archetypes world- for res $ \(e, b, rels) -> do- r <-- ( \r -> do- t <- filterQueryIO f world r.target+ Just !x -> x+ relRunQueryInternal (R (Q' q !f)) !archetypes !world = do+ !res <- relRunQueryInternal @True (R @c Any) archetypes world+ for res $ \(!e, !b, !rels) -> do+ !r <-+ ( \(!r) -> do+ !t <- filterEntity f world r.target if t then- runQueryEntity q world r.target+ fmap (From r.target) <$> runQueryEntity q world r.target else pure Nothing ) rels pure $ case r of Nothing -> (e, False, undefined)- Just x -> (e, b, x)+ Just !x -> (e, b, x) relQueryTypes _ = Set.singleton (typeRep $ Proxy @c, RelQ) -instance (Component c) => Queryable (R c Entity) (Result (Rel c)) where- runQueryEntity (R target) world entity = do- res <- tryGetEntityRel @c target world entity+instance (Component c) => Queryable (R c Entity) (Rel c) where+ runQueryEntity (R !target) !world !entity = do+ !res <- tryGetEntityRel @c target world entity return $ case res of- Just (Just x) -> Just $ Result (Rel x target, entity)+ Just (Just !x) -> Just (Rel x target) _ -> Nothing - runQueryInternal (R target) archetypes world = map (\(a, b) -> (a, True, b)) <$> tryGetRels @c target world archetypes+ runQueryInternal (R !target) archetypes world = map (\(!a, !b) -> (a, True, b)) <$> tryGetRels @c target world archetypes queryTypes (R target) = Set.singleton (typeRep $ Proxy @c, RelQ' target) -instance (RelQuery (RelExclusivity c) (R c Any) out) => Queryable (R c Any) out where- runQueryEntity = relRunQueryEntity @(RelExclusivity c)+instance (RelQuery (IsExclusiveRel c) (R c Any) out) => Queryable (R c Any) out where+ runQueryEntity = relRunQueryEntity @(IsExclusiveRel c) - runQueryInternal = relRunQueryInternal @(RelExclusivity c)+ runQueryInternal = relRunQueryInternal @(IsExclusiveRel c) - queryTypes = relQueryTypes @(RelExclusivity c)+ queryTypes = relQueryTypes @(IsExclusiveRel c) -instance (RelQuery (RelExclusivity c) (R c (Q q)) out) => Queryable (R c (Q q)) out where- runQueryEntity = relRunQueryEntity @(RelExclusivity c)+instance (RelQuery (IsExclusiveRel c) (R c (Q q)) out) => Queryable (R c (Q q)) out where+ runQueryEntity = relRunQueryEntity @(IsExclusiveRel c) - runQueryInternal = relRunQueryInternal @(RelExclusivity c)+ runQueryInternal = relRunQueryInternal @(IsExclusiveRel c) - queryTypes = relQueryTypes @(RelExclusivity c)+ queryTypes = relQueryTypes @(IsExclusiveRel c) -instance (RelQuery (RelExclusivity c) (R c (Q' q f)) out) => Queryable (R c (Q' q f)) out where- runQueryEntity = relRunQueryEntity @(RelExclusivity c)+instance (RelQuery (IsExclusiveRel c) (R c (Q' q f)) out) => Queryable (R c (Q' q f)) out where+ runQueryEntity = relRunQueryEntity @(IsExclusiveRel c) - runQueryInternal = relRunQueryInternal @(RelExclusivity c)+ runQueryInternal = relRunQueryInternal @(IsExclusiveRel c) - queryTypes = relQueryTypes @(RelExclusivity c)+ queryTypes = relQueryTypes @(IsExclusiveRel c) -instance (RelQuery Inclusive (R c e) out) => Queryable (R' c e) out where- runQueryEntity (R' e) = relRunQueryEntity @Inclusive (R @c e)- runQueryInternal (R' e) = relRunQueryInternal @Inclusive (R @c e)- queryTypes (R' e) = relQueryTypes @Inclusive (R @c e)+instance (RelQuery False (R c e) out) => Queryable (R' c e) out where+ runQueryEntity (R' !e) = relRunQueryEntity @False (R @c e)+ runQueryInternal (R' !e) = relRunQueryInternal @False (R @c e)+ queryTypes (R' !e) = relQueryTypes @False (R @c e) -instance (Component c) => Queryable (M c) (Maybe (Result c)) where- runQueryEntity _ world entity = do- res <- tryGetEntityComponent @c world entity+instance (Component c) => Queryable (M c) (Maybe c) where+ runQueryEntity _ !world !entity = do+ !res <- tryGetEntityComponent @c world entity case res of- Nothing -> return Nothing- Just Nothing -> return $ Just Nothing- Just (Just x) -> return $ Just $ Just $ Result (x, entity)+ Nothing -> pure Nothing+ Just Nothing -> pure $ Just Nothing+ Just (Just !x) -> pure $ Just $ Just x - runQueryInternal _ archetypes world = map (\(a, b) -> (a, True, b)) <$> tryGetComponentsMaybe @c world archetypes+ runQueryInternal _ !archetypes !world = map (\(!a, !b) -> (a, True, b)) <$> tryGetComponentsMaybe @c world archetypes queryTypes _ = Set.empty -instance (Component c) => Queryable (MR c Entity) (Maybe (Result (Rel c))) where- runQueryEntity (MR target) world entity = do- res <- tryGetEntityRel @c target world entity- return $ case res of+instance (Component c) => Queryable (MR c Entity) (Maybe (Rel c)) where+ runQueryEntity (MR !target) !world !entity = do+ !res <- tryGetEntityRel @c target world entity+ pure $ case res of Nothing -> Nothing Just Nothing -> Just Nothing- Just (Just x) -> Just $ Just (Result (Rel x target, entity))+ Just (Just !x) -> Just $ Just (Rel x target) - runQueryInternal (MR target) archetypes world = map (\(a, b) -> (a, True, b)) <$> tryGetRelsMaybe @c target world archetypes+ runQueryInternal (MR !target) !archetypes !world = map (\(!a, !b) -> (a, True, b)) <$> tryGetRelsMaybe @c target world archetypes queryTypes _ = Set.empty -instance (Component c) => RelQuery Inclusive (MR c Any) (Maybe [Result (Rel c)]) where+instance (Component c) => RelQuery False (MR c Any) (Maybe [Rel c]) where relRunQueryEntity _ = tryGetEntityRelCollection @c - relRunQueryInternal _ archetypes world = do- x <- tryGetRelCollections @c world archetypes- return $ flip map x $ \(e, x) ->+ relRunQueryInternal _ !archetypes !world = do+ !x <- tryGetRelCollections @c world archetypes+ pure $ flip map x $ \(!e, !x) -> case x of [] -> (e, True, Nothing)- x -> (e, True, Just x)+ !x -> (e, True, Just x) relQueryTypes _ = Set.empty -instance (Component c) => RelQuery Exclusive (MR c Any) (Maybe (Result (Rel c))) where- relRunQueryEntity _ world entity = do- res <- tryGetEntityRelCollection @c world entity+instance (Component c) => RelQuery True (MR c Any) (Maybe (Rel c)) where+ relRunQueryEntity _ !world !entity = do+ !res <- tryGetEntityRelCollection @c world entity pure $ case res of- Just (Just [x]) -> Just (Just x)+ Just (Just [!x]) -> Just (Just x) Just (Just _) -> undefined Just Nothing -> Just Nothing Nothing -> Nothing - relRunQueryInternal _ archetypes world = do- x <- tryGetRelCollections @c world archetypes- return $ flip map x $ \(e, x) ->+ relRunQueryInternal _ !archetypes !world = do+ !x <- tryGetRelCollections @c world archetypes+ pure $ flip map x $ \(!e, !x) -> case x of [] -> (e, True, Nothing)- [x] -> (e, True, Just x)+ [!x] -> (e, True, Just x) _ -> undefined relQueryTypes _ = Set.empty -instance (Component c, Queryable q out) => RelQuery Inclusive (MR c (Q q)) (Maybe [out]) where- relRunQueryEntity (MR (Q q)) world entity = do- res <- relRunQueryEntity @Inclusive (MR @c Any) world entity+instance (Component c, Queryable q out) => RelQuery False (MR c (Q q)) (Maybe [From out]) where+ relRunQueryEntity (MR (Q !q)) !world !entity = do+ !res <- relRunQueryEntity @False (MR @c Any) world entity case res of Nothing -> pure Nothing Just Nothing -> pure $ Just Nothing- Just (Just r) ->+ Just (Just !r) -> Just <$> do- let targets = map (\x -> x.target) r- results <- catMaybes <$> for targets (runQueryEntity q world)+ let !targets = map (\(!x) -> x.target) r+ !results <- catMaybes <$> for targets (\(!x) -> fmap (From x) <$> runQueryEntity q world x) case results of [] -> pure Nothing- x -> pure $ Just x+ !x -> pure $ Just x - relRunQueryInternal (MR (Q q)) archetypes world = do- res <- relRunQueryInternal @Inclusive (MR @c Any) archetypes world- for res $ \(e, b, r) -> do+ relRunQueryInternal (MR (Q !q)) !archetypes !world = do+ !res <- relRunQueryInternal @False (MR @c Any) archetypes world+ for res $ \(!e, !b, !r) -> do case r of Nothing -> pure (e, b, Nothing)- Just r -> do- rs <- for r (\x -> runQueryEntity q world x.target)+ Just !r -> do+ !rs <- for r (\(!x) -> fmap (From x.target) <$> runQueryEntity q world x.target) case catMaybes rs of [] -> pure (e, b, Nothing)- x -> pure (e, b, Just x)+ !x -> pure (e, b, Just x) relQueryTypes _ = Set.empty -instance (Component c, Queryable q out, Collectable f QueryFilter) => RelQuery Inclusive (MR c (Q' q f)) (Maybe [out]) where- relRunQueryEntity (MR (Q' q f')) world entity = do- let f :: QueryFilter = collect f'- res <- relRunQueryEntity @Inclusive (MR @c Any) world entity+instance (Component c, Queryable q out) => RelQuery False (MR c (Q' q QueryFilter)) (Maybe [From out]) where+ relRunQueryEntity (MR (Q' !q !f)) !world !entity = do+ !res <- relRunQueryEntity @False (MR @c Any) world entity case res of Nothing -> pure Nothing Just Nothing -> pure $ Just Nothing- Just (Just r) ->+ Just (Just !r) -> Just <$> do- let targets = map (\x -> x.target) r- results <-+ let !targets = map (\(!x) -> x.target) r+ !results <- catMaybes <$> for targets- ( \t -> do- b <- filterQueryIO f world t+ ( \(!t) -> do+ !b <- filterEntity f world t if b then- runQueryEntity q world t+ fmap (From t) <$> runQueryEntity q world t else pure Nothing ) case results of [] -> pure Nothing- x -> pure $ Just x+ !x -> pure $ Just x - relRunQueryInternal (MR (Q' q f')) archetypes world = do- let f :: QueryFilter = collect f'- res <- relRunQueryInternal @Inclusive (MR @c Any) archetypes world- for res $ \(e, b, r) -> do+ relRunQueryInternal (MR (Q' !q !f)) !archetypes !world = do+ !res <- relRunQueryInternal @False (MR @c Any) archetypes world+ for res $ \(!e, !b, !r) -> do case r of Nothing -> pure (e, b, Nothing)- Just r -> do- rs <-+ Just !r -> do+ !rs <- for r- ( \x -> do- t <- filterQueryIO f world x.target+ ( \(!x) -> do+ !t <- filterEntity f world x.target if t then- runQueryEntity q world x.target+ fmap (From x.target) <$> runQueryEntity q world x.target else pure Nothing ) case catMaybes rs of [] -> pure (e, b, Nothing)- x -> pure (e, b, Just x)+ !x -> pure (e, b, Just x) relQueryTypes _ = Set.empty -instance (Component c, Queryable q out) => RelQuery Exclusive (MR c (Q q)) (Maybe out) where- relRunQueryEntity (MR (Q q)) world entity = do- res <- relRunQueryEntity @Exclusive (MR @c Any) world entity+instance (Component c, Queryable q out) => RelQuery True (MR c (Q q)) (Maybe (From out)) where+ relRunQueryEntity (MR (Q !q)) !world !entity = do+ !res <- relRunQueryEntity @True (MR @c Any) world entity case res of Nothing -> pure Nothing Just Nothing -> pure $ Just Nothing- Just (Just x) -> Just <$> runQueryEntity q world x.target+ Just (Just !x) -> Just . fmap (From x.target) <$> runQueryEntity q world x.target - relRunQueryInternal (MR (Q q)) archetypes world = do- res <- relRunQueryInternal @Exclusive (MR @c Any) archetypes world- for res $ \(e, b, r) -> do+ relRunQueryInternal (MR (Q !q)) !archetypes !world = do+ !res <- relRunQueryInternal @True (MR @c Any) archetypes world+ for res $ \(!e, !b, !r) -> do case r of Nothing -> pure (e, b, Nothing)- Just r -> do- res <- runQueryEntity q world r.target+ Just !r -> do+ !res <- fmap (From r.target) <$> runQueryEntity q world r.target pure (e, b, res) relQueryTypes _ = Set.empty -instance (Component c, Queryable q out, Collectable f QueryFilter) => RelQuery Exclusive (MR c (Q' q f)) (Maybe out) where- relRunQueryEntity (MR (Q' q f')) world entity = do- let f :: QueryFilter = collect f'- res <- relRunQueryEntity @Exclusive (MR @c Any) world entity+instance (Component c, Queryable q out) => RelQuery True (MR c (Q' q QueryFilter)) (Maybe (From out)) where+ relRunQueryEntity (MR (Q' !q !f)) world entity = do+ !res <- relRunQueryEntity @True (MR @c Any) world entity case res of Nothing -> pure Nothing Just Nothing -> pure $ Just Nothing- Just (Just x) -> do- t <- filterQueryIO f world x.target+ Just (Just !x) -> do+ !t <- filterEntity f world x.target if t then- Just <$> runQueryEntity q world x.target+ Just . fmap (From x.target) <$> runQueryEntity q world x.target else pure (Just Nothing) - relRunQueryInternal (MR (Q' q f')) archetypes world = do- let f :: QueryFilter = collect f'- res <- relRunQueryInternal @Exclusive (MR @c Any) archetypes world- for res $ \(e, b, r) -> do+ relRunQueryInternal (MR (Q' !q !f)) !archetypes !world = do+ !res <- relRunQueryInternal @True (MR @c Any) archetypes world+ for res $ \(!e, !b, !r) -> do case r of Nothing -> pure (e, b, Nothing)- Just r -> do- res <- do- t <- filterQueryIO f world r.target+ Just !r -> do+ !res <- do+ !t <- filterEntity f world r.target if t then- runQueryEntity q world r.target+ fmap (From r.target) <$> runQueryEntity q world r.target else pure Nothing pure (e, b, res) relQueryTypes _ = Set.empty -instance (RelQuery (RelExclusivity c) (MR c Any) out) => Queryable (MR c Any) out where- runQueryEntity = relRunQueryEntity @(RelExclusivity c)+instance (RelQuery (IsExclusiveRel c) (MR c Any) out) => Queryable (MR c Any) out where+ runQueryEntity = relRunQueryEntity @(IsExclusiveRel c) - runQueryInternal = relRunQueryInternal @(RelExclusivity c)+ runQueryInternal = relRunQueryInternal @(IsExclusiveRel c) queryTypes _ = Set.empty -instance (RelQuery (RelExclusivity c) (MR c (Q q)) out) => Queryable (MR c (Q q)) out where- runQueryEntity = relRunQueryEntity @(RelExclusivity c)+instance (RelQuery (IsExclusiveRel c) (MR c (Q q)) out) => Queryable (MR c (Q q)) out where+ runQueryEntity = relRunQueryEntity @(IsExclusiveRel c) - runQueryInternal = relRunQueryInternal @(RelExclusivity c)+ runQueryInternal = relRunQueryInternal @(IsExclusiveRel c) queryTypes _ = Set.empty -instance (RelQuery (RelExclusivity c) (MR c (Q' q f)) out) => Queryable (MR c (Q' q f)) out where- runQueryEntity = relRunQueryEntity @(RelExclusivity c)+instance (RelQuery (IsExclusiveRel c) (MR c (Q' q f)) out) => Queryable (MR c (Q' q f)) out where+ runQueryEntity = relRunQueryEntity @(IsExclusiveRel c) - runQueryInternal = relRunQueryInternal @(RelExclusivity c)+ runQueryInternal = relRunQueryInternal @(IsExclusiveRel c) queryTypes _ = Set.empty instance (Component c) => Queryable (Has c) Bool where- runQueryEntity _ world entity = do- list <- runQueryEntity (M @c) world entity+ runQueryEntity _ !world !entity = do+ !list <- runQueryEntity (M @c) world entity case list of Nothing -> return Nothing- Just x -> return $ Just (isJust x)+ Just !x -> pure $ Just (isJust x) - runQueryInternal _ archetypes world = do- list <- runQueryInternal (M @c) archetypes world- return $ map (Data.Bifunctor.second isJust) list+ runQueryInternal _ !archetypes !world = do+ !list <- runQueryInternal (M @c) archetypes world+ pure $ map (Data.Bifunctor.second isJust) list queryTypes _ = Set.empty instance (Component c) => Queryable (HasR c Any) Bool where- runQueryEntity _ world entity = do- res <- tryGetEntityRelCollection @c world entity- return $ case res of+ runQueryEntity _ !world !entity = do+ !res <- tryGetEntityRelCollection @c world entity+ pure $ case res of Nothing -> Nothing Just Nothing -> Just False Just (Just _) -> Just True - runQueryInternal _ archetypes world = do- res <- tryGetRelCollections @c world archetypes- return $ map ((\(a, b) -> (a, True, b)) . Data.Bifunctor.second null) res+ runQueryInternal _ !archetypes !world = do+ !res <- tryGetRelCollections @c world archetypes+ pure $ map ((\(!a, !b) -> (a, True, b)) . Data.Bifunctor.second null) res queryTypes _ = Set.empty instance (Component c) => Queryable (HasR c Entity) Bool where- runQueryEntity (HasR target) world entity = do- res <- tryGetEntityRel @c target world entity- return $ case res of+ runQueryEntity (HasR !target) !world !entity = do+ !res <- tryGetEntityRel @c target world entity+ pure $ case res of Nothing -> Nothing Just Nothing -> Just False Just (Just _) -> Just True - runQueryInternal (HasR target) archetypes world = do- res <- runQueryInternal (MR @c target) archetypes world- return $ map (Data.Bifunctor.second isNothing) res+ runQueryInternal (HasR !target) !archetypes !world = do+ !res <- runQueryInternal (MR @c target) archetypes world+ pure $ map (Data.Bifunctor.second isNothing) res queryTypes _ = Set.empty instance (Component c, Queryable q out) => Queryable (HasR c (Q q)) Bool where- runQueryEntity (HasR (Q q)) world entity = do- res <- tryGetEntityRelCollection @c world entity+ runQueryEntity (HasR (Q !q)) !world !entity = do+ !res <- tryGetEntityRelCollection @c world entity case res of Nothing -> pure Nothing Just Nothing -> pure $ Just False- Just (Just r) -> do- res <- catMaybes <$> for r (\r -> runQueryEntity q world r.target)+ Just (Just !r) -> do+ !res <- catMaybes <$> for r (\(!r) -> runQueryEntity q world r.target) case res of [] -> pure $ Just False _ -> pure $ Just True - runQueryInternal (HasR (Q q)) archetypes world = do- res <- relRunQueryInternal @Inclusive (MR @c Any) archetypes world- for res $ \(e, b, r) -> do+ runQueryInternal (HasR (Q !q)) !archetypes !world = do+ !res <- relRunQueryInternal @False (MR @c Any) archetypes world+ for res $ \(!e, !b, !r) -> do case r of Nothing -> pure (e, b, False)- Just r -> do- res <- catMaybes <$> for r (\r -> runQueryEntity q world r.target)+ Just !r -> do+ !res <- catMaybes <$> for r (\(!r) -> runQueryEntity q world r.target) case res of [] -> pure (e, b, False) _ -> pure (e, b, True) queryTypes _ = Set.empty -instance (Component c, Queryable q out, Collectable f QueryFilter) => Queryable (HasR c (Q' q f)) Bool where- runQueryEntity (HasR (Q' q f')) world entity = do- let f :: QueryFilter = collect f'- res <- tryGetEntityRelCollection @c world entity+instance (Component c, Queryable q out) => Queryable (HasR c (Q' q QueryFilter)) Bool where+ runQueryEntity (HasR (Q' !q !f)) !world !entity = do+ !res <- tryGetEntityRelCollection @c world entity case res of Nothing -> pure Nothing Just Nothing -> pure $ Just False- Just (Just r) -> do- res <-+ Just (Just !r) -> do+ !res <- catMaybes <$> for r- ( \r -> do- t <- filterQueryIO f world r.target+ ( \(!r) -> do+ !t <- filterEntity f world r.target if t then runQueryEntity q world r.target@@ -531,19 +588,18 @@ [] -> pure $ Just False _ -> pure $ Just True - runQueryInternal (HasR (Q' q f')) archetypes world = do- let f :: QueryFilter = collect f'- res <- relRunQueryInternal @Inclusive (MR @c Any) archetypes world- for res $ \(e, b, r) -> do+ runQueryInternal (HasR (Q' !q !f)) !archetypes !world = do+ !res <- relRunQueryInternal @False (MR @c Any) archetypes world+ for res $ \(!e, !b, !r) -> do case r of Nothing -> pure (e, b, False)- Just r -> do- res <-+ Just !r -> do+ !res <- catMaybes <$> for r- ( \r -> do- t <- filterQueryIO f world r.target+ ( \(!r) -> do+ !t <- filterEntity f world r.target if t then runQueryEntity q world r.target@@ -555,130 +611,151 @@ queryTypes _ = Set.empty -instance (Queryable qd out, Mappable MapQueryVal out out') => Queryable (Val qd) out' where- runQueryEntity (Val qd) b c = do- x <- runQueryEntity qd b c- return $ fmap (mapTuple @MapQueryVal) x- runQueryInternal (Val qd) b c = do- x <- runQueryInternal qd b c- return $ map (\(a, b, c) -> (a, b, mapTuple @MapQueryVal c)) x-- queryTypes (Val qd) = queryTypes qd--filterQuery :: (MonadSystem w m) => QueryFilter -> Entity -> m Bool-filterQuery NoFilter _ = pure True-filterQuery (QFWith (x, Nothing)) entity = do- world <- unsafeGetWorld- comp <- liftIO $ getComponentId x world.components+filterEntity' :: FilterComponent -> Entity -> World -> IO Bool+filterEntity' (FilterComponent (!x, Nothing, Nothing)) !entity !world = do+ !comp <- getComponentId x world.components case comp of- Nothing -> return False- Just (ComponentId (# id, _ #)) -> do- Just (ComponentType (_ :: Proxy a)) <- liftIO $ runQueryEntity (Val (C @ComponentType)) world (Entity (# id, 0## #))- a <- liftIO $ runQueryEntity (Has @a) world entity+ Nothing -> pure False+ Just (ComponentId (# !id, _ #)) -> do+ Just (ComponentType (_ :: Proxy a)) <- runQueryEntity (C @ComponentType) world (Entity (# id, 0## #))+ !a <- runQueryEntity (Has @a) world entity pure $ fromMaybe False a-filterQuery (QFWith (x, Just e)) entity = do- world <- unsafeGetWorld- comp <- liftIO $ getComponentId x world.components+filterEntity' (FilterComponent (!x, Just !e, Nothing)) !entity !world = do+ !comp <- getComponentId x world.components case comp of Nothing -> return False- Just (ComponentId (# id, _ #)) -> do- Just (ComponentType (_ :: Proxy a)) <- liftIO $ runQueryEntity (Val (C @ComponentType)) world (Entity (# id, 0## #))- a <- liftIO $ runQueryEntity (HasR @a e) world entity+ Just (ComponentId (# !id, _ #)) -> do+ Just (ComponentType (_ :: Proxy a)) <- runQueryEntity (C @ComponentType) world (Entity (# id, 0## #))+ !a <- runQueryEntity (HasR @a e) world entity pure $ fromMaybe False a-filterQuery (QFWithRelAny x) entity = do- world <- unsafeGetWorld- comp <- liftIO $ getComponentId x world.components+filterEntity' (FilterComponent (!x, _, Just _)) !entity !world = do+ !comp <- liftIO $ getComponentId x world.components case comp of Nothing -> return False- Just (ComponentId (# id, _ #)) -> do- Just (ComponentType (_ :: Proxy a)) <- liftIO $ runQueryEntity (Val (C @ComponentType)) world (Entity (# id, 0## #))- a <- liftIO $ runQueryEntity (HasR @a Any) world entity+ Just (ComponentId (# !id, _ #)) -> do+ Just (ComponentType (_ :: Proxy a)) <- liftIO $ runQueryEntity (C @ComponentType) world (Entity (# id, 0## #))+ !a <- liftIO $ runQueryEntity (HasR @a Any) world entity pure $ fromMaybe False a-filterQuery (QFChanged (x, Nothing) f) entity = do- world <- unsafeGetWorld- comp <- liftIO $ getComponentId x world.components- case comp of- Nothing -> return False- Just comp -> do- addedChanged' f comp entity-filterQuery (QFChanged (x, Just e) f) entity = do++filterEntity :: QueryFilter -> World -> Entity -> IO Bool+filterEntity f world entity = do+ !pointer <- getPointer entity world.entities++ case pointer of+ Nothing -> pure False+ Just !x -> do+ (EntityPointer (# !archetypeId, _ #)) <- readIORef x+ let Tables tables = world.tables+ table <- Vec.unsafeRead tables (I# archetypeId)+ filterArchetype f world table++filterArchetype :: QueryFilter -> World -> Table -> IO Bool+filterArchetype NoFilter _ _ = pure True+filterArchetype (With !a) !b !t = filterArchetype' (toFilterComponent a) b t.components+filterArchetype (Without !a) !b !t = not <$> filterArchetype' (toFilterComponent a) b t.components+filterArchetype (Changed a f1 f2) !b !t = do+ map <- readIORef t.ticks+ runSystem (addedChanged (qfChangedF f1 f2) (toFilterComponent a) map t) b+filterArchetype (And !a0 !a1) !b !t = (&&) <$> filterArchetype a0 b t <*> filterArchetype a1 b t+filterArchetype (Or !a0 !a1) !b !t = (||) <$> filterArchetype a0 b t <*> filterArchetype a1 b t++addedChanged :: forall m w. (MonadSystem w m) => (ComponentTick -> Tick -> Tick -> Bool) -> FilterComponent -> Map ComponentId ComponentTick -> Table -> m Bool+addedChanged !f (FilterComponent (!c, Nothing, Nothing)) m _ = do+ !world <- unsafeGetWorld+ !id <- liftIO $ getComponentId c world.components+ case id of+ Nothing -> pure False+ Just !id -> do+ -- !ticks <- liftIO $ tryGetEntityTicks e id world+ let ticks = Map.lookup id m+ case ticks of+ Nothing -> pure False+ Just !ticks -> do+ (!lastSystemTick, !currentSystemTick) <- liftIO $ getSystemTicksInternal world+ pure $ f ticks lastSystemTick currentSystemTick+addedChanged !f (FilterComponent (!c, Just !entity, Nothing)) !e _ = do world <- unsafeGetWorld- comp <- liftIO $ getComponentId x world.components- case comp of- Nothing -> return False- Just comp -> do- addedChanged' f (setCompIdTarget (Just e) comp) entity-filterQuery (QFChangedRelAny x f) entity = do+ !id <- liftIO $ getComponentId c world.components+ case id of+ Nothing -> pure False+ Just (ComponentId (# !id, _ #)) -> do+ let ticks = Map.lookup (ComponentId (# id, Just entity #)) e+ case ticks of+ Nothing -> pure False+ Just !ticks -> do+ (!lastSystemTick, !currentSystemTick) <- liftIO $ getSystemTicksInternal world+ pure $ f ticks lastSystemTick currentSystemTick+addedChanged !f (FilterComponent (!c, _, Just _)) m t = do world <- unsafeGetWorld- comp <- liftIO $ getComponentId x world.components- case comp of+ !id <- liftIO $ getComponentId c world.components+ case id of Nothing -> return False- Just (ComponentId (# id, _ #)) -> do- components <- liftIO $ findComponentsOfEntity world entity- case components of- Nothing -> return True- Just components' -> do- let components = filter (\(ComponentId (# id', a #)) -> isJust a && isTrue# (eqWord# id id')) components'- and <$> mapM (\c -> addedChanged' f c entity) components-filterQuery (QFCheckRaw (_, Nothing, ErasedCheck (f :: (c -> Bool)))) entity = do- w <- unsafeGetWorld- a <- liftIO $ runQueryEntity (C @c) w entity- pure $ case a of- Nothing -> False- Just a -> f $ value a-filterQuery (QFCheckRaw (_, Just e, ErasedCheck (f :: (c -> Bool)))) entity = do- world <- unsafeGetWorld- a <- liftIO $ runQueryEntity (R @c e) world entity- pure $ case a of- Nothing -> False- Just a -> f a.comp-filterQuery (QFCheckRawRelAny (_, ErasedCheck (f :: (c -> Bool)))) entity = do- world <- unsafeGetWorld- a <- liftIO $ runQueryEntity (R' @c Any) world entity- pure $ case a of- Nothing -> False- Just a -> any (\x -> f x.comp) a-filterQuery (a `QFAnd` b) entity = do- a <- filterQuery a entity- b <- filterQuery b entity- pure $ a && b-filterQuery (a `QFOr` b) entity = do- a <- filterQuery a entity- b <- filterQuery b entity- pure $ a || b-filterQuery (QFNot a) entity = do- a <- filterQuery a entity- pure $ not a+ Just (ComponentId (# !id, _ #)) -> do+ -- Just (ComponentType (_ :: Proxy a)) <- liftIO $ runQueryEntity (C @ComponentType) world (Entity (# id, 0## #)) + -- !rels <- single $ mkGet e (R' @a Any)++ anyM+ ( \(ComponentId (# id', e #)) -> do+ if not (isTrue# (eqWord# id' id))+ then+ pure False+ else do+ let ticks = Map.lookup (ComponentId (# id', e #)) m+ case ticks of+ Nothing -> pure False+ Just !ticks -> do+ (!lastSystemTick, !currentSystemTick) <- liftIO $ getSystemTicksInternal world+ pure $ f ticks lastSystemTick currentSystemTick+ )+ t.components++-- case rels of+-- Nothing -> pure False+-- Just !rels -> do+-- and+-- <$> for+-- rels+-- ( \(Rel _ !target) -> do+-- addedChanged f (FilterComponent (c, Just target, Nothing)) e+-- )++getSystemTicksInternal :: World -> IO (Tick, Tick)+getSystemTicksInternal !world = do+ let (SystemId !sys) = world.systemId+ Just !a <- runQueryEntity (C @LastSystemTick) world sys+ Just !b <- runQueryEntity (C @SystemTick) world sys+ pure (a.inner, b.inner)++-- filterEntity NoFilter _ _ = pure True+-- filterEntity (With !x) world entity = filterEntity' (toFilterComponent x) entity world+-- filterEntity (Without !x) world entity = not <$> filterEntity' (toFilterComponent x) entity world+-- filterEntity (Not !a) world entity = not <$> filterEntity a world entity+-- filterEntity (And !a !b) world entity = (&&) <$> filterEntity a world entity <*> filterEntity b world entity+-- filterEntity (Or !a !b) world entity = (||) <$> filterEntity a world entity <*> filterEntity b world entity+ findComponentsOfEntity :: World -> Entity -> IO (Maybe [ComponentId])-findComponentsOfEntity world entity = do- pointer <- getPointer entity world.entities+findComponentsOfEntity !world !entity = do+ !pointer <- getPointer entity world.entities case pointer of Nothing -> return Nothing- Just x -> do- (EntityPointer (# archetypeId, _ #)) <- readIORef x+ Just !x -> do+ (EntityPointer (# !archetypeId, _ #)) <- readIORef x - x <- Vec.read world.tables.inner (I# archetypeId)+ !x <- Vec.read world.tables.inner (I# archetypeId) pure $ Just x.components -addedChanged' :: forall m w. (MonadSystem w m) => (ComponentTicks -> Tick -> Tick -> Bool) -> ComponentId -> Entity -> m Bool-addedChanged' f id entity = do- world <- unsafeGetWorld- ticks <- liftIO $ tryGetEntityTicks entity id world- case ticks of- Nothing -> return False- Just ticks -> do- (lastSystemTick, currentSystemTick) <- liftIO $ getSystemTicksInternal world- return $ f ticks lastSystemTick currentSystemTick--getSystemTicksInternal :: World -> IO (Tick, Tick)-getSystemTicksInternal world = do- let (SystemId sys) = world.systemId- Just a <- runQueryEntity (C @LastSystemTick) world sys- Just b <- runQueryEntity (C @SystemTick) world sys- return (a.inner, b.inner)+-- addedChanged' :: forall m w. (MonadSystem w m) => (ComponentTicks -> Tick -> Tick -> Bool) -> ComponentId -> Entity -> m Bool+-- addedChanged' !f !id !entity = do+-- !world <- unsafeGetWorld+-- !ticks <- liftIO $ tryGetEntityTicks entity id world+-- case ticks of+-- Nothing -> pure False+-- Just !ticks -> do+-- (!lastSystemTick, !currentSystemTick) <- liftIO $ getSystemTicksInternal world+-- pure $ f ticks lastSystemTick currentSystemTick instance {-# OVERLAPPING #-} (Queryable q0 o0, Queryable q1 o1) => Queryable (q0, q1) (o0, o1) where runQueryEntity (q0, q1) world entity = do
src/Mischief/ECS/World/Query/TH.hs view
@@ -1,104 +1,95 @@ {-# OPTIONS_GHC -Wno-overlapping-patterns #-} -module Mischief.ECS.World.Query.TH (q, s, g) where+module Mischief.ECS.World.Query.TH (q, qd, qf) where import Control.Monad-import Control.Monad.IO.Class-import Data.Maybe import Data.Text (Text) import Data.Text qualified as T-import Data.Void import Language.Haskell.TH-import Language.Haskell.TH qualified import Language.Haskell.TH.Quote-import Language.Haskell.TH.Syntax-import Mischief.ECS.Collectable-import Mischief.ECS.Components-import Mischief.ECS.Components.Common hiding (Name)-import Mischief.ECS.Entities-import Mischief.ECS.Hidden-import Mischief.ECS.Log-import Mischief.ECS.Utils import Mischief.ECS.World.Query-import Mischief.ECS.World.Query.QueryFilter-import Mischief.ECS.World.Query.Queryable hiding (Q) import Mischief.ECS.World.Query.TH.Common import Mischief.ECS.World.Query.TH.QD-import Mischief.ECS.World.Query.TH.QF (Qf, pCheck, pQf, quoteQf)-import Text.Megaparsec (MonadParsec (eof), Parsec, choice, optional, parse, parseTest, runParserT, some, (<|>))+import Mischief.ECS.World.Query.TH.QF (Qf, pQf, quoteQf)+import Text.Megaparsec (MonadParsec (eof, try), optional, parse, some, (<|>)) import Text.Megaparsec.Char-import Text.Megaparsec.Char.Lexer qualified as L q :: QuasiQuoter q = QuasiQuoter- { quoteExp = \str -> do- let x = parse (whitespace *> pQuery <* eof) "inline_input" (T.pack str)+ { quoteExp = \(!str) -> do+ let !x = parse (whitespace *> (try pGet <|> pQuery) <* eof) "inline_input" (T.pack str) case x of- Left f -> error (show f)- Right x -> quoteQuery x,+ Left !f -> error (show f)+ Right !x -> quoteQuery x, quotePat = undefined, quoteType = undefined, quoteDec = undefined } -data Query = Query Qd (Maybe Qf) deriving (Show)+data QueryBuilder = QueryBuilder Qd (Maybe Qf) (Maybe Text) deriving (Show) -pQuery :: Parser Query+pQuery :: Parser QueryBuilder pQuery = do- qd <- pQd+ !qd <- pQd whitespace - qf <- optional $ do+ !qf <- optional $ do void $ char '/' whitespace pQf - pure $ Query qd qf+ pure $ QueryBuilder qd qf Nothing -quoteQuery :: Query -> Q Exp-quoteQuery (Query qd Nothing) = AppE (VarE 'query) <$> quoteQd qd-quoteQuery (Query qd (Just qf)) = do- qd <- quoteQd qd- qf <- quoteQf qf- return $ AppE (AppE (VarE 'query') qd) qf+pGet :: Parser QueryBuilder+pGet = do+ !name <- T.pack <$> some alphaNumChar+ whitespace -s :: QuasiQuoter-s =+ void $ char '.'+ whitespace++ QueryBuilder !a !b _ <- pQuery+ pure $ QueryBuilder a b (Just name)++quoteQuery :: QueryBuilder -> Q Exp+quoteQuery (QueryBuilder !qd Nothing Nothing) = AppE (VarE 'mkQuery) <$> quoteQd qd+quoteQuery (QueryBuilder !qd Nothing (Just !e)) = do+ !e <- getValueName e+ !qd <- quoteQd qd+ pure $ AppE (AppE (VarE 'mkGet) (VarE e)) qd+quoteQuery (QueryBuilder !qd (Just !qf) Nothing) = do+ !qd <- quoteQd qd+ !qf <- quoteQf qf+ pure $ AppE (AppE (VarE 'mkQuery') qd) qf+quoteQuery (QueryBuilder !qd (Just !qf) (Just !e)) = do+ !e <- getValueName e+ !qd <- quoteQd qd+ !qf <- quoteQf qf+ pure $ AppE (AppE (AppE (VarE 'mkGet') (VarE e)) qd) qf++qf :: QuasiQuoter+qf = QuasiQuoter- { quoteExp = \str -> do- let x = parse (whitespace *> pQuery <* eof) "inline_input" (T.pack str)+ { quoteExp = \(!str) -> do+ let !x = parse (whitespace *> pQf <* eof) "inline_input" (T.pack str) case x of- Left f -> error (show f)- Right x -> quoteSingle x,+ Left !f -> error (show f)+ Right !x -> quoteQf x, quotePat = undefined, quoteType = undefined, quoteDec = undefined } -quoteSingle :: Query -> Q Exp-quoteSingle (Query qd Nothing) = AppE (VarE 'single) <$> quoteQd qd-quoteSingle (Query qd (Just qf)) = do- qd <- quoteQd qd- qf <- quoteQf qf- return $ AppE (AppE (VarE 'single') qd) qf--g :: QuasiQuoter-g =+qd :: QuasiQuoter+qd = QuasiQuoter- { quoteExp = \str -> do- let x = parse (whitespace *> pQuery <* eof) "inline_input" (T.pack str)+ { quoteExp = \(!str) -> do+ let !x = parse (whitespace *> pQd <* eof) "inline_input" (T.pack str) case x of- Left f -> error (show f)- Right x -> quoteGet x,+ Left !f -> error (show f)+ Right !x -> quoteQd x, quotePat = undefined, quoteType = undefined, quoteDec = undefined }--quoteGet :: Query -> Q Exp-quoteGet (Query qd Nothing) = AppE (VarE 'get) <$> quoteQd qd-quoteGet (Query qd (Just qf)) = do- qd <- quoteQd qd- qf <- quoteQf qf- return $ AppE (AppE (VarE 'get') qd) qf
src/Mischief/ECS/World/Query/TH/Common.hs view
@@ -1,42 +1,31 @@ module Mischief.ECS.World.Query.TH.Common where -import Control.Monad-import Control.Monad.IO.Class import Data.Maybe import Data.Text (Text) import Data.Text qualified as T import Data.Void import Language.Haskell.Meta.Parse as M import Language.Haskell.TH-import Language.Haskell.TH qualified-import Language.Haskell.TH.Quote-import Language.Haskell.TH.Syntax-import Mischief.ECS.Components (Component)-import Mischief.ECS.World.Query import Mischief.ECS.World.Query.Markers hiding (Q)-import Mischief.ECS.World.Query.Markers qualified as Markers-import Mischief.ECS.World.Query.QueryFilter-import Mischief.ECS.World.Query.Queryable-import Mischief.ECS.World.Query.Queryable qualified as Queryable-import Text.Megaparsec (MonadParsec (eof, lookAhead, notFollowedBy, try), Parsec, choice, many, manyTill, noneOf, optional, parseTest, some, (<|>))+import Text.Megaparsec (Parsec, many, optional, (<|>)) import Text.Megaparsec.Char import Text.Megaparsec.Char.Lexer qualified as L -data CompType = Single | Pair Text | PairAny deriving (Show)+data CompType = Single | Pair Text | PairAny | Resource deriving (Show) type Parser = Parsec Void Text pTup :: Parser a -> Parser [a]-pTup p = do- r <- optional p+pTup !p = do+ !r <- optional p whitespace - comma <- optional $ string ","+ !comma <- optional $ string "," whitespace case r of Nothing -> return []- Just r -> do+ Just !r -> do case comma of (Just _) -> ([r] ++) <$> pTup p _ -> return [r]@@ -53,38 +42,45 @@ pNameRec :: Parser Text pNameRec = do- s <- T.pack <$> many (alphaNumChar <|> (' ' <$ space1) <|> char ',')- o <- optional $ (char '(' *> whitespace) *> pNameRec <* (char ')' *> whitespace)+ !s <- T.pack <$> many (alphaNumChar <|> (' ' <$ space1) <|> char ',')+ whitespace + !o <- optional $ (char '(' *> whitespace) *> pNameRec <* (char ')' *> whitespace)+ whitespace+ case o of- Nothing -> return s- Just o -> do- n <- pNameRec- return $ s <> "(" <> o <> ")" <> n+ Nothing -> do+ pure s+ Just !o -> do+ !n <- pNameRec -getTypeName :: Text -> Q Name-getTypeName name = do- t <- lookupTypeName $ T.unpack name- return $ fromMaybe (error $ "Invalid type: " ++ T.unpack name ++ ".") t+ pure $ s <> "(" <> o <> ")" <> n +getType :: Text -> Type+getType !name = do+ let !t = M.parseType (T.unpack name)+ case t of+ Left !e -> error e+ Right !t -> t+ getValueName :: Text -> Q Name-getValueName name = do- t <- lookupValueName $ T.unpack name- return $ fromMaybe (error $ "Invalid value: " ++ T.unpack name ++ ".") t+getValueName !name = do+ !t <- lookupValueName $ T.unpack name+ pure $ fromMaybe (error $ "Invalid value: " ++ T.unpack name ++ ".") t processC :: Text -> Q Exp-processC name = do- let t = M.parseType (T.unpack name)+processC !name = do+ let !t = M.parseType (T.unpack name) case t of- Left e -> error e- Right t -> return $ AppTypeE (ConE 'C) t+ Left !e -> error e+ Right !t -> pure $ AppTypeE (ConE 'C) t processR :: Text -> Exp -> Q Exp-processR name e = do- name <- getTypeName name- return $ AppE (AppTypeE (ConE 'R) (ConT name)) e+processR !name !e = do+ let !name' = getType name+ pure $ AppE (AppTypeE (ConE 'R) name') e relExp :: CompType -> Q Exp relExp PairAny = return $ ConE 'Any-relExp (Pair x) = VarE <$> getValueName x+relExp (Pair !x) = VarE <$> getValueName x relExp _ = undefined
src/Mischief/ECS/World/Query/TH/QD.hs view
@@ -1,26 +1,15 @@ module Mischief.ECS.World.Query.TH.QD where import Control.Monad-import Control.Monad.IO.Class-import Data.Maybe import Data.Text (Text) import Data.Text qualified as T-import Data.Void-import Language.Haskell.Meta.Parse as M import Language.Haskell.TH-import Language.Haskell.TH qualified-import Language.Haskell.TH.Quote-import Language.Haskell.TH.Syntax-import Mischief.ECS.Components (Component)-import Mischief.ECS.World.Query+import Mischief.ECS.Components (Res (Res)) import Mischief.ECS.World.Query.Markers hiding (Q) import Mischief.ECS.World.Query.Markers qualified as Markers-import Mischief.ECS.World.Query.QueryFilter-import Mischief.ECS.World.Query.Queryable-import Mischief.ECS.World.Query.Queryable qualified as Queryable import Mischief.ECS.World.Query.TH.Common import Mischief.ECS.World.Query.TH.QF qualified as QF-import Text.Megaparsec (MonadParsec (eof, lookAhead, notFollowedBy, try), Parsec, choice, many, manyTill, noneOf, optional, parseTest, some, (<|>))+import Text.Megaparsec (MonadParsec (notFollowedBy, try), choice, manyTill, optional, some, (<|>)) import Text.Megaparsec.Char import Text.Megaparsec.Char.Lexer qualified as L @@ -39,15 +28,9 @@ pEl :: Parser Qd pEl = try ((char '(' *> whitespace) *> (Tup <$> pTup pEl) <* (char ')' *> whitespace)) <|> pSingle --- case bracket of--- Nothing -> pSingle--- Just _ -> Tup <$> pTup pEl--data TestG a b = TestG deriving (Component)- pSingle :: Parser Qd pSingle = do- try pEntity <|> try pVal <|> try pMaybe <|> try pHas <|> try pValStar <|> try (pTrans Nothing) <|> pType Nothing+ try pEntity <|> try pMaybe <|> try pHas <|> try pValStar <|> try (pTrans Nothing) <|> pType Nothing pEntity :: Parser Qd pEntity = do@@ -62,13 +45,6 @@ whitespace Val' <$> pEl -pVal :: Parser Qd-pVal = do- void $ choice [string "Val", string "val", string "V", string "v"] <* notFollowedBy alphaNumChar- whitespace-- Val' <$> pEl- pMaybe :: Parser Qd pMaybe = do void $ choice [string "Maybe", string "maybe", string "M", string "m"] <* notFollowedBy alphaNumChar@@ -84,16 +60,16 @@ try (pTrans (Just H')) <|> pType (Just H') pTrans :: Maybe Mod -> Parser Qd-pTrans mod = do- name <- pNameTup <|> T.pack <$> some alphaNumChar+pTrans !mod = do+ !name <- pNameTup <|> T.pack <$> some alphaNumChar whitespace void $ string "->" whitespace - qd <- (char '(' *> whitespace) *> (Tup <$> pTup pEl) <* whitespace+ !qd <- (char '(' *> whitespace) *> (Tup <$> pTup pEl) <* whitespace - qf <- optional $ do+ !qf <- optional $ do void $ char '/' whitespace QF.pQf@@ -102,7 +78,7 @@ void $ char ')' whitespace - return . Trans $+ pure . Trans $ QdTrans { name, mod,@@ -110,38 +86,55 @@ exp = qd } -pType :: Maybe Mod -> Parser Qd-pType mod = do- -- name <- pTypeGeneric <|> T.pack <$> some alphaNumChar- name <- pNameTup <|> T.pack <$> some alphaNumChar+pRes :: Maybe Mod -> Parser Qd+pRes !mod = do+ void $ choice ["Res", "res"] whitespace - target <- optional $ do- void $ string "->"- whitespace- r <- string "*" <|> T.pack <$> some alphaNumChar- whitespace- return r-- let compType = case target of- Nothing -> Single- Just "*" -> PairAny- Just e -> Pair e+ !name <- pNameTup <|> T.pack <$> some alphaNumChar+ whitespace - return . Type $+ pure . Type $ QdType { name,- compType,+ compType = Resource, mod } -pName :: Parser Text-pName = T.pack <$> some alphaNumChar <|> pNameTup+pType :: Maybe Mod -> Parser Qd+pType !mod =+ pRes mod <|> pTypeSimple+ where+ pTypeSimple = do+ !name <- pNameTup <|> T.pack <$> some alphaNumChar+ whitespace + !target <- optional $ do+ void $ string "->"+ whitespace+ !r <- string "*" <|> T.pack <$> some alphaNumChar+ whitespace+ return r++ let !compType = case target of+ Nothing -> Single+ Just "*" -> PairAny+ Just e -> Pair e++ pure . Type $+ QdType+ { name,+ compType,+ mod+ }+ pTypeGeneric :: Parser Text pTypeGeneric = T.pack <$> (char '{' *> manyTill L.charLiteral (char '}')) quoteQd :: Qd -> Q Exp+quoteQd (Type QdType {name, compType = Resource, mod = Nothing}) = processRes name+quoteQd (Type QdType {name, compType = Resource, mod = Just M'}) = processMRes name+quoteQd (Type QdType {name, compType = Resource, mod = Just H'}) = processHasRes name quoteQd (Type QdType {name, compType = Single, mod = Nothing}) = processC name quoteQd (Type QdType {name, compType = Single, mod = Just M'}) = processM name quoteQd (Type QdType {name, compType = Single, mod = Just H'}) = processH name@@ -158,32 +151,47 @@ quoteQd Entity' = return $ ConE 'E relTrans :: Qd -> Maybe QF.Qf -> Q Exp-relTrans exp Nothing = AppE (ConE 'Markers.Q) <$> quoteQd exp-relTrans exp (Just f) = do- qd <- quoteQd exp- qf <- QF.quoteQf f+relTrans !exp Nothing = AppE (ConE 'Markers.Q) <$> quoteQd exp+relTrans !exp (Just !f) = do+ !qd <- quoteQd exp+ !qf <- QF.quoteQf f pure $ AppE (AppE (ConE 'Markers.Q') qd) qf processVal :: Exp -> Exp processVal = AppE (ConE 'Val) +processRes :: Text -> Q Exp+processRes !name = do+ let !name' = getType name+ pure $ AppTypeE (ConE 'Res) name'++processMRes :: Text -> Q Exp+processMRes !name = do+ let !name' = getType name+ pure $ AppTypeE (ConE 'MRes) name'++processHasRes :: Text -> Q Exp+processHasRes !name = do+ let !name' = getType name+ pure $ AppTypeE (ConE 'HasRes) name'+ processM :: Text -> Q Exp-processM name = do- name <- getTypeName name- return $ AppTypeE (ConE 'M) (ConT name)+processM !name = do+ let !name' = getType name+ pure $ AppTypeE (ConE 'M) name' processMR :: Text -> Exp -> Q Exp-processMR name e = do- name <- getTypeName name- return $ AppE (AppTypeE (ConE 'MR) (ConT name)) e+processMR !name !e = do+ let !name' = getType name+ pure $ AppE (AppTypeE (ConE 'MR) name') e processH :: Text -> Q Exp-processH name = do- name <- getTypeName name- return $ AppTypeE (ConE 'Has) (ConT name)+processH !name = do+ let !name' = getType name+ pure $ AppTypeE (ConE 'Has) name' processHR :: Text -> Exp -> Q Exp-processHR name e = do- name <- getTypeName name- return $ AppE (AppTypeE (ConE 'HasR) (ConT name)) e+processHR !name !e = do+ let !name' = getType name+ pure $ AppE (AppTypeE (ConE 'HasR) name') e
src/Mischief/ECS/World/Query/TH/QF.hs view
@@ -1,30 +1,18 @@+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}+ module Mischief.ECS.World.Query.TH.QF where import Control.Monad-import Control.Monad.IO.Class-import Data.Maybe import Data.Text (Text) import Data.Text qualified as T-import Data.Void import Language.Haskell.Meta.Parse-import Language.Haskell.Meta.Parse as M import Language.Haskell.TH-import Language.Haskell.TH qualified-import Language.Haskell.TH.Quote-import Language.Haskell.TH.Syntax-import Mischief.ECS.Components (Component)-import Mischief.ECS.World.Query-import Mischief.ECS.World.Query.Markers hiding (Q) import Mischief.ECS.World.Query.QueryFilter-import Mischief.ECS.World.Query.Queryable hiding (Q) import Mischief.ECS.World.Query.TH.Common--- import Mischief.ECS.World.Query.TH.QD (CompType (..), Parser, pTup, whitespace)--- import Mischief.ECS.World.Query.TH.QD qualified as QD-import Text.Megaparsec (MonadParsec (eof, lookAhead, notFollowedBy, try), Parsec, choice, many, manyTill, noneOf, optional, parseTest, satisfy, some, (<|>))+import Text.Megaparsec (MonadParsec (notFollowedBy, try), choice, many, optional, satisfy, some, (<|>)) import Text.Megaparsec.Char-import Text.Megaparsec.Char.Lexer qualified as L -data Qf = With' [QfType] | Added' [QfType] | Changed' [QfType] | Not' Qf | Tup' [Qf] | Or' Qf Qf | Check' CompType Text deriving (Show)+data Qf = With' [QfType] | Without' [QfType] | Changed' [QfType] Text Text | Tup' [Qf] | And' Qf Qf | Or' Qf Qf deriving (Show) data QfType = QfType {name :: Text, compType :: CompType} deriving (Show) @@ -36,24 +24,27 @@ pOr :: Parser Qf pOr = do- s <- pSingle+ !s <- pSingle whitespace - or <- optional $ choice [string "|.", string "||", string "or", string "OR", string "Or"] <* whitespace+ !or <- optional $ choice [string "||", string "or", string "OR", string "Or"] <* whitespace case or of- Nothing -> return s+ Nothing -> pure s Just _ -> foldr Or' s <$> pTup' -pSingle :: Parser Qf-pSingle = try pWith <|> pWithout <|> pAdded <|> try pChanged <|> pCheck <|> pNot--pNot :: Parser Qf-pNot = do- void $ choice [string "Not", string "not", string "!"]+pAnd :: Parser Qf+pAnd = do+ !s <- pSingle whitespace - Tup' <$> pTup'+ !and <- optional $ choice [string "&&", string "and", string "AND", string "And"] <* whitespace+ case and of+ Nothing -> return s+ Just _ -> foldr And' s <$> pTup' +pSingle :: Parser Qf+pSingle = try pWith <|> pWithout <|> try pChanged+ pWith :: Parser Qf pWith = do void $ choice [string "With", string "with"] <* notFollowedBy alphaNumChar@@ -64,109 +55,102 @@ pWithout = do void $ choice [string "Without", string "without"] <* notFollowedBy alphaNumChar whitespace- Not' . With' <$> pTypes--pAdded :: Parser Qf-pAdded = do- void $ choice [string "Added", string "added"] <* notFollowedBy alphaNumChar- whitespace- Added' <$> pTypes+ Without' <$> pTypes pChanged :: Parser Qf pChanged = do void $ choice [string "Changed", string "changed"] <* notFollowedBy alphaNumChar whitespace- Changed' <$> pTypes--pCheck :: Parser Qf-pCheck = do- void $ choice [string "Check", string "check"] <* notFollowedBy alphaNumChar- whitespace-- f <- pF- whitespace+ types <- pTypes - target <- optional $ do- void $ string "->"- whitespace- r <- string "*" <|> T.pack <$> some alphaNumChar+ lambdas <- optional $ do+ void $ char '('+ l1 <- pfLambda "(" whitespace- return r - let compType = case target of- Nothing -> Single- Just "*" -> PairAny- Just e -> Pair e-- return $ Check' compType f+ void $ char '('+ l2 <- pfLambda "("+ pure (l1, l2) -pF :: Parser Text-pF = try ((char '(' *> whitespace) *> pfLambda "(" <* whitespace) <|> T.pack <$> some alphaNumChar+ case lambdas of+ Nothing -> pure $ Changed' types "(>=)" "(<)"+ Just (f1, f2) -> pure $ Changed' types f1 f2 pfLambda :: String -> Parser Text-pfLambda str = do- x <- many (satisfy (/= ')'))+pfLambda !str = do+ !x <- many (satisfy (/= ')')) void $ char ')'- let str' = str ++ x ++ [')']+ let !str' = str ++ x ++ [')'] case parseExp str' of Left _ -> do pfLambda str'- Right _ -> return $ T.pack str'+ Right _ -> pure $ T.pack str' pTypes :: Parser [QfType] pTypes = try ((char '(' *> whitespace) *> (concat <$> pTup pTypes) <* (char ')' *> whitespace)) <|> (: []) <$> pType pType :: Parser QfType pType = do- name <- pNameTup <|> T.pack <$> some alphaNumChar+ !name <- pNameTup <|> T.pack <$> some alphaNumChar whitespace - target <- optional $ do+ !target <- optional $ do void $ string "->" whitespace- r <- string "*" <|> T.pack <$> some alphaNumChar+ !r <- string "*" <|> T.pack <$> some alphaNumChar whitespace- return r+ pure r - let compType = case target of+ let !compType = case target of Nothing -> Single Just "*" -> PairAny Just e -> Pair e - return $+ pure $ QfType { name, compType } quoteQf :: Qf -> Q Exp-quoteQf (Tup' qf) = processTup qf-quoteQf (With' x) = AppE (ConE 'With) <$> processTypes x-quoteQf (Changed' x) = AppE (ConE 'Changed) <$> processTypes x-quoteQf (Added' x) = AppE (ConE 'Added) <$> processTypes x-quoteQf (Or' x y) = do- x <- quoteQf x- y <- quoteQf y- return $ AppE (AppE (ConE 'Or) x) y-quoteQf (Not' x) = AppE (ConE 'Not) <$> quoteQf x-quoteQf (Check' c f) = case parseExp (T.unpack f) of- Left x -> error x- Right x -> processCheck c x--processCheck :: CompType -> Exp -> Q Exp-processCheck Single f = return $ AppE (ConE 'Check) f-processCheck (Pair e') f = do- e <- getValueName e'- return $ AppE (AppE (ConE 'CheckR) (VarE e)) f-processCheck PairAny f = return $ AppE (AppE (ConE 'CheckR) (ConE 'Any)) f+quoteQf (Tup' !qf) = processTup qf+quoteQf (With' !x) = processTypes (ConE 'With) x+quoteQf (Without' !x) = processTypes (ConE 'Without) x+quoteQf (Changed' !x f1 f2) = do+ let (Right f1') = parseExp (T.unpack f1)+ let (Right f2') = parseExp (T.unpack f2)+ processChanged (ConE 'Changed) x f1' f2'+quoteQf (Or' !x !y) = do+ !x <- quoteQf x+ !y <- quoteQf y+ pure $ AppE (AppE (ConE 'Or) x) y+quoteQf (And' !x !y) = do+ !x <- quoteQf x+ !y <- quoteQf y+ pure $ AppE (AppE (ConE 'And) x) y processTup :: [Qf] -> Q Exp-processTup [x] = quoteQf x-processTup t = TupE . map Just <$> forM t quoteQf+processTup [] = pure $ VarE '()+processTup [!x] = quoteQf x+processTup (!x : !xs) = do+ !x' <- quoteQf x+ AppE (AppE (ConE 'And) x') <$> processTup xs -processTypes :: [QfType] -> Q Exp-processTypes [x] = processType x-processTypes t = TupE . map Just <$> forM t processType+processTypes :: Exp -> [QfType] -> Q Exp+processTypes _ [] = pure $ VarE '()+processTypes !exp [!x] = AppE exp <$> processType x+processTypes !exp (!x : !xs) = do+ !x' <- processType x+ AppE (AppE (ConE 'And) (AppE exp x')) <$> processTypes exp xs++processChanged :: Exp -> [QfType] -> Exp -> Exp -> Q Exp+processChanged _ [] _ _ = pure $ VarE '()+processChanged !exp [!x] f1 f2 = do+ r <- processType x+ pure $ AppE (AppE (AppE exp r) f1) f2+processChanged !exp (!x : !xs) f1 f2 = do+ !x' <- processType x+ AppE (AppE (ConE 'And) (AppE (AppE (AppE exp x') f1) f2)) <$> processTypes exp xs processType :: QfType -> Q Exp processType (QfType {name, compType = Single}) = processC name
src/Mischief/ECS/World/Remove.hs view
@@ -1,35 +1,27 @@ {-# LANGUAGE AllowAmbiguousTypes #-} -module Mischief.ECS.World.Remove (remove, delete, removeRel, triggerRemoveEvent) where+module Mischief.ECS.World.Remove (remove, removeRel, triggerRemoveEvent) where import Control.Monad import Control.Monad.Reader import Data.Data import Data.Foldable import Data.IORef-import Data.Map qualified as Map-import Data.Maybe-import Data.Set (Set)-import Data.Set qualified as Set+import Data.Text qualified as T import GHC.Base (Int (..), eqWord#, isTrue#)-import Mischief.ECS.Archetypes import Mischief.ECS.Archetypes.Graph import Mischief.ECS.Collectable import Mischief.ECS.Components-import Mischief.ECS.Components.Common+import Mischief.ECS.Components.HooksDef (HookContext (..), HookContextRel (..)) import Mischief.ECS.Components.Spawn import Mischief.ECS.Entities-import Mischief.ECS.EntityDef-import Mischief.ECS.EventDef import Mischief.ECS.Events import Mischief.ECS.Log-import Mischief.ECS.Tables import Mischief.ECS.World import Mischief.ECS.World.Change (changeArchetype) import Mischief.ECS.World.Query import Mischief.ECS.World.Query.Markers import Mischief.ECS.World.Query.Queryable-import Mischief.ECS.World.Utils newtype ToRemove = ToRemove {inner :: [(ComponentType, Maybe Entity, Maybe Any)]} deriving newtype (Semigroup) @@ -37,90 +29,81 @@ erase _ = ToRemove [(ComponentType $ Proxy @c, Nothing, Nothing)] instance (Component c) => EraseIntoStorage (R c Entity) ToRemove where- erase (R e) = ToRemove [(ComponentType $ Proxy @c, Just e, Nothing)]+ erase (R !e) = ToRemove [(ComponentType $ Proxy @c, Just e, Nothing)] instance (Component c) => EraseIntoStorage (R c Any) ToRemove where erase _ = ToRemove [(ComponentType $ Proxy @c, Nothing, Just Any)] --- remove :: forall r. (Removable r) => Entity -> System ()--- remove entity = do--- types <- getTypes (Proxy @r)--- removeFromEntity (Set.toList types) entity--class Delete r where- delete :: r -> System ()--class Delete' r isRel where- delete' :: r -> System ()--instance (Delete' (Result r) (IsComp r)) => Delete (Result r) where- delete = delete' @(Result r) @(IsComp r)--instance (Component c) => Delete' (Result c) True where- delete' :: Result c -> System ()- delete' result = remove (C @c) (entityOf result)--instance (Component c) => Delete' (Result (Rel c)) False where- delete' :: Result (Rel c) -> System ()- delete' result = remove (R @c result.target) (entityOf result)- remove :: (Collectable c ToRemove) => c -> Entity -> System ()-remove c entity = do- let list :: ToRemove = collect c+remove !c !entity = do+ let !list :: ToRemove = collect c for_ list.inner $ \case- (x, Nothing, Nothing) -> do- comp <- getOrAddComponentId x+ (!x, Nothing, Nothing) -> do+ !comp <- getOrAddComponentId x removeFromEntity [comp] entity- (x, Just target, _) -> do- comp <- getOrAddPairId (Pair (x, target))+ (!x, Just !target, _) -> do+ !comp <- getOrAddPairId (Pair (x, target)) removeFromEntity [comp] entity- (x, _, Just _) -> do+ (!x, _, Just _) -> do removeRelationshipsFromEntity x entity removeRel :: forall c. (Component c) => Entity -> Entity -> System () removeRel = removeRelationshipFromEntity @c removeRelationshipFromEntity :: forall c. (Component c) => Entity -> Entity -> System ()-removeRelationshipFromEntity target entity = do- componentId <- getOrAddPairId (Pair (ComponentType $ Proxy @c, target))+removeRelationshipFromEntity !target !entity = do+ !componentId <- getOrAddPairId (Pair (ComponentType $ Proxy @c, target)) removeFromEntity [componentId] entity removeRelationshipsFromEntity :: ComponentType -> Entity -> System ()-removeRelationshipsFromEntity x entity = do- world <- unsafeGetWorld- ids <- liftIO $ findComponentsOfEntity world entity- (ComponentId (# id, _ #)) <- getOrAddComponentId x- for_ ids $ \ids' -> do- let ids = filter (\(ComponentId (# id', _ #)) -> isTrue# $ eqWord# id id') ids'+removeRelationshipsFromEntity !x !entity = do+ !world <- unsafeGetWorld+ !ids <- liftIO $ findComponentsOfEntity world entity+ (ComponentId (# !id, _ #)) <- getOrAddComponentId x+ for_ ids $ \(!ids') -> do+ let !ids = filter (\(ComponentId (# !id', _ #)) -> isTrue# $ eqWord# id id') ids' removeFromEntity ids entity removeFromEntity :: [ComponentId] -> Entity -> System ()-removeFromEntity components entity = do- world <- unsafeGetWorld- pointer <- liftIO $ getPointer entity world.entities+removeFromEntity !components !entity = do+ !world <- unsafeGetWorld+ !pointer <- liftIO $ getPointer entity world.entities case pointer of- Nothing -> warn $ "Removal failed: Entity " <> text entity <> " is not alive."- Just pointer -> do- (EntityPointer (# archetypeId, _ #)) <- liftIO $ readIORef pointer+ Nothing -> warn $ "Removal failed: Entity " <> T.show entity <> " is not alive."+ Just !pointer -> do+ (EntityPointer (# !archetypeId, _ #)) <- liftIO $ readIORef pointer - (newArchetype, removedComponents) <- getArchetypeOnRemove (ArchetypeId $ I# archetypeId) components+ (!newArchetype, !removedComponents) <- getArchetypeOnRemove (ArchetypeId $ I# archetypeId) components triggerRemoveEvent removedComponents entity void $ changeArchetype entity newArchetype Nothing triggerRemoveEvent :: [ComponentId] -> Entity -> System ()-triggerRemoveEvent components entity = do- for_ components $ \(ComponentId (# id, target #)) -> do- Just t <- get (C @ComponentType) (Entity (# id, 0## #))+triggerRemoveEvent !components !entity = do+ for_ components $ \(ComponentId (# !id, !target #)) -> do+ Just !t <- single $ mkGet (Entity (# id, 0## #)) (C @ComponentType) case target of- Nothing -> triggerRemoveEventC (value t) entity- Just target -> triggerRemoveEventR (value t) target entity+ Nothing -> triggerRemoveEventC t entity+ Just !target -> triggerRemoveEventR t target entity triggerRemoveEventC :: ComponentType -> Entity -> System ()-triggerRemoveEventC (ComponentType (_ :: Proxy t)) entity =- runEvent $ eraseEvent $ OnRemove @t entity+triggerRemoveEventC (ComponentType (_ :: Proxy t)) !entity = do+ trigger $ OnRemove @t entity + let !context = HookContext {entity}+ !m <- meta @t+ Just !hooks <- single $ mkGet m (M @ComponentRemoveHooks)+ for_ hooks $ \(ComponentRemoveHooks !h) -> do+ for_ h $ \(!h) -> h context+ triggerRemoveEventR :: ComponentType -> Entity -> Entity -> System ()-triggerRemoveEventR (ComponentType (_ :: Proxy t)) target entity =- runEvent $ eraseEvent $ OnRemoveRel @t entity target+triggerRemoveEventR (ComponentType (_ :: Proxy t)) !target !entity = do+ trigger $ OnRemoveRel @t entity target++ let !context = HookContextRel {entity, target}++ !m <- meta @t+ Just !hooks <- single $ mkGet m (M @ComponentRemoveHooksRel)+ for_ hooks $ \(ComponentRemoveHooksRel !h) -> do+ for_ h $ \(!h) -> h context
src/Mischief/ECS/World/Spawn.hs view
@@ -2,126 +2,132 @@ import Control.Monad import Control.Monad.IO.Class-import Control.Monad.Reader (MonadReader (..), ReaderT (runReaderT))-import Data.Data+import Data.Foldable import Data.IORef-import Data.Map qualified as Map import Data.Maybe-import Data.Set qualified as Set+import Data.Text qualified as T import GHC.Base (Int (..)) import GHC.Stack import Mischief.ECS.Archetypes.Graph (getArchetypeOnSpawn) import Mischief.ECS.Components import Mischief.ECS.Components.Bundle import Mischief.ECS.Components.Common+import Mischief.ECS.Components.Spawn import Mischief.ECS.Entities-import Mischief.ECS.EventDef-import Mischief.ECS.Hidden import Mischief.ECS.Log-import Mischief.ECS.Observer import Mischief.ECS.Tables import Mischief.ECS.Vec qualified as Vec import Mischief.ECS.World import Mischief.ECS.World.Change import Mischief.ECS.World.Defer import Mischief.ECS.World.Insert-import Mischief.ECS.World.Prefs import Mischief.ECS.World.Remove import Mischief.ECS.World.Utils -- | Spawn an entity given a bundle of components. spawn :: (HasCallStack, Bundle b) => b -> System Entity-spawn bundle =+spawn !bundle = do- world <- unsafeGetWorld- entity <- liftIO $ getNewEntity world.entities+ !world <- unsafeGetWorld+ !entity <- liftIO $ getNewEntity world.entities spawnEntity entity bundle- return entity+ pure entity +-- | Same as 'spawn' but discards the returned entity.+spawn_ :: (HasCallStack, Bundle b) => b -> System ()+spawn_ = void . spawn++-- | Spawn an entity given a bundle of components, inside a @ParSystem@.+--+-- This will immediately reserve and return an Entity index which can be used, while defering+-- the actual spawn. spawnDefer :: (Bundle b) => b -> ParSystem Entity-spawnDefer bundle = do- world <- unsafeGetWorld- entity <- liftIO $ getNewEntity world.entities+spawnDefer !bundle = do+ !world <- unsafeGetWorld+ !entity <- liftIO $ getNewEntity world.entities defer $ spawnEntity entity bundle- return entity+ pure entity data SpawnEventsSettings = WithSpawnEvents | WithoutSpawnEvents +-- | Spawn an Entity given an existing, reserved id. This is not meant for general use. spawnEntity :: (HasCallStack, Bundle b) => Entity -> b -> System ()-spawnEntity entity bundle = do- world <- unsafeGetWorld- let BundleData {elements} = addComponentToBundleData (Name (show entity)) $ bundleData bundle+spawnEntity !entity !bundle = do+ !world <- unsafeGetWorld+ let BundleData {elements = !elements, resources = !resources, external = !external} = addComponentToBundleData (Name (show entity)) $ bundleData bundle - currentTick <- liftIO $ readIORef world.tick- bundleD <- liftIO $ processBundleElements world ComponentTicks {changed = currentTick, added = currentTick} elements+ for_ external $ \(!e, !s) -> do+ insert s e - archetype <- getArchetypeOnSpawn $ map (\x -> x.id) bundleD.elements+ for_ resources $ \BundleElement {component = ErasedComponent (!val :: c)} -> do+ !m <- meta @c+ insert val m - entityPointer <- liftIO $ newIORef $ EntityPointer (# 0#, 0# #)+ !currentTick <- liftIO $ readIORef world.tick+ !bundleD <- liftIO $ processBundleElements world elements - liftIO $ insertEntityIntoTables (ProcessedBundleData {elements = []}) world.tables (ArchetypeId 0) (entity, entityPointer)+ !archetype <- getArchetypeOnSpawn $ map (.id) bundleD.elements + !entityPointer <- liftIO $ newIORef $ EntityPointer (# 0#, 0# #)++ liftIO $ insertEntityIntoTables (ProcessedBundleData {elements = []}) currentTick [] world.tables (ArchetypeId 0) (entity, entityPointer)+ liftIO $ insertPointer entity entityPointer world.entities - ChangeResult {requiredComponentsAdded} <- changeArchetype entity archetype (Just bundleD)+ ChangeResult {requiredComponentsAdded = !requiredComponentsAdded} <- changeArchetype entity archetype (Just bundleD) - unless world.prefs.supressEvents $ do- triggerInsertEvent (ProcessedBundleData $ requiredComponentsAdded ++ bundleD.elements) entity+ !supressEvents <- fromMaybe False <$> getPref @SupressEventsPref --- insertNew (Name (show entity)) entity+ unless supressEvents $ do+ let !d = ProcessedBundleData $ requiredComponentsAdded ++ bundleD.elements+ triggerAddEvent d entity+ triggerSetEvent d entity +-- Spawn an entity as having no components and then immediately insert a bundle on it. Useful for certain engine internals, should be avoided. spawnEntityByInsert :: (Bundle b) => Entity -> b -> System ()-spawnEntityByInsert entity bundle = do- world <- unsafeGetWorld-- entityPointer <- liftIO $ newIORef $ EntityPointer (# 0#, 0# #)+spawnEntityByInsert !entity !bundle = do+ !world <- unsafeGetWorld - liftIO $ insertEntityIntoTables (ProcessedBundleData {elements = []}) world.tables (ArchetypeId 0) (entity, entityPointer)+ !entityPointer <- liftIO $ newIORef $ EntityPointer (# 0#, 0# #) + !currentTick <- liftIO $ readIORef world.tick+ liftIO $ insertEntityIntoTables (ProcessedBundleData {elements = []}) currentTick [] world.tables (ArchetypeId 0) (entity, entityPointer) liftIO $ insertPointer entity entityPointer world.entities- insert bundle entity insertNew (Name (show entity)) entity -spawnObserverOrdered :: forall e. (Event e) => Observer e -> Int -> System ()-spawnObserverOrdered observer order = do- void $ spawn (observer, ObserverOrder order)--spawnObserver :: forall e. (Event e) => Observer e -> System ()-spawnObserver e = spawnObserverOrdered e 0---- | Spawn an entity given a bundle of components.+-- | Spawn an entity given a bundle of components, in IO. spawnIO :: (Bundle b) => World -> b -> IO Entity-spawnIO world bundle =+spawnIO !world !bundle = do- entity <- liftIO $ getNewEntity world.entities+ !entity <- liftIO $ getNewEntity world.entities runSystem (spawnEntity entity bundle) world- return entity+ pure entity --- | Despawn an entity.+-- | Despawn an entity. This will trigger the @OnRemove@ events and hooks on all its components. despawn :: Entity -> System ()-despawn entity =+despawn !entity = do- world <- unsafeGetWorld- pointer <- liftIO $ getPointer entity world.entities+ !world <- unsafeGetWorld+ !pointer <- liftIO $ getPointer entity world.entities case pointer of- Nothing -> warn $ "Despawn failed: Entity " <> text entity <> " is not alive."- Just pointer -> do- let Tables tables = world.tables+ Nothing -> warn $ "Despawn failed: Entity " <> T.show entity <> " is not alive."+ Just !pointer -> do+ let Tables !tables = world.tables - (EntityPointer (# archetypeId, _ #)) <- liftIO $ readIORef pointer+ (EntityPointer (# !archetypeId, _ #)) <- liftIO $ readIORef pointer - table <- Vec.read tables (I# archetypeId)+ !table <- Vec.read tables (I# archetypeId) - c <- liftIO $ collectComponentIdsFromTable table+ !c <- liftIO $ collectComponentIdsFromTable table triggerRemoveEvent c entity - (EntityPointer (# newArchetypeId, newRowIndex #)) <- liftIO $ readIORef pointer+ (EntityPointer (# !newArchetypeId, !newRowIndex #)) <- liftIO $ readIORef pointer - table <- Vec.read tables (I# newArchetypeId)+ !table <- Vec.read tables (I# newArchetypeId) void $ liftIO $ takeComponentsFromTable (EntityPointer (# newArchetypeId, newRowIndex #)) table liftIO $ removeEntity entity world.entities
− src/Mischief/ECS/World/Systems.hs
@@ -1,1 +0,0 @@-module Mischief.ECS.World.Systems where
src/Mischief/ECS/World/Utils.hs view
@@ -2,248 +2,215 @@ module Mischief.ECS.World.Utils where -import Control.Concurrent.STM-import Control.Monad import Control.Monad.IO.Class (MonadIO (liftIO))-import Control.Monad.Primitive (PrimMonad (..))-import Control.Monad.Reader.Class (MonadReader (..), asks)-import Control.Monad.Trans (MonadTrans (..))-import Control.Monad.Trans.Reader (ReaderT (runReaderT)) import Data.Bifunctor qualified import Data.IORef-import Data.List-import Data.Map qualified as Map-import Data.Maybe (fromMaybe, isNothing) import Data.Proxy import Data.Set (Set) import Data.Set qualified as Set-import Data.Text (Text) import Data.Typeable-import GHC.Stack-import Mischief.ECS.Archetypes import Mischief.ECS.Components import Mischief.ECS.Components.Bundle import Mischief.ECS.Components.Spawn import Mischief.ECS.Entities-import Mischief.ECS.EntityDef-import Mischief.ECS.EventDef-import Mischief.ECS.Hidden-import Mischief.ECS.Log import Mischief.ECS.Tables-import Mischief.ECS.Utils import Mischief.ECS.World-import Mischief.ECS.World.Prefs -- | Process a 'BundleElement', turning its 'TypeRep' into a 'ComponentId'.-processBundleElement :: World -> ComponentTicks -> (BundleElement ErasedComponent) -> IO ProcessedBundleElement-processBundleElement world ticks BundleElement {rep = (ComponentRep r), component} =+processBundleElement :: World -> BundleElement ErasedComponent -> IO ProcessedBundleElement+processBundleElement !world BundleElement {rep = (ComponentRep !r), component = !component} = do- id <- runSystem (getOrAddComponentId r) world- return+ !id <- runSystem (getOrAddComponentId r) world+ pure ProcessedBundleElement { id,- component =- ComponentData- { value = component,- ticks- }+ component }-processBundleElement world ticks BundleElement {rep = (PairRep (r, entity)), component} =+processBundleElement !world BundleElement {rep = (PairRep (!r, !entity)), component = !component} = do- id <- runSystem (getOrAddPairId (Pair (r, entity))) world- return+ !id <- runSystem (getOrAddPairId (Pair (r, entity))) world+ pure ProcessedBundleElement { id,- component =- ComponentData- { value = component,- ticks- }+ component } -- | Process a set of 'BundleElement's into a 'ProcessedBundleData'.-processBundleElements :: World -> ComponentTicks -> Set (BundleElement ErasedComponent) -> IO ProcessedBundleData-processBundleElements world ticks elements =+processBundleElements :: World -> Set (BundleElement ErasedComponent) -> IO ProcessedBundleData+processBundleElements !world !elements = do- elements <- mapM (processBundleElement world ticks) (Set.toList elements)- return+ !elements <- mapM (processBundleElement world) (Set.toList elements)+ pure ProcessedBundleData {elements} -- | Combine two 'ProcessedBundleData's, merging their sets of elements. combineProcessedBundles :: ProcessedBundleData -> ProcessedBundleData -> ProcessedBundleData-combineProcessedBundles bundle1 bundle2 =- let elements = Set.toList $ Set.union (Set.fromList bundle1.elements) (Set.fromList bundle2.elements)+combineProcessedBundles !bundle1 !bundle2 =+ let !elements = Set.toList $ Set.union (Set.fromList bundle1.elements) (Set.fromList bundle2.elements) in ProcessedBundleData {elements} -- | Check if a 'ComponentId' is inside a 'ProcessedBundleData'. isInProcessedBundle :: ProcessedBundleData -> ComponentId -> Bool-isInProcessedBundle ProcessedBundleData {elements} id = id `elem` map (\element -> element.id) elements+isInProcessedBundle ProcessedBundleData {elements = !elements} !id = id `elem` map (.id) elements -- | Sets the change tick of certain elements of the bundle to the specified 'Tick'.-setChangedTickOfComponents :: ProcessedBundleData -> (ComponentId -> Bool) -> Tick -> ProcessedBundleData-setChangedTickOfComponents ProcessedBundleData {elements} shouldChange tick =- ProcessedBundleData- { elements =- map- ( \ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed}}} ->- if shouldChange id- then ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed = tick}}}- else ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed}}}- )- elements- }+-- setChangedTickOfComponents :: ProcessedBundleData -> (ComponentId -> Bool) -> Tick -> ProcessedBundleData+-- setChangedTickOfComponents ProcessedBundleData {elements = !elements} !shouldChange !tick =+-- ProcessedBundleData+-- { elements =+-- map+-- ( \ProcessedBundleElement {id = !id, component = ComponentData {value = !value, ticks = ComponentTicks {added = !added, changed = !changed}}} ->+-- if shouldChange id+-- then ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed = tick}}}+-- else ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed}}}+-- )+-- elements+-- } -- | Sets the added tick of certain elements of the bundle to the specified 'Tick'.-setAddedTickOfComponents :: ProcessedBundleData -> (ComponentId -> Bool) -> Tick -> ProcessedBundleData-setAddedTickOfComponents ProcessedBundleData {elements} shouldChange tick =- ProcessedBundleData- { elements =- map- ( \ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed}}} ->- if shouldChange id- then ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added = tick, changed}}}- else ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed}}}- )- elements- }-+-- setAddedTickOfComponents :: ProcessedBundleData -> (ComponentId -> Bool) -> Tick -> ProcessedBundleData+-- setAddedTickOfComponents ProcessedBundleData {elements = !elements} !shouldChange !tick =+-- ProcessedBundleData+-- { elements =+-- map+-- ( \ProcessedBundleElement {id = !id, component = ComponentData {value = !value, ticks = ComponentTicks {added = !added, changed = !changed}}} ->+-- if shouldChange id+-- then ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added = tick, changed}}}+-- else ProcessedBundleElement {id, component = ComponentData {value, ticks = ComponentTicks {added, changed}}}+-- )+-- elements+-- } removeComponentFromProcessedBundle :: ComponentId -> ProcessedBundleData -> ProcessedBundleData-removeComponentFromProcessedBundle componentId bundle =+removeComponentFromProcessedBundle !componentId !bundle = do- let elements = filter (\x -> x.id /= componentId) bundle.elements+ let elements = filter ((/= componentId) . (.id)) bundle.elements in ProcessedBundleData {elements} -tryGetEntityRelCollection :: forall c. (Component c) => World -> Entity -> IO (Maybe (Maybe [Result (Rel c)]))-tryGetEntityRelCollection world entity =+tryGetEntityRelCollection :: forall c. (Component c) => World -> Entity -> IO (Maybe (Maybe [Rel c]))+tryGetEntityRelCollection !world !entity = do- componentId <- getComponentId (typeRep $ Proxy @c) world.components+ !componentId <- getComponentId (typeRep $ Proxy @c) world.components case componentId of- Nothing -> return Nothing- Just componentId -> do- pointer <- getPointer entity world.entities+ Nothing -> pure Nothing+ Just !componentId -> do+ !pointer <- getPointer entity world.entities case pointer of Nothing -> do- return Nothing- Just pointer ->+ pure Nothing+ Just !pointer -> do- pointer <- readIORef pointer- res <- tryGetRelCollectionFromTables world.tables entity pointer componentId- return $ Just res+ !pointer <- readIORef pointer+ !res <- tryGetRelCollectionFromTables world.tables entity pointer componentId+ pure $ Just $ fmap (map snd) res tryGetEntityComponent :: forall c. (Component c) => World -> Entity -> IO (Maybe (Maybe c))-tryGetEntityComponent world entity =+tryGetEntityComponent !world !entity = do- pointer <- getPointer entity world.entities+ !pointer <- getPointer entity world.entities case pointer of- Nothing -> return Nothing- Just pointer ->+ Nothing -> pure Nothing+ Just !pointer -> do- componentId <- getComponentId (typeRep $ Proxy @c) world.components+ !componentId <- getComponentId (typeRep $ Proxy @c) world.components case componentId of- Nothing -> return $ Just Nothing- Just componentId -> do- pointer <- readIORef pointer- res <- tryGetComponentFromTables world.tables pointer componentId- return $ Just res+ Nothing -> pure $ Just Nothing+ Just !componentId -> do+ !pointer <- readIORef pointer+ !res <- tryGetComponentFromTables world.tables pointer componentId+ pure $ Just res tryGetEntityRel :: forall c. (Component c) => Entity -> World -> Entity -> IO (Maybe (Maybe c))-tryGetEntityRel target world entity =+tryGetEntityRel !target !world !entity = do- pointer <- getPointer entity world.entities+ !pointer <- getPointer entity world.entities case pointer of- Nothing -> return Nothing- Just pointer ->+ Nothing -> pure Nothing+ Just !pointer -> do- componentId <- getComponentId (typeRep $ Proxy @c) world.components+ !componentId <- getComponentId (typeRep $ Proxy @c) world.components case componentId of Nothing -> return Nothing- Just (ComponentId (# id, _ #)) -> do- pointer <- readIORef pointer- res <- tryGetComponentFromTables world.tables pointer (ComponentId (# id, Just target #))- return $ Just res+ Just (ComponentId (# !id, _ #)) -> do+ !pointer <- readIORef pointer+ !res <- tryGetComponentFromTables world.tables pointer (ComponentId (# id, Just target #))+ pure $ Just res -tryGetRelCollections :: forall c. (Component c) => World -> [ArchetypeId] -> IO [(Entity, [Result (Rel c)])]-tryGetRelCollections world archetypes =+tryGetRelCollections :: forall c. (Component c) => World -> [ArchetypeId] -> IO [(Entity, [Rel c])]+tryGetRelCollections !world !archetypes = do- componentId <- getComponentId (typeRep $ Proxy @c) world.components+ !componentId <- getComponentId (typeRep $ Proxy @c) world.components case componentId of- Nothing -> return []- Just componentId ->+ Nothing -> pure []+ Just !componentId -> tryGetRelCollectionsFromTables world.tables archetypes componentId -tryGetComponents :: forall c. (Component c) => World -> [ArchetypeId] -> IO [(Entity, Result c)]-tryGetComponents world archetypes =+tryGetComponents :: forall c. (Component c) => World -> [ArchetypeId] -> IO [(Entity, c)]+tryGetComponents !world !archetypes = do- componentId <- getComponentId (typeRep $ Proxy @c) world.components+ !componentId <- getComponentId (typeRep $ Proxy @c) world.components case componentId of- Nothing -> return []- Just componentId ->+ Nothing -> pure []+ Just !componentId -> tryGetComponentsFromTables world.tables archetypes componentId tryGetEntities :: World -> [ArchetypeId] -> IO [Entity]-tryGetEntities world = tryGetEntitiesFromTables world.tables+tryGetEntities !world = tryGetEntitiesFromTables world.tables -tryGetRels :: forall c. (Component c) => Entity -> World -> [ArchetypeId] -> IO [(Entity, Result (Rel c))]-tryGetRels target world archetypes =+tryGetRels :: forall c. (Component c) => Entity -> World -> [ArchetypeId] -> IO [(Entity, Rel c)]+tryGetRels !target !world !archetypes = do- componentId <- getComponentId (typeRep $ Proxy @c) world.components+ !componentId <- getComponentId (typeRep $ Proxy @c) world.components case componentId of- Nothing -> return []- Just componentId -> do- res <- tryGetComponentsFromTables world.tables archetypes (setCompIdTarget (Just target) componentId) -- {entity = Just target}- return $ map (\(e, res) -> (e, Result (Rel (value res) target, entityOf res))) res+ Nothing -> pure []+ Just !componentId -> do+ !res <- tryGetComponentsFromTables world.tables archetypes (setCompIdTarget (Just target) componentId) -- {entity = Just target}+ pure $ map (\(!e, !res) -> (e, Rel res target)) res -tryGetRelsMaybe :: forall c. (Component c) => Entity -> World -> [ArchetypeId] -> IO [(Entity, Maybe (Result (Rel c)))]-tryGetRelsMaybe target world archetypes =+tryGetRelsMaybe :: forall c. (Component c) => Entity -> World -> [ArchetypeId] -> IO [(Entity, Maybe (Rel c))]+tryGetRelsMaybe !target !world !archetypes = do- componentId <- getComponentId (typeRep $ Proxy @c) world.components+ !componentId <- getComponentId (typeRep $ Proxy @c) world.components case componentId of Nothing -> do- e <- tryGetEntities world archetypes- return $ map (,Nothing) e- Just componentId -> do- res <- tryGetComponentsFromTablesMaybe world.tables archetypes (setCompIdTarget (Just target) componentId) -- {entity = Just target}- return $+ !e <- tryGetEntities world archetypes+ pure $ map (,Nothing) e+ Just !componentId -> do+ !res <- tryGetComponentsFromTablesMaybe world.tables archetypes (setCompIdTarget (Just target) componentId) -- {entity = Just target}+ pure $ map ( Data.Bifunctor.second- (fmap (\res -> Result (Rel (value res) target, entityOf res)))+ (fmap (`Rel` target)) ) res -tryGetComponentsMaybe :: forall c. (Component c) => World -> [ArchetypeId] -> IO [(Entity, Maybe (Result c))]-tryGetComponentsMaybe world archetypes =+tryGetComponentsMaybe :: forall c. (Component c) => World -> [ArchetypeId] -> IO [(Entity, Maybe c)]+tryGetComponentsMaybe !world !archetypes = do- componentId <- getComponentId (typeRep $ Proxy @c) world.components+ !componentId <- getComponentId (typeRep $ Proxy @c) world.components case componentId of Nothing -> do- e <- tryGetEntities world archetypes- return $ map (,Nothing) e- Just componentId ->+ !e <- tryGetEntities world archetypes+ pure $ map (,Nothing) e+ Just !componentId -> tryGetComponentsFromTablesMaybe world.tables archetypes componentId -tryGetTicks :: ComponentId -> World -> [ArchetypeId] -> IO [Maybe ComponentTicks]-tryGetTicks componentId world archetypes = tryGetTicksFromTables world.tables archetypes componentId+tryGetTicks :: ComponentId -> World -> [ArchetypeId] -> IO [Maybe ComponentTick]+tryGetTicks !componentId !world !archetypes = tryGetTicksFromTables world.tables archetypes componentId -tryGetEntityTicks :: Entity -> ComponentId -> World -> IO (Maybe ComponentTicks)-tryGetEntityTicks entity componentId world = do- pointer <- getPointer entity world.entities- case pointer of- Nothing -> return Nothing- Just pointer -> do- pointer <- readIORef pointer- tryGetEntityTicksFromTables world.tables pointer componentId+-- tryGetEntityTicks :: Entity -> ComponentId -> World -> IO (Maybe ComponentTicks)+-- tryGetEntityTicks !entity !componentId !world = do+-- !pointer <- getPointer entity world.entities+-- case pointer of+-- Nothing -> pure Nothing+-- Just !pointer -> do+-- !pointer <- readIORef pointer+-- tryGetEntityTicksFromTables world.tables pointer componentId isAlive :: forall m w. (MonadSystem w m) => Entity -> m Bool-isAlive entity = do- world <- unsafeGetWorld+isAlive !entity = do+ !world <- unsafeGetWorld liftIO $ isAliveIO entity world.entities--expect :: (HasCallStack) => forall m w a. (MonadSystem w m) => Text -> Maybe a -> m a-expect t a = withFrozenCallStack $ do- case a of- Nothing -> panic t >>= const undefined- Just x -> return x newtype GetSystem = GetSystem (forall c. (Component c) => Proxy c -> Entity -> System (Maybe c))
+ test/Changes.hs view
@@ -0,0 +1,114 @@+module Changes where++import Data.Foldable+import Data.Kind+import Data.Text qualified as T+import Mischief.ECS+import Mischief.ECS.App (register, setSystemTicks, setTick, simulateSystem, tick)+import Mischief.ECS.Components+import Mischief.ECS.Components.Spawn (meta)+import Mischief.ECS.Fixed (FixedPlugin, FixedUpdate)+import Mischief.ECS.Relationships.ChildOf+import Mischief.ECS.Relationships.Graph qualified as Graph+import Mischief.ECS.Time qualified as Time+import Utils++main :: IO ()+main = do+ runTests+ [ test1,+ test2,+ test3,+ test4,+ test5+ ]++test1 :: System ()+test1 = simulateSystem $ do+ setTick $ Tick 0++ a <- spawn (Name "A", Comp1)+ b <- spawn (Name "B", Comp1)+ c <- spawn (Name "C", Comp1)++ setSystemTicks (Tick 0) (Tick 1)++ assertqWith+ [q|Name / With Comp1, Changed Name|]+ [(a, Name "A"), (b, Name "B"), (c, Name "C")]++test2 :: System ()+test2 = simulateSystem $ do+ setTick $ Tick 19++ a <- spawn (Name "A", Comp1)+ b <- spawn (Name "B", Comp1)+ c <- spawn (Name "C", Comp1)++ setSystemTicks (Tick 1) (Tick 3)++ assertqWith+ [q|Name / With Comp1, Changed Name (\a b -> a == Tick 19 && b == Tick 1) (\a b -> a == Tick 19 && b == Tick 3)|]+ [(a, Name "A"), (b, Name "B"), (c, Name "C")]++test3 :: System ()+test3 = simulateSystem $ do+ setTick $ Tick 1++ a <- spawn (Name "A", Comp1)+ b <- spawn (Name "B", Comp1)+ c <- spawn (Name "C", Comp1)++ setSystemTicks (Tick 1) (Tick 3)++ assertqWith+ [q|Name / Changed Name (>=) (<)|]+ [(a, Name "A"), (b, Name "B"), (c, Name "C")]++test4 :: System ()+test4 = simulateSystem $ do+ setTick $ Tick 1+ a <- spawn (Name "A", Comp1)+ b <- spawn (Name "B", Comp1)+ c <- spawn (Name "C", Comp1)++ setTick $ Tick 2+ setSystemTicks (Tick 1) (Tick 3)++ assertqWith+ [q|Name / Changed Name (>=) (<)|]+ [(a, Name "A"), (b, Name "B"), (c, Name "C")]++ setSystemTicks (Tick 2) (Tick 3)++ assertqWith+ [q|Name / Changed Name|]+ []++ insert (Name "A") a++ assertqWith+ [q|Name / Changed Name (>=) (<)|]+ [(a, Name "A"), (b, Name "B"), (c, Name "C")]++ setTick $ Tick 3+ setSystemTicks (Tick 3) (Tick 3)++ insert (Comp2, Name "A") a++ assertqWith+ [q|Name / Changed Name (==) (==)|]+ [(a, Name "A")]++test5 :: System ()+test5 = simulateSystem $ do+ setTick $ Tick 1+ a <- spawn (Name "A")+ b <- spawn (Name "B")+ c <- spawn (Name "C")++ setSystemTicks (Tick 1) (Tick 3)++ assertqContainsWith+ [q|Name / Changed Name|]+ [(a, Name "A"), (b, Name "B"), (c, Name "C")]
test/Main.hs view
@@ -1,334 +1,136 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE MultiWayIf #-}--{- HLINT ignore "Use newtype instead of data" -}- module Main where -import Control.Monad (void, when)-import Control.Monad.IO.Class-import Data.Default+import Changes qualified import Data.Foldable-import Data.List ((!?))-import Data.Traversable-import Mischief.ECS.Hooks qualified as Hooks-import Mischief.ECS.Interval qualified as Interval-import Mischief.ECS.Observers qualified as Observers-import Mischief.ECS.Prelude-import Mischief.ECS.Stdin qualified as Stdin-import Mischief.ECS.Stdout-import Mischief.ECS.Systems qualified as Systems-import Mischief.ECS.Timer (Timer)-import Mischief.ECS.Timer qualified as Timer-import System.Exit-import System.Random-import System.Random.Stateful+import Data.Kind+import Mischief.ECS+import Mischief.ECS.App (setSystemTicks, simulateSystem)+import Mischief.ECS.Components+import Mischief.ECS.Fixed (FixedPlugin, FixedUpdate)+import Mischief.ECS.Relationships.ChildOf+import Mischief.ECS.Relationships.Graph qualified as Graph+import Mischief.ECS.Time qualified as Time+import Utils main :: IO () main = do- app <- newApp MainPlugin- runApp app--data MainPlugin = MainPlugin deriving (Eq)--instance Plugin MainPlugin where- init _ = do- Stdin.init- Systems.add Startup (spawnGrid, spawnWalls)- Systems.add Update printGrid-- interval <- Interval.start 2000000 spawnCoin-- insertRes =<< newGen- insertRes $ Coins 0-- plugins _ = plug (PlayerPlugin, EnemyPlugin, TimePlugin)--data PlayerPlugin = PlayerPlugin deriving (Eq)--instance Plugin PlayerPlugin where- init _ = do- Systems.add Startup $ spawnPlayer `after` spawnGrid- Systems.add Update movePlayer- Systems.add Update $ collectCoins `after` movePlayer-- void $ Observers.spawn onDamage--data EnemyPlugin = EnemyPlugin deriving (Eq)--instance Plugin EnemyPlugin where- init _ = do- Systems.add Startup spawnEnemies- Systems.add Update moveEnemies- Systems.add Update $ tryDamage `after` movePlayer `after` moveEnemies--data Tile = Tile deriving (Component)--newtype Pos = Pos {pos :: (Int, Int)} deriving (Component, Show)--data Grid = Grid [[Entity]] deriving (Component)--getTile :: (Int, Int) -> System (Maybe Entity)-getTile (x, y) = do- grid <- res @Grid- pure $ do- Grid tiles <- grid- line <- tiles !? x- line !? y--gridH :: Int-gridH = 10--gridW :: Int-gridW = 20--spawnGrid :: System ()-spawnGrid = do- tiles <- for [0 .. gridH - 1] $ \i -> for [0 .. gridW - 1] $ \j ->- spawn (Tile, Pos (i, j))-- insertRes $ Grid tiles--moveBy :: (Int, Int) -> Entity -> System (Maybe Entity)-moveBy (x, y) entity = do- Just (Pos (x', y')) <- [g|*Pos|] entity- getTile (x' + x, y' + y)--data Player = Player--instance Component Player where- required = require @Health--data OnTile = OnTile--instance Component OnTile where- type RelExclusivity OnTile = Exclusive--spawnPlayer :: System ()-spawnPlayer = do- Just tile <- getTile (5, 5)- void $ spawn (Player, Rel OnTile tile)--data Wall = Wall deriving (Component)--spawnWall :: (Int, Int) -> System Entity-spawnWall pos = do- Just tile <- getTile pos- spawn (Wall, Rel OnTile tile)--spawnWalls :: System ()-spawnWalls = do- for_ [0 .. gridW - 1] $ \i -> spawnWall (0, i)- for_ [0 .. gridW - 1] $ \i -> spawnWall (gridH - 1, i)- for_ [1 .. gridH - 2] $ \i -> spawnWall (i, 0)- for_ [1 .. gridH - 2] $ \i -> spawnWall (i, gridW - 1)--showTile :: Entity -> System Char-showTile tile = do- player <- tileHas @Player tile- enemy <- tileHas @Enemy tile- wall <- tileHas @Wall tile- coin <- tileHas @Coin tile-- pure $- if- | player -> '@'- | wall -> '#'- | enemy -> '!'- | coin -> '$'- | otherwise -> '.'--showGrid :: System String-showGrid = do- Just (Grid tiles) <- res @Grid- lines <- for tiles $ traverse showTile- return $ unlines lines--showHealth :: System String-showHealth = do- Just health <- [s|Health / With Player|]- pure $ "Health: " ++ show health.hp--showCoins :: System String-showCoins = do- Just (Coins c) <- res @Coins- pure $ "Coins: " ++ show c--printGrid :: System ()-printGrid = do- grid <- showGrid- health <- showHealth- coins <- showCoins- printClear $ health ++ "\n" ++ grid ++ "\n" ++ coins ++ "\n"--movePlayer :: System ()-movePlayer = do- c <- Stdin.readLast- for_ c $ \case- 'w' -> movePlayerBy (-1, 0)- 's' -> movePlayerBy (1, 0)- 'a' -> movePlayerBy (0, -1)- 'd' -> movePlayerBy (0, 1)- _ -> pure ()--movePlayerBy :: (Int, Int) -> System ()-movePlayerBy dir = do- Just player <- single' E (With (C @Player))-- Just (tile, pos) <- [g|OnTile -> (Entity, *Pos)|] player- newTile <- moveBy dir tile-- for_ newTile $ \t ->- tileIsFree t >>= flip when (insert (Rel OnTile t) player)--hasWall :: Entity -> System Bool-hasWall = tileHas @Wall--data Enemy = Enemy--instance Component Enemy where- required = require @Cooldown--data Cooldown = Cooldown {timer :: Timer} deriving (Component)--instance Default Cooldown where- def = Cooldown $ Timer.new 0.5 Timer.Repeat--data Rand = Rand (IOGenM StdGen) deriving (Component)--newGen :: System Rand-newGen = Rand <$> (newIOGenM =<< initStdGen)--randomPos :: System (Int, Int)-randomPos = do- Just (Rand gen) <- res @Rand- i <- applyIOGen (uniformR (1, gridH - 1)) gen- j <- applyIOGen (uniformR (1, gridW - 1)) gen- return (i, j)--randomTile :: System Entity-randomTile = unwrap <$> (getTile =<< randomPos)--spawnEnemy :: System Entity-spawnEnemy = do- tile <- randomTile- spawn (Enemy, Rel OnTile tile)--spawnEnemies :: System ()-spawnEnemies = for_ [0 .. 4] $ const spawnEnemy--decideEnemyDir :: Pos -> Pos -> System (Int, Int)-decideEnemyDir (Pos (ex, ey)) (Pos (px, py)) = do- left <- tileAtPosIsFree (ex - 1, ey)- up <- tileAtPosIsFree (ex, ey - 1)- right <- tileAtPosIsFree (ex + 1, ey)- down <- tileAtPosIsFree (ex, ey + 1)-- pure $- if- | ex > px && left -> (-1, 0)- | ey > py && up -> (0, -1)- | ex < px && right -> (1, 0)- | ey < py && down -> (0, 1)- | otherwise -> (0, 0)--moveEnemies :: System ()-moveEnemies = do- Just pos <- [s|OnTile -> (*Pos) / With Player|]- delta <- deltaTime-- enemies <- [q|Entity, OnTile -> (Entity, *Pos), Cooldown / With Enemy|]- for_ enemies $ \(enemy, (enemyTile, enemyPos), cooldown) -> do- let (timer, finished) = Timer.tick delta cooldown.timer- set cooldown $ Cooldown timer-- when finished $ do- diff <- decideEnemyDir enemyPos pos+ runTests+ [ test1,+ test2,+ test3,+ test4,+ test5+ ] - newTile <- moveBy diff enemyTile- for_ newTile $ \t -> do- insert (Rel OnTile t) enemy+ Changes.main -tileHas :: forall c. (QueryType c) => Entity -> System Bool-tileHas tile = not . null <$> [q|Entity / With (c, OnTile -> tile)|]+data P -tileAtPosIsFree :: (Int, Int) -> System Bool-tileAtPosIsFree pos = do- tile <- getTile pos- maybe (pure False) tileIsFree tile+instance Plugin P where+ init = systems s & schedule @FixedUpdate+ deps = [dep @FixedPlugin] -tileIsFree :: Entity -> System Bool-tileIsFree tile = do- wall <- tileHas @Wall tile- enemy <- tileHas @Enemy tile- player <- tileHas @Player tile- pure $ not (wall || enemy || player)+s :: System ()+s = do+ delta <- Time.delta+ elapsed <- Time.elapsed+ info [i|#{delta} -- #{elapsed}|] -data Health = Health {hp :: Int} deriving (Component)+test1 :: System ()+test1 = do+ a <- spawn (Name "A", Comp1)+ b <- spawn (Name "B", Comp1)+ c <- spawn (Name "C", Comp1, Comp2)+ d <- spawn (Name "D", Comp2)+ e <- spawn (Name "E", Comp2)+ f <- spawn (Name "F", Comp1, Comp3)+ g <- spawn (Name "G", Comp2, Comp3) -instance Default Health where- def = Health 100+ assertqWith+ [q|Name / With Comp1|]+ [(a, Name "A"), (b, Name "B"), (c, Name "C"), (f, Name "F")] -data Damage = Damage {amount :: Int} deriving (Event)+ assertqWith+ [q|Name / With Comp2|]+ [(c, Name "C"), (d, Name "D"), (e, Name "E"), (g, Name "G")] -onDamage :: Damage -> System ()-onDamage dmg = do- player <- [s|(Entity, Health) / With Player, Without Invincible|]+ assertqWith+ [q|Name / With Comp3|]+ [(f, Name "F"), (g, Name "G")] - for_ player $ \(entity, health) -> do- modify health $ \(Health x) -> Health $ max (x - dmg.amount) 0+ assertqWith+ [q|Name / With Comp3, Without Comp2|]+ [(f, Name "F")] - insert Invincible entity- delay 1000000 $ remove (C @Invincible) entity+ assertqWith+ [q|Name / With Comp1, With Comp2|]+ [(c, Name "C")] - Just health <- update health- when (health.hp == 0) $ liftIO exitSuccess+test2 :: System ()+test2 = do+ a <- spawn (Pos 3, Velocity 2, Comp1)+ b <- spawn (Pos 4, Velocity 2, Comp2, Comp3)+ c <- spawn (Pos 5, Velocity 3) -isAdjacent :: Pos -> Pos -> Bool-isAdjacent (Pos (x1, y1)) (Pos (x2, y2)) =- let dx = abs (x1 - x2)- dy = abs (y1 - y2)- in (dx == 1 && dy == 0) || (dx == 0 && dy == 1)+ [q|Pos, Velocity / With Comp1 || (With Comp2, With Comp3)|]+ & qinsert (\(Pos p, Velocity v) -> Pos $ p + v)+ & query_ -tryDamage :: System ()-tryDamage = do- Just player <- [s|OnTile -> (*Pos) / With Player|]- enemies <- [q|OnTile -> (*Pos) / With Enemy|]+ assertqWith+ [q|Pos|]+ [(a, Pos 5), (b, Pos 6), (c, Pos 5)] - for_ enemies $ \pos -> do- when (isAdjacent pos player) $ do- trigger (Damage 5)+test3 :: System ()+test3 = do+ a <- spawn (Pos 3, Velocity 5)+ insert (Rel (Likes 1) a) a+ b <- spawn (Pos 4, Velocity 2, Rel (Likes 5) a)+ c <- spawn (Pos 2, Velocity 1, Rel (Likes 1) a, Rel (Likes 2) b)+ d <- spawn (Pos 3, Rel (Likes 2) a, Rel (Likes 3) c) -data Invincible = Invincible deriving (Component)+ query_ $ do+ (e, Pos p, r) <- [q|Entity, Pos, Likes -> *|] -data Coin = Coin deriving (Component)+ [q|Entity, Velocity|]+ & qmapMaybe (\(entity, v) -> fmap ((v,) . (.comp)) (find ((== entity) . (.target)) r))+ & qcollect+ & qmap (foldr (\(From _ (Velocity x, Likes l)) y -> x * l + y) 0)+ & qrefocus e+ & qinsert (\x -> Pos $ x + p) -spawnCoin :: System ()-spawnCoin = do- tile <- randomTile- free <- tileIsFree tile- if free- then- void $ spawn (Coin, Rel OnTile tile)- else- spawnCoin+ assertqWith+ [q|Pos|]+ [(a, Pos 8), (b, Pos 29), (c, Pos 11), (d, Pos 16)] -data Coins = Coins Int deriving (Component)+test4 :: System ()+test4 = do+ a <- spawn (Name "A", Comp1)+ b <- spawn (Name "B", Rel ChildOf a, Comp1)+ c <- spawn (Name "C", Rel ChildOf a, Comp1)+ d <- spawn (Name "D", Comp1)+ e <- spawn (Name "E", Rel ChildOf d, Comp1) -collectCoins :: System ()-collectCoins = do- Just playerTile <- [s|OnTile -> (Entity) / With Player|]- coins <- [q|Entity / With OnTile -> playerTile, With Coin|]+ query_ $ do+ (e, name) <- [q|Entity, Name / With Comp1|]+ [q|/ With ChildOf -> e|]+ & qinsert (const name) - Just (Coins c) <- res @Coins- insertRes $ Coins $ c + length coins+ assertqWith+ [q|Name / With Comp1|]+ [(a, Name "A"), (b, Name "A"), (c, Name "A"), (d, Name "D"), (e, Name "D")] - for_ coins despawn+test5 :: System ()+test5 = do+ a <- spawn (Name "A", Comp1)+ b <- spawn (Name "B", Rel ChildOf a, Comp1)+ c <- spawn (Name "C", Rel ChildOf a, Comp1)+ d <- spawn (Name "D", Comp1)+ e <- spawn (Name "E", Rel ChildOf d, Comp1) -data Likes = Likes Int deriving (Component)+ [q|ChildOf -> (Name) / With Comp1|]+ & qinsert (\(From _ n) -> n)+ & query_ -a :: System ()-a = do- people <- query (C @Name, R @Likes (Q (C @Name)))- for_ people $ \(name, names) -> do- info $ text name <> " likes " <> text names+ assertqWith+ [q|Name / With Comp1|]+ [(a, Name "A"), (b, Name "A"), (c, Name "A"), (d, Name "D"), (e, Name "D")]
+ test/Utils.hs view
@@ -0,0 +1,64 @@+module Utils where++import Data.List (isSubsequenceOf, sortBy, sortOn)+import GHC.Stack (HasCallStack)+import Mischief.ECS+import Mischief.ECS (App (..), Component, MonadSystem, System)+import Mischief.ECS.App (App (..))+import Mischief.ECS.EntityDef+import Mischief.ECS.World (runSystem)++runTests :: [System ()] -> IO ()+runTests [] = pure ()+runTests (x : xs) = do+ app <- newApp+ sys app x+ runTests xs++sys :: App -> System () -> IO ()+sys app s = runSystem s app.world++assertEq :: (HasCallStack, Eq a, Show a, Applicative m) => a -> a -> m ()+assertEq a b | a == b = pure ()+assertEq a b = error $ "assert eq failed:\n" ++ show a ++ "\n" ++ show b++assertContains :: (HasCallStack, Eq a, Show a, Applicative m) => [a] -> [a] -> m ()+assertContains a b =+ if isSubsequenceOf b a+ then pure ()+ else+ error $ "assert contains failed:\n" ++ show a ++ "\n" ++ show b++assertq :: (HasCallStack, Eq a, Show a, MonadSystem w m) => Query m a -> Query m a -> m ()+assertq a b = do+ a <- sortOn fst <$> query (qentity a)+ b <- sortOn fst <$> query (qentity b)+ assertEq a b++assertqContains :: (HasCallStack, Eq a, Show a, MonadSystem w m) => Query m a -> Query m a -> m ()+assertqContains a b = do+ a <- sortOn fst <$> query (qentity a)+ b <- sortOn fst <$> query (qentity b)+ assertContains a b++assertqWith :: (HasCallStack, Eq a, Show a, MonadSystem w m) => Query m a -> [(Entity, a)] -> m ()+assertqWith a b = do+ a <- sortOn fst <$> query (qentity a)+ assertEq a (sortOn fst b)++assertqContainsWith :: (HasCallStack, Eq a, Show a, MonadSystem w m) => Query m a -> [(Entity, a)] -> m ()+assertqContainsWith a b = do+ a <- sortOn fst <$> query (qentity a)+ assertContains a (sortOn fst b)++newtype Pos = Pos Int deriving (Component, Eq, Show)++newtype Velocity = Velocity Int deriving (Component, Eq, Show)++newtype Likes = Likes Int deriving (Component, Eq, Show)++data Comp1 = Comp1 deriving (Component, Eq, Show)++data Comp2 = Comp2 deriving (Component, Eq, Show)++data Comp3 = Comp3 deriving (Component, Eq, Show)