game: Fix permanent ghosts. Supervisor cleans up after crashes and normal exits.

This commit is contained in:
Loïc Hoguin 2010-06-13 19:05:28 +02:00
parent 3d9c91852a
commit dd26d4f1e5
2 changed files with 145 additions and 127 deletions

View File

@ -60,6 +60,12 @@ users_select(GID) ->
Val
end.
%% @doc Select exactly one user by its Pid. Return an #users record.
users_select_by_pid(Pid) ->
[User] = do(qlc:q([X || X <- mnesia:table(users), X#users.pid =:= Pid])),
User.
%% @doc Select all users. Return a list of #users records.
users_select_all() ->

View File

@ -18,7 +18,7 @@
-module(egs_game).
-export([start/0]). % external
-export([listen/0, accept/1, process/2, char_select/3, area_load/6, loop/3, loop/4]). % internal
-export([supervisor_init/0, supervisor/0, listen/1, accept/2, process/2, char_select/3, area_load/6, loop/3, loop/4]). % internal
-include("include/records.hrl").
-include("include/network.hrl").
@ -27,25 +27,55 @@
%% @doc Start the game server.
start() ->
Pid = spawn_link(?MODULE, listen, []),
Pid.
SPid = spawn(?MODULE, supervisor_init, []),
LPid = spawn(?MODULE, listen, [SPid]),
[{listener, LPid}, {supervisor, SPid}].
%% @doc Game processes supervisor initialization.
supervisor_init() ->
process_flag(trap_exit, true),
supervisor().
%% @doc Game processes supervisor. Make sure everything is cleaned up when an unexpected error occurs.
supervisor() ->
receive
{link, Pid} ->
link(Pid);
{'EXIT', Pid, _} ->
supervisor_close(Pid);
_ ->
reload
after 5000 ->
reload
end,
?MODULE:supervisor().
%% @doc Close the connection for the given user and cleanup.
supervisor_close(Pid) ->
User = egs_db:users_select_by_pid(Pid),
log(User#users.gid, "quit"),
lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_unspawn, User} end, egs_db:users_select_others_in_area(User)),
egs_db:users_delete(User#users.gid).
%% @doc Listen for connections.
listen() ->
process_flag(trap_exit, true),
listen(SPid) ->
{ok, LSocket} = ssl:listen(?GAME_PORT, ?GAME_LISTEN_OPTIONS),
?MODULE:accept(LSocket).
?MODULE:accept(LSocket, SPid).
%% @doc Accept connections.
accept(LSocket) ->
accept(LSocket, SPid) ->
case ssl:transport_accept(LSocket, 5000) of
{ok, CSocket} ->
ssl:ssl_accept(CSocket),
try
send_0202(CSocket),
Pid = spawn_link(?MODULE, process, [CSocket, 0]),
Pid = spawn(?MODULE, process, [CSocket, 0]),
SPid ! {link, Pid},
ssl:controlling_process(CSocket, Pid)
catch
_:_ ->
@ -54,7 +84,7 @@ accept(LSocket) ->
_ ->
reload
end,
?MODULE:accept(LSocket).
?MODULE:accept(LSocket, SPid).
%% @doc Process the new connections.
%% Send an hello packet, authenticate the user and send him to character select.
@ -218,9 +248,9 @@ counter_load(CSocket, GID, QuestID, ZoneID, MapID, EntryID) ->
AreaName = "Mission counter",
QuestFile = "data/lobby/counter.quest.nbl",
ZoneFile = "data/lobby/counter.zone.nbl",
try
% broadcast unspawn to other people
lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_unspawn, User} end, egs_db:users_select_others_in_area(OldUser)),
% load counter
send_0c00(CSocket, GID, 16#7fffffff),
send_020e(CSocket, QuestFile),
send_0a05(CSocket, GID),
@ -240,11 +270,7 @@ counter_load(CSocket, GID, QuestID, ZoneID, MapID, EntryID) ->
send_0201(CSocket, GID, User, Char),
send_0a06(CSocket, GID),
send_0208(CSocket, GID),
send_0236(CSocket, GID)
catch
_:_ ->
close(CSocket, GID)
end.
send_0236(CSocket, GID).
%% @doc Load the given map as a standard lobby.
%% @todo Probably save the map type in the users table.
@ -285,7 +311,6 @@ area_load(CSocket, GID, AreaType, IsStart, OldUser, User, QuestFile, ZoneFile, A
true ->
ZoneChange = if OldUser#users.questid =:= User#users.questid, OldUser#users.zoneid =:= User#users.zoneid -> false; true -> true end
end,
try
% broadcast spawn and unspawn to other people
lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_unspawn, User} end, egs_db:users_select_others_in_area(OldUser)),
if AreaType =:= lobby ->
@ -360,11 +385,7 @@ area_load(CSocket, GID, AreaType, IsStart, OldUser, User, QuestFile, ZoneFile, A
end,
send_0233(CSocket, GID, egs_db:users_select_others_in_area(User)),
send_0208(CSocket, GID),
send_0236(CSocket, GID)
catch
_:_ ->
close(CSocket, GID)
end.
send_0236(CSocket, GID).
myroom_send_packet(CSocket, Filename) ->
{ok, << _:32, File/bits >>} = file:read_file(Filename),
@ -401,9 +422,9 @@ loop(CSocket, GID, Version, SoFar) ->
[dispatch(CSocket, GID, Version, Orig) || Orig <- Packets],
?MODULE:loop(CSocket, GID, Version, Rest);
{ssl_closed, _} ->
close(CSocket, GID);
exit(ssl_closed);
{ssl_error, _, _} ->
close(CSocket, GID);
exit(ssl_error);
_ ->
?MODULE:loop(CSocket, GID, Version, SoFar)
after 1000 ->
@ -411,15 +432,6 @@ loop(CSocket, GID, Version, SoFar) ->
?MODULE:loop(CSocket, GID, Version, SoFar)
end.
%% @doc Close the connection for the given user.
close(CSocket, GID) ->
log(GID, "quit"),
User = egs_db:users_select(GID),
lists:foreach(fun(Other) -> Other#users.pid ! {psu_player_unspawn, User} end, egs_db:users_select_others_in_area(User)),
egs_db:users_delete(GID),
ssl:close(CSocket).
%% @doc Dispatch the command to the right handler.
%% Command 0b05 uses the channel for something else. Conflicts could occur. Better to just ignore it anyway.