Packages

An in memory lobby that handles storing members and their state.

Current section

Files

Jump to
lobby lib lobby.ex
Raw

lib/lobby.ex

defmodule Lobby do
use GenServer
@moduledoc """
This module defines the GenServer that is the lobby GenServer
The lobby's sole purpose is to manage members, their ids, and thei current state.
"""
# PUBLIC API
@doc """
Starts and returns a Lobby GenServer process
First argument is the name you want to give the lobby, if any.
"""
@spec start_link(atom) :: {:ok, pid} | {:error, String.t}
def start_link(name \\ nil) do
GenServer.start_link(__MODULE__, %{name: name}, name: name)
end
@doc """
Creates and returns a new member id and state
## Example
iex> Lobby.new_member(:test_lobby)
{:ok, {1, %{}}}
"""
@spec new_member(pid | atom) :: {:ok, {number, map}} | {:error, String.t}
def new_member(lobby) do
GenServer.call(lobby, :new_member)
end
@doc """
Returns the state for the given member id
## Example
iex> Lobby.get_member(:test_lobby, 0)
{:ok, %{}}
"""
@spec get_member(pid | atom, number) :: {:ok, map} | {:error, String.t}
def get_member(lobby, member_id) do
GenServer.call(lobby, {:get_member, member_id})
end
@doc """
Returns the map that represents the entire lobby
## Examples
iex> Lobby.lobby(:test_lobby)
{:ok, %{0 => %{}}}
"""
@spec lobby(pid | atom) :: {:ok, map}
def lobby(lobby) do
GenServer.call(lobby, :lobby)
end
@doc """
Removes the memmber for the given member_id from the lobby
## Example
iex> Lobby.remove_member(:test_lobby, 0)
iex> Lobby.get_member(:test_lobby, 0)
{:error, "No member for id"}
"""
@spec remove_member(pid | atom, number) :: :ok
def remove_member(lobby, member_id) do
GenServer.cast(lobby, {:remove_member, member_id})
end
@doc """
Updates the member for the given id
## Example
iex> Lobby.update_member(:test_lobby, 0, %{sweet: :state})
iex> Lobby.get_member(:test_lobby, 0)
{:ok, %{sweet: :state}}
"""
@spec update_member(pid | atom, number, map) :: :ok
def update_member(lobby, member_id, member) do
GenServer.cast(lobby, {:update_member, member_id, member})
end
def init(options) do
{:ok, initial_state(options)}
end
def handle_call(:new_member, _from, state) do
{:ok, member, last_memeber_id} = create_new_member(state)
{:reply, {:ok, member}, %{state | last_memeber_id: last_memeber_id}}
end
def handle_call({:get_member, member_id}, _from, state) do
{:reply, get_member_state(state, member_id), state}
end
def handle_call(:lobby, _from, state) do
lobby = get_lobby(state)
{:reply, {:ok, lobby}, state}
end
def handle_cast({:remove_member, member_id}, state) do
add_spare_id(state, member_id)
remove_member_state(state, member_id)
{:noreply, state}
end
def handle_cast({:update_member, member_id, member}, state) do
set_member_state(state, member_id, member)
{:noreply, state}
end
defp initial_state(options = %{name: name}) do
%{
table: new_table(name),
options: options,
last_memeber_id: :not_created_yet,
}
end
## LOBBY
defp get_lobby(%{table: table}), do: get(table, :lobby)
## MEMBER
defp create_new_member(state) do
{:ok, new_member_id, last_memeber_id} = new_member_id(state)
set_member_state(state, new_member_id, %{})
{:ok, {new_member_id, %{}}, last_memeber_id}
end
defp set_member_state(state = %{table: table}, member_id, member) do
updated_lobby =
state
|> get_lobby
|> Map.put(member_id, member)
set(table, :lobby, updated_lobby)
end
defp remove_member_state(state = %{table: table}, member_id) do
updated_lobby =
state
|> get_lobby
|> Map.drop([member_id])
set(table, :lobby, updated_lobby)
end
defp get_member_state(state, member_id) do
state
|> get_lobby
|> Map.get(member_id)
|> case do
nil -> {:error, "No member for id"}
:removed -> {:error, "No member for id"}
member_state -> {:ok, member_state}
end
end
defp new_member_id(%{last_memeber_id: :not_created_yet}), do: {:ok, 0, 0}
defp new_member_id(state = %{last_memeber_id: last_id, table: table}) do
spare_ids(state)
|> case do
[] ->
new_id = last_id + 1
{:ok, new_id, new_id}
[spare_id | rest] ->
set(table, :spare_ids, rest)
{:ok, spare_id, last_id}
end
end
defp spare_ids(%{table: table}) do
get(table, :spare_ids)
end
defp add_spare_id(state = %{table: table}, spare_id) do
updated_spare_ids = [spare_id | spare_ids(state)]
set(table, :spare_ids, updated_spare_ids)
end
## TABLE
defp new_table(name) do
table =
"#{name}_table"
|> String.to_atom
|> :ets.new([:ordered_set, :protected, :named_table])
table
|> set(:spare_ids, [])
|> set(:lobby, %{})
end
defp get(table, key) do
table
|> :ets.lookup(key)
|> case do
[] -> nil
[{^key, value}] -> value
end
end
defp set(table, key, value) do
table |> :ets.insert({key, value})
table
end
end