aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorlthms <contact@thomasletan.fr>2018-01-02 07:47:27 +0000
committerThomas Letan <contact@thomasletan.fr>2018-01-24 08:11:29 +0100
commit9c53e89876191cf3228484dd60766760923ba901 (patch)
tree79f91f3a8a8654db87aa89e7c369c719fe825592
parentfix: (Un)registering puppets blocks to avoid range race conditions (diff)
puppeteer, system: Introduce cast_return and call_return
-rw-r--r--lib/lkn/core/puppeteer.ex53
-rw-r--r--lib/lkn/core/specs.ex12
-rw-r--r--lib/lkn/core/system.ex99
-rw-r--r--test/core_test.exs21
4 files changed, 115 insertions, 70 deletions
diff --git a/lib/lkn/core/puppeteer.ex b/lib/lkn/core/puppeteer.ex
index 996c78b..ab373ea 100644
--- a/lib/lkn/core/puppeteer.ex
+++ b/lib/lkn/core/puppeteer.ex
@@ -53,6 +53,7 @@ defmodule Lkn.Core.Puppeteer do
)
defmacro __using__(args) do
+
plugin_clients = case args do
[do: use_block] ->
Specs.gen_server_plugin_entry_point(
@@ -76,7 +77,9 @@ defmodule Lkn.Core.Puppeteer do
alias Lkn.Core.Instance
alias Lkn.Core, as: L
- defmodule State do
+ unquote(Specs.gen_server_returns())
+
+ defmodule PrivateState do
@moduledoc false
defstruct [
@@ -86,7 +89,7 @@ defmodule Lkn.Core.Puppeteer do
:state,
]
- @type t :: %State{
+ @type t :: %PrivateState{
puppeteer_key: Puppeteer.k,
instance_key: Lkn.Prelude.Option.t(Instance.k),
map_key: Lkn.Prelude.Option.t(L.Map.k),
@@ -95,80 +98,92 @@ defmodule Lkn.Core.Puppeteer do
@spec new(Puppeteer.t, Lkn.Core.Puppeteer.state) :: t
def new(pk, s) do
- %State{
+ %PrivateState{
puppeteer_key: pk,
instance_key: Lkn.Prelude.Option.nothing(),
map_key: Lkn.Prelude.Option.nothing(),
state: s,
}
end
+
+ def update(state, opts) do
+ st = Keyword.get(opts, :state, state.state)
+ instance = Keyword.get(opts, :instance, state.instance_key)
+ mapk = Keyword.get(opts, :map, state.map_key)
+
+ %PrivateState{state|
+ state: st,
+ map_key: mapk,
+ instance_key: instance
+ }
+ end
end
def init({puppeteer_key, args}) do
{:ok, s} = init_state(args)
- {:ok, State.new(puppeteer_key, s)}
+ {:ok, PrivateState.new(puppeteer_key, s)}
end
def handle_cast({:puppet_enter, instance_key, puppet_key, digest}, state) do
if state.instance_key == Lkn.Prelude.Option.some(instance_key) do
- s2 = puppet_enter(state.state, state.instance_key, puppet_key, digest)
+ opts = puppet_enter(state.state, state.instance_key, puppet_key, digest)
- {:noreply, %State{state|state: s2}}
+ {:noreply, PrivateState.update(state, opts)}
else
{:noreply, state}
end
end
def handle_cast({:puppet_leave, instance_key, puppet_key}, state) do
if state.instance_key == Lkn.Prelude.Option.some(instance_key) do
- s2 = puppet_leave(state.state, state.instance_key, puppet_key)
+ opts = puppet_leave(state.state, state.instance_key, puppet_key)
- {:noreply, %State{state|state: s2}}
+ {:noreply, PrivateState.update(state, opts)}
else
{:noreply, state}
end
end
def handle_cast({:instance_digest, instance_key, map_key, map, puppets}, state) do
if state.instance_key == Lkn.Prelude.Option.some(instance_key) do
- s2 = instance_digest(state.state, instance_key, map_key, map, puppets)
- {:noreply, %State{state|state: s2}}
+ opts = instance_digest(state.state, instance_key, map_key, map, puppets)
+ {:noreply, PrivateState.update(state, opts)}
else
{:noreply, state}
end
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)
+ opts = 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}}
+ {:noreply, PrivateState.update(state, opts)}
else
{:noreply, state}
end
end
def handle_cast({:spec, {name, args}}, state) do
- s = :erlang.apply(__MODULE__, name, [state.puppeteer_key|args]++[state.instance_key, state.state])
+ opts = :erlang.apply(__MODULE__, name, [state.puppeteer_key|args]++[state.instance_key, state.state])
- {:noreply, %State{state|state: s}}
+ {:noreply, PrivateState.update(state, opts)}
end
def handle_cast({:plugin, {name, args}}, state) do
name = String.to_atom(String.replace_suffix(Atom.to_string(name), "", "_plugin"))
- s = :erlang.apply(__MODULE__, name, [state.puppeteer_key|args]++[state.instance_key, state.state])
+ opts = :erlang.apply(__MODULE__, name, [state.puppeteer_key|args]++[state.instance_key, state.state])
- {:noreply, %State{state|state: s}}
+ {:noreply, PrivateState.update(state, opts)}
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)}
+ state = %PrivateState{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])
+ {res, opts} = :erlang.apply(__MODULE__, name, [state.puppeteer_key|args] ++ [state.state])
- {:reply, res, %State{state|state: s}}
+ {:reply, res, PrivateState.update(state, opts)}
end
unquote(plugin_clients)
diff --git a/lib/lkn/core/specs.ex b/lib/lkn/core/specs.ex
index 986b29a..fc3be37 100644
--- a/lib/lkn/core/specs.ex
+++ b/lib/lkn/core/specs.ex
@@ -54,6 +54,18 @@ defmodule Lkn.Core.Specs do
end
end
+ def gen_server_returns() do
+ quote do
+ defp cast_return(opts \\ []) do
+ opts
+ end
+
+ defp call_return(res, opts \\ []) do
+ {res, opts}
+ end
+ end
+ end
+
def gen_server_from_specs(block, key_type, key_to_name, state_type, keywords \\ []) do
block = case block do
{:__block__, _, x} -> x
diff --git a/lib/lkn/core/system.ex b/lib/lkn/core/system.ex
index aa8577a..c57dd54 100644
--- a/lib/lkn/core/system.ex
+++ b/lib/lkn/core/system.ex
@@ -126,27 +126,38 @@ defmodule Lkn.Core.System do
@typedoc "A set of “compatible” puppets."
@type puppets :: MapSet.t(Puppet.t)
- defmodule State do
+ defmodule PrivateState do
@moduledoc false
defstruct [
:map_key,
:instance_key,
:puppets,
+ :state,
]
- @type t :: %State{
+ @type t :: %PrivateState{
map_key: Map.k,
instance_key: Instance.k,
puppets: System.puppets,
+ state: term,
}
- @spec new(Instance.k, Map.k) :: t
- def new(instance_key, map_key) do
- %State{
+ @spec new(Instance.k, Map.k, term) :: t
+ def new(instance_key, map_key, public) do
+ %PrivateState{
puppets: MapSet.new(),
instance_key: instance_key,
map_key: map_key,
+ state: public,
+ }
+ end
+
+ def update(state, opts) do
+ st = Keyword.get(opts, :state, state.state)
+
+ %PrivateState{state|
+ state: st
}
end
@@ -154,14 +165,14 @@ defmodule Lkn.Core.System do
def put(state, puppet_key) do
puppets = MapSet.put(state.puppets, puppet_key)
- %State{state|
+ %PrivateState{state|
puppets: puppets
}
end
@spec delete(t, Puppet.k) :: t
def delete(state, puppet_key) do
- %State{state|
+ %PrivateState{state|
puppets: MapSet.delete(state.puppets, puppet_key)
}
end
@@ -199,7 +210,7 @@ defmodule Lkn.Core.System do
instance_key :: Instance.k,
map_key :: Map.k,
puppets :: System.puppets,
- puppet_key :: Puppet.k) :: state
+ puppet_key :: Puppet.k) :: term
@doc """
A hook function which is called when a “compatible” puppet leaves the Instance.
@@ -209,7 +220,7 @@ defmodule Lkn.Core.System do
instance_key :: Instance.k,
map_key :: Map.k,
puppets :: System.puppets,
- puppet_key :: Puppet.k) :: state
+ puppet_key :: Puppet.k) :: term
@doc """
A macro to ease the definition of a new System which provides the
@@ -231,6 +242,8 @@ defmodule Lkn.Core.System do
@behaviour Lkn.Core.System
+ unquote(Specs.gen_server_returns())
+
unquote(Specs.gen_server_from_specs(
block,
key_type,
@@ -259,53 +272,54 @@ defmodule Lkn.Core.System do
{:ok, st}
end
- defp priv_handle_cast({:notify, notification}, priv: priv, pub: pub) do
- State.notify(priv, notification)
- [priv: priv, pub: pub]
+ defp priv_handle_cast({:notify, notification}, state) do
+ PrivateState.notify(state, notification)
+
+ {:noreply, state}
end
- defp priv_handle_call({:register_puppet, entity_key}, _from, priv: priv, pub: pub) do
- {res, priv, pub} = if Lkn.Core.Entity.has_component?(entity_key, __MODULE__) do
- {true, State.put(priv, entity_key), puppet_enter(pub, priv.instance_key, priv.map_key, priv.puppets, entity_key)}
+ defp priv_handle_call({:register_puppet, entity_key}, _from, state) do
+ if Lkn.Core.Entity.has_component?(entity_key, __MODULE__) do
+ state = PrivateState.put(state, entity_key)
+ opts = puppet_enter(state.state, state.instance_key, state.map_key, state.puppets, entity_key)
+
+ {:reply, true, PrivateState.update(state, opts)}
else
- {false, priv, pub}
+ {:reply, false, state}
end
-
- {:reply, res, [priv: priv, pub: pub]}
end
- defp priv_handle_call({:unregister_puppet, entity_key}, _from, priv: priv, pub: pub) do
- {res, priv, pub} =
+ defp priv_handle_call({:unregister_puppet, entity_key}, _from, state) do
if Lkn.Core.Entity.has_component?(entity_key, __MODULE__) do
- priv = State.delete(priv, entity_key)
- {true, priv, puppet_leave(pub, priv.instance_key, priv.map_key, priv.puppets, entity_key)}
+ opts = puppet_leave(state.state, state.instance_key, state.map_key, state.puppets, entity_key)
+ state = PrivateState.delete(state, entity_key)
+
+ {:reply, true, PrivateState.update(state, opts)}
else
- {false, priv, pub}
+ {:reply, false, state}
end
-
- {:reply, res, [priv: priv, pub: pub]}
end
- defp priv_handle_call(:population_size, _from, priv: priv, pub: pub) do
- {:reply, State.population(priv), [priv: priv, pub: pub]}
+ defp priv_handle_call(:population_size, _from, state) do
+ {:reply, PrivateState.population(state), state}
end
def handle_cast({:priv, cmd}, state) do
- {:noreply, priv_handle_cast(cmd, state)}
+ priv_handle_cast(cmd, state)
end
- def handle_cast({:spec, {name, args}}, priv: priv, pub: pub) do
+ def handle_cast({:spec, {name, args}}, state) do
name = String.to_atom(String.replace_suffix(Atom.to_string(name), "", "_impl"))
- s = :erlang.apply(__MODULE__, name, [priv.instance_key|args] ++ [priv.map_key, priv.puppets, pub])
+ opts = :erlang.apply(__MODULE__, name, [state.instance_key|args] ++ [state.map_key, state.puppets, state.state])
- {:noreply, [priv: priv, pub: s]}
+ {:noreply, PrivateState.update(state, opts)}
end
- def handle_call({:spec, {name, args}}, _call, priv: priv, pub: pub) do
+ def handle_call({:spec, {name, args}}, _call, state) do
name = String.to_atom(String.replace_suffix(Atom.to_string(name), "", "_impl"))
- {res, s} = :erlang.apply(__MODULE__, name, [priv.instance_key|args] ++ [priv.map_key, priv.puppets, pub])
+ {res, opts} = :erlang.apply(__MODULE__, name, [state.instance_key|args] ++ [state.map_key, state.puppets, state.state])
- {:reply, res, [priv: priv, pub: s]}
+ {:reply, res, PrivateState.update(state, opts)}
end
- def handle_call({:priv, cmd}, from, priv: priv, pub: pub) do
- priv_handle_call(cmd, from, priv: priv, pub: pub)
+ def handle_call({:priv, cmd}, from, state) do
+ priv_handle_call(cmd, from, state)
end
@spec notify(any) :: :ok
@@ -319,12 +333,13 @@ defmodule Lkn.Core.System do
@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(instance_key, map_key),
- ],
- name: Name.system(instance_key, module))
+ pub = module.init_state(instance_key, map_key)
+
+ GenServer.start_link(
+ module,
+ PrivateState.new(instance_key, map_key, pub),
+ name: Name.system(instance_key, module)
+ )
end
@doc false
diff --git a/test/core_test.exs b/test/core_test.exs
index 71aeed8..6d7accf 100644
--- a/test/core_test.exs
+++ b/test/core_test.exs
@@ -417,19 +417,21 @@ defsystem Test.System do
def puppet_enter(:ok, _instance_key, _map_key, _entities, key) do
notify(&(Test.Puppeteer.Specs.emit(&1, {:enter, key})))
- :ok
+
+ cast_return()
end
def puppet_leave(:ok, _instance_key, _map_key, _entities, key) do
notify(&(Test.Puppeteer.Specs.emit(&1, {:leave, key})))
- :ok
+
+ cast_return()
end
cast level_up(puppet_key :: Puppet.k) do
notif = Test.System.Puppet.level_up(puppet_key)
notify(&(Test.Puppeteer.Specs.emit(&1, {:level_up, notif})))
- state
+ cast_return()
end
end
@@ -542,7 +544,8 @@ defmodule Test.Puppeteer do
for i <- 0..(n-1) do
send state, {:wizz, i, key}
end
- state
+
+ cast_return()
end
end
@@ -555,15 +558,15 @@ defmodule Test.Puppeteer do
end
def instance_digest(state, _instance_key, map_key, _map, _puppets) do
- state
+ cast_return()
end
def puppet_enter(state, _instance_key, _puppet_key, _digest) do
- state
+ cast_return()
end
def puppet_leave(state, _instance_key, _puppet_key) do
- state
+ cast_return()
end
def destroy(puppeteer_key, state, _instance_key, _reason) do
@@ -572,12 +575,12 @@ defmodule Test.Puppeteer do
def leave_instance(target, _instance_key) do
send(target, :kick)
- target
+ cast_return()
end
def emit(_puppeteer_key, msg, instance_key, target) do
send target, msg
- target
+ cast_return()
end
end