aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorlthms <contact@thomasletan.fr>2017-05-01 17:17:17 +0000
committerThomas Letan <contact@thomasletan.fr>2018-01-24 08:10:12 +0100
commit933bbeb7d8cd2514f6200652ca13b9240de4e856 (patch)
treebe16edfe8b461d001ee7301384ff5aa4d025dcdd
parentsystem: Add a new macro to easily define new Systems (diff)
chore: Add the documentation for the System module
-rw-r--r--lib/lkn/core/system.ex167
-rw-r--r--test/core_test.exs6
2 files changed, 148 insertions, 25 deletions
diff --git a/lib/lkn/core/system.ex b/lib/lkn/core/system.ex
index 71c1521..f17fb65 100644
--- a/lib/lkn/core/system.ex
+++ b/lib/lkn/core/system.ex
@@ -17,7 +17,99 @@
#
defmodule Lkn.Core.System do
@moduledoc """
- A behaviour module for implementing a System.
+ A behaviour module for implementing and querying a System.
+
+ **Note:** You are not supposed to spawn a System yourself. The
+ `Lkn.Core.Instance` takes care of that for you. This is why you
+ will not find any `start*` function here.
+
+ The core of this module is the `defsystem/2` macro. It is the
+ recommended way to implement of System, that is a Process that
+ handles one aspect of the gameplay. Before we going any further,
+ lets have a look at a following minimal example.
+
+ defsystem Sys do
+ # 1. Specify which 'Component's are required by this 'System'.
+ @map Sys.MapSpec
+ @puppet Sys.PuppetSpec
+
+ # 2. Implementation of the 'System' callbacks.
+ def init_state(_instance_key, _map_key) do
+ # return some state
+ end
+
+ def puppet_enter(state, _instance_key, _map_key, _puppets, _puppet_key)
+ # we do nothing, but we could
+ state
+ end
+
+ def puppet_leave(state, _instance_key, _map_key, _puppets, _puppet_key)
+ # we do nothing, but we could
+ state
+ end
+
+ # 3. Define some specific functions
+ cast func1(a :: integer, b :: boolean) do
+ # here you can use 'a' and 'b', but also 'state',
+ # 'puppets', 'map_key' and 'instance_key'
+
+ # you have to return the new state value
+ state
+ end
+ end
+
+ ## Map and Puppet Components
+
+ A System handles one facet of the gameplay. Its purpose is to
+ manipulate the set of “compatible” of Puppets. A System is tied to
+ two Component Specification Modules (that is two modules defined
+ with the `Lkn.Core.Component.defcomponent/2` macro). A Component is
+ a Proxy to manipulate an Entity. It abstracts away its underlying
+ structure and expose a common interface. See `Lkn.Core.Component` to
+ learn how to specify then implement a Component.
+
+ To define which Component is required for a Map and which one is
+ required for a Puppet, we use the `@map` and `@puppet` annotations.
+
+ ## The System Behaviour
+
+ The System callbacks can be divided into two categories. The
+ `init_map/2` function is called only one time to initialize the
+ inner state of the System. This state implementation is at the
+ discretion of the developer. The other callbacks are a set of hooks
+ which are called when a particular event occures.
+
+ ## The `cast` Keyword
+
+ In addition to the System callbacks which are shared by all the
+ Systems, each System exposes a public interface the Puppeteer can
+ use to actually play. The function interface are defined using the
+ `cast` keyword.
+
+ You can use the `cast` keyword as follows: `cast
+ <func_name>([arg_name :: arg_type]*) do <block> end`. Inside the
+ block, you can use the arguments you have defined. In addition, you
+ also can use the following variables:
+
+ - `state` is the current state of the System, as initialized by
+ `init_state/2` and modified by the System callbacks and functions
+ - `instance_key` is the key to identify the Instance which spawns
+ the system, it can be used to reach its other systems
+ - `map_key` is the key to identify the map on which the puppets have
+ been spawned
+ - `puppets` is a `MapSet` of all the Puppets currently in the
+ Instance and “compatible” with the System
+
+ In addition to this variables, you also can use the `notify/1`
+ function. It takes a lambda of types `(Lkn.Core.Puppeteer.k ->
+ any)`. It can be used to reach all the Puppeteers that have
+ populated the Instance with their Puppets.
+
+ The block has to return the new system state.
+
+ The `defsystem` will generate a client function to use this
+ function. In addition to the specified arguments, it takes an
+ additional one: an Instance key.
"""
use Lkn.Prelude
@@ -28,7 +120,10 @@ defmodule Lkn.Core.System do
alias Lkn.Core.System
alias Lkn.Core.Instance
+ @typedoc "A System defined using the `defsystem/2` macro."
@type m :: module
+
+ @typedoc "A set of “compatible” puppets."
@type puppets :: MapSet.t(Puppet.t)
defmodule State do
@@ -86,11 +181,44 @@ defmodule Lkn.Core.System do
end
end
+ @typedoc """
+ The inner state of the Server process.
+ """
@type state :: any
- @callback init_state(Map.k) :: state
- @callback puppet_enter(state, System.puppets, Puppet.k) :: state
- @callback puppet_leave(state, System.puppets, Puppet.k) :: state
+ @doc """
+ Initialize the state of the system.
+
+ With the `instance_key`, you can trigger other systems of the same
+ instance. With the `map_key`, you can request through the Map
+ Component some information about the map.
+ """
+ @callback init_state(instance_key :: Instance.k, map_key :: Map.k) :: state
+
+ @doc """
+ A hook function which is called when a “compatible” puppet enters the Instance.
+ """
+ @callback puppet_enter(
+ state :: state,
+ instance_key :: Instance.k,
+ map_key :: Map.k,
+ puppets :: System.puppets,
+ puppet_key :: Puppet.k) :: state
+
+ @doc """
+ A hook function which is called when a “compatible” puppet leaves the Instance.
+ """
+ @callback puppet_leave(
+ state :: state,
+ instance_key :: Instance.k,
+ map_key :: Map.k,
+ puppets :: System.puppets,
+ puppet_key :: Puppet.k) :: state
+
+ @doc """
+ A macro to ease the definition of a new System which provides the
+ [cast](#module-the-keyword) keyword.
+ """
defmacro defsystem(name, do: block) do
alias Lkn.Core.Specs
@@ -141,7 +269,7 @@ defmodule Lkn.Core.System do
end
defp priv_handle_cast({:register_puppet, entity_key}, priv: priv, pub: pub) do
{priv, pub} = if Lkn.Core.Entity.has_component?(entity_key, __MODULE__) do
- {State.put(priv, entity_key), puppet_enter(pub, priv.puppets, entity_key)}
+ {State.put(priv, entity_key), puppet_enter(pub, priv.instance_key, priv.map_key, priv.puppets, entity_key)}
else
{priv, pub}
end
@@ -152,7 +280,7 @@ defmodule Lkn.Core.System do
{priv, pub} =
if Lkn.Core.Entity.has_component?(entity_key, __MODULE__) do
priv = State.delete(priv, entity_key)
- {priv, puppet_leave(pub, priv.puppets, entity_key)}
+ {priv, puppet_leave(pub, priv.instance_key, priv.map_key, priv.puppets, entity_key)}
else
{priv, pub}
end
@@ -192,39 +320,34 @@ defmodule Lkn.Core.System do
end
end
- @spec start_link(System.m, Instance.k, Map.k) :: GenServer.on_start
+ @doc false
+ @spec start_link(m, Instance.k, Map.k) :: GenServer.on_start
def start_link(module, instance_key, map_key) do
GenServer.start_link(module,
[
priv: State.new(instance_key, map_key),
- pub: module.init_state(map_key),
+ pub: module.init_state(instance_key, map_key),
],
name: Name.system(instance_key, module))
end
@doc false
- @spec cast(Instance.k, System.m, term) :: :ok
- def cast(instance_key, system, cmd) do
- GenServer.cast(Name.system(instance_key, system), {:pub, cmd})
- end
-
- @doc false
- @spec call(Instance.k, System.m, term) :: any
- def call(instance_key, system, cmd) do
- GenServer.call(Name.system(instance_key, system), {:pub, cmd})
- end
-
- @spec register_puppet(Instance.k, System.m, Puppet.k) :: :ok
+ @spec register_puppet(Instance.k, m, Puppet.k) :: :ok
def register_puppet(instance_key, system, puppet_key) do
GenServer.cast(Name.system(instance_key, system), {:priv, {:register_puppet, puppet_key}})
end
- @spec unregister_puppet(Instance.k, System.m, Puppet.k) :: :ok
+ @doc false
+ @spec unregister_puppet(Instance.k, m, Puppet.k) :: :ok
def unregister_puppet(instance_key, system, puppet_key) do
GenServer.cast(Name.system(instance_key, system), {:priv, {:unregister_puppet, puppet_key}})
end
- @spec population_size(Instance.k, module) :: integer
+ @doc """
+ Returns the number of “compatible” Puppets of a given System
+ registered to a given Instance.
+ """
+ @spec population_size(Instance.k, m) :: integer
def population_size(instance_key, system) do
GenServer.call(Name.system(instance_key, system), {:priv, :population_size})
end
diff --git a/test/core_test.exs b/test/core_test.exs
index 55830b8..286a7cf 100644
--- a/test/core_test.exs
+++ b/test/core_test.exs
@@ -311,7 +311,7 @@ defsystem Test.System do
@puppet Test.System.Puppet
@map Test.System.Map
- def init_state(_map_key) do
+ def init_state(_instance_key, _map_key) do
:ok
end
@@ -319,12 +319,12 @@ defsystem Test.System do
Lkn.Core.System.start_link(__MODULE__, instance_key, map_key)
end
- def puppet_enter(:ok, _entities, key) do
+ def puppet_enter(:ok, _instance_key, _map_key, _entities, key) do
notify(&(Test.Puppeteer.Specs.emit(&1, {:enter, key})))
:ok
end
- def puppet_leave(:ok, _entities, key) do
+ def puppet_leave(:ok, _instance_key, _map_key, _entities, key) do
notify(&(Test.Puppeteer.Specs.emit(&1, {:leave, key})))
:ok
end