packages feed

apecs 0.2.0.3 → 0.2.1.0

raw patch · 11 files changed

+424/−270 lines, 11 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Apecs: All :: All
- Apecs: [unEntity] :: Entity c -> Int
- Apecs: data All
- Apecs: setOrDelete :: forall w c. (IsRuntime c, Has w c) => Entity c -> Safe c -> System w ()
- Apecs: slice :: forall w c q. (Query q (Storage c), Has w c) => q -> System w (Slice c)
- Apecs.Stores: ByComponent :: c -> ByComponent c
- Apecs.Stores: ByIndex :: Int -> ByIndex a
- Apecs.Stores: class Bounded a => ToIndex a
- Apecs.Stores: data IndexTable s
- Apecs.Stores: instance (Apecs.Stores.ToIndex (Apecs.Types.Stores s), Apecs.Types.Initializable s) => Apecs.Types.Initializable (Apecs.Stores.IndexTable s)
- Apecs.Stores: instance (Apecs.Types.SafeRW s ~ GHC.Base.Maybe (Apecs.Types.Stores s), Apecs.Stores.ToIndex (Apecs.Types.Stores s), Apecs.Types.Store s) => Apecs.Types.HasMembers (Apecs.Stores.IndexTable s)
- Apecs.Stores: instance (Apecs.Types.SafeRW s ~ GHC.Base.Maybe (Apecs.Types.Stores s), Apecs.Stores.ToIndex (Apecs.Types.Stores s), Apecs.Types.Store s) => Apecs.Types.Store (Apecs.Stores.IndexTable s)
- Apecs.Stores: instance (Apecs.Types.SafeRW s ~ GHC.Base.Maybe (Apecs.Types.Stores s), Apecs.Types.Store s) => Apecs.Types.Store (Apecs.Stores.Cache n s)
- Apecs.Stores: instance (Apecs.Types.Stores s ~ c, Apecs.Stores.ToIndex (Apecs.Types.Stores s)) => Apecs.Types.Query (Apecs.Stores.ByComponent c) (Apecs.Stores.IndexTable s)
- Apecs.Stores: instance (Apecs.Types.Stores s ~ c, Apecs.Stores.ToIndex (Apecs.Types.Stores s)) => Apecs.Types.Query (Apecs.Stores.ByIndex c) (Apecs.Stores.IndexTable s)
- Apecs.Stores: instance (GHC.TypeLits.KnownNat n, Apecs.Types.Initializable s) => Apecs.Types.Initializable (Apecs.Stores.Cache n s)
- Apecs.Stores: instance Apecs.Types.HasMembers s => Apecs.Types.HasMembers (Apecs.Stores.Cache n s)
- Apecs.Stores: newtype ByComponent c
- Apecs.Stores: newtype ByIndex a
- Apecs.Stores: toIndex :: ToIndex a => a -> Int
- Apecs.System: setOrDelete :: forall w c. (IsRuntime c, Has w c) => Entity c -> Safe c -> System w ()
- Apecs.System: slice :: forall w c q. (Query q (Storage c), Has w c) => q -> System w (Slice c)
- Apecs.Types: All :: All
- Apecs.Types: [unEntity] :: Entity c -> Int
- Apecs.Types: class Query q s
- Apecs.Types: data All
- Apecs.Types: explSlice :: Query q s => s -> q -> IO (Vector Int)
- Apecs.Types: instance Apecs.Types.HasMembers s => Apecs.Types.Query Apecs.Types.All s
- Apecs.Util: ConcatQueries :: [q] -> ConcatQueries q
- Apecs.Util: instance Apecs.Types.Query q s => Apecs.Types.Query (Apecs.Util.ConcatQueries q) s
- Apecs.Util: newtype ConcatQueries q
+ Apecs: data Global c
+ Apecs: data Map c
+ Apecs: data Set c
+ Apecs: data Unique c
+ Apecs: set' :: forall w c. (IsRuntime c, Has w c) => Entity c -> Safe c -> System w ()
+ Apecs.Logs: FromPure :: (IORef (l c)) -> FromPure l c
+ Apecs.Logs: byEnum :: Enum c => EnumTable c -> c -> System w (Slice c)
+ Apecs.Logs: byIndex :: EnumTable c -> Int -> System w (Slice c)
+ Apecs.Logs: class Log l c
+ Apecs.Logs: class PureLog l c
+ Apecs.Logs: data EnumTable c
+ Apecs.Logs: data LVec1 l c
+ Apecs.Logs: data LVec2 l1 l2 c
+ Apecs.Logs: data LVec3 l1 l2 l3 c
+ Apecs.Logs: data Logger l s
+ Apecs.Logs: getLog :: forall w c l. (IsRuntime c, Has w c, HasLog (Storage c) l, Log l c) => System w (l c)
+ Apecs.Logs: instance (Apecs.Logs.Log l (Apecs.Types.Stores s), Apecs.Stores.Cachable s) => Apecs.Types.HasMembers (Apecs.Logs.Logger l s)
+ Apecs.Logs: instance (Apecs.Logs.Log l (Apecs.Types.Stores s), Apecs.Stores.Cachable s) => Apecs.Types.Initializable (Apecs.Logs.Logger l s)
+ Apecs.Logs: instance (Apecs.Logs.Log l (Apecs.Types.Stores s), Apecs.Stores.Cachable s) => Apecs.Types.Store (Apecs.Logs.Logger l s)
+ Apecs.Logs: instance (Apecs.Logs.Log l1 c, Apecs.Logs.Log l2 c) => Apecs.Logs.Log (Apecs.Logs.LVec2 l1 l2) c
+ Apecs.Logs: instance (Apecs.Logs.Log l1 c, Apecs.Logs.Log l2 c, Apecs.Logs.Log l3 c) => Apecs.Logs.Log (Apecs.Logs.LVec3 l1 l2 l3) c
+ Apecs.Logs: instance (GHC.Enum.Bounded c, GHC.Enum.Enum c) => Apecs.Logs.Log Apecs.Logs.EnumTable c
+ Apecs.Logs: instance Apecs.Logs.HasLog (Apecs.Logs.Logger l s) l
+ Apecs.Logs: instance Apecs.Logs.Log l c => Apecs.Logs.Log (Apecs.Logs.LVec1 l) c
+ Apecs.Logs: instance Apecs.Logs.PureLog l c => Apecs.Logs.Log (Apecs.Logs.FromPure l) c
+ Apecs.Logs: logEmpty :: Log l c => IO (l c)
+ Apecs.Logs: logOnDestroy :: Log l c => l c -> Entity a -> c -> IO ()
+ Apecs.Logs: logOnSet :: Log l c => l c -> Entity a -> Maybe c -> c -> IO ()
+ Apecs.Logs: logReset :: Log l c => l c -> IO ()
+ Apecs.Logs: newtype FromPure l c
+ Apecs.Logs: pureEmpty :: PureLog l c => l c
+ Apecs.Logs: pureOnDestroy :: PureLog l c => Entity a -> c -> l c -> l c
+ Apecs.Logs: pureOnSet :: PureLog l c => Entity a -> Maybe c -> c -> l c -> l c
+ Apecs.Stores: class (Initializable s, HasMembers s, Store s, SafeRW s ~ Maybe (Stores s)) => Cachable s
+ Apecs.Stores: data Unique c
+ Apecs.Stores: instance (GHC.TypeLits.KnownNat n, Apecs.Stores.Cachable s) => Apecs.Stores.Cachable (Apecs.Stores.Cache n s)
+ Apecs.Stores: instance (GHC.TypeLits.KnownNat n, Apecs.Stores.Cachable s) => Apecs.Types.Initializable (Apecs.Stores.Cache n s)
+ Apecs.Stores: instance Apecs.Stores.Cachable (Apecs.Stores.Map s)
+ Apecs.Stores: instance Apecs.Stores.Cachable s => Apecs.Types.HasMembers (Apecs.Stores.Cache n s)
+ Apecs.Stores: instance Apecs.Stores.Cachable s => Apecs.Types.Store (Apecs.Stores.Cache n s)
+ Apecs.Stores: instance Apecs.Types.HasMembers (Apecs.Stores.Unique c)
+ Apecs.Stores: instance Apecs.Types.Initializable (Apecs.Stores.Unique c)
+ Apecs.Stores: instance Apecs.Types.Store (Apecs.Stores.Unique c)
+ Apecs.System: set' :: forall w c. (IsRuntime c, Has w c) => Entity c -> Safe c -> System w ()
+ Apecs.Util: unEntity :: Entity a -> Int

