Current section
Files
Jump to
Current section
Files
lib/room_manager/room_db.ex
defmodule Garuda.RoomManager.RoomDb do
@moduledoc """
Stores the info of all the game-rooms and functions to manage those data.
Orwell uses data from RoomDb, for rendering the live interactive dashboard
"""
alias Garuda.MatchMaker.Matcher
alias Garuda.RoomManager.Records
use GenServer
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@spec save_init_room_state(pid(), map()) :: any()
@doc """
Saves the specific game-room info with its pid as key
* `room_pid` - pid of the particular game-room
* `info` - A map of game-room info
"""
def save_init_room_state(room_pid, info) do
GenServer.call(__MODULE__, {"save_room", {room_pid, info}})
end
@doc """
Adds a new player details to the room. (Expecting room is already created)
* `room_pid` - pid of the game-room
* `opts` - A keyword-list of player-info
"""
@spec on_room_join(pid(), Keyword.t()) :: String.t()
def on_room_join(room_pid, opts) do
GenServer.call(__MODULE__, {"room_join", room_pid, opts})
end
@doc """
Updates a game-room's info.
Use cases are usually to update the live info regarding the no:of
players in a game-room.
"""
@spec update_room_state(pid(), map()) :: :ok
def update_room_state(room_pid, update_info) do
GenServer.cast(__MODULE__, {:update_room, room_pid, update_info})
end
@doc """
Returns the game-channel name associated with the game-room's pid.
* pid - game-room pid.
This function is mostly used by game-rooms to get the game-channel name, so
that they can send messages to the channel.
corresponding game-channels.
"""
@spec get_channel_name(pid()) :: String.t()
def get_channel_name(pid) do
GenServer.call(__MODULE__, {"get_channel_name", pid})
end
@spec delete_room(pid()) :: any()
@doc """
Deletes a game-room info with a pid.
"""
def delete_room(room_pid) do
GenServer.call(__MODULE__, {"delete_room", room_pid})
end
@doc """
Saves the game-channel info with its pid as key.
As of now this is mainly used for getting the actual no:of connections on the server.
"""
@spec on_channel_connection(pid(), map()) :: any()
def on_channel_connection(channel_pid, info) do
GenServer.call(__MODULE__, {"channel_join", {channel_pid, info}})
end
@spec on_channel_terminate(pid()) :: any()
@doc """
Deletes a game-channel's info with give channel pid
"""
def on_channel_terminate(channel_pid) do
GenServer.call(__MODULE__, {"channel_leave", channel_pid})
end
@spec get_stats :: map()
@doc """
Returns overall game server info, required for Monitoring
"""
def get_stats do
GenServer.call(__MODULE__, :get_stats)
end
@spec get_room_state(String.t()) :: map()
@doc """
Returns the state of a given `room_id`
* `room_id` - Unique combination of room_name + ":" + room_id., ex ("tictactoe:ACFBEBW")
"""
def get_room_state(room_id) do
GenServer.call(__MODULE__, {:get_room_state, room_id})
end
@doc """
Removes the player from RoomDb.
* room_pid - pid of the game-room
* player_id - unique_id of player
"""
@spec on_player_leave(pid(), String.t()) :: any()
def on_player_leave(room_pid, player_id) do
GenServer.call(__MODULE__, {"room_left", room_pid, player_id})
end
@doc """
Updates player_id key in the room with reconnection_timer ref
"""
@spec update_timer_ref(pid(), String.t(), any()) :: any
def update_timer_ref(room_pid, player_id, ref) do
GenServer.call(__MODULE__, {"update_timer_ref", room_pid, player_id, ref})
end
@doc """
Returns player_id key in the room with reconnection_timer ref
"""
@spec get_timer_ref(pid(), String.t()) :: any
def get_timer_ref(room_pid, player_id) do
GenServer.call(__MODULE__, {"get_timer_ref", room_pid, player_id})
end
@doc """
Returns rejoin status of player in the room
"""
@spec has_rejoined(pid(), String.t()) :: any
def has_rejoined(room_pid, player_id) do
GenServer.call(__MODULE__, {"has_rejoined", room_pid, player_id})
end
@impl true
def init(_opts) do
{:ok, %{"rooms" => %{}, "channels" => %{}}}
end
@impl true
def handle_call({"delete_room", room_pid}, _from, state) do
room_name = state["rooms"][room_pid]["room_name"]
match_id = state["rooms"][room_pid]["match_id"]
# IO.puts("#{room_name}:#{match_id}")
room_id = "#{room_name}:#{match_id}"
Matcher.delete_room(room_id)
{popped_val, state} = pop_in(state["rooms"][room_pid])
{:reply, popped_val, state}
end
@impl true
def handle_call({"save_room", {room_pid, info}}, _from, state) do
state = put_in(state["rooms"][room_pid], info)
{:reply, "ok", state}
end
@impl true
def handle_call({"channel_leave", channel_pid}, _from, state) do
{popped_val, state} = pop_in(state["channels"][channel_pid])
{:reply, popped_val, state}
end
@impl true
def handle_call({"channel_join", {channel_pid, _info}}, _from, state) do
state = put_in(state["channels"][channel_pid], %{})
{:reply, "ok", state}
end
@impl true
def handle_call(:get_stats, _from, state) do
stats = %{
"channel_count" => Map.keys(state["channels"]) |> Enum.count(),
"room_count" => Map.keys(state["rooms"]) |> Enum.count(),
"rooms" => state["rooms"]
}
{:reply, stats, state}
end
@impl true
def handle_call({"get_channel_name", room_pid}, _from, state) do
room_name = state["rooms"][room_pid]["room_name"]
match_id = state["rooms"][room_pid]["match_id"]
{:reply, "room_" <> room_name <> ":" <> match_id, state}
end
@impl true
def handle_call({:get_room_state, room_id}, _from, state) do
room_state =
case Records.is_process_registered(room_id) do
true -> :sys.get_state(Records.via_tuple(room_id))
false -> %{}
end
{:reply, room_state, state}
end
@impl true
def handle_call({"room_join", room_pid, opts}, _from, state) do
player_id = Keyword.get(opts, :player_id)
{status, state} = add_to_room(state["rooms"][room_pid], player_id, room_pid, state)
{:reply, status, state}
end
@impl true
def handle_call({"room_left", room_pid, player_id}, _from, state) do
{popped_val, state} = pop_in(state["rooms"][room_pid]["players"][player_id])
room_name = state["rooms"][room_pid]["room_name"]
match_id = state["rooms"][room_pid]["match_id"]
room_id = room_name <> ":" <> match_id
Matcher.remove_player(room_id, player_id)
state = manage_room_deletion(room_pid, state)
# Disposing the game-room, if no player in roomDb's room
# Or call on_leave on game-room.
{:reply, popped_val, state}
end
@impl true
def handle_call({"update_timer_ref", room_pid, player_id, ref}, _from, state) do
state =
case ref do
ref when is_reference(ref) ->
put_in(state["rooms"][room_pid]["players"][player_id]["recon_ref"], ref)
_ ->
put_in(state["rooms"][room_pid]["players"][player_id], %{
"recon_ref" => ref,
"rejoin" => false
})
end
{:reply, "updated", state}
end
@impl true
def handle_call({"get_timer_ref", room_pid, player_id}, _from, state) do
ref = state["rooms"][room_pid]["players"][player_id]["recon_ref"]
{:reply, ref, state}
end
@impl true
def handle_call({"has_rejoined", room_pid, player_id}, _from, state) do
status = state["rooms"][room_pid]["players"][player_id]["rejoin"]
{:reply, status, state}
end
## helper
defp add_to_room(nil, _player_id, _room_pid, state), do: {"error", state}
defp add_to_room(_players, player_id, room_pid, state) do
case state["rooms"][room_pid]["players"][player_id] do
nil ->
state =
put_in(state["rooms"][room_pid]["players"][player_id], %{
"recon_ref" => true,
"rejoin" => false
})
{"ok", state}
_ ->
state = put_in(state["rooms"][room_pid]["players"][player_id]["rejoin"], true)
{"already_exists", state}
end
end
def manage_room_deletion(room_pid, state) do
player_count =
state["rooms"][room_pid]["players"]
|> Enum.count()
case player_count do
0 ->
{_popped_val, state} = pop_in(state["rooms"][room_pid])
state
_ ->
state
end
end
end