apecs

A fast ECS for game engine programming

https://github.com/jonascarpay/apecs#readme

Version on this page:0.2.4.0
LTS Haskell 22.18:0.9.6
Stackage Nightly 2024-04-22:0.9.6
Latest on Hackage:0.9.6

See all snapshots apecs appears in

BSD-3-Clause licensed by Jonas Carpay
Maintained by [email protected]
This version can be pinned in stack with:apecs-0.2.4.0@sha256:ea811d0b1da408b0219737e283a2cc0a287bd0256bff50d67e6df73c219d6cc6,1668

apecs

hackage | documentation | tutorials

apecs is an Entity Component System inspired by specs and Entitas. It exposes a DSL that translates to fast storage operations, resulting in expressivity without sacrificing performance or safety.

There is an example below, and a tutorial can be found here. For a general introduction to ECS, see this talk or here.

Performance

Performance is good. Running the ecs-bench pos_vel benchmark shows that we can keep up with specs, which was written in Rust:

specs apecs
build 699 us 285 us
update 34 us 46 us

There is a performance guide here.

Example

import Apecs
import Apecs.TH (makeWorld)
import Apecs.Stores (Cache)
import Linear

newtype Position = Position (V2 Double) deriving Show
-- Turn Position into a component by specifiying the type of its Storage
instance Component Position where
  -- The simplest store is a Map
  type Storage Position = Map Position

newtype Velocity = Velocity (V2 Double)
instance Component Velocity where
  -- We can add a Cache for faster access
  type Storage Velocity = Cache 100 (Map Velocity)

data Player = Player -- A single constructor component for tagging the player
instance Component Player where
  -- Unique contains at most one component. See the Stores module.
  type Storage Player = Unique Player

-- Generate a world type and instances
makeWorld "World" [''Position, ''Velocity, ''Player]

game :: System World ()
game = do
  -- Create new entities
  ety <- newEntity (Position 0)
  -- Components can be composed using tuples
  newEntity (Position 0, Velocity 1)
  newEntity (Position 1, Velocity 1, Player)

  -- set (over)writes components
  set ety (Velocity 2)

  let stepVelocity (Position p, Velocity v) = Position (v+p)

  -- Side effects
  liftIO$ putStrLn "Stepping velocities"
  -- rmap maps a pure function over all entities in its domain
  rmap stepVelocity
  -- prmap n does the same, but in parallel
  prmap 2 stepVelocity

  -- Print all positions
  cmapM_ $ \(Position p) -> liftIO (print p)

main :: IO ()
main = initWorld >>= runSystem game