Packages

Multiplayer game server framework

Current section

Files

Jump to
overworld src ow_beacon.erl
Raw

src/ow_beacon.erl

-module(ow_beacon).
-behaviour(gen_server).
-behaviour(ow_rpc).
-define(SERVER, ?MODULE).
% Public API
-export([start/0, stop/0, last/0, get_by_id/1, dump/0]).
% ow callbacks
-export([rpc_info/0, beacon/1]).
% genserver callbacks
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
% milliseconds
-define(HEARTBEAT, 1000).
% The number of beacons to hold in memory
-define(WINDOW_SIZE, 25).
-define(BEACON, 16#0019).
-spec rpc_info() -> ow_rpc:callbacks().
rpc_info() ->
[
#{
opcode => ?BEACON,
s2c_call => session_beacon,
encoder => overworld_pb
}
].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% API
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
stop() ->
gen_server:stop(?SERVER),
ok.
last() ->
gen_server:call(?MODULE, last).
get_by_id(ID) ->
gen_server:call(?MODULE, {get_by_id, ID}).
dump() ->
gen_server:call(?MODULE, dump).
% It makes no sense for the client to send beacons to the server, just throw
% away these messages
beacon(_) ->
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% genserver callbacks
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init([]) ->
{ok, new_state()}.
handle_call({get_by_id, ID}, _From, State = {Window, _Timer}) ->
case lists:keyfind(ID, 1, Window) of
false ->
io:format("Don't have this ID: ~p~n", [ID]),
io:format("Window is : ~p~n", [Window]),
{reply, 0, State};
{ID, Time} ->
{reply, Time, State}
end;
handle_call(last, _From, {[Now | _Rest], Timer}) ->
{reply, Now, {Now, Timer}};
handle_call(dump, _From, {Window, Timer}) ->
{reply, Window, {Window, Timer}};
handle_call(_Call, _From, State) ->
{reply, State, State}.
handle_cast(_Req, State) ->
{noreply, State}.
handle_info(tick, {Window, OldTimer}) ->
% Stop the old timer
erlang:cancel_timer(OldTimer),
% Create a new unique integer as the beacon ID
% TODO: SEED ME
% 32-bit unsigned max
ID = rand:uniform(4_294_967_295),
% Push the beacon ID + current time to the stack
Beacon = {ID, erlang:monotonic_time()},
% Encode the beacon and broadcast it to all clients.
% n.b., There's a very small race here. A client could conceivably respond
% faster than the server has updated the list.
ow_session:broadcast(encode_beacon(ID)),
NewTimer = erlang:send_after(?HEARTBEAT, self(), tick),
{noreply, {push(Beacon, Window), NewTimer}};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% internal functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
new_state() ->
ID = erlang:unique_integer(),
Now = erlang:monotonic_time(),
NewTimer = erlang:send_after(?HEARTBEAT, self(), tick),
{[{ID, Now}], NewTimer}.
% Push the latest beacon onto a fixed length list of the last ?WINDOW_SIZE
% number of beacons.
push(Beacon, List) ->
L1 = lists:sublist(List, ?WINDOW_SIZE - 1),
[Beacon | L1].
encode_beacon(ID) ->
OpCode = <<?BEACON:16>>,
Msg = overworld_pb:encode_msg(#{id => ID}, session_beacon),
[OpCode, Msg].