defmodule Strom.Loop do use GenServer @default_timeout 5_000 defstruct data: [], pid: nil, infinite: false, last_data_at: nil, timeout: @default_timeout def start, do: start([]) def start(%__MODULE__{} = loop), do: loop def start(opts) do loop = %__MODULE__{ timeout: Keyword.get(opts, :timeout, @default_timeout) } {:ok, pid} = GenServer.start_link(__MODULE__, loop) __state__(pid) end @impl true def init(%__MODULE__{} = loop), do: {:ok, %{loop | pid: self()}} def call(%__MODULE__{} = loop), do: GenServer.call(loop.pid, :get_data) def call(%__MODULE__{} = loop, data), do: GenServer.call(loop.pid, {:put_data, data}) def stop(%__MODULE__{} = loop), do: GenServer.call(loop.pid, :stop) def infinite?(%__MODULE__{infinite: infinite}), do: infinite def __state__(pid) when is_pid(pid), do: GenServer.call(pid, :__state__) @impl true def handle_call(:get_data, _from, %__MODULE__{data: data} = loop) do last_data_at = if is_nil(loop.last_data_at), do: time_now(), else: loop.last_data_at loop = %{loop | data: [], last_data_at: last_data_at} case data do [] -> if time_now() - last_data_at > loop.timeout do {:reply, {:error, {:halt, loop}}, loop} else {:reply, {:ok, {[], loop}}, loop} end data -> {:reply, {:ok, {data, loop}}, loop} end end def handle_call({:put_data, data}, _from, %__MODULE__{} = loop) do loop = %{loop | data: loop.data ++ [data], last_data_at: time_now()} {:reply, {:ok, {[], loop}}, loop} end def handle_call(:stop, _from, %__MODULE__{} = loop) do {:stop, :normal, :ok, loop} end def handle_call(:__state__, _from, state), do: {:reply, state, state} defp time_now do "Etc/UTC" |> DateTime.now!() |> DateTime.to_unix(:millisecond) end end