Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/simre1/hero
Haskell ECS using sparse sets
https://github.com/simre1/hero
ecs entity-component-system game-development haskell
Last synced: 2 months ago
JSON representation
Haskell ECS using sparse sets
- Host: GitHub
- URL: https://github.com/simre1/hero
- Owner: Simre1
- License: mit
- Created: 2022-08-07T20:37:54.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2022-09-03T20:07:28.000Z (over 2 years ago)
- Last Synced: 2023-03-10T22:56:38.304Z (almost 2 years ago)
- Topics: ecs, entity-component-system, game-development, haskell
- Language: Haskell
- Homepage:
- Size: 645 KB
- Stars: 5
- Watchers: 1
- Forks: 1
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Hero
Hero is an entity component system in Haskell and is inspired by [_apecs_](https://github.com/jonascarpay/apecs). Hero aims to be more performant than _apecs_ by using sparse sets as the main data structure for storing component data. In the future, it also intends to parallelize systems automatically as well.
## Entities
Entities are objects which have components. You can create entities with the `createEntity` system and add components to them.
## Components
Components are Haskell datastructure and mostly contain raw data.
```haskell
data Position = Position Float Float
```In order to use `Position` as a component, you need to make it an instance of `Component`.
```haskell
instance Component Position where
type Store Position = BoxedSparseSet
-- StorableSparseSet is faster but Position would need to implement Storable
```## Stores
Each component has a store which holds the component data. Depending on the use of the component, different stores might be best.
The most important stores are:
- `BoxedSparseSet`: Can be used with any datatype. Each entity has its own component value.
- `StorableSparseSet`: Can be used with `Storable` datatypes. Each entity has its own component value. Faster than `BoxedSparseSet`.
- `Global`: Can be used with any datatype. Each entity has the same component value. Can also be accessed without an entity.## Systems
Systems are functions which operate on the components of a world. For example, you can map the `Position` component of every entity:
```haskell
cmap_ $ \(Position x y) -> Position (x + 1) y
````System`s have the type `system :: System input output`. `System`s can be chained together through their `Applicative`, `Category` and `Arrow` instance. However, `System`s dot have an instance for `Monad`! If one is familiar with arrowized FRP, then they will feel that `System`s have a similar interface as signal functions.
`System`s do not have a `Monad` instance since they are separated into a compilation and a run phase. Before you can run a `System`, you need to compile it with `compileSystem :: System m input output -> World -> IO (input -> IO output)`. In the compilation phase, `System`s look up the used components so that run time is faster.
## Example
```haskell
{-# LANGUAGE TypeFamilies #-}
import Control.Monad ( forM_ )
import Hero
import Data.Foldable (for_)data Position = Position Int Int
data Velocity = Velocity Int Int
instance Component Position where
type Store Position = BoxedSparseSetinstance Component Velocity where
type Store Velocity = BoxedSparseSetmain :: IO ()
main = do
world <- createWorld 10000
runSystem <- compileSystem system world
runSystem ()system :: System () ()
system =
-- Create two entities
(pure (Position 0 0, Velocity 1 0) >>> createEntity) *>
(pure (Position 10 0, Velocity 0 1) >>> createEntity) *>
-- Map position 10 times
for_ [1..10] (\_ -> cmap_ (\(Position x y, Velocity vx vy) -> Position (x + vx) (y + vy))) *>-- Print the current position
cmapM_ (\(Position x y) -> print (x,y))
```## Installation
To download the project and execute it, you need at least _GHC 9_. I have tested it with _GHC 9.2.1_.
Then, you can run the following commands to build the project.
```
git clone https://github.com/Simre1/hero
cd hero
cabal build all
```To run the example, do:
```
cabal run example
```To run the tests, do:
```
cabal test
```To run the benchmarks, do:
```
cabal run hero-bench
cabal run apecs-bench
```To generate **documentation**, do:
```
cabal haddock
cabal haddock hero-sdl2
```### Hero SDL2
The folder hero-sdl2 contains a small libary to use SDL2 with Hero. It needs the C libraries `SDL2`, `SDL2_gdx` and `SDL2_image`.
The Hero SDL2 examples can be run with:
```
cabal run rotating-shapes
cabal run image-rendering
cabal run move-shape
```More information can be found in [`hero-sdl2`](hero-sdl2/README.md).
### Cabal dependency
To use _Hero_ as a dependency, add `hero` to the `build-depends` section. Additional, create a `cabal.project` with:
```
packages: *.cabalsource-repository-package
type: git
location: https://github.com/Simre1/hero
```If you also want to use `hero-sdl2`, add `hero-sdl2` to the `build-depends` section as well. The `cabal.project` is then:
```
packages: *.cabalsource-repository-package
type: git
location: https://github.com/Simre1/herosource-repository-package
type: git
location: https://github.com/Simre1/hero
subdir: hero-sdl2
```## Benchmark
A basic benchmark seems to suggest that `Hero` is much faster. However, it relies heavily on
GHC inlining. It is best to use concrete types when working with systems or add an `INLINE` pragma to
functions which deal with polymorphic systems.The following queries are used to test the iteration speed of both libraries:
```
cmap_ (\(Velocity vx vy, Acceleration ax ay) -> Velocity (vx + ax) (vy + ay)) *>
cmap_ (\(Position x y, Velocity vx vy) -> Position (x + vx) (y + vy))
```### Hero
```
simple physics (3 components): OK (0.20s)
394 μs ± 25 μs
```### Apecs
```
simple physics (3 components): OK (0.13s)
17.5 ms ± 1.5 ms
```