diff --git a/Emakefile b/Emakefile
index bbf9d01..0ea9b3b 100644
--- a/Emakefile
+++ b/Emakefile
@@ -17,6 +17,8 @@
% along with EGS. If not, see .
{'src/egs.erl', [{outdir, "ebin"}]}.
+{'src/egs_app.erl', [{outdir, "ebin"}]}.
+{'src/egs_sup.erl', [{outdir, "ebin"}]}.
{'src/egs_cron.erl', [{outdir, "ebin"}]}.
{'src/egs_db.erl', [{outdir, "ebin"}]}.
{'src/egs_game.erl', [{outdir, "ebin"}]}.
diff --git a/Makefile b/Makefile
index a56d557..6632380 100644
--- a/Makefile
+++ b/Makefile
@@ -31,10 +31,10 @@ clean:
rm -f erl_crash.dump
fclean: clean
- rm -rf Mnesia.console*
+ rm -rf Mnesia.egs*
run:
@echo "EGS is free software available under the GNU GPL version 3"
@echo "Copyright (C) 2010 Loic Hoguin"
@echo
- erl -ssl protocol_version '{sslv3}' -sname console -pa ebin -eval 'egs:start()'
+ erl -ssl protocol_version '{sslv3}' -sname egs -pa ebin -s egs
diff --git a/ebin/egs.app b/ebin/egs.app
new file mode 100644
index 0000000..dd65314
--- /dev/null
+++ b/ebin/egs.app
@@ -0,0 +1,30 @@
+%%-*- mode: erlang -*-
+{application, egs, [
+ {description, "EGS online action-RPG game server"},
+ {vsn, "0.1"},
+ {modules, [
+ egs,
+ egs_app,
+ egs_sup,
+ egs_cron,
+ egs_db,
+ egs_game,
+ egs_login,
+ egs_patch,
+ egs_proto,
+ psu_appearance,
+ psu_characters,
+ psu_missions,
+ psu_parser
+ ]},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib,
+ crypto,
+ ssl,
+ mnesia
+ ]},
+ {mod, {egs_app, []}},
+ {env, []}
+]}.
diff --git a/src/egs.erl b/src/egs.erl
index cf787a2..4834165 100644
--- a/src/egs.erl
+++ b/src/egs.erl
@@ -1,59 +1,73 @@
-% EGS: Erlang Game Server
-% Copyright (C) 2010 Loic Hoguin
-%
-% This file is part of EGS.
-%
-% EGS is free software: you can redistribute it and/or modify
-% it under the terms of the GNU General Public License as published by
-% the Free Software Foundation, either version 3 of the License, or
-% (at your option) any later version.
-%
-% EGS is distributed in the hope that it will be useful,
-% but WITHOUT ANY WARRANTY; without even the implied warranty of
-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-% GNU General Public License for more details.
-%
-% You should have received a copy of the GNU General Public License
-% along with EGS. If not, see .
+%% @author Loïc Hoguin
+%% @copyright 2010 Loïc Hoguin.
+%% @doc EGS startup code.
+%%
+%% This file is part of EGS.
+%%
+%% EGS is free software: you can redistribute it and/or modify
+%% it under the terms of the GNU General Public License as published by
+%% the Free Software Foundation, either version 3 of the License, or
+%% (at your option) any later version.
+%%
+%% EGS is distributed in the hope that it will be useful,
+%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%% GNU General Public License for more details.
+%%
+%% You should have received a copy of the GNU General Public License
+%% along with EGS. If not, see .
-module(egs).
-compile(export_all).
-include("include/records.hrl").
--define(MODULES, [egs, egs_cron, egs_db, egs_game, egs_login, egs_patch, egs_proto, psu_appearance, psu_characters, psu_missions, psu_parser]).
+-define(MODULES, [egs, egs_app, egs_sup, egs_cron, egs_db, egs_game, egs_login, egs_patch, egs_proto, psu_appearance, psu_characters, psu_missions, psu_parser]).
-%% @doc Start all the application servers. Return the PIDs of the listening processes.
+%% @spec ensure_started(App) -> ok
+%% @doc Make sure the given App is started.
+ensure_started(App) ->
+ case application:start(App) of
+ ok -> ok;
+ {error, {already_started, App}} -> ok
+ end.
+%% @spec start() -> ok
+%% @doc Start the EGS server.
start() ->
- application:start(crypto),
- application:start(ssl),
+ ensure_started(crypto),
+ ensure_started(ssl),
ssl:seed(crypto:rand_bytes(256)),
- egs_db:create(),
- Cron = egs_cron:start(),
- Game = egs_game:start(),
- Login = egs_login:start(),
- Patch = egs_patch:start(),
- [{patch, Patch}, {login, Login}, {game, Game}, {cron, Cron}].
+ ensure_started(mnesia),
+ application:start(egs).
+
+%% @spec stop() -> ok
+%% @doc Stop the EGS server.
+stop() ->
+ Res = application:stop(egs),
+ application:stop(mnesia),
+ application:stop(ssl),
+ application:stop(crypto),
+ Res.
%% @doc Reload all the modules.
-
+%% @todo Do it the OTP way.
reload() ->
[code:soft_purge(Module) || Module <- ?MODULES],
[code:load_file(Module) || Module <- ?MODULES].
%% @doc Send a global message.
-
+%% @todo Move that in a psu module.
global(Type, Message) ->
lists:foreach(fun(User) -> egs_proto:send_global(User#users.socket, Type, Message) end, egs_db:users_select_all()).
%% @doc Warp all players to a new map.
-
+%% @todo Move that in a psu module.
warp(QuestID, ZoneID, MapID, EntryID) ->
lists:foreach(fun(User) -> User#users.pid ! {psu_warp, QuestID, ZoneID, MapID, EntryID} end, egs_db:users_select_all()).
%% @doc Warp one player to a new map.
-
+%% @todo Move that in a psu module.
warp(GID, QuestID, ZoneID, MapID, EntryID) ->
User = egs_db:users_select(GID),
User#users.pid ! {psu_warp, QuestID, ZoneID, MapID, EntryID}.
diff --git a/src/egs_app.erl b/src/egs_app.erl
new file mode 100644
index 0000000..ae949e6
--- /dev/null
+++ b/src/egs_app.erl
@@ -0,0 +1,75 @@
+%% @author Loïc Hoguin
+%% @copyright 2010 Loïc Hoguin.
+%% @doc Callbacks for the egs application.
+%%
+%% This file is part of EGS.
+%%
+%% EGS is free software: you can redistribute it and/or modify
+%% it under the terms of the GNU General Public License as published by
+%% the Free Software Foundation, either version 3 of the License, or
+%% (at your option) any later version.
+%%
+%% EGS is distributed in the hope that it will be useful,
+%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%% GNU General Public License for more details.
+%%
+%% You should have received a copy of the GNU General Public License
+%% along with EGS. If not, see .
+
+-module(egs_app).
+-behaviour(application).
+-export([start/2, stop/1]).
+
+-include("include/records.hrl").
+
+%% @spec start(_Type, _StartArgs) -> ServerRet
+%% @doc application start callback for egs.
+start(_Type, _StartArgs) ->
+ case is_fresh_startup() of
+ true ->
+ db_init();
+ {exists, Tables} ->
+ ok = mnesia:wait_for_tables(Tables, 20000)
+ end,
+ egs_sup:start_link().
+
+%% @spec stop(_State) -> ServerRet
+%% @doc application stop callback for egs.
+stop(_State) ->
+ ok.
+
+%% @spec is_fresh_startup() -> true | false
+%% @doc Returns true if mnesia has not been initialized with
+%% the sherl schema.
+%% Thanks to Dale Harvey for this function posted to
+%% the erlang questions mailing list.
+is_fresh_startup() ->
+ Node = node(),
+ case mnesia:system_info(tables) of
+ [schema] -> true;
+ Tables ->
+ case mnesia:table_info(schema, cookie) of
+ {_, Node} -> {exists, Tables};
+ _ -> true
+ end
+ end.
+
+%% @todo doc
+%% @todo Rename ids to counters. Remove objects and use a FSM instead.
+db_init() ->
+ Nodes = [node()],
+ case mnesia:system_info(is_running) of
+ yes ->
+ error_logger:info_report("stopping mnesia"),
+ mnesia:stop();
+ _ -> pass
+ end,
+ mnesia:create_schema(Nodes),
+ error_logger:info_report("mnesia schema created"),
+ error_logger:info_report("starting mnesia"),
+ mnesia:start(),
+ mnesia:create_table(ids, [{attributes, record_info(fields, ids)}]),
+ mnesia:create_table(objects, [{attributes, record_info(fields, objects)}]),
+ mnesia:create_table(users, [{attributes, record_info(fields, users)}]),
+ error_logger:info_report("mnesia tables created").
diff --git a/src/egs_cron.erl b/src/egs_cron.erl
index 7e440a8..dbe526f 100644
--- a/src/egs_cron.erl
+++ b/src/egs_cron.erl
@@ -26,8 +26,8 @@
start() ->
KeepAlivePid = spawn_link(?MODULE, keepalive, []),
- CleanupPid = spawn_link(?MODULE, cleanup, []),
- [{keepalive, KeepAlivePid}, {cleanup, CleanupPid}].
+ spawn_link(?MODULE, cleanup, []),
+ {ok, KeepAlivePid}.
%% @doc Cleanup the users table of failures to log into the game server.
diff --git a/src/egs_db.erl b/src/egs_db.erl
index aad9d42..395c61e 100644
--- a/src/egs_db.erl
+++ b/src/egs_db.erl
@@ -31,15 +31,6 @@ do(Q) ->
{atomic, Val} = mnesia:transaction(F),
Val.
-%% @doc Create the database.
-
-create() ->
- mnesia:create_schema([node()]),
- mnesia:start(),
- mnesia:create_table(ids, [{attributes, record_info(fields, ids)}]),
- mnesia:create_table(objects, [{attributes, record_info(fields, objects)}]),
- mnesia:create_table(users, [{attributes, record_info(fields, users)}]).
-
%% @doc Retrieve the next unique ID.
next(Type) ->
diff --git a/src/egs_game.erl b/src/egs_game.erl
index 65ed5db..506be8a 100644
--- a/src/egs_game.erl
+++ b/src/egs_game.erl
@@ -27,9 +27,9 @@
%% @doc Start the game server.
start() ->
- SPid = spawn(?MODULE, supervisor_init, []),
- LPid = spawn(?MODULE, listen, [SPid]),
- [{listener, LPid}, {supervisor, SPid}].
+ SPid = spawn_link(?MODULE, supervisor_init, []),
+ LPid = spawn_link(?MODULE, listen, [SPid]),
+ {ok, LPid}.
%% @doc Game processes supervisor initialization.
diff --git a/src/egs_login.erl b/src/egs_login.erl
index 2fc8061..65e8a79 100644
--- a/src/egs_login.erl
+++ b/src/egs_login.erl
@@ -26,10 +26,10 @@
%% @doc Start the login server. Currently AOTI JP and US only.
start() ->
- JPPidE1 = spawn_link(?MODULE, listen, [?LOGIN_PORT_JP_ONE, 10000001]),
- JPPidE2 = spawn_link(?MODULE, listen, [?LOGIN_PORT_JP_TWO, 20000001]),
+ spawn_link(?MODULE, listen, [?LOGIN_PORT_JP_ONE, 10000001]),
+ spawn_link(?MODULE, listen, [?LOGIN_PORT_JP_TWO, 20000001]),
USPid = spawn_link(?MODULE, listen, [?LOGIN_PORT_US, 30000001]),
- [{jp_e1, JPPidE1}, {jp_e2, JPPidE2}, {us, USPid}].
+ {ok, USPid}.
%% @doc Listen for connections.
diff --git a/src/egs_patch.erl b/src/egs_patch.erl
index cbb0e12..1f20c92 100644
--- a/src/egs_patch.erl
+++ b/src/egs_patch.erl
@@ -25,9 +25,9 @@
%% @doc Start the patch server. Currently supports AOTI US and JP.
start() ->
- JPPid = spawn_link(?MODULE, listen, [?PATCH_PORT_JP]),
+ spawn_link(?MODULE, listen, [?PATCH_PORT_JP]),
USPid = spawn_link(?MODULE, listen, [?PATCH_PORT_US]),
- [{jp, JPPid}, {us, USPid}].
+ {ok, USPid}.
%% @doc Listen for connections.
diff --git a/src/egs_sup.erl b/src/egs_sup.erl
new file mode 100644
index 0000000..43b6fdb
--- /dev/null
+++ b/src/egs_sup.erl
@@ -0,0 +1,54 @@
+%% @author Loïc Hoguin
+%% @copyright 2010 Loïc Hoguin.
+%% @doc Supervisor for the egs application.
+%%
+%% This file is part of EGS.
+%%
+%% EGS is free software: you can redistribute it and/or modify
+%% it under the terms of the GNU General Public License as published by
+%% the Free Software Foundation, either version 3 of the License, or
+%% (at your option) any later version.
+%%
+%% EGS is distributed in the hope that it will be useful,
+%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%% GNU General Public License for more details.
+%%
+%% You should have received a copy of the GNU General Public License
+%% along with EGS. If not, see .
+
+-module(egs_sup).
+-behaviour(supervisor).
+-export([init/1]). %% Supervisor callbacks.
+-export([start_link/0, upgrade/0]). %% Other functions.
+
+%% @spec start_link() -> ServerRet
+%% @doc API for starting the supervisor.
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% @spec upgrade() -> ok
+%% @doc Add processes if necessary.
+upgrade() ->
+ {ok, {_, Specs}} = init([]),
+ Old = sets:from_list(
+ [Name || {Name, _, _, _} <- supervisor:which_children(?MODULE)]),
+ New = sets:from_list([Name || {Name, _, _, _, _, _} <- Specs]),
+ Kill = sets:subtract(Old, New),
+ sets:fold(fun (Id, ok) ->
+ supervisor:terminate_child(?MODULE, Id),
+ supervisor:delete_child(?MODULE, Id),
+ ok
+ end, ok, Kill),
+ [supervisor:start_child(?MODULE, Spec) || Spec <- Specs],
+ ok.
+
+%% @spec init([]) -> SupervisorTree
+%% @doc supervisor callback.
+init([]) ->
+ %% Start egs_cron, egs_game, egs_login, egs_patch. To be replaced by configurable modules.
+ Processes = [{egs_cron, {egs_cron, start, []}, permanent, 5000, worker, dynamic},
+ {egs_game, {egs_game, start, []}, permanent, 5000, worker, dynamic},
+ {egs_login, {egs_login, start, []}, permanent, 5000, worker, dynamic},
+ {egs_patch, {egs_patch, start, []}, permanent, 5000, worker, dynamic}],
+ {ok, {{one_for_one, 10, 10}, Processes}}.