apecs
Fast Entity-Component-System library for game programming
https://github.com/jonascarpay/apecs#readme
| Version on this page: | 0.7.3 |
| LTS Haskell 24.16: | 0.9.6 |
| Stackage Nightly 2025-10-25: | 0.9.6 |
| Latest on Hackage: | 0.9.6 |
BSD-3-Clause licensed by Jonas Carpay
Maintained by [email protected]
This version can be pinned in stack with:
apecs-0.7.3@sha256:b7ec9dbb978c9778b69b71b0d4abc74e97cdcaf006717980fb753cb41ee9cadc,2133Module documentation for 0.7.3
Depends on 5 packages(full list with versions):
Used by 2 packages in nightly-2019-07-18(full list with versions):
apecs 
apecs is an Entity Component System (ECS) library inspired by specs and Entitas. ECS presents a data-driven approach to game development, that elegantly tackles many of the unique issues of game programming.
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.
- Cool

Links
- documentation on hackage
- tutorial and other examples
- paper (prepublication) (see #19)
- apecs-physics - 2D physics using the Chipmunk2D engine
- apecs-gloss - Simple frontend for gloss-based rendering
- apecs-stm - STM-based stores for easy concurrency
By other authors
- An Introduction to Developing Games in Haskell with Apecs by Ashley Smith
Status
| Package | Hackage | Stack LTS | Stack Nightly |
|---|---|---|---|
| apecs | |||
| apecs-physics | |||
| apecs-gloss | |||
| apecs-stm | - | - | |
| examples | - | - | - |
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.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 entireData.Proxymodule, but instead justProxy (..).- Added (approximate?) lower and upper version bounds to dependencies
[0.7.1]
Added
$=and$~operators as synonyms forsetandgetrespectively
Removed
getAllandcount, which were made redundant bycfold.
[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 incmap.
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) fromApecs.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 forSystemT 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 andEitherStore
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, andExplMembers. This makes it illegal to e.g. iterate over aNot.- phantom arguments are now given as
Proxyvalues, re-exported fromData.Proxy. This makes phantom arguments explicit and avoids undefined values.