defmodule Reactivity.Signal.Derived do use GenServer require Logger alias Reactivity.Signal.Derived # last_value: The last generated value. # generator : The lambda that will generate the next value. # sleep : The time between each new signal. defstruct last_value: nil, generator: nil, children: [], parent: nil ############# # GenServer # ############# def start_link(args) do GenServer.start_link(__MODULE__, args, name: __MODULE__) end def init(%{val: v, gen_func: f}) do {:ok, %Derived{last_value: v, generator: f, children: []}} end ############# # Callbacks # ############# def handle_cast(m, state) do Logger.debug "Cast: #{inspect m}" {:noreply, state} end def handle_call({:add_child, closure}, _from, state) do new_state = %{state | children: [closure | state.children]} {:reply, :ok, new_state} end def handle_call(_m, _from, state) do {:reply, :ok, state} end def handle_info({:tick, new_value}, state) do # Compute the new value new_value = state.generator.(new_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} {: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) do {:ok, pid} = GenServer.start_link(__MODULE__, %{val: default, gen_func: gen_func}) {:ok, {:signal, :derived, pid}} end @doc """ Adds a child to the given signal. """ def add_child(parent, {:signal, _, child}) do GenServer.call(parent, {:add_child, child}) end end