Behaviour for GameServer hooks/callbacks.
Implement this behaviour in your hooks module to receive lifecycle events from the GameServer and run custom game logic.
Setup
- Create a module implementing this behaviour
- Configure it in your GameServer instance
Example
defmodule MyGame.Hooks do
@behaviour GameServer.Hooks
@impl true
def after_user_register(user) do
# Give new users starting coins
GameServer.Accounts.update_user(user, %{
metadata: Map.put(user.metadata || %{}, "coins", 100)
})
end
@impl true
def after_user_login(user) do
# Log login
:ok
end
@impl true
def after_user_updated(user) do
# React to user profile changes (e.g., sync display name to external system)
:ok
end
# Lobby hooks
@impl true
def before_lobby_create(attrs) do
# Validate or modify lobby creation attributes
{:ok, attrs}
end
@impl true
def after_lobby_create(_lobby), do: :ok
@impl true
def before_lobby_join(user, lobby, opts) do
# Check if user can join (e.g., level requirements)
{:ok, {user, lobby, opts}}
end
@impl true
def before_group_create(user, attrs) do
# Check if user can create a group (e.g., enough coins in metadata)
coins = get_in(user.metadata, ["coins"]) || 0
if coins >= 50 do
{:ok, attrs}
else
{:error, :not_enough_coins}
end
end
@impl true
def before_group_join(user, group, opts) do
# Check if user can join group (e.g., level requirements based on metadata)
{:ok, {user, group, opts}}
end
@impl true
def after_lobby_join(_user, _lobby), do: :ok
@impl true
def before_lobby_leave(user, lobby) do
{:ok, {user, lobby}}
end
@impl true
def after_lobby_leave(_user, _lobby), do: :ok
@impl true
def before_lobby_update(_lobby, attrs) do
{:ok, attrs}
end
@impl true
def after_lobby_update(_lobby), do: :ok
@impl true
def before_lobby_delete(lobby) do
{:ok, lobby}
end
@impl true
def after_lobby_delete(_lobby), do: :ok
@impl true
def before_user_kicked(host, target, lobby) do
{:ok, {host, target, lobby}}
end
@impl true
def after_user_kicked(_host, _target, _lobby), do: :ok
@impl true
def after_lobby_host_change(_lobby, _new_host_id), do: :ok
# Custom RPC handlers - define your own functions!
# These are called from game clients via the RPC channel.
#
# def give_coins(amount, opts) do
# caller = Keyword.get(opts, :caller)
# # Update user's coins...
# {:ok, %{new_balance: 150}}
# end
endHook Types
User Lifecycle Hooks
after_user_register/1- Called after a new user registersafter_user_login/1- Called after a user logs inafter_user_updated/1- Called after a user is updated (fire-and-forget)
Lobby Lifecycle Hooks
Before hooks can block operations by returning {:error, reason}.
After hooks are fire-and-forget.
before_lobby_create/1- Before lobby creation, receives attrs mapafter_lobby_create/1- After lobby is createdbefore_lobby_join/3- Before user joins lobbyafter_lobby_join/2- After user joins lobbybefore_group_create/2- Before group creation, receives(user, attrs). Return{:ok, attrs}to allow or{:error, reason}to blockafter_group_create/1- After group is created (fire-and-forget)before_group_join/3- Before user is accepted into a group (public join, invite accept, or request approval)before_chat_message/2- Before a chat message is sent, receives(user, attrs). Return{:ok, attrs}to allow (and optionally modify), or{:error, reason}to blockafter_chat_message/1- After a chat message is persisted (fire-and-forget)before_lobby_leave/2- Before user leaves lobbyafter_lobby_leave/2- After user leaves lobbybefore_lobby_update/2- Before lobby is updatedafter_lobby_update/1- After lobby is updatedbefore_lobby_delete/1- Before lobby is deletedafter_lobby_delete/1- After lobby is deletedbefore_user_kicked/3- Before user is kicked from lobbyafter_user_kicked/3- After user is kicked from lobbyafter_lobby_host_change/2- After lobby host changesbefore_kv_get/2- Called before a KVgetto determine whether a key should be publicly readable (:public) or restricted (:private)Custom RPC Functions
Game clients can call RPCs exposed by your hooks module in two ways:
Exported functions: any exported function in your hooks module (other than the callbacks above) can be called from game clients.
Dynamic functions (custom hooks): your
after_startup/0callback may return a list of dynamic exports describing additional callable function names that do not need to exist as exported Elixir functions. These dynamic calls are dispatched toon_custom_hook/2.
# Client calls: rpc("give_coins", {amount: 50}) def give_coins(amount, opts) do
caller = Keyword.get(opts, :caller) # Your game logic here {:ok, %{success: true}}end
Return values:
{:ok, data}- Success, data is sent back to client{:error, reason}- Error, reason is sent back to client:ok- Success with no data
Summary
Types
Result type for before hooks
Options passed to hooks that accept an options map/keyword list.
A lobby struct from GameServer.Lobbies.Lobby
A dynamic RPC export returned by after_startup/0.
A user struct from GameServer.Accounts.User
Callbacks
Called before a KV get/2 is performed. Implementations should return
:public if the key may be read publicly, or :private to restrict access.
Handle a dynamically-exported RPC function.
Functions
Use this macro to get default implementations for all callbacks.
Returns the raw caller value for the current hook invocation.
Returns the caller's numeric id when available.
Returns the user struct for the current caller when available.
Types
@type hook_result(t) :: {:ok, t} | {:error, term()}
Result type for before hooks
Options passed to hooks that accept an options map/keyword list.
Common keys include :user_id (pos_integer) and other domain-specific
options. Hooks may accept either a map or keyword list for convenience.
@type lobby() :: GameServer.Lobbies.Lobby.t()
A lobby struct from GameServer.Lobbies.Lobby
A dynamic RPC export returned by after_startup/0.
The minimal shape is:
%{hook: "custom_hello"}Optionally, provide :meta for tooling / UI hints:
%{hook: "custom_hello", meta: %{description: "...", args: [...], example_args: [...]}}Note: the runtime also accepts string keys (e.g. %{"hook" => ...}), but
this type focuses on the atom-keyed form.
@type user() :: GameServer.Accounts.User.t()
A user struct from GameServer.Accounts.User
Callbacks
@callback after_startup() :: :ok | [rpc_export()]
@callback before_chat_message(user(), attrs :: map()) :: hook_result(map())
@callback before_group_create(user(), map()) :: hook_result(map())
@callback before_kv_get(String.t(), kv_opts()) :: hook_result(:public | :private)
Called before a KV get/2 is performed. Implementations should return
:public if the key may be read publicly, or :private to restrict access.
Receives the key and an opts map/keyword (see kv_opts/0). Return
either the bare atom (e.g. :public) or {:ok, :public}; return {:error, reason}
to block the read.
@callback before_lobby_create(attrs :: map()) :: hook_result(map())
@callback before_lobby_delete(lobby()) :: hook_result(lobby())
@callback before_lobby_leave(user(), lobby()) :: hook_result({user(), lobby()})
@callback before_lobby_update(lobby(), attrs :: map()) :: hook_result(map())
@callback before_stop() :: any()
Handle a dynamically-exported RPC function.
This callback is invoked when a client calls a function name that was
registered at runtime from after_startup/0.
Receives the function name and the argument list.
Functions
Use this macro to get default implementations for all callbacks.
This allows you to only implement the callbacks you need.
Example
defmodule MyGame.Hooks do
use GameServer.Hooks
@impl true
def after_user_register(user) do
# Only implement what you need
:ok
end
end
@spec caller() :: any() | nil
Returns the raw caller value for the current hook invocation.
When GameServer executes a hook function, it may inject a :caller into the
hook task's process dictionary. This helper fetches that raw value.
@spec caller_id() :: integer() | nil
Returns the caller's numeric id when available.
If the caller is a GameServer.Accounts.User struct, returns its id.
If the caller is a map, returns :id or "id".
If the caller is already an integer, returns it.
Otherwise returns nil.
@spec caller_user() :: GameServer.Accounts.User.t() | nil
Returns the user struct for the current caller when available.
This is a convenience wrapper over caller/0 that returns a user struct when
the caller is already a %GameServer.Accounts.User{}.
In the full GameServer application this may also resolve ids/maps via the DB. In the SDK it only returns the struct when it is already present.