apecs
Fast Entity-Component-System library for game programming
https://github.com/jonascarpay/apecs#readme
| LTS Haskell 24.17: | 0.9.6 | 
| Stackage Nightly 2025-10-31: | 0.9.6 | 
| Latest on Hackage: | 0.9.6 | 
apecs-0.9.6@sha256:983fe81336e2a3229c55de174f43c32d6b74a17f7fee6fbe2554bebffcd2d332,2164Module documentation for 0.9.6
apecs
apecs is an Entity Component System (ECS) library for game development.
apecs aims to be
- 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
- documentation on hackage
- tutorial and other examples
- community chat at #apecson the haskell gamedev discord or#haskell-game:matrix.org
Games/articles
- Notakto, and associated blog post/apecs tutorial by @Ashe
- mallRL - a grocery shopping roguelike by @nmaehlmann
- An implementation of the Unity tutorial project using apecs by @mewhhaha
- SpaceMar by @dpwiz
- Achtung die haskell by @mewhhaha
- (Your game here)
Packages
- 
apecs-physics - 2D physics using the Chipmunk2D engine 
- 
apecs-gloss - Simple frontend for gloss-based rendering 
- 
apecs-stm - STM-based stores for easy concurrency 
Example
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE TypeFamilies          #-}
import Apecs
import Linear (V2 (..))
newtype Position = Position (V2 Double) deriving Show
newtype Velocity = Velocity (V2 Double) deriving Show
data Flying = Flying
makeWorldAndComponents "World" [''Position, ''Velocity, ''Flying]
game :: System World ()
game = do
  newEntity (Position 0, Velocity 1)
  newEntity (Position 2, Velocity 1)
  newEntity (Position 1, Velocity 2, Flying)
  -- 1. Add velocity to position
  -- 2. Apply gravity to non-flying entities
  -- 3. Print a list of entities and their positions
  cmap $ \(Position p, Velocity v) -> Position (v+p)
  cmap $ \(Velocity v, _ :: Not Flying) -> Velocity (v - V2 0 1)
  cmapM_ $ \(Position p, Entity e) -> liftIO . print $ (e, p)
