Current section

Files

Jump to
grains lib grains.ex
Raw

lib/grains.ex

defmodule Grains do
@moduledoc """
Grains describes data flow as a graph with interchangeable parts.
To accomplish that grains divides the graph (`Recipe`) from the
implementation of the nodes (`Grains`).
Together these parts can be used to make a `Bread` which describes
how the graph translates to processes.
Each process has a symbolic short name, which is resolved
to the registered name of the process via a map (`%Bread{}.process_map`).
"""
alias Grains.Bread
alias Grains.Recipe
alias Grains.Supervisor
defstruct [:map]
@doc """
This function describes the default implementations
for auxiliary grains, for example the periodic timer.
Currently these default grains are provided:
iex> Grains.default_grains()
%{periodic: Grains.Timer}
"""
def default_grains() do
%{
periodic: Grains.Timer
}
end
def new(map) do
%__MODULE__{map: map}
end
@doc """
Merges two grain maps into one.
The function works the same as `Map.merge/2`: all grains in the second
argument are added to the first, overriding any existing one.
"""
def merge(%__MODULE__{map: a}, %__MODULE__{map: b}) do
a
|> Map.merge(b)
|> new()
end
defdelegate new_recipe(name, new), to: Grains.Recipe, as: :new
defdelegate merge_recipes(name, a, b), to: Grains.Recipe, as: :merge
defdelegate start_supervised(recipe, grains, args \\ []), to: Grains.Supervisor, as: :start_link
@spec make_name(Recipe.t, atom, atom) :: atom
def make_name(recipe = %Recipe{}, bread_id, short_name) do
[recipe.name, bread_id, short_name] |> Module.concat()
end
@spec make_bread_name(Recipe.t, atom()) :: atom()
def make_bread_name(recipe = %Recipe{}, bread_id) do
[recipe.name, bread_id] |> Module.concat()
end
@spec get_name(Supervisor.t, atom) :: atom()
def get_name(supervisor, short_name) do
Module.concat([Supervisor.get_root_name(supervisor), short_name])
end
@spec concat_name(Bread.t, atom) :: atom
def concat_name(bread = %Bread{}, grain) do
Module.concat([bread.name, grain])
end
@doc """
Retrieve a grain's substate.
This is similar to `:sys.get_state/1`. Note that this function should usually
not be used, but it can be useful for debugging or testing.
## Errors
The function exits the caller on error. For example, if no bread with the supplied
id can be found, this results in a `no_proc` exit.
"""
@spec get_substate(atom) :: term()
def get_substate(bread_id) do
bread_id |> :sys.get_state() |> Map.get(:substate)
end
@doc """
Description of a periodic grain.
"""
def periodic(grain, period, opts \\ %{with_timestamps: false}) do
{:periodic, grain, Map.put(opts, :period, period)}
end
@doc """
Combine two sets of recipes and grains.
"""
def combine({ra = %Recipe{}, ga = %Grains{}}, {rb = %Recipe{}, gb = %Grains{}}) do
rab = Recipe.merge(ra.name, ra, rb)
gab = Grains.merge(ga, gb)
{rab, gab}
end
end