apecs

A fast ECS for game engine programming https://github.com/jonascarpay/apecs#readme

Version on this page:0.2.4.7
LTS Haskell 15.15:0.9.1
Stackage Nightly 2020-06-05:0.9.1
Latest on Hackage:0.9.1

See all snapshots apecs appears in

BSD-3-Clause licensed by Jonas Carpay
Maintained by [email protected]

Module documentation for 0.2.4.7

This version can be pinned in stack with:[email protected]:77eda9c1beb77ecb8e41a53872bb3881a6f9df54bed3d9d1cf2315b5ebe22791,1647

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 physics engine written on top of apecs, check out phycs. For a general introduction to ECS, see this talk or here.

Performance

Performance is good. Running ecs-bench shows that apecs is competitive with the fastest Rust ECS frameworks.

pos_vel build pos_vel step parallel build parallel step
apecs 239 34 777 371
calx 261 21 442 72
constellation 306 10 567 256
froggy 594 13 1565 97
specs 753 38 1016 205

Benchmarks

There is a performance guide here.

Example

import Apecs
import Apecs.Stores (Cache)
import Apecs.Concurrent (prmap)
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