main :: IO ()
main = initWorld >>= runSystem game
Changes
[0.9.6]
Changed
- (#110) Relax upper bound on mtl: 2.3 -> 2.4
- (#117) Fix TH symbol leaking, fixing (#116)
- (#121) Properly export PrinterfromApecs.Experimental.Reactive
- (#121) Fix haddocks for Apecs.Experimental.Reactive
- (#125) Force IntMapinExplDestroyinstance forMap
- (#128) Use SystemTinstead ofSysteminrunGCtype signature
- (#131) Enable -XTypeOperatorsto prevent GHC warnings
Added
- (#121) ComponentCounter
- (#123) SystemTMonadUnliftIOinstance
- (#126) Export cmapIffrom mainApecsmodule
- (#130) Docs for performance considerations when reading composite components
- (#132) Apecs.Experimental.Children
Removed
- (#112) Custom setup for C sources
[0.9.5]
Added
- (#99) collect
Changed
- (#84) Updated links to articles
[0.9.4]
Changed
- (#86) Add support for GHC 9.2 and Template Haskell 2.18
[0.9.3]
Added
- newEntity_ = void . newEntity
[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 modifyon non-existent components crashes
[0.9.0]
Added
- (#59) Expose makeMapComponents, which createsComponentinstances withMapstores
Changed
- (#60) Add Componenttype names in non-existent component errors
- Relaxed the type of modifyto allow a different return type
- Constrain the cmapM_type(c -> SystemT w m ()) -> SystemT w m (), to make it clearer that the inner function does not updateComponents
- Simplify nix infrastructure
[0.8.3]
Changed
- (#58) Added support for Template Haskell 2.15.0.0 through CPP flags
[0.8.2]
Changed
- (#55) Fixed a bug where components where not properly deleted from the cache following the cache bitmasking update
[0.8.1]
Changed
- Changed Caches to use bitmasks instead of the remainder operation. This makes caches up to three times faster.
- Fixed bug in Cachewhere the same entity appeared in member list of both the cache and the underlying store
[0.8.0]
There are a number of unsolved problems in apecs’ design space. Most notably, it needs a good way to do streaming and reactivity, or find a way to integrate with existing solutions. I’m hesitant to accept some of the feature requests I’ve gotten because they would be obsoleted when we figure this out, and I don’t want to pollute the API with unstable features.
However, I don’t think we should let perfect get in the way of good.
So, apecs 0.8 have new Apecs.Experimental.* modules, that I want to use for features that might or might not get removed or changed.
They should provide some conveniences, but the catch is that their API might undergo significant changes between point releases (and therefore within LTS’es).
Some of the existing modules were already marked experimental, and those have been moved.
Added
- Experimental Headcomponent
- OrdMapand- IxMapreactive maps
Changed
- Moved ReadOnlytoApecs.StoressinceEntityCounternow depends on it
- Moved spatial hashing to Experimental.Util
- Moved RedirectandStores.ExtratoExperimental.Stores
- Moved ReactivetoExperimental.Reactive
- rgethas been replaced by- withReactive
- Improved error messages for unsafe operations
[0.7.3]
Changed
- Added Data.Semigroup to Stores.Extra to build with GHC 8.2.2 in hackage matrix
[0.7.2]
Changed
- Fixed bug in the Pushdownstore
- Apecsmodule no longer re-exports the entire- Data.Proxymodule, but instead just- Proxy (..).
- Added (approximate?) lower and upper version bounds to dependencies
[0.7.1]
Added
- $=and- $~operators as synonyms for- setand- getrespectively
Removed
- getAlland- count, which were made redundant by- cfold.
[0.7.0]
Added
- The Reactivestore and module is a redesign of theRegisterstore, and provides a more general solution for ‘stores that perform additional actions when written to’.
- The Apecs.Stores.Extrasubmodule, which contains thePushdownandReadOnlystores.Pushdownadds pushdown semantics to stores, andReadOnlyhides theExplSetinstances of whatever it wraps.
- The EntityCounterand associated functions have all been specified toIO, sinceGlobal EntityCounteronly works in IO. Furthermore,EntityCounternow uses aReadOnlystore, to prevent users from accidentally changing its value.
- Redirectcomponent that writes to another entity in- cmap.
Changed
- Default stores have MonadIO m => minstances, rather thanIO. This makes it easier to nestSystemT.
- All apecs packages have been consolidated into a single git repo.
- Apecs.Componentscontains the components (and corresponding stores) from- Apecs.Core.
[0.6.0.0]
Changed
- Nothing, but since 0.5.1 was API-breaking I’ve decided to bump to 0.6
[0.5.1.1]
Changed
- Registerneeds UndecidableInstances in GHC 8.6.2, I’m looking for a way around this. I’ve removed it for now.
[0.5.1.0]
Added
- The Registerstore, which allows reverse lookups for bounded enums. For example, ifBoolhas storageRegister (Map Bool),regLookup Truewill yield a list of all entities with aTruecomponent. Can also be used to emulate a hash table, wherefromEnumis the hashing function. This allows us to make simple spatial hashes. I’m open to suggestions for better names than Register.
- cmapIf, cmap with a conditional test
Changed
- ExplInitnow too takes a monad argument.
- Started rewrite of the test suite
- Caches now internally use -2 to denote absence, to avoid possible conflict with -1 as a global entity
Removed
- The STM instances have been removed, to be moved to their own package
[0.5.0.0]
Changed
- System w ais now a synonym for- SystemT w IO a. A variable monad argument allows apecs to be run in monads like ST or STM. Most of the library has been rewritten to be as permissive as possible in its monad argument.
Added
- STM stores. These will be moved to a separate package soon.
[0.4.1.2]
Changed
- Either can now be deleted, deleting Either a bis the same as deleting(a,b).
- Some were missing their inline pragma’s, now they don’t
[0.4.1.1]
Changed
- Export Get,Set,Destroy,Membersby default
- Export cfold,cfoldM,cfoldM_by default
- Fix () instance
[0.4.1.0]
Added
- cfold,- cfoldM,- cfoldM_
- Eitherinstances and- EitherStore
Changed
- Changed MaybeStore implementation to no longer use -1 for missing entities.
- Fixed some outdated documentation.
- Change the globalvoid entity to -2, just to be sure it won’t conflict if accidentally used in a cache.
[0.4.0.0]
Added
- A changelog
Changed
- Storeis now split into 5 separate type classes;- ExplInit,- ExplGet,- ExplSet,- ExplDestroy, and- ExplMembers. This makes it illegal to e.g. iterate over a- Not.
- phantom arguments are now given as Proxyvalues, re-exported fromData.Proxy. This makes phantom arguments explicit and avoids undefined values.
