From 1c9fdbfc0e77efd8c27a99676ce9de83db30105d Mon Sep 17 00:00:00 2001 From: lthms Date: Fri, 22 Sep 2017 15:07:48 +0000 Subject: puppeteer: Allow a Puppeteer impl to expose its own cast functions Before, a given Puppeteer implementation could only expose the functions of its Specifictaion. It was quite limited and prevented several scenario where a Puppeteer could be influenced by some master, e.g. a Player Puppeteer by network events, an Instance logger by the Instance itself, etc. This patch addresses this limitation. --- lib/lkn/core/puppeteer.ex | 21 ++++++++++++++++++++- lib/lkn/core/specs.ex | 36 +++++++++++++++++++++++++++++------- test/core_test.exs | 27 ++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/lib/lkn/core/puppeteer.ex b/lib/lkn/core/puppeteer.ex index a164e1a..521e256 100644 --- a/lib/lkn/core/puppeteer.ex +++ b/lib/lkn/core/puppeteer.ex @@ -43,7 +43,18 @@ defmodule Lkn.Core.Puppeteer do defmodule unquote(name) do unquote(Specs.gen_server_from_specs(block, key_type, key_to_name, state_type)) - defmacro __using__(_) do + defmacro __using__(args) do + plugin_clients = case args do + [do: use_block] -> + Specs.gen_server_plugin_entry_point(use_block, + quote do Lkn.Core.Puppeteer.k end, + &(Lkn.Core.Name.puppeteer(&1)), + quote do Lkn.Core.Puppeteer.state end) + _ -> + quote do + end + end + quote do @behaviour unquote(__MODULE__) @behaviour Lkn.Core.Puppeteer @@ -102,6 +113,12 @@ defmodule Lkn.Core.Puppeteer do {:noreply, %State{state|state: s}} 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.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__) @@ -115,6 +132,8 @@ defmodule Lkn.Core.Puppeteer do {:reply, res, %State{state|state: s}} end + + unquote(plugin_clients) end end end diff --git a/lib/lkn/core/specs.ex b/lib/lkn/core/specs.ex index 9c3b305..99ac28a 100644 --- a/lib/lkn/core/specs.ex +++ b/lib/lkn/core/specs.ex @@ -16,6 +16,27 @@ defmodule Lkn.Core.Specs do end)) end + def gen_server_plugin_entry_point(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, [], [], []) + + check_specs(casts, calls, legit, [allow_impl: true, allow_specs: false]) + + plugin = quote do :plugin end + + casts_client = Enum.map(casts, &(cast_client(plugin, var_name("key"), key_type, key_to_name, &1))) + casts_behaviour = Enum.map(casts, &(cast_server(&1, var_name("key"), key_type, [], state_type, "_plugin"))) + + quote do + unquote(casts_client) + unquote(casts_behaviour) + end + end + def gen_server_from_specs(block, key_type, key_to_name, state_type, keywords \\ []) do block = case block do {:__block__, _, x} -> x @@ -30,8 +51,10 @@ defmodule Lkn.Core.Specs do key_name = Keyword.get(keywords, :key_name, var_name("key")) additional_args = Keyword.get(keywords, :additional_args, []) - casts_client = Enum.map(casts, &(cast_client(key_name, key_type, key_to_name, &1))) - calls_client = Enum.map(calls, &(call_client(key_name, key_type, key_to_name, &1))) + spec = quote do :spec end + + casts_client = Enum.map(casts, &(cast_client(spec, key_name, key_type, key_to_name, &1))) + calls_client = Enum.map(calls, &(call_client(spec, key_name, key_type, key_to_name, &1))) casts_behaviour = Enum.map(casts, &(cast_server(&1, key_name, key_type, additional_args, state_type, impl_suffix))) calls_behaviour = Enum.map(calls, &(call_behaviour(&1, key_name, key_type, additional_args, state_type))) @@ -45,7 +68,7 @@ defmodule Lkn.Core.Specs do end end - defp cast_client(key_name, key_type, key_to_name, cast) do + defp cast_client(namespace, key_name, key_type, key_to_name, cast) do cast = case cast do {cast, _} -> cast cast -> cast @@ -68,12 +91,12 @@ defmodule Lkn.Core.Specs do @spec unquote({name, [], argtypes}) :: :ok def unquote({name, [], arglistcl}) do GenServer.cast(unquote(key_to_name).(unquote(key_name)), - {:spec, {unquote(name), unquote(arglist)}}) + {unquote(namespace), {unquote(name), unquote(arglist)}}) end end end - defp call_client(key_name, key_type, key_to_name, call) do + defp call_client(namespace, key_name, key_type, key_to_name, call) do name = call.fun.name call_doc = if call.doc != :none do @@ -91,7 +114,7 @@ defmodule Lkn.Core.Specs do @spec unquote({name, [], argtypes}) :: unquote(call.ret) def unquote({name, [], arglistcl}) do GenServer.call(unquote(key_to_name).(unquote(key_name)), - {:spec, {unquote(name), unquote(arglist)}}) + {unquote(namespace), {unquote(name), unquote(arglist)}}) end end end @@ -145,7 +168,6 @@ defmodule Lkn.Core.Specs do end end - defp parse_specs(block, casts, calls, legit) do case block do [{:@, _, [{:doc, _, [docstring]}]}, diff --git a/test/core_test.exs b/test/core_test.exs index 286a7cf..50039a0 100644 --- a/test/core_test.exs +++ b/test/core_test.exs @@ -68,6 +68,24 @@ defmodule Lkn.Core.Test do Option.some(4) = Lkn.Core.Entity.read(entity_key, :level) end + test "spawning puppeteer and testing its private cast" do + puppeteer_key = UUID.uuid4() + + {:ok, _} = Test.Puppeteer.start_link(puppeteer_key) + + Test.Puppeteer.wizz(puppeteer_key, 2) + + receive do + {:wizz, n, x} -> assert x == puppeteer_key && n == 0 + after 100 -> raise "Waiting for 100ms the message {:wizz, 0, #{inspect puppeteer_key}}" + end + + receive do + {:wizz, n, x} -> assert x == puppeteer_key && n == 1 + after 100 -> raise "Waiting for 100ms the message {:wizz, 1, #{inspect puppeteer_key}}" + end + end + test "spawning everything" do map_key = UUID.uuid4() puppeteer_key = UUID.uuid4() @@ -415,7 +433,14 @@ defpuppeteer Test.Puppeteer.Specs do end defmodule Test.Puppeteer do - use Test.Puppeteer.Specs, state: pid() + use Test.Puppeteer.Specs do + cast wizz(n :: number) do + for i <- 0..(n-1) do + send state, {:wizz, i, key} + end + state + end + end def start_link(puppeteer_key) do Puppeteer.start_link(__MODULE__, puppeteer_key, self()) -- cgit v1.2.3