defmodule Reactivity.Signal.Source do use GenServer require Logger alias Reactivity.Signal.Source alias Reactivity.Registry # last_value: The last generated value. # generator : The lambda that will generate the next value. # sleep : The time between each new signal. defstruct name: nil, last_value: nil, generator: nil, sleep: 10, children: [] ############# # GenServer # ############# def start_link(args) do GenServer.start_link(__MODULE__, args, name: __MODULE__) end def init(%{val: v, gen_func: f, sleep: t, name: n}) do # Send first tick already. Process.send_after(self(), :tick, t) {:ok, %Source{last_value: v, generator: f, sleep: t, children: [], name: n}} end ############# # Callbacks # ############# def handle_cast(m, state) do Logger.debug "Cast: #{inspect m}" {:noreply, state} end def handle_call({:add_child, pid}, _from, state) do Logger.debug "Adding child to #{state.name}" new_state = %{state | children: [pid | state.children]} {:reply, :ok, new_state} end def handle_call(m, from, state) do Logger.debug "Call: #{inspect m} from #{inspect from}" {:reply, :ok, state} end def handle_info(:tick, state) do Logger.debug "#{state.name} ticking..." # Compute the new value new_value = state.generator.(state.last_value) # Tick all my children. Enum.map(state.children, fn(c) -> send(c, {:tick, new_value}) end) # Compute the new state. new_state = %{state | last_value: new_value} Logger.debug "#{state.name}: #{inspect new_value}" Process.send_after(self(), :tick, state.sleep) {:noreply, new_state} end def handle_info(m, state) do Logger.debug "Info: #{inspect m}" {:noreply, state} end ########### # Private # ########### ############# # Interface # ############# @doc """ Creates a new source signal based on a function. It's not the point to be used in programs, merely as a simulation. """ def new(gen_func, default, time, name) do {:ok, pid} = GenServer.start_link(__MODULE__, %{val: default, gen_func: gen_func, sleep: time, name: name}, name: name) Registry.add_source({:signal, :source, pid}, name) {:ok, {:signal, :source, pid}} end def add_child(parent, {:signal, _, child}) do GenServer.call(parent, {:add_child, child}) end end