Files

README.md view
@@ -1,5 +1,5 @@ # apecs-##### [hackage](https://hackage.haskell.org/package/apecs) | [documentation](https://hackage.haskell.org/package/apecs-0.2.0.2/docs/Apecs.html) | [tutorial](https://github.com/jonascarpay/apecs/blob/master/tutorials/RTS.md)+##### [hackage](https://hackage.haskell.org/package/apecs) | [documentation](https://hackage.haskell.org/package/apecs-0.2.0.2/docs/Apecs.html) | [tutorials](https://github.com/jonascarpay/apecs/blob/master/tutorials/)  apecs is an Entity Component System inspired by [specs](https://github.com/slide-rs/specs) and [Entitas](https://github.com/sschmid/Entitas-CSharp). It exposes a DSL that translates to fast storage operations, resulting in expressivity without sacrificing performance or safety.
apecs.cabal view
@@ -1,5 +1,5 @@ name:                apecs-version:             0.2.0.3+version:             0.2.1.0 homepage:            https://github.com/jonascarpay/apecs#readme license:             BSD3 license-file:        LICENSE@@ -19,6 +19,7 @@     Apecs,     Apecs.Types,     Apecs.Stores,+    Apecs.Logs,     Apecs.System,     Apecs.Slice,     Apecs.Util@@ -34,19 +35,22 @@     -Odph     -fno-warn-unused-top-binds -test-suite apecs-spec+test-suite apecs-test   type:     exitcode-stdio-1.0   main-is:-    Spec.hs+    Main.hs   hs-source-dirs:-    spec+    test   build-depends:     base >= 4.7 && < 5,     apecs,-    QuickCheck+    QuickCheck,+    containers,+    vector   default-language:     Haskell2010+  ghc-options: -Wall  benchmark apecs-bench   type:
− spec/Spec.hs
@@ -1,108 +0,0 @@-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE ScopedTypeVariables #-}--import Test.QuickCheck-import Test.QuickCheck.Monadic-import Control.Monad--import Apecs-import Apecs.Types-import Apecs.Util-import qualified Apecs.Stores as S--type Vec = (Double, Double)--newtype Position = Position Vec deriving (Arbitrary, Eq, Show)-instance Component Position where-  type Storage Position = S.Map Position--newtype CachePos = CachePos Vec deriving (Arbitrary, Eq, Show)-instance Component CachePos where-  type Storage CachePos = S.Map CachePos---newtype Velocity = Velocity Vec deriving (Arbitrary, Eq, Show)-instance Component Velocity where-  type Storage Velocity = S.Map Velocity---data Flag = Flag-instance Arbitrary Flag where arbitrary = return Flag-instance S.Flag Flag where flag = Flag-instance Component Flag where-  type Storage Flag = S.Set Flag---newtype RandomEntity a = RandomEntity (Entity a) deriving (Eq, Show)-instance Arbitrary (RandomEntity a) where-  arbitrary = RandomEntity . Entity <$> arbitrary--newtype W1 c = W1 {getIdentity :: (Storage c)}-instance Component c => Has (W1 c) c where getStore = System $ asks getIdentity--data W2 a b = W2 { c1 :: Storage a-                 , c2 :: Storage b-                 }--instance (Component a, Component b) => Has (W2 a b) a where getStore = System $ asks c1-instance (Component a, Component b) => Has (W2 a b) b where getStore = System $ asks c2--getSetPos :: [(RandomEntity Position, Position)] -> RandomEntity Position -> Position -> Property-getSetPos cs (RandomEntity e) p = monadicIO $ run f >>= assert-  where-    f = do-      w :: Storage Position <- initStore-      runWith (W1 w) $ do-        forM_ cs $ \(RandomEntity ety, pos) -> set ety pos-        set e p-        Safe r <- get e-        return (r == Just p)--getSetCPos :: [(RandomEntity CachePos, CachePos)] -> RandomEntity CachePos -> CachePos -> Property-getSetCPos cs (RandomEntity e) p = monadicIO $ run f >>= assert-  where-    f = do-      w :: Storage CachePos <- initStore-      runWith (W1 w) $ do-        forM_ cs $ \(RandomEntity ety, pos) -> set ety pos-        set e p-        Safe r <- get e-        return (r == Just p)--getSetVCPos :: [(RandomEntity (Velocity, CachePos), (Velocity, CachePos))] -> RandomEntity (Velocity, CachePos) -> (Velocity, CachePos) -> Property-getSetVCPos cs (RandomEntity e) (v,p) = monadicIO $ run f >>= assert-  where-    f = do-      wp :: Storage CachePos <- initStore-      wv :: Storage Velocity <- initStore-      runWith (W2 wp wv) $ do-        forM_ cs $ \(RandomEntity ety, pos) -> set ety pos-        set e (v,p)-        Safe r <- get e-        return (r == (Just v, Just p))--cmapVP :: [(RandomEntity (Velocity, CachePos), (Velocity, CachePos))] -> RandomEntity (Velocity, CachePos) -> (Velocity, CachePos) -> Property-cmapVP cs (RandomEntity e) (v,p) = monadicIO $ run f >>= assert-  where-    f = do-      let swapP (CachePos (x,y)) = CachePos (y,x)-          swapV (Velocity (x,y)) = Velocity (y,x)-      wp :: Storage CachePos <- initStore-      wv :: Storage Velocity <- initStore-      runWith (W2 wp wv) $ do-        forM_ cs $ \(RandomEntity ety, pos) -> set ety pos-        set e (v,p)-        cmap $ \(v,p) -> (swapV v, swapP p)-        Safe r <- get e-        return (r == (Just $ swapV v, Just $ swapP p))---main = do-  quickCheck getSetPos-  quickCheck getSetCPos-  quickCheck getSetVCPos-  quickCheck cmapVP
src/Apecs.hs view
@@ -4,7 +4,9 @@   -- * Types     System(..),     Component(..), Entity(..), Slice, Has(..), Safe(..), cast,+    Map, Set, Unique, Global, +   -- * Initializable     initStoreWith, @@ -12,7 +14,7 @@     destroy, exists, owners, resetStore,    -- * Store wrapper functions-    get, set, setOrDelete, modify,+    get, set, set', modify,     cmap, cmapM, cmapM_, cimapM, cimapM_,     rmap', rmap, wmap, wmap', cmap', @@ -20,9 +22,6 @@   -- * GlobalRW wrapper functions     readGlobal, writeGlobal, modifyGlobal, -  -- * Query-    slice, All(..),-   -- * Other     runSystem, runWith, @@ -38,4 +37,5 @@ import Apecs.Types import Apecs.System import Apecs.Slice as SL+import Apecs.Stores 
+ src/Apecs/Logs.hs view
@@ -0,0 +1,207 @@+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, ConstraintKinds #-}+{-# LANGUAGE ScopedTypeVariables, FlexibleInstances, FlexibleContexts #-}+{-# LANGUAGE TypeFamilyDependencies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE Strict #-}++module Apecs.Logs+  ( -- * Types and classes+    Log(..), PureLog(..), FromPure(..), Logger, getLog,+    LVec1, LVec2, LVec3,++    -- * EnumTable+    EnumTable, byIndex, byEnum,+  ) where++import Data.IORef+import qualified Data.Vector.Mutable as VM+import qualified Data.IntSet as S+import Control.Monad.Reader++import Apecs.Types+import Apecs.Stores+import Apecs.Slice++-- | A PureLog is a piece of state @l c@ that is updated when components @c@ are written or destroyed.+--   Note that @l :: * -> *@+class PureLog l c where+  pureEmpty :: l c+  pureOnSet :: Entity a -> Maybe c -> c -> l c -> l c+  pureOnDestroy :: Entity a -> c -> l c -> l c++-- | A Log is a PureLog with mutable state.+class Log l c where+  logEmpty     :: IO (l c)+  logOnSet     :: l c -> Entity a -> Maybe c -> c -> IO ()+  logOnDestroy :: l c -> Entity a -> c -> IO ()+  logReset     :: l c -> IO ()++class HasLog s l where+  explGetLog :: s -> l (Stores s)++instance HasLog (Logger l s) l where+  {-# INLINE explGetLog #-}+  explGetLog (Logger l _) = l++-- | Produces the log indicated by the return type.+{-# INLINE getLog #-}+getLog :: forall w c l. (IsRuntime c, Has w c, HasLog (Storage c) l, Log l c) => System w (l c)+getLog = do s :: Storage c <- getStore+            return (explGetLog s)+++-- | FromPure turns a PureLog into a Log+newtype FromPure l c = FromPure (IORef (l c))+instance PureLog l c => Log (FromPure l) c where+  {-# INLINE logEmpty #-}+  logEmpty = FromPure <$> newIORef pureEmpty+  {-# INLINE logOnSet #-}+  logOnSet (FromPure lref) e old new = modifyIORef' lref (pureOnSet e old new)+  {-# INLINE logOnDestroy #-}+  logOnDestroy (FromPure lref) e c = modifyIORef' lref (pureOnDestroy e c)+  {-# INLINE logReset #-}+  logReset (FromPure lref) = writeIORef lref pureEmpty++-- | A @Logger l@ of some store updates its @Log l@ with the writes and deletes to @Store s@+data Logger l s = Logger (l (Stores s)) s++instance (Log l (Stores s), Cachable s) => Initializable (Logger l s) where+  type InitArgs (Logger l s) = InitArgs s+  initStoreWith args = Logger <$> logEmpty <*> initStoreWith args++instance (Log l (Stores s), Cachable s) => HasMembers (Logger l s) where+  {-# INLINE explDestroy #-}+  explDestroy (Logger l s) ety = do+    mc <- explGet s ety+    case mc of+      Just c -> logOnDestroy l (Entity ety) c >> explDestroy s ety+      _ -> return ()++  {-# INLINE explExists #-}+  explExists (Logger _ s) ety = explExists s ety+  {-# INLINE explMembers #-}+  explMembers (Logger _ s) = explMembers s+  {-# INLINE explReset #-}+  explReset (Logger l s) = logReset l >> explReset s+  {-# INLINE explImapM_ #-}+  explImapM_ (Logger _ s) = explImapM_ s+  {-# INLINE explImapM #-}+  explImapM (Logger _ s) = explImapM s++instance (Log l (Stores s), Cachable s) => Store (Logger l s) where+  type SafeRW (Logger l s) = SafeRW s+  type Stores (Logger l s) = Stores s++  {-# INLINE explGetUnsafe #-}+  explGetUnsafe (Logger _ s) ety = explGetUnsafe s ety+  {-# INLINE explGet #-}+  explGet (Logger _ s) ety = explGet s ety+  {-# INLINE explSet #-}+  explSet (Logger l s) ety x = do+    mc <- explGet s ety+    logOnSet l (Entity ety) mc x+    explSet s ety x++  {-# INLINE explSetMaybe #-}+  explSetMaybe s ety (Nothing) = explDestroy s ety+  explSetMaybe s ety (Just x) = explSet s ety x++  {-# INLINE explModify #-}+  explModify (Logger l s) ety f = do+    mc <- explGet s ety+    case mc of+      Just c -> explSet (Logger l s) ety (f c)+      Nothing -> return ()++  {-# INLINE explCmapM_ #-}+  explCmapM_  (Logger _ s) = explCmapM_  s+  {-# INLINE explCmapM #-}+  explCmapM   (Logger _ s) = explCmapM   s+  {-# INLINE explCimapM_ #-}+  explCimapM_ (Logger _ s) = explCimapM_ s+  {-# INLINE explCimapM #-}+  explCimapM  (Logger _ s) = explCimapM  s++-- | Composite Log consisting of 1 Log+newtype LVec1 l c = LVec1 (l c)+instance Log l c => Log (LVec1 l) c where+  {-# INLINE logEmpty #-}+  logEmpty = LVec1 <$> logEmpty+  {-# INLINE logOnSet #-}+  logOnSet     (LVec1 l) e old new = logOnSet l e old new+  {-# INLINE logOnDestroy #-}+  logOnDestroy (LVec1 l) e c       = logOnDestroy l e c+  {-# INLINE logReset #-}+  logReset     (LVec1 l)           = logReset l++-- | Composite Log consisting of 2 Logs+data LVec2 l1 l2 c = LVec2 (l1 c) (l2 c)+instance (Log l1 c, Log l2 c) => Log (LVec2 l1 l2) c where+  {-# INLINE logEmpty #-}+  logEmpty = LVec2 <$> logEmpty <*> logEmpty+  {-# INLINE logOnSet #-}+  logOnSet     (LVec2 l1 l2) e old new = logOnSet l1 e old new >> logOnSet l2 e old new+  {-# INLINE logOnDestroy #-}+  logOnDestroy (LVec2 l1 l2) e c       = logOnDestroy l1 e c >> logOnDestroy l2 e c+  {-# INLINE logReset #-}+  logReset     (LVec2 l1 l2)           = logReset l1 >> logReset l2++-- | Composite Log consisting of 3 Logs+data LVec3 l1 l2 l3 c = LVec3 (l1 c) (l2 c) (l3 c)+instance (Log l1 c, Log l2 c, Log l3 c) => Log (LVec3 l1 l2 l3) c where+  {-# INLINE logEmpty #-}+  logEmpty = LVec3 <$> logEmpty <*> logEmpty <*> logEmpty+  {-# INLINE logOnSet #-}+  logOnSet (LVec3 l1 l2 l3) e old new = do+    logOnSet l1 e old new+    logOnSet l2 e old new+    logOnSet l3 e old new+  {-# INLINE logOnDestroy #-}+  logOnDestroy (LVec3 l1 l2 l3) e c = do+    logOnDestroy l1 e c+    logOnDestroy l2 e c+    logOnDestroy l3 e c+  {-# INLINE logReset #-}+  logReset (LVec3 l1 l2 l3) = do+    logReset l1+    logReset l2+    logReset l3++-- | Hashtable that maintains buckets of entities whose @fromEnum c@ produces the same value+newtype EnumTable c = EnumTable (VM.IOVector S.IntSet)+instance (Bounded c, Enum c) => Log EnumTable c where+  {-# INLINE logEmpty #-}+  logEmpty = do+    let lo = fromEnum (minBound :: c)+        hi = fromEnum (maxBound :: c)++    if lo == 0+       then EnumTable <$> VM.replicate (hi+1) mempty+       else error "Attempted to initialize EnumTable for a component with a non-zero minBound"++  {-# INLINE logOnSet #-}+  logOnSet (EnumTable vec) (Entity e) old new = do+    case old of+      Nothing -> return ()+      Just c -> VM.modify vec (S.delete e) (fromEnum c)+    VM.modify vec (S.insert e) (fromEnum new)++  {-# INLINE logOnDestroy #-}+  logOnDestroy (EnumTable vec) (Entity e) c = VM.modify vec (S.delete e) (fromEnum c)++  {-# INLINE logReset #-}+  logReset (EnumTable vec) = forM_ [0..VM.length vec - 1] (\e -> VM.write vec e mempty)++-- | Query the @EnumTable@ by an index (the result of @fromEnum@).+--   Will return an empty slice if @index < 0@ of @index >= fromEnum (maxBound)@.+{-# INLINE byIndex #-}+byIndex :: EnumTable c -> Int -> System w (Slice c)+byIndex (EnumTable vec) c+  | c < 0                  = return mempty+  | c >= VM.length vec - 1 = return mempty+  | otherwise = liftIO$ sliceFromList . S.toList <$> VM.read vec c++-- | Query the @EnumTable@ by an example enum.+--   Will not perform bound checks, so crashes if @fromEnum c < 0 && fromEnum c > fromEnum maxBound @.+byEnum :: Enum c => EnumTable c -> c -> System w (Slice c)+byEnum (EnumTable vec) c = liftIO$ sliceFromList . S.toList <$> VM.read vec (fromEnum c)
src/Apecs/Stores.hs view
@@ -5,12 +5,11 @@ {-# LANGUAGE FlexibleContexts, FlexibleInstances #-} {-# LANGUAGE ConstraintKinds, DataKinds, KindSignatures #-} {-# LANGUAGE TypeApplications #-}-{-# LANGUAGE UndecidableInstances #-}  module Apecs.Stores-  ( Map, Set, Flag(..), Cache,+  ( Map, Set, Flag(..), Cache, Unique,     Global,-    IndexTable, ToIndex(..), ByIndex(..), ByComponent(..),+    Cachable,   ) where  import qualified Data.IntMap.Strict as M@@ -110,6 +109,70 @@   {-# INLINE explCmap #-}   {-# INLINE explModify #-} +-- | A Unique contains exactly one component belonging to some entity.+--   Writing to it overwrites both the previous component and its owner.+data Unique c = Unique (IORef Int) (IORef c)+instance Initializable (Unique c) where+  type InitArgs (Unique c) = ()+  initStoreWith _ = Unique <$> newIORef (-1) <*> newIORef undefined+instance HasMembers (Unique c) where+  explDestroy (Unique eref _) ety = do e <- readIORef eref; when (e==ety) (writeIORef eref (-1))+  explMembers (Unique eref _) = U.singleton <$> readIORef eref+  explReset   (Unique eref _) = writeIORef eref (-1)+  explExists  (Unique eref _) ety = (==ety) <$> readIORef eref+  explImapM_  (Unique eref _) ma = do e <- liftIO (readIORef eref); when (e /= -1) (void$ ma e)+  explImapM   (Unique eref _) ma = do+    e <- liftIO (readIORef eref)+    if e /= -1 then return [] else pure <$> ma e+  {-# INLINE explDestroy #-}+  {-# INLINE explMembers #-}+  {-# INLINE explExists #-}+  {-# INLINE explReset #-}+  {-# INLINE explImapM_ #-}+  {-# INLINE explImapM #-}++instance Store (Unique c) where+  type SafeRW (Unique c) = Maybe c+  type Stores (Unique c) = c+  explGetUnsafe (Unique _ cref) _ = readIORef cref+  explGet       (Unique eref cref) ety = do+    e <- readIORef eref+    if e == ety then Just <$> readIORef cref else return Nothing++  explSet       (Unique eref cref) ety x = writeIORef eref ety >> writeIORef cref x+  explSetMaybe  s ety Nothing = explDestroy s ety+  explSetMaybe  s ety (Just x) = explSet s ety x+  explCmap      (Unique _    cref) f = modifyIORef' cref f+  explModify    (Unique eref cref) ety f = do+    e <- readIORef eref+    when (e==ety) (modifyIORef' cref f)++  explCmapM (Unique eref cref) ma = do+    e <- liftIO$ readIORef eref+    if e /= -1 then liftIO (readIORef cref) >>= fmap pure . ma+               else return []++  explCmapM_ (Unique eref cref) ma = do+    e <- liftIO$ readIORef eref+    when (e /= -1) . void $ liftIO (readIORef cref) >>= ma++  explCimapM (Unique eref cref) ma = do+    e <- liftIO$ readIORef eref+    if e /= -1 then liftIO (readIORef cref) >>= fmap pure . ma . (,) e+               else return []++  explCimapM_ (Unique eref cref) ma = do+    e <- liftIO$ readIORef eref+    when (e /= -1) . void $ liftIO (readIORef cref) >>= ma . (,) e++  {-# INLINE explGetUnsafe #-}+  {-# INLINE explGet #-}+  {-# INLINE explSet #-}+  {-# INLINE explSetMaybe #-}+  {-# INLINE explCmap #-}+  {-# INLINE explModify #-}++ -- | Constant value. Not very practical, but fun to write. newtype Const c = Const c instance Initializable (Const c) where@@ -155,7 +218,11 @@ data Cache (n :: Nat) s =   Cache Int (UM.IOVector Int) (VM.IOVector (Stores s)) s -instance (KnownNat n, Initializable s) => Initializable (Cache n s) where+class (Initializable s, HasMembers s, Store s, SafeRW s ~ Maybe (Stores s)) => Cachable s+instance Cachable (Map s)+instance (KnownNat n, Cachable s) => Cachable (Cache n s)++instance (KnownNat n, Cachable s) => Initializable (Cache n s) where   type InitArgs (Cache n s) = (InitArgs s)   initStoreWith args = do     let n = fromIntegral$ natVal (Proxy @n)@@ -164,7 +231,7 @@     child <- initStoreWith args     return (Cache n tags cache child) -instance HasMembers s => HasMembers (Cache n s) where+instance Cachable s => HasMembers (Cache n s) where   {-# INLINE explDestroy #-}   explDestroy (Cache n tags _ s) ety = do     tag <- UM.unsafeRead tags (ety `mod` n)@@ -199,7 +266,7 @@     as2 <- explImapM s ma     return (as1 ++ as2) -instance (SafeRW s ~ Maybe (Stores s), Store s) => Store (Cache n s) where+instance Cachable s => Store (Cache n s) where   type SafeRW (Cache n s) = SafeRW s   type Stores (Cache n s) = Stores s @@ -265,107 +332,3 @@         r <- liftIO$ VM.read cache e         void$ ma (e, r)     explCimapM_ s ma---- | A component that can be hashed to a table index.---   minBound must hash to the lowest possible value, maxBound must hash to the highest.---   For Enums, toIndex = fromEnum-class Bounded a => ToIndex a where-  toIndex :: a -> Int--- | A query to an IndexTable by an explicit index-newtype ByIndex a     = ByIndex Int--- | A query to an IndexTable by a reference component-newtype ByComponent c = ByComponent c--- | A table that keeps a hashtable of indices along with its writes.--- TODO: Benchmark? hashing function as argument?-data IndexTable s = IndexTable-  { table :: VM.IOVector S.IntSet-  , wrapped :: s-  }--instance (ToIndex (Stores s), Initializable s) => Initializable (IndexTable s) where-  type InitArgs (IndexTable s) = InitArgs s-  initStoreWith args = do-    let lo = toIndex (minBound :: Stores s)-        hi = toIndex (maxBound :: Stores s)-        size = hi - lo + 1-    s <- initStoreWith args-    tab <- VM.replicate size mempty-    return (IndexTable tab s)--instance (SafeRW s ~ Maybe (Stores s), ToIndex (Stores s), Store s) => HasMembers (IndexTable s) where-  {-# INLINE explDestroy #-}-  explDestroy (IndexTable tab s) ety = do-    mc <- explGet s ety-    case mc of-      Just c -> do-        VM.modify tab (S.delete ety) (toIndex c)-        explDestroy s ety-      _ -> return ()--  {-# INLINE explExists #-}-  explExists  (IndexTable _ s) ety = explExists  s ety-  {-# INLINE explMembers #-}-  explMembers (IndexTable _ s) = explMembers s--  {-# INLINE explReset #-}-  explReset (IndexTable tab s) = do-    forM_ [0 .. VM.length tab-1] $ \e -> VM.write tab e mempty-    explReset s--  {-# INLINE explImapM_ #-}-  explImapM_ (IndexTable _ s) = explImapM_ s--  {-# INLINE explImapM #-}-  explImapM (IndexTable _ s) = explImapM s--instance (SafeRW s ~ Maybe (Stores s), ToIndex (Stores s), Store s) => Store (IndexTable s) where-  type SafeRW (IndexTable s) = SafeRW s-  type Stores (IndexTable s) = Stores s-  {-# INLINE explGetUnsafe #-}-  explGetUnsafe (IndexTable _ s) ety = explGetUnsafe s ety-  {-# INLINE explGet #-}-  explGet (IndexTable _ s) ety = explGet s ety-  {-# INLINE explSet #-}-  explSet (IndexTable tab s) ety x = do-    let indexNew = toIndex x-    mc <- explGet s ety-    case mc of-      Nothing -> VM.modify tab (S.insert ety) indexNew-      Just c  -> do let indexOld = toIndex c-                    unless (indexOld == indexNew) $ do-                      VM.modify tab (S.delete ety) indexOld-                      VM.modify tab (S.insert ety) indexNew-    explSet s ety x-  {-# INLINE explSetMaybe #-}-  explSetMaybe s ety Nothing = explDestroy s ety-  explSetMaybe s ety (Just x) = explSet s ety x-  {-# INLINE explModify #-}-  explModify (IndexTable tab s) ety f = do-    mc <- explGet s ety-    case mc of-      Nothing -> return ()-      Just c  -> do let indexOld = toIndex c-                        x = f c-                        indexNew = toIndex c-                    unless (indexOld == indexNew) $ do-                      VM.modify tab (S.delete ety) indexOld-                      VM.modify tab (S.insert ety) indexNew-                    explSet s ety x--  explCmapM_  (IndexTable _ s) = explCmapM_  s-  explCmapM   (IndexTable _ s) = explCmapM   s-  explCimapM_ (IndexTable _ s) = explCimapM_ s-  explCimapM  (IndexTable _ s) = explCimapM  s-  {-# INLINE explCmapM_ #-}-  {-# INLINE explCmapM #-}-  {-# INLINE explCimapM_ #-}-  {-# INLINE explCimapM #-}--instance (Stores s ~ c, ToIndex (Stores s)) => Query (ByComponent c) (IndexTable s) where-  {-# INLINE explSlice #-}-  explSlice (IndexTable tab _) (ByComponent c) = U.fromList . S.elems <$> VM.read tab (toIndex c)--instance (Stores s ~ c, ToIndex (Stores s)) => Query (ByIndex c) (IndexTable s) where-  {-# INLINE explSlice #-}-  explSlice (IndexTable tab _) (ByIndex ix) = U.fromList . S.elems <$> VM.read tab ix-
src/Apecs/System.hs view
@@ -63,8 +63,8 @@   liftIO$ explSet s ety x  -- | Same as @set@, but uses Safe to possibly delete a component-setOrDelete :: forall w c. (IsRuntime c, Has w c) => Entity c -> Safe c -> System w ()-setOrDelete (Entity ety) (Safe c) = do+set' :: forall w c. (IsRuntime c, Has w c) => Entity c -> Safe c -> System w ()+set' (Entity ety) (Safe c) = do   s :: Storage c <- getStore   liftIO$ explSetMaybe s ety c @@ -177,14 +177,6 @@                         U.forM_ sl $ \ e -> do                           r <- explGet sr e                           explSetMaybe sw e (getSafe . f . Safe $ r)----- | Performs a query-{-# INLINE slice #-}-slice :: forall w c q. (Query q (Storage c), Has w c) => q -> System w (Slice c)-slice q = do-  s :: Storage c <- getStore-  liftIO$ Slice <$> explSlice s q  -- | Reads a global value {-# INLINE readGlobal #-}
src/Apecs/Types.hs view
@@ -12,7 +12,7 @@ import qualified Data.Vector.Unboxed as U  -- | An Entity is really just an Int. The type variable is used to keep track of reads and writes, but can be freely cast.-newtype Entity c = Entity {unEntity :: Int} deriving (Eq, Ord, Show)+newtype Entity c = Entity Int deriving (Eq, Ord, Show)  -- | A slice is a list of entities, represented by a Data.Unbox.Vector of Ints. newtype Slice c = Slice {unSlice :: U.Vector Int} deriving (Show, Monoid)@@ -47,7 +47,8 @@   -- | Returns an unboxed vector of member indices   explMembers :: s -> IO (U.Vector Int) -  -- | Removes all components. Default implementation iterates over members and calls explDestroy.+  -- | Removes all components.+  --   Equivalent to calling @explDestroy@ on each member   {-# INLINE explReset #-}   explReset :: s -> IO ()   explReset s = do@@ -83,13 +84,14 @@   explSetMaybe  :: s -> Int -> SafeRW s -> IO ()    -- | Modifies an element in the store.+  --   Equivalent to reading a value, and then writing the result of the function application.   {-# INLINE explModify #-}   explModify :: s -> Int -> (Stores s -> Stores s) -> IO ()   explModify s ety f = do etyExists <- explExists s ety                           when etyExists $ explGetUnsafe s ety >>= explSet s ety . f    -- | Maps over all elements of this store.-  --   The default implementation can be replaced by an optimized one+  --   Equivalent to getting a list of all entities with this component, and then explModifying each of them.   explCmap :: s -> (Stores s -> Stores s) -> IO ()   {-# INLINE explCmap #-}   explCmap s f = explMembers s >>= U.mapM_ (\ety -> explModify s ety f)@@ -138,17 +140,6 @@   explGlobalModify :: s -> (c -> c) -> IO ()   explGlobalModify s f = do r <- explGlobalRead s                             explGlobalWrite s (f r)---- | Classes of queries @q@ that can be performed on a store @s@.-class Query q s where-  -- | Returns a slice of the results of the query-  explSlice :: s -> q -> IO (U.Vector Int)---- | Query that returns all members, equivalent to @members@-data All = All-instance HasMembers s => Query All s where-  {-# INLINE explSlice #-}-  explSlice s _ = explMembers s  -- | Casts for entities and slices class Cast a b where
src/Apecs/Util.hs view
@@ -4,12 +4,13 @@  module Apecs.Util (   -- * Utility-  initStore, ConcatQueries(..), runGC,+  initStore, runGC, unEntity,    -- * EntityCounter   EntityCounter, initCounter, nextEntity, newEntity,    -- * Spatial hashing+  -- $hash   quantize, flatten, region, inbounds,    -- * Timing@@ -30,6 +31,9 @@ initStore :: (Initializable s, InitArgs s ~ ()) => IO s initStore = initStoreWith () +unEntity :: Entity a -> Int+unEntity (Entity e) = e+ -- | Secretly just an int in a newtype newtype EntityCounter = EntityCounter Int deriving (Num, Eq, Show) instance Component EntityCounter where@@ -58,20 +62,15 @@ runGC :: System w () runGC = liftIO performMajorGC --- | Sequentially performs a series of queries and concatenates their result.---   Especially useful when iterating over an IndexTable-newtype ConcatQueries q = ConcatQueries [q]-instance Query q s => Query (ConcatQueries q) s where-  explSlice s (ConcatQueries qs) = mconcat <$> traverse (explSlice s) qs---- | The following functions are for spatial hashing.---   The idea is that your spatial hash is defined by two vectors;---     - The cell size vector contains real components and dictates---       how large each cell in your table is spatially.---       It is used to translate from world-space to table space---     - The field size vector contains integral components and dictates how---       many cells your field consists of in each direction.---       It is used to translate from table-space to a flat integer+-- $hash+-- The following functions are for spatial hashing.+-- The idea is that your spatial hash is defined by two vectors;+--   - The cell size vector contains real components and dictates+--     how large each cell in your table is spatially.+--     It is used to translate from world-space to table space+--   - The field size vector contains integral components and dictates how+--     many cells your field consists of in each direction.+--     It is used to translate from table-space to a flat integer  -- | Quantize turns a world-space coordinate into a table-space coordinate by dividing --   by the given cell size and round components towards negative infinity@@ -103,7 +102,6 @@ inbounds :: (Num (v a), Ord a, Applicative v, Foldable v)          => v a -> v a -> Bool inbounds size vec = and (liftA2 (>=) vec 0) && and (liftA2 (<=) vec size)-   -- | Runs a system and gives its execution time in seconds
+ test/Main.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleContexts, FlexibleInstances #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}++{-# OPTIONS_GHC -w #-}++import Test.QuickCheck+import Test.QuickCheck.Monadic+import Control.Monad+import qualified Data.IntSet as S+import qualified Data.Vector.Unboxed as U+import Data.IORef++import Apecs+import Apecs.Types+import Apecs.Util+import qualified Apecs.Stores as S+import qualified Apecs.Logs as S++type Vec = (Double, Double)++newtype Position = Position Vec deriving (Arbitrary, Eq, Show)+instance Component Position where+  type Storage Position = S.Logger (S.FromPure Members) (S.Map Position)++newtype CachePos = CachePos Vec deriving (Arbitrary, Eq, Show)+instance Component CachePos where+  type Storage CachePos = S.Map CachePos+++newtype Velocity = Velocity Vec deriving (Arbitrary, Eq, Show)+instance Component Velocity where+  type Storage Velocity = S.Map Velocity+++data Flag = Flag+instance Arbitrary Flag where arbitrary = return Flag+instance S.Flag Flag where flag = Flag+instance Component Flag where+  type Storage Flag = S.Set Flag+++newtype Members c = Members S.IntSet+instance S.PureLog Members c where+  pureEmpty = Members mempty+  pureOnSet (Entity e) _ _ (Members s) = Members $ S.insert e s+  pureOnDestroy (Entity e) _ (Members s) = Members $ S.delete e s++newtype RandomEntity a = RandomEntity (Entity a) deriving (Eq, Show)+instance Arbitrary (RandomEntity a) where+  arbitrary = RandomEntity . Entity <$> arbitrary++newtype W1 c = W1 {w1c1 :: (Storage c)}+instance Component c => Has (W1 c) c where getStore = System $ asks w1c1++data W2 a b = W2 { w2c1 :: Storage a , w2c2 :: Storage b }+instance (Component a, Component b) => Has (W2 a b) a where getStore = System $ asks w2c1+instance (Component a, Component b) => Has (W2 a b) b where getStore = System $ asks w2c2++getSetPos :: [(RandomEntity Position, Position)] -> RandomEntity Position -> Position -> Property+getSetPos cs (RandomEntity e) p = monadicIO $ run f >>= assert+  where+    f = do+      w :: Storage Position <- initStore+      runWith (W1 w) $ do+        forM_ cs $ \(RandomEntity ety, pos) -> set ety pos+        set e p+        Safe r <- get e+        Slice sl1 :: Slice Position <- owners+        S.FromPure ref :: S.FromPure Members Position <- S.getLog+        Members set <- liftIO$ readIORef ref+        return (r == Just p && sl1 == U.fromList (S.toList set))++getSetVCPos :: [(RandomEntity (Velocity, CachePos), (Velocity, CachePos))] -> RandomEntity (Velocity, CachePos) -> (Velocity, CachePos) -> Property+getSetVCPos cs (RandomEntity e) (v,p) = monadicIO $ run f >>= assert+  where+    f = do+      wp :: Storage CachePos <- initStore+      wv :: Storage Velocity <- initStore+      runWith (W2 wp wv) $ do+        forM_ cs $ \(RandomEntity ety, pos) -> set ety pos+        set e (v,p)+        Safe r <- get e+        return (r == (Just v, Just p))++cmapVP :: [(RandomEntity (Velocity, CachePos), (Velocity, CachePos))] -> RandomEntity (Velocity, CachePos) -> (Velocity, CachePos) -> Property+cmapVP cs (RandomEntity e) (v,p) = monadicIO $ run f >>= assert+  where+    f = do+      let swapP (CachePos (x,y)) = CachePos (y,x)+          swapV (Velocity (x,y)) = Velocity (y,x)+      wp :: Storage CachePos <- initStore+      wv :: Storage Velocity <- initStore+      runWith (W2 wp wv) $ do+        forM_ cs $ \(RandomEntity ety, pos) -> set ety pos+        set e (v,p)+        cmap $ \(v,p) -> (swapV v, swapP p)+        Safe r <- get e+        return (r == (Just $ swapV v, Just $ swapP p))++main = do+  quickCheck getSetPos+  quickCheck getSetVCPos+  quickCheck cmapVP
tutorials/RTS.md view
@@ -288,5 +288,5 @@ There will be at least one more tutorial, on how to make things fast. We'll be taking a look at   - How to cache your components for O(1) reads and writes-  - How to use an IndexTable to add queries to your component storages-  - How to use those IndexTables to get a free spatial hash of our positions+  - How to use add Logs to your component storages+  - How to use those Logs to get a free spatial hash of our positions