From 9c53e89876191cf3228484dd60766760923ba901 Mon Sep 17 00:00:00 2001 From: lthms Date: Tue, 2 Jan 2018 07:47:27 +0000 Subject: puppeteer, system: Introduce cast_return and call_return --- lib/lkn/core/puppeteer.ex | 53 ++++++++++++++++--------- lib/lkn/core/specs.ex | 12 ++++++ lib/lkn/core/system.ex | 99 +++++++++++++++++++++++++++-------------------- test/core_test.exs | 21 +++++----- 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 -- cgit v1.2.3