Current section

Files

Jump to
finitomata lib finitomata supervision supervisor.ex
Raw

lib/finitomata/supervision/supervisor.ex

defmodule Finitomata.Supervisor do
@moduledoc since: "0.26.0"
@moduledoc """
The behaviour for actual _FSM_ implementations across _Finitomata_ framework.
It ships with two predefined implementations, `Finitomata` for local
applications and `Infinitomata` for distributed ones.
"""
@typedoc "The module, implementing this behaviour"
@type t :: module()
@doc "Returns whether the _FSM_ instance under `id` “branch” and with `fsm_name` name is alive"
@callback alive?(id :: Finitomata.id(), fsm_name :: Finitomata.fsm_name()) :: boolean()
@doc "Starts the new _FSM_ instance under `id` “branch,” the semantics is similar to `DynamicSupervisor.start_child/2`"
@callback start_fsm(
id :: Finitomata.id(),
fsm_name :: Finitomata.fsm_name(),
implementation :: Finitomata.implementation(),
payload :: Finitomata.payload()
) :: DynamicSupervisor.on_start_child()
@doc "Sends the event with an optional payload to the running _FSM_ instance to initiate a transition"
@callback transition(
id :: Finitomata.id(),
fsm_name :: Finitomata.fsm_name(),
event_payload ::
Finitomata.Transition.event()
| {Finitomata.Transition.event(), Finitomata.State.payload()},
delay :: non_neg_integer()
) :: :ok
@doc "Effectively initiates the `on_timer/2` callback imminently, resetting the timer"
@callback timer_tick(id :: Finitomata.id(), fsm_name :: Finitomata.fsm_name()) :: :ok
@doc "The `Finitomata` “branch” child specification, used from supervision trees to start supervised _FSM_ “branchs”"
@callback child_spec(id :: Finitomata.id()) :: Supervisor.child_spec()
@doc "Returns all the active _FSM_ instances under the `id` “branch,” it might be a heavy map"
@callback all(Finitomata.id()) :: %{
optional(Finitomata.fsm_name()) => %{
optional(:node) => node(),
optional(:reference) => reference(),
optional(:module) => Finitomata.implementation(),
pid: pid()
}
}
@doc "Returns the state of the _FSM_ instance under `id` “branch” with `fsm_name` name"
@callback state(
id :: Finitomata.id(),
fsm_name :: Finitomata.fsm_name(),
reload? :: :cached | :payload | :state | :full | (Finitomata.State.t() -> any())
) ::
nil | Finitomata.State.t() | Finitomata.State.payload() | any()
use Supervisor
@doc false
def start_link(id \\ nil)
def start_link([]), do: start_link(nil)
def start_link(id: id), do: start_link(id)
def start_link(id),
do: Supervisor.start_link(__MODULE__, id, name: supervisor_name(id))
@impl Supervisor
@doc false
def init(id) do
children =
[
{Registry,
keys: :unique, name: registry_name(id), partitions: System.schedulers_online()},
{Finitomata.StateCache, id},
{Finitomata.Manager, name: manager_name(id)}
]
Supervisor.init(children, strategy: :rest_for_one)
end
@doc false
@spec infinitomata_name(Finitomata.id()) :: module()
def infinitomata_name(id \\ nil), do: fq_module(id, Infinitomata, true)
@doc false
@spec supervisor_name(Finitomata.id()) :: module()
def supervisor_name(id \\ nil), do: id |> fq_module(Supervisor, true) |> uninfinitomata()
@doc false
@spec registry_name(Finitomata.id()) :: module()
def registry_name(id \\ nil), do: id |> fq_module(Registry, true) |> uninfinitomata()
@doc false
@spec manager_name(Finitomata.id()) :: module()
def manager_name(id \\ nil), do: id |> fq_module(Manager, true) |> uninfinitomata()
@doc false
@spec throttler_name(Finitomata.id()) :: module()
def throttler_name(id \\ nil), do: id |> fq_module(Throttler, true) |> uninfinitomata()
@doc false
@spec state_cache_name(Finitomata.id()) :: module()
def state_cache_name(id \\ nil), do: id |> fq_module(StateCache, true) |> uninfinitomata()
@spec fq_module(id :: any(), who :: any(), atomize? :: boolean()) :: module() | [any()]
defp fq_module(id, who, false) when is_atom(id) and is_atom(who), do: [Finitomata, id, who]
defp fq_module(id, who, false) when is_atom(id),
do: fq_module(id, Module.concat([inspect(who)]), false)
defp fq_module(id, who, false), do: fq_module(Module.concat([inspect(id)]), who, false)
defp fq_module(id, who, true) when is_atom(id) and is_atom(who) do
id
|> fq_module(who, false)
|> Enum.reject(&is_nil/1)
|> Enum.map(&inspect/1)
|> smart_concat()
end
defp fq_module(id, who, true) when is_atom(id),
do: fq_module(id, Module.concat([inspect(who)]), true)
defp fq_module(id, who, true), do: fq_module(Module.concat([inspect(id)]), who, true)
defp uninfinitomata(mod),
do: mod |> Module.split() |> Enum.reject(&(&1 == "Infinitomata")) |> Module.concat()
defp smart_concat([fqn]), do: Module.concat([fqn])
defp smart_concat([fqn, id_who]) do
if String.starts_with?(id_who, fqn),
do: Module.concat([id_who]),
else: Module.concat([fqn, id_who])
end
defp smart_concat([fqn, id, who]) do
if String.starts_with?(id, fqn),
do: Module.concat([id, who]),
else: Module.concat([fqn, id, who])
end
end