Current section

Files

Jump to
buffered lib buffered_counter.ex
Raw

lib/buffered_counter.ex

defmodule BufferedCounter do
@behaviour :gen_statem
defmodule Counter do
defstruct number: 0, change: 0, threshold: 0, timeout: 0, flush_callback: nil
end
# Interface
def start_link(
%{start: start, threshold: threshold, timeout: timeout},
flush_callback,
opts \\ []
) do
:gen_statem.start_link(
__MODULE__,
%Counter{
number: start,
threshold: threshold,
timeout: timeout,
flush_callback: flush_callback
},
opts
)
end
def add(pid, change) do
:gen_statem.call(pid, {:add, change})
end
def flush(pid) do
:gen_statem.call(pid, :flush)
end
# Callbacks
def callback_mode() do
[:handle_event_function, :state_enter]
end
def init(data) do
{:ok, :idle, data}
end
# State callbacks
def handle_event({:call, from}, {:add, 0}, _state, _buffer) do
# Ignore void add
{:keep_state_and_data, {:reply, from, :ok}}
end
def handle_event({:call, from}, {:add, num_to_add}, _, %Counter{} = counter) do
{next_state, new_counter, flush_list} =
counter
|> __add(num_to_add)
flush_list |> Enum.each(counter.flush_callback)
{:next_state, next_state, new_counter, {:reply, from, :ok}}
end
def handle_event({:call, from}, :flush, _, %Counter{} = counter) do
__handle_flush_event(counter) |> Tuple.append({:reply, from, :ok})
end
def handle_event(:enter, :idle, :buffering, %Counter{timeout: timeout}) do
{:keep_state_and_data, {:state_timeout, timeout, :flush}}
end
def handle_event(:enter, _, :idle, _) do
:keep_state_and_data
end
def handle_event(:state_timeout, :flush, :buffering, %Counter{} = counter) do
__handle_flush_event(counter)
end
# Private
defp __add(%Counter{} = counter, num_to_add) when is_number(num_to_add) and num_to_add != 0 do
new_change = counter.change + num_to_add
if abs(new_change) >= counter.threshold do
new_number = counter.number + new_change
{:idle, %Counter{counter | number: new_number, change: 0}, [new_number]}
else
{:buffering, %Counter{counter | change: new_change}, []}
end
end
defp __handle_flush_event(%Counter{} = counter) do
new_number = counter.number + counter.change
counter.flush_callback.(new_number)
{:next_state, :idle, %Counter{counter | number: new_number, change: 0}}
end
end