Current section
Files
Jump to
Current section
Files
src/ow_enet.erl
-module(ow_enet).
-behaviour(gen_server).
% API
-export([
start/1
]).
% Required gen_server callbacks
-export([
init/1,
terminate/2,
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3
]).
-type peerinfo() :: map().
-type qos() :: {
reliable | unreliable | unsequenced | undefined,
non_neg_integer() | undefined
}.
-export_type([qos/0]).
%%===========================================================================
%% API
%%===========================================================================
%%---------------------------------------------------------------------------
%% @doc Starts the ENet connection handler with any initial state
%% @end
%%---------------------------------------------------------------------------
-spec start(map()) -> gen_server:start_ret().
start(PeerInfo) ->
gen_server:start_link(?MODULE, [PeerInfo], []).
%%===========================================================================
%% Callbacks
%%===========================================================================
%%---------------------------------------------------------------------------
%% @doc Handler is initialized for any new connection and logs the foreign IP
%% @end
%%---------------------------------------------------------------------------
-spec init([peerinfo()]) -> {ok, peerinfo()}.
init([PeerInfo]) ->
IP = inet:ntoa(maps:get(ip, PeerInfo)),
{ok, SessionPid} = ow_session_sup:new([{proxy, self()}]),
logger:debug("Started session ~p for ENet conn. ~p", [SessionPid, IP]),
% Trap exits from the enet child processes
process_flag(trap_exit, true),
% Add a new key to the peerInfo map containing Overworld session
% information
{ok, PeerInfo#{session_pid => SessionPid}}.
% Required ballbacks
handle_call({call, Msg}, _From, PeerInfo) ->
{reply, Msg, PeerInfo}.
handle_cast(_Msg, PeerInfo) ->
{noreply, PeerInfo}.
% Handle messages from other processes
handle_info({enet, disconnected, remote, _Pid, _When}, PeerInfo) ->
#{ip := RawIP} = PeerInfo,
IP = inet:ntoa(RawIP),
logger:notice("~p: ENet client disconnected", [IP]),
{stop, normal, PeerInfo};
handle_info({enet, _Channel, {reliable, Msg}}, PeerInfo) ->
decode_and_reply(Msg, PeerInfo),
{noreply, PeerInfo};
handle_info({enet, _Channel, {unreliable, _Seq, Msg}}, PeerInfo) ->
decode_and_reply(Msg, PeerInfo),
{noreply, PeerInfo};
handle_info({enet, _Channel, {unsequenced, _Group, Msg}}, PeerInfo) ->
decode_and_reply(Msg, PeerInfo),
{noreply, PeerInfo};
handle_info(
{_From, client_msg, {MsgType, Msg}}, PeerInfo = #{channels := Channels}
) ->
% Handle a message from another overworld process
channelize_msg(MsgType, Msg, Channels),
{noreply, PeerInfo};
handle_info({reconnect_session, SessionPID}, PeerInfo) ->
{noreply, PeerInfo#{session_pid := SessionPID}};
handle_info(Msg, PeerInfo) ->
logger:warning("Unimplemented response for message: ~p", [Msg]),
% Ignore all other messages
{noreply, PeerInfo}.
%%---------------------------------------------------------------------------
%% @doc Clean up the ENet handler by calling the session's disconnect callback
%% @end
%%---------------------------------------------------------------------------
-spec terminate(any(), peerinfo()) -> ok.
terminate(_Reason, #{session_pid := SessionPID}) ->
ok = ow_session_util:disconnect(SessionPID).
code_change(_OldVsn, PeerInfo, _Extra) -> {ok, PeerInfo}.
%%===========================================================================
%% Internal functions
%%===========================================================================
decode_and_reply(Msg, PeerInfo) ->
#{channels := Channels, session_pid := SessionPID} = PeerInfo,
%TODO: Fix me
case ow_protocol:route(Msg, SessionPID) of
ok ->
ok;
{MsgType, Msg1} ->
channelize_msg(MsgType, Msg1, Channels)
end.
channelize_msg(MsgType, Msg, Channels) ->
#{encoder := Encoder, channel := Channel, qos := QOS} = ow_protocol:rpc(
MsgType, client
),
#{interface := EncoderMod, app := App, lib := EncoderLib} = Encoder,
EncodedMsg = erlang:apply(EncoderMod, encode, [
Msg, MsgType, EncoderLib, App
]),
FlatMsg = iolist_to_binary(EncodedMsg),
ChannelPID = maps:get(Channel, Channels),
case QOS of
reliable ->
enet:send_reliable(ChannelPID, FlatMsg);
unreliable ->
enet:send_unreliable(ChannelPID, FlatMsg);
unsequenced ->
enet:send_unsequenced(ChannelPID, FlatMsg)
end.