aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorlthms <contact@thomasletan.fr>2017-04-30 07:16:14 +0000
committerThomas Letan <contact@thomasletan.fr>2018-01-24 08:09:36 +0100
commit01aa43fdbf13c597bd5da7f8bc69608d68fe3113 (patch)
tree9e62940fb3568abe2106d8e7ce352000eda6cd59
parententity: Simplify the implementation of both Puppet and Map (diff)
puppeteer: Add the macro defpuppeteer to specify a Puppeteer interface
-rw-r--r--lib/lkn/core/component.ex135
-rw-r--r--lib/lkn/core/puppeteer.ex167
-rw-r--r--lib/lkn/core/specs.ex104
-rw-r--r--test/core_test.exs47
4 files changed, 242 insertions, 211 deletions
diff --git a/lib/lkn/core/component.ex b/lib/lkn/core/component.ex
index b79a6f6..51b5a63 100644
--- a/lib/lkn/core/component.ex
+++ b/lib/lkn/core/component.ex
@@ -15,110 +15,23 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
-alias Lkn.Core.Specs
-
defmodule Lkn.Core.Component do
- defp cast_client(module_name, cast) do
- name = cast.fun.name
- entity_key_type = quote do
- Lkn.Core.Entity.k
- end
-
- cast_doc = if cast.doc != :none do
- quote do
- @doc unquote(cast.doc)
- end
- end
-
- arglist = Enum.map(cast.fun.arguments, &(&1.name))
- arglistcl = [{:entity_key, [], nil}|arglist]
- argtypes = [entity_key_type|Enum.map(cast.fun.arguments, &(&1.type))]
-
- quote do
- unquote(cast_doc)
- @spec unquote({name, [], argtypes}) :: :ok
- def unquote({name, [], arglistcl}) do
- GenServer.cast(Lkn.Core.Name.component(unquote({:entity_key, [], nil}), unquote(module_name)),
- {:spec, {unquote(name), unquote(arglist)}})
- end
- end
- end
+ alias Lkn.Core.Specs
- defp call_client(module_name, call) do
- name = call.fun.name
- entity_key_type = quote do
- Lkn.Core.Entity.k
- end
-
- call_doc = if call.doc != :none do
- quote do
- @doc unquote(call.doc)
- end
- end
-
- arglist = Enum.map(call.fun.arguments, &(&1.name))
- arglistcl = [{:entity_key, [], nil}|arglist]
- argtypes = [entity_key_type|Enum.map(call.fun.arguments, &(&1.type))]
+ @type state :: any
- quote do
- unquote(call_doc)
- @spec unquote({name, [], argtypes}) :: unquote(call.ret)
- def unquote({name, [], arglistcl}) do
- GenServer.call(Lkn.Core.Name.component(unquote({:entity_key, [], nil}), unquote(module_name)),
- {:spec, {unquote(name), unquote(arglist)}})
- end
- end
- end
-
- defp cast_behaviour(cast) do
- name = cast.fun.name
- entity_key_type = quote do
- Lkn.Core.Entity.k
- end
-
- arglistcl = [{:::, [], [{:entity_key, [], nil}, entity_key_type]}
- |Enum.map(cast.fun.arguments, &({:::, [], [&1.name, &1.type]}))]
-
- quote do
- @callback unquote({name, [], arglistcl}) :: :ok
- end
- end
-
- defp call_behaviour(call) do
- name = call.fun.name
- entity_key_type = quote do
- Lkn.Core.Entity.k
- end
-
- arglistcl = [{:::, [], [{:entity_key, [], nil}, entity_key_type]}
- |Enum.map(call.fun.arguments, &({:::, [], [&1.name, &1.type]}))]
-
- quote do
- @callback unquote({name, [], arglistcl}) :: unquote(call.ret)
- end
- end
+ @callback init_state(Lkn.Core.Entity.k) :: {:ok, state} | :error
defmacro defcomponent(name, do: block) do
- block = case block do
- {:__block__, _, x} -> x
- x -> [x]
- end
-
- {casts, calls, legit} = Specs.parse_specs(block, [], [], [])
-
- casts_client = Enum.map(casts, &(cast_client(name, &1)))
- calls_client = Enum.map(calls, &(call_client(name, &1)))
-
- casts_behaviour = Enum.map(casts, &(cast_behaviour(&1)))
- calls_behaviour = Enum.map(calls, &(call_behaviour(&1)))
+ state_type = quote do Lkn.Core.Component.state end
+ key_type = quote do Lkn.Core.Component.k end
+ key_to_name = quote do
+ &(Lkn.Core.Name.component(&1, unquote(name)))
+ end
quote do
defmodule unquote(name) do
- unquote(legit)
- unquote(casts_client)
- unquote(calls_client)
- unquote(casts_behaviour)
- unquote(calls_behaviour)
+ unquote(Specs.gen_server_from_specs(block, key_type, key_to_name, state_type))
def system do
@system
@@ -126,7 +39,20 @@ defmodule Lkn.Core.Component do
defmacro __using__(_) do
quote do
+ defmodule State do
+ defstruct [
+ :entity_key,
+ :state,
+ ]
+
+ @type t :: %State{
+ entity_key: Lkn.Core.Entity.k,
+ state: Lkn.Core.Component.state,
+ }
+ end
+
@behaviour unquote(__MODULE__)
+ @behaviour Lkn.Core.Component
use GenServer
@@ -143,6 +69,11 @@ defmodule Lkn.Core.Component do
GenServer.start_link(__MODULE__, entity_key, name: Name.component(entity_key, unquote(__MODULE__)))
end
+ def init(entity_key) do
+ {:ok, s} = init_state(entity_key)
+ {:ok, %State{entity_key: entity_key, state: s}}
+ end
+
@spec read(Entity.k, Properties.prop) :: Option.t(Properties.value)
defp read(entity_key, p) do
Properties.read(entity_key, p)
@@ -153,16 +84,16 @@ defmodule Lkn.Core.Component do
Properties.write(entity_key, p, v)
end
- def handle_cast({:spec, {name, args}}, entity_key) do
- :erlang.apply(__MODULE__, name, [entity_key|args])
+ def handle_cast({:spec, {name, args}}, state) do
+ s = :erlang.apply(__MODULE__, name, [state.entity_key|args] ++ [state.state])
- {:noreply, entity_key}
+ {:noreply, %State{state|state: s}}
end
- def handle_call({:spec, {name, args}}, _call, entity_key) do
- res = :erlang.apply(__MODULE__, name, [entity_key|args])
+ def handle_call({:spec, {name, args}}, _call, state) do
+ {res, s} = :erlang.apply(__MODULE__, name, [state.entity_key|args] ++ [state.state])
- {:reply, res, entity_key}
+ {:reply, res, %State{state|state: s}}
end
end
end
diff --git a/lib/lkn/core/puppeteer.ex b/lib/lkn/core/puppeteer.ex
index 5044865..a164e1a 100644
--- a/lib/lkn/core/puppeteer.ex
+++ b/lib/lkn/core/puppeteer.ex
@@ -18,6 +18,7 @@
defmodule Lkn.Core.Puppeteer do
alias Lkn.Core.Instance
alias Lkn.Core.Name
+ alias Lkn.Core.Specs
@typedoc """
A key to identify and reach a given Puppeteer.
@@ -30,94 +31,93 @@ defmodule Lkn.Core.Puppeteer do
@type m :: module
@type init_args :: any
- @type state :: any
-
- defmacro __using__(state: state_t) do
- quote do
- use Lkn.Prelude
- use GenServer
-
- alias Lkn.Core.Instance
- alias Lkn.Core, as: L
-
- @type state :: unquote(state_t)
-
- defmodule State do
- @moduledoc false
-
- defstruct [
- :puppeteer_key,
- :map_key,
- :instance_key,
- :state,
- ]
-
- @type t :: %State{
- puppeteer_key: Puppeteer.k,
- instance_key: Lkn.Prelude.Option.t(Instance.k),
- map_key: Lkn.Prelude.Option.t(L.Map.k),
- state: unquote(state_t),
- }
-
- @spec new(Puppeteer.t, unquote(state_t)) :: t
- def new(pk, s) do
- %State{
- puppeteer_key: pk,
- instance_key: Lkn.Prelude.Option.nothing(),
- map_key: Lkn.Prelude.Option.nothing(),
- state: s,
- }
- end
- end
-
- def init({puppeteer_key, args}) do
- {:ok, s} = init_state(args)
- {:ok, State.new(puppeteer_key, s)}
- end
- def handle_cast({:leave_instance, instance_key}, state) do
- if state.instance_key == Lkn.Prelude.Option.some(instance_key) do
- s2 = leave_instance(state.state, instance_key)
+ @type state :: any
- Instance.unregister_puppeteer(instance_key, state.puppeteer_key)
+ defmacro defpuppeteer(name, do: block) do
+ state_type = quote do Lkn.Core.Puppeteer.state end
+ key_type = quote do Lkn.Core.Puppeteer.k end
+ key_to_name = &(Lkn.Core.Name.puppeteer(&1))
- {:noreply, %State{state|instance_key: Lkn.Prelude.Option.nothing(), state: s2}}
- else
- {:noreply, state}
+ quote do
+ defmodule unquote(name) do
+ unquote(Specs.gen_server_from_specs(block, key_type, key_to_name, state_type))
+
+ defmacro __using__(_) do
+ quote do
+ @behaviour unquote(__MODULE__)
+ @behaviour Lkn.Core.Puppeteer
+
+ use GenServer
+
+ alias Lkn.Core.Instance
+ alias Lkn.Core, as: L
+
+ defmodule State do
+ @moduledoc false
+
+ defstruct [
+ :puppeteer_key,
+ :map_key,
+ :instance_key,
+ :state,
+ ]
+
+ @type t :: %State{
+ puppeteer_key: Puppeteer.k,
+ instance_key: Lkn.Prelude.Option.t(Instance.k),
+ map_key: Lkn.Prelude.Option.t(L.Map.k),
+ state: Lkn.Core.Puppeteer.state,
+ }
+
+ @spec new(Puppeteer.t, Lkn.Core.Puppeteer.state) :: t
+ def new(pk, s) do
+ %State{
+ puppeteer_key: pk,
+ instance_key: Lkn.Prelude.Option.nothing(),
+ map_key: Lkn.Prelude.Option.nothing(),
+ state: s,
+ }
+ end
+ end
+
+ def init({puppeteer_key, args}) do
+ {:ok, s} = init_state(args)
+ {:ok, State.new(puppeteer_key, s)}
+ end
+
+ def handle_cast({:leave_instance, instance_key}, state) do
+ if state.instance_key == Lkn.Prelude.Option.some(instance_key) do
+ s2 = leave_instance(state.state, instance_key)
+
+ Instance.unregister_puppeteer(instance_key, state.puppeteer_key)
+
+ {:noreply, %State{state|instance_key: Lkn.Prelude.Option.nothing(), state: s2}}
+ else
+ {:noreply, state}
+ end
+ end
+ def handle_cast({:spec, {name, args}}, state) do
+ s = :erlang.apply(__MODULE__, name, [state.puppeteer_key|args]++[state.state])
+
+ {:noreply, %State{state|state: s}}
+ end
+
+ def handle_call({:find_instance, map_key}, _from, state) do
+ instance_key = Lkn.Core.Pool.register_puppeteer(map_key, state.puppeteer_key, __MODULE__)
+
+ state = %State{state|instance_key: Lkn.Prelude.Option.some(instance_key)}
+
+ {:reply, instance_key, state}
+ end
+ def handle_call({:spec, {name, args}}, _call, state) do
+ {res, s} = :erlang.apply(__MODULE__, name, [state.puppeteer_key|args] ++ [state.state])
+
+ {:reply, res, %State{state|state: s}}
+ end
+ end
end
end
- def handle_cast({:specific, msg}, state) do
- s2 = puppeteer_handle_cast(msg, state.state)
- {:noreply, %State{state|state: s2}}
- end
-
- def handle_call({:find_instance, map_key}, _from, state) do
- instance_key = Lkn.Core.Pool.register_puppeteer(map_key, state.puppeteer_key, __MODULE__)
-
- state = %State{state|instance_key: Lkn.Prelude.Option.some(instance_key)}
-
- {:reply, instance_key, state}
- end
-
- def puppeteer_handle_cast(_msg, state) do
- state
- end
-
- def handle_info(msg, state) do
- state = %State{state|state: handle_message(state.state, msg)}
-
- {:noreply, state}
- end
-
- def cast(puppeteer_key, msg) do
- GenServer.cast(Name.puppeteer(puppeteer_key), {:specific, msg})
- end
-
- @behaviour Lkn.Core.Puppeteer
-
- defoverridable [
- puppeteer_handle_cast: 2,
- ]
end
end
@@ -126,7 +126,6 @@ defmodule Lkn.Core.Puppeteer do
GenServer.start_link(module, {puppeteer_key, args}, name: Name.puppeteer(puppeteer_key))
end
- @callback handle_message(state, any) :: state
@callback init_state(init_args) :: {:ok, state}|:error
@callback leave_instance(state, Instance.k) :: state
diff --git a/lib/lkn/core/specs.ex b/lib/lkn/core/specs.ex
index 4a62820..ceab803 100644
--- a/lib/lkn/core/specs.ex
+++ b/lib/lkn/core/specs.ex
@@ -1,7 +1,109 @@
defmodule Lkn.Core.Specs do
@moduledoc false
- def parse_specs(block, casts, calls, legit) do
+ def gen_server_from_specs(block, key_type, key_to_name, state_type) do
+ block = case block do
+ {:__block__, _, x} -> x
+ x -> [x]
+ end
+
+ {casts, calls, legit} = parse_specs(block, [], [], [])
+
+ casts_client = Enum.map(casts, &(cast_client(key_type, key_to_name, &1)))
+ calls_client = Enum.map(calls, &(call_client(key_type, key_to_name, &1)))
+
+ casts_behaviour = Enum.map(casts, &(cast_behaviour(&1, state_type)))
+ calls_behaviour = Enum.map(calls, &(call_behaviour(&1, state_type)))
+
+ quote do
+ unquote(legit)
+ unquote(casts_client)
+ unquote(calls_client)
+ unquote(casts_behaviour)
+ unquote(calls_behaviour)
+ end
+ end
+
+ defp cast_client(key_type, key_to_name, cast) do
+ name = cast.fun.name
+
+ cast_doc = if cast.doc != :none do
+ quote do
+ @doc unquote(cast.doc)
+ end
+ end
+
+ arglist = Enum.map(cast.fun.arguments, &(&1.name))
+ arglistcl = [{:key, [], nil}|arglist]
+ argtypes = [key_type|Enum.map(cast.fun.arguments, &(&1.type))]
+
+ quote do
+ unquote(cast_doc)
+ @spec unquote({name, [], argtypes}) :: :ok
+ def unquote({name, [], arglistcl}) do
+ GenServer.cast(unquote(key_to_name).(unquote({:key, [], nil})),
+ {:spec, {unquote(name), unquote(arglist)}})
+ end
+ end
+ end
+
+ defp call_client(key_type, key_to_name, call) do
+ name = call.fun.name
+
+ call_doc = if call.doc != :none do
+ quote do
+ @doc unquote(call.doc)
+ end
+ end
+
+ arglist = Enum.map(call.fun.arguments, &(&1.name))
+ arglistcl = [{:key, [], nil}|arglist]
+ argtypes = [key_type|Enum.map(call.fun.arguments, &(&1.type))]
+
+ quote do
+ unquote(call_doc)
+ @spec unquote({name, [], argtypes}) :: unquote(call.ret)
+ def unquote({name, [], arglistcl}) do
+ GenServer.call(unquote(key_to_name).(unquote({:key, [], nil})),
+ {:spec, {unquote(name), unquote(arglist)}})
+ end
+ end
+ end
+
+ defp cast_behaviour(cast, state_type) do
+ name = cast.fun.name
+ entity_key_type = quote do
+ Lkn.Core.Entity.k
+ end
+
+ arglistcl = [{:::, [], [{:entity_key, [], nil}, entity_key_type]}
+ |Enum.map(cast.fun.arguments, &({:::, [], [&1.name, &1.type]}))] ++ [
+ {:::, [], [{:state, [], nil}, state_type]}
+ ]
+
+ quote do
+ @callback unquote({name, [], arglistcl}) :: unquote(state_type)
+ end
+ end
+
+ defp call_behaviour(call, state_type) do
+ name = call.fun.name
+ entity_key_type = quote do
+ Lkn.Core.Entity.k
+ end
+
+ arglistcl = [{:::, [], [{:entity_key, [], nil}, entity_key_type]}
+ |Enum.map(call.fun.arguments, &({:::, [], [&1.name, &1.type]}))] ++ [
+ {:::, [], [{:state, [], nil}, state_type]}
+ ]
+
+ quote do
+ @callback unquote({name, [], arglistcl}) :: {unquote(state_type), unquote(call.ret)}
+ end
+ end
+
+
+ defp parse_specs(block, casts, calls, legit) do
case block do
[{:@, _, [{:doc, _, [docstring]}]},
{:@, _, [{:cast, _, [fun]}]}
diff --git a/test/core_test.exs b/test/core_test.exs
index b9a456a..982edc6 100644
--- a/test/core_test.exs
+++ b/test/core_test.exs
@@ -283,8 +283,10 @@ end
######################################################################
# SYSTEM #
# #
-
-import Lkn.Core.Component
+import Lkn.Core.Component, only: [defcomponent: 2]
+import Lkn.Core.Map, only: [defmap: 2]
+import Lkn.Core.Puppeteer, only: [defpuppeteer: 2]
+import Lkn.Core.Puppet, only: [defpuppet: 2]
defcomponent Test.System.Puppet do
@system Test.System
@@ -319,18 +321,19 @@ defmodule Test.System do
end
def puppet_enter(:ok, _entities, key) do
- notify(&(Test.Puppeteer.emit(&1, {:enter, key})))
+ notify(&(Test.Puppeteer.Specs.emit(&1, {:enter, key})))
:ok
end
def puppet_leave(:ok, _entities, key) do
- notify(&(Test.Puppeteer.emit(&1, {:leave, key})))
+ notify(&(Test.Puppeteer.Specs.emit(&1, {:leave, key})))
:ok
end
def system_cast({:level_up, entity_key}, _entities, :ok) do
notif = Test.System.Puppet.level_up(entity_key)
- notify(&(Test.Puppeteer.emit(&1, {:level_up, notif})))
+
+ notify(&(Test.Puppeteer.Specs.emit(&1, {:level_up, notif})))
:ok
end
@@ -343,12 +346,12 @@ end
######################################################################
# MAP #
# #
-import Lkn.Core.Map
-
defmodule Test.Map.Component do
use Test.System.Map
- def level_max(_), do: 7
+ def level_max(_, _), do: 7
+
+ def init_state(_), do: {:ok, :ok}
end
defmap Test.Map do
@@ -374,17 +377,17 @@ end
defmodule Test.Entity.Component do
use Test.System.Puppet
- def level_up(entity_key) do
+ def init_state(_), do: {:ok, :ok}
+
+ def level_up(entity_key, :ok) do
Option.some(lvl) = read(entity_key, :level)
write(entity_key, :level, lvl + 1)
- {lvl, lvl + 1}
+ {{lvl, lvl + 1}, :ok}
end
end
-import Lkn.Core.Puppet
-
defpuppet Test.Entity do
@components [Test.Entity.Component]
@@ -412,8 +415,13 @@ end
######################################################################
# PUPPETEER #
# #
+
+defpuppeteer Test.Puppeteer.Specs do
+ @cast emit(msg :: any)
+end
+
defmodule Test.Puppeteer do
- use Puppeteer, state: pid()
+ use Test.Puppeteer.Specs, state: pid()
def start_link(puppeteer_key) do
Puppeteer.start_link(__MODULE__, puppeteer_key, self())
@@ -428,19 +436,10 @@ defmodule Test.Puppeteer do
target
end
- def puppeteer_handle_cast({:emit, msg}, target) do
- send(target, msg)
-
+ def emit(_puppeteer_key, msg, target) do
+ send target, msg
target
end
-
- def handle_message(target, _msg) do
- target
- end
-
- def emit(puppeteer_key, msg) do
- cast(puppeteer_key, {:emit, msg})
- end
end