Skip to main content

lib/pi/plugin/waiters.ex

defmodule Pi.Plugin.Waiters do
  @moduledoc "ETS-backed session waiter registry for interactive plugins."

  use GenServer

  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def install do
    case GenServer.whereis(__MODULE__) do
      nil -> GenServer.start_link(__MODULE__, [], name: __MODULE__)
      _pid -> :ok
    end
  end

  @impl true
  def init(_opts), do: {:ok, %{tables: MapSet.new()}}

  @impl true
  def handle_call({:ensure_table, table}, _from, state) do
    create_table(table)
    {:reply, :ok, %{state | tables: MapSet.put(state.tables, table)}}
  end

  def ensure_table!(table) when is_atom(table) do
    if table?(table) do
      :ok
    else
      ensure_owned_table!(table)
    end
  end

  def register(table, session_id, pid)
      when is_atom(table) and is_binary(session_id) and is_pid(pid) do
    ensure_table!(table)
    :ets.insert(table, {session_id, pid})
    :ok
  end

  def unregister(table, session_id) when is_atom(table) and is_binary(session_id) do
    if table?(table), do: :ets.delete(table, session_id)
    :ok
  end

  def pop(table, session_id) when is_atom(table) and is_binary(session_id) do
    if table?(table) do
      case :ets.lookup(table, session_id) do
        [{^session_id, pid}] ->
          :ets.delete(table, session_id)
          {:ok, pid}

        [] ->
          :error
      end
    else
      :error
    end
  end

  def table?(table) when is_atom(table), do: :ets.info(table) != :undefined

  defp ensure_owned_table!(table) do
    if Process.whereis(__MODULE__) do
      GenServer.call(__MODULE__, {:ensure_table, table})
    else
      raise "#{inspect(__MODULE__)} is not running"
    end
  end

  defp create_table(table) do
    case :ets.info(table) do
      :undefined -> :ets.new(table, [:named_table, :public, :set])
      _info -> table
    end
  end
end