Skip to content

Instantly share code, notes, and snippets.

@yosriady
Last active January 11, 2017 10:10
Show Gist options
  • Save yosriady/c8e1afcbbddfce52d776634d8ff2947f to your computer and use it in GitHub Desktop.
Save yosriady/c8e1afcbbddfce52d776634d8ff2947f to your computer and use it in GitHub Desktop.
Entity Component System in Elixir
# A component is a minimal data object needed for a specific purpose.
# A component has no behaviour.
# A component class validates a given configuration and creates run-time data structures.
# Implemented as an Elixir Agent!
defmodule Component
@type options :: [key: type]
# Functions that have to be implemented by Components (if any)
@callback new(options) :: struct()
defmacro __using__(_options) do
quote do
@behaviour Component
# Built-in functions inherited by all Components
def type, do: __MODULE__ end # Does this work? Supposed to return the type of the component
# Overridable built-in functions
defoverridable [new: 1]
end
end
end
# An entity is a container of components. An entity is solely the sum of its components.
# An entity is a unique, autogenerated identifier and a list of components with specific configurations.
# An entity is nothing without its components.
# Entities themselves store only metadata, contain no state, and have no behaviour.
# An entity's components gives the entity state and systems give entities behaviour (through components.)
defmodule Entity
defstruct [:id, :components]
@type id :: String.t
@type components :: list()
@type t :: %Entity{
id: String.t,
components: components
}
@spec new(components, id) :: t
def new(components, id \\ random_string(64)) do
// TODO: Need to instantiate component agents and
// register their names in some process registry
%Entity{
id: id,
components: components
}
end
defp random_string(length) do
:crypto.strong_rand_bytes(length) |> Base.url_encode64 |> binary_part(0, length)
end
end
# A concrete implementation of a Component.
defmodule PositionComponent do
use Component
defstruct [:x, :y]
@type x :: integer
@type y :: integer
@type t :: %Position{
x: x,
y: y
}
@type options :: [key: type]
@type new(options) :: t
def new(options) do
%Position{
x: options[:x] || 0,
y: options[:y] || 0
}
end
end
# Systems are the only place where behaviour is kept.
# Implemented as an Elixir GenServer!
defmodule System
defstruct [:component_types, :options]
@type component_type :: String.t
@type component_types :: list(component_type)
@type component_state :: struct()
@type component_states :: list(component_state)
# Functions that have to be implemented by Systems
@doc """
`new` instantiates a system (GenServer) with given configuration
"""
@callback new(options) :: pid
@doc """
`update` iterates over all components it applies to
and return the new states for those components.
"""
@callback update(component_states) :: component_states
defmacro __using__(_options) do
quote do
use GenServer # http://elixir-lang.org/docs/stable/elixir/GenServer.html
@behaviour System
# Built-in functions inherited by all Systems
def start, do: :TODO # Turn on system GenServer
def stop, do: :TODO # Turn off system Genserver
end
end
end
# A concrete implementation of a Systsem.
defmodule WindSystem
use System
def new(options) do
# TODO
end
def update(component_states) do
# TODO
end
end
# Example world defined using ECS
iex> bird = Entity.new([Position.new(10, 10)])
iex> bird.id
12sadf313dfsasfd08
iex> bird.position
{x: 10, y: 10}
iex> wind_system = WindSystem.new(delta_x: 1, delta_y: 2)
iex> wind_system.start
iex> wind_system.update
iex> bird.position
{x: 11, y: 12}
iex> wind)system.update
iex> bird.position
{x: 12, y: 14}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment