apecs 0.9.1 → 0.9.2
raw patch · 10 files changed
+123/−76 lines, 10 filesdep +exceptionsPVP ok
version bump matches the API change (PVP)
Dependencies added: exceptions
API changes (from Hackage documentation)
+ Apecs.Core: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Apecs.Core.SystemT w m)
+ Apecs.Core: instance Control.Monad.Catch.MonadMask m => Control.Monad.Catch.MonadMask (Apecs.Core.SystemT w m)
+ Apecs.Core: instance Control.Monad.Catch.MonadThrow m => Control.Monad.Catch.MonadThrow (Apecs.Core.SystemT w m)
Files
- CHANGELOG.md +6/−0
- README.md +46/−25
- apecs.cabal +2/−1
- src/Apecs.hs +9/−8
- src/Apecs/Core.hs +4/−1
- src/Apecs/Experimental/Reactive.hs +2/−0
- src/Apecs/Stores.hs +26/−17
- src/Apecs/System.hs +1/−0
- src/Apecs/TH.hs +18/−16
- src/Apecs/Util.hs +9/−8
CHANGELOG.md view
@@ -1,3 +1,9 @@+## [0.9.2]+### Changed+- (#68) Add instances of MonadThrow, MonadCatch, and MonadMask to SystemT+- Cleaned up the README+- Small haddock fixes+ ## [0.9.1] ### Changed - (#63) Fixed bug where `modify` on non-existent components crashes
README.md view
@@ -1,37 +1,58 @@-# apecs [](https://travis-ci.org/jonascarpay/apecs)-apecs is an _Entity Component System_ (ECS) library inspired by [specs](https://github.com/slide-rs/specs) and [Entitas](https://github.com/sschmid/Entitas-CSharp).-ECS presents a data-driven approach to game development, that elegantly tackles many of the unique issues of game programming.+# apecs +apecs is an [_Entity Component System_](https://en.wikipedia.org/wiki/Entity_component_system) (ECS) library for game development.+ apecs aims to be-* **Fast** - Performance is competitive with Rust ECS libraries (see benchmark results below).-* **Concise** - Game logic is expressed using a small number of powerful combinators.-* **Safe** - The `cmap`/`cfold`-DSL completely hides the dangers of the low-level API.-* **Extensible** - At its heart apecs is just a data manipulation DSL that can be implemented with any number of backends. As a monad transformer it easily integrates into larger applications.+* **Fast** - Performance is competitive with Rust ECS libraries (see benchmark results below)+* **Safe** - Completely hides the dangers of the low-level machinery+* **Concise** - Game logic is expressed using a small number of powerful combinators+* **Flexible** - Easily add new modules or backends * **Cool** -+ -#### Links+### Links - [documentation on hackage](https://hackage.haskell.org/package/apecs/docs/Apecs.html)-- [tutorial](../examples/Shmup.md) and other [examples](../examples/)-- [paper (prepublication)](../apecs/prepub.pdf) (see [#19](https://github.com/jonascarpay/apecs/issues/19))-- [apecs-physics](../apecs-physics/) - 2D physics using the [Chipmunk2D](https://github.com/slembcke/Chipmunk2D) engine-- [apecs-gloss](../apecs-gloss/) - Simple frontend for [gloss](http://hackage.haskell.org/package/gloss)-based rendering-- [apecs-stm](../apecs-stm/) - STM-based stores for easy concurrency+- [tutorial](https://github.com/jonascarpay/apecs/blob/master/examples/Shmup.md) and other [examples](../examples/) -##### By other authors-- [An Introduction to Developing Games in Haskell with Apecs](https://blog.aas.sh/posts/2018-09-10-Making-A-Game-With-Haskell-And-Apecs/) by Ashley Smith+##### Games/articles+- [An Introduction to Developing Games in Haskell with Apecs](https://aas.sh/blog/making-a-game-with-haskell-and-apecs/)+- [mallRL](https://github.com/nmaehlmann/mallRL) - a _grocery shopping roguelike_+- [An implementation of the Unity tutorial project using apecs](https://github.com/mewhhaha/apecs-unity-tutorial-haskell)+- [(Your game here)](https://github.com/jonascarpay/apecs/pulls) -#### Status-| Package | Hackage | Stack LTS | Stack Nightly |-|---|---|---|---|-| [apecs](apecs/) | [](https://hackage.haskell.org/package/apecs) | [](https://www.stackage.org/package/apecs) | [](https://www.stackage.org/package/apecs)-| [apecs-physics](apecs-physics/) | [](https://hackage.haskell.org/package/apecs-physics) | [](https://www.stackage.org/package/apecs-physics) | [](https://www.stackage.org/package/apecs-physics) |-| [apecs-gloss](apecs-gloss/) | [](https://hackage.haskell.org/package/apecs-gloss) | [](https://www.stackage.org/package/apecs-gloss) | [](https://www.stackage.org/package/apecs-gloss) |-| [apecs-stm](apecs-stm/) | [](https://hackage.haskell.org/package/apecs-stm) | - | - |-| [examples](examples/) | - | - | - |+##### Packages+- [apecs](https://github.com/jonascarpay/apecs/tree/master/apecs) -#### Example+ [](https://hackage.haskell.org/package/apecs)+ [](https://www.stackage.org/package/apecs)+ [](https://www.stackage.org/package/apecs)+ [](https://travis-ci.org/jonascarpay/apecs)++- [apecs-physics](https://github.com/jonascarpay/apecs/tree/master/apecs-physics) - 2D physics using the [Chipmunk2D](https://github.com/slembcke/Chipmunk2D) engine++ [](https://hackage.haskell.org/package/apecs-physics)+ [](https://www.stackage.org/package/apecs-physics)+ [](https://www.stackage.org/package/apecs-physics) + [](https://travis-ci.org/jonascarpay/apecs)++- [apecs-gloss](https://github.com/jonascarpay/apecs/tree/master/apecs-gloss) - Simple frontend for [gloss](http://hackage.haskell.org/package/gloss)-based rendering++ [](https://hackage.haskell.org/package/apecs-gloss) + [](https://www.stackage.org/package/apecs-gloss) + [](https://www.stackage.org/package/apecs-gloss) + [](https://travis-ci.org/jonascarpay/apecs)++- [apecs-stm](https://github.com/jonascarpay/apecs/tree/master/apecs-stm) - STM-based stores for easy concurrency++ [](https://hackage.haskell.org/package/apecs-stm)+ [](https://travis-ci.org/jonascarpay/apecs)++- [*examples*](https://github.com/jonascarpay/apecs/tree/master/examples/)++ [](https://travis-ci.org/jonascarpay/apecs)++### Example ```haskell {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}
apecs.cabal view
@@ -1,5 +1,5 @@ name: apecs-version: 0.9.1+version: 0.9.2 homepage: https://github.com/jonascarpay/apecs#readme license: BSD3 license-file: LICENSE@@ -44,6 +44,7 @@ base >= 4.9 && < 5, array >= 0.4 && < 0.6, containers >= 0.5 && < 0.8,+ exceptions >= 0.10.0 && < 0.11, mtl >= 2.2 && < 2.3, template-haskell >= 2.12 && < 3, vector >= 0.11 && < 0.13
src/Apecs.hs view
@@ -27,12 +27,13 @@ asks, ask, liftIO, lift, Proxy (..) ) where -import Control.Monad.Reader (ask, asks, lift, liftIO)-import Data.Proxy+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Reader (ask, asks, lift)+import Data.Proxy -import Apecs.Components-import Apecs.Core-import Apecs.Stores-import Apecs.System-import Apecs.TH-import Apecs.Util+import Apecs.Components+import Apecs.Core+import Apecs.Stores+import Apecs.System+import Apecs.TH+import Apecs.Util
src/Apecs/Core.hs view
@@ -11,6 +11,8 @@ module Apecs.Core where +import Control.Monad.Catch+import Control.Monad.IO.Class import Control.Monad.Reader import qualified Data.Vector.Unboxed as U @@ -26,7 +28,7 @@ -- * Allow type-based lookup of a component's store through @getStore@. -- -- * Lift side effects into their host Monad.-newtype SystemT w m a = SystemT {unSystem :: ReaderT w m a} deriving (Functor, Monad, Applicative, MonadTrans, MonadIO)+newtype SystemT w m a = SystemT {unSystem :: ReaderT w m a} deriving (Functor, Monad, Applicative, MonadTrans, MonadIO, MonadThrow, MonadCatch, MonadMask) type System w a = SystemT w IO a deriving instance Monad m => MonadReader w (SystemT w m)@@ -37,6 +39,7 @@ type Storage c -- | @Has w m c@ means that world @w@ can produce a @Storage c@.+-- It is parameterized over @m@ to allow stores to be foreign. class (Monad m, Component c) => Has w m c where getStore :: SystemT w m (Storage c)
src/Apecs/Experimental/Reactive.hs view
@@ -25,6 +25,8 @@ , IxMap, ixLookup ) where +import Control.Monad+import Control.Monad.IO.Class import Control.Monad.Reader import qualified Data.Array.IO as A import qualified Data.IntMap.Strict as IM
src/Apecs/Stores.hs view
@@ -17,20 +17,22 @@ -- Register, regLookup ) where +import Control.Monad+import Control.Monad.IO.Class import Control.Monad.Reader+import Data.Bits (shiftL, (.&.)) import qualified Data.IntMap.Strict as M import Data.IORef import Data.Proxy-import Data.Bits (shiftL, (.&.))-import Data.Typeable (Typeable, typeRep)+import Data.Typeable (Typeable, typeRep) import qualified Data.Vector.Mutable as VM import qualified Data.Vector.Unboxed as U import qualified Data.Vector.Unboxed.Mutable as UM import GHC.TypeLits -import Apecs.Core+import Apecs.Core --- | A map based on @Data.IntMap.Strict@. O(log(n)) for most operations.+-- | A map based on 'Data.IntMap.Strict'. O(log(n)) for most operations. newtype Map c = Map (IORef (M.IntMap c)) type instance Elem (Map c) = c@@ -66,7 +68,7 @@ -- | A Unique contains zero or one component. -- Writing to it overwrites both the previous component and its owner.--- Its main purpose is to be a @Map@ optimized for when only ever one component inhabits it.+-- Its main purpose is to be a 'Map' optimized for when only ever one component inhabits it. newtype Unique c = Unique (IORef (Maybe (Int, c))) type instance Elem (Unique c) = c instance MonadIO m => ExplInit m (Unique c) where@@ -99,14 +101,14 @@ Nothing -> mempty Just (ety, _) -> U.singleton ety --- | A @Global@ contains exactly one component.+-- | A 'Global' contains exactly one component. -- The initial value is 'mempty' from the component's 'Monoid' instance.+-- Querying a 'Global' at /any/ Entity yields this one component, effectively sharing the component between /all/ entities. ----- When operating on a Global, any entity arguments are ignored.--- A Global component can be read with @get 0@ or @get 1@ or even @get undefined@.--- This means that you can read and write Globals while @cmap@ping over other components.+-- A Global component can be read with @'get' 0@ or @'get' 1@ or even @'get' undefined@.+-- The convenience entity 'global' is defined as -1, and can be used to make operations on a global more explicit, i.e. 'Time t <- get global'. ----- The integer @global@ is defined as -1, and can be used to make operations on a global explicit, i.e. 'Time t <- get global'.+-- You also can read and write Globals during a 'cmap' over other components. newtype Global c = Global (IORef c) type instance Elem (Global c) = c instance (Monoid c, MonadIO m) => ExplInit m (Global c) where@@ -123,20 +125,26 @@ {-# INLINE explSet #-} explSet (Global ref) _ c = liftIO$ writeIORef ref c --- | An empty type class indicating that the store behaves like a regular map, and can therefore safely be cached.+-- | Class of stores that behave like a regular map, and can therefore safely be cached.+-- This prevents stores like `Unique` and 'Global', which do /not/ behave like simple maps, from being cached. class Cachable s instance Cachable (Map s) instance (KnownNat n, Cachable s) => Cachable (Cache n s) -- | A cache around another store.--- Caches store their members in a fixed-size vector, so operations run in O(1).--- Caches can provide huge performance boosts, especially for large numbers of components.+-- Caches store their members in a fixed-size vector, so read/write operations become O(1).+-- Caches can provide huge performance boosts, especially when working with large numbers of components.+-- -- The cache size is given as a type-level argument. ----- Note that iterating over a cache is linear in cache size, so sparsely populated caches might actually decrease performance.+-- Note that iterating over a cache is linear in cache size, so sparsely populated caches might /decrease/ performance. -- In general, the exact size of the cache does not matter as long as it reasonably approximates the number of components present. ----- The cache uses entity (-2) to internally represent missing entities, so be wary when manually manipulating entities.+-- The cache uses entity (-2) internally to represent missing entities.+-- If you manually manipulate Entity values, be careful that you do not use (-2)+--+-- The actual cache is not necessarily the given argument, but the next biggest power of two.+-- This is allows most operations to be expressed as bit masks, for a large potential performance boost. data Cache (n :: Nat) s = Cache Int (UM.IOVector Int) (VM.IOVector (Elem s)) s @@ -199,8 +207,9 @@ stored <- explMembers s >>= liftIO . U.filterM etyFilter return $! cached U.++ stored --- | Wrapper that makes a store read-only by hiding its 'ExplSet' and 'ExplDestroy'. Use 'setReadOnly' and 'destroyReadOnly' to override.--- This is used to protect the 'EntityCounter'.+-- | Wrapper that makes a store read-only by hiding its 'ExplSet' and 'ExplDestroy' instances.+-- This is primarily used to protect the 'EntityCounter' from accidental overwrites.+-- Use 'setReadOnly' and 'destroyReadOnly' to override. newtype ReadOnly s = ReadOnly s type instance Elem (ReadOnly s) = Elem s
src/Apecs/System.hs view
@@ -6,6 +6,7 @@ module Apecs.System where +import Control.Monad import Control.Monad.Reader import Data.Proxy import qualified Data.Vector.Unboxed as U
src/Apecs/TH.hs view
@@ -8,19 +8,19 @@ , makeMapComponents ) where -import Control.Monad-import Language.Haskell.TH+import Control.Monad+import Language.Haskell.TH -import Apecs.Core-import Apecs.Stores-import Apecs.Util (EntityCounter)+import Apecs.Core+import Apecs.Stores+import Apecs.Util (EntityCounter) genName :: String -> Q Name genName s = mkName . show <$> newName s --- | Same as 'makeWorld', but has no 'EntityCounter'+-- | Same as 'makeWorld', but does not include an 'EntityCounter'+-- You don't typically want to use this, but it's exposed in case you know what you're doing. makeWorldNoEC :: String -> [Name] -> Q [Dec]--- makeWorldNoEC _ [] = do makeWorldNoEC worldName cTypes = do cTypesNames <- forM cTypes $ \t -> do rec <- genName "rec"@@ -61,7 +61,7 @@ let ct = return$ ConT comp head <$> [d| instance Component $ct where type Storage $ct = Map $ct |] --- | Calls 'makeWorld' and 'makeMapComponents', i.e. makes a world and also defines @Component@ instances with a @Map@ store.+-- | Calls 'makeWorld' and 'makeMapComponents', i.e. makes a world and also defines 'Component' instances with a 'Map' store. makeWorldAndComponents :: String -> [Name] -> Q [Dec] makeWorldAndComponents worldName cTypes = do wdecls <- makeWorld worldName cTypes@@ -70,19 +70,21 @@ {-| -> makeWorld "WorldName" [''Component1, ''Component2, ...]+The typical way to create a @world@ record, associated 'Has' instances, and initialization function. +> makeWorld "MyWorld" [''Component1, ''Component2, ...]+ turns into -> data WorldName = WorldName Component1 Component2 ... EntityCounter-> instance WorldName `Has` Component1 where ...-> instance WorldName `Has` Component2 where ...+> data MyWorld = MyWorld Component1 Component2 ... EntityCounter+> instance MyWorld `Has` Component1 where ...+> instance MyWorld `Has` Component2 where ... > ...-> instance WorldName `Has` EntityCounter where ...+> instance MyWorld `Has` EntityCounter where ... >-> initWorldName :: IO WorldName-> initWorldName = WorldName <$> initStore <*> initStore <*> ... <*> initStore+> initMyWorld :: IO MyWorld+> initMyWorld = MyWorld <$> initStore <*> initStore <*> ... <*> initStore -|-}+-} makeWorld :: String -> [Name] -> Q [Dec] makeWorld worldName cTypes = makeWorldNoEC worldName (cTypes ++ [''EntityCounter])
src/Apecs/Util.hs view
@@ -16,15 +16,16 @@ EntityCounter(..), nextEntity, newEntity, ) where -import Control.Applicative (liftA2)-import Control.Monad.Reader-import Data.Monoid-import Data.Semigroup-import System.Mem (performMajorGC)+import Control.Applicative (liftA2)+import Control.Monad.IO.Class+import Control.Monad.Reader+import Data.Monoid+import Data.Semigroup+import System.Mem (performMajorGC) -import Apecs.Core-import Apecs.Stores-import Apecs.System+import Apecs.Core+import Apecs.Stores+import Apecs.System -- | Convenience entity, for use in places where the entity value does not matter, i.e. a global store. global :: Entity