Hi there,
I've been exploring
Entity Component System as an architectural pattern in Elm and documented my experience in this
gist. It is a bit long, perhaps a bit boring, and has quite a bit of code (as in, small but complete examples: 100+ lines of code) but I felt like I had to because it was about code architecture and hello world doesn't cut it and also I wrote it as I was working on the examples. So, please forgive any typos and grammar issues.
Basically, the examples involve making squares move with the keyboard and then seeing what happens to the code if you add squares that you don't want to move with the keyboard, and then what happens if you try to add gravity, and what about adding circles, etc...
It must be noted that I'm still in the process of learning functional programming and was looking into this architectural pattern because I came across a few stumbling blocks while working on my game in Elm.
Basically what happened is that I came across a moment when I wanted to add a feature but that that feature involved substantial changes to the codebase and that I had to work hard to get around the fact that you can't have a heterogeneous list in Elm. It led me to write lots of code that looks very similar.
The TL;DR of the writeup is that Entity Component System lends itself more to modifying the code than using records as in the examples. It is basically the plugin model. An entity is a list of components. A component is just some data. And the system applies some "actions" which are functions that modify the components of entities.
type Entity = Entity (List Component)
type Component =
Position Float Float |
Velocity Float Float
fixed = Entity [
Position 0 0
]
moving = Entity [
Position 20 0,
Velocity 1 2
]
This allows you to just slam all your entities into one big list (even if they have different components):
entities = [fixed, moving]
and then you can just create function to operate on individual or multiple components.
moveEntity : Entity -> Entity
moveEntity entity =
case (getPosition entity, getVelocity entity) of
(Just (Position x y), Just (Velocity vx vy)) ->
updatePosition (Position (x + vx) (y + vy)) entity
_ -> entity
where getPosition gets the position component of an entity if it exists, getVelocity gets the velocity component of an entity if it exists, and updatePosition updates the position component of an entity if it exists
getPosition : Entity -> Maybe Component
getVelocity : Entity -> Maybe Component
updatePosition : Component -> Entity -> Entity
The only big problem that I've found with this approach is that it requires a bit of boilerplate to do well. Basically, for every component you add, you need to provide a get, update, and filter function in order to do anything interesting with entities. There may be better ways to do this but I couldn't think of any and I've outlined a couple of ways in the piece of how to mitigate this problem.
I don't know how interesting this may be but I felt like sharing this experience of using Elm and trying to find ways to write better and more flexible apps in Elm,
Hassan Hayat