egs_users: Remove mnesia from users handling. Convert to a gen_server.

Mnesia has now been fully removed from EGS. It will be replaced by Riak
at a later time, when we need to store permanent data to disk.
This commit is contained in:
Loïc Hoguin 2011-02-20 20:00:04 +01:00
parent 8eae404797
commit 0b8c4dbd85
8 changed files with 166 additions and 137 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
deps deps
Mnesia*

View File

@ -27,6 +27,3 @@ deps:
clean: clean:
@./rebar clean @./rebar clean
rm -f erl_crash.dump rm -f erl_crash.dump
fclean: clean
rm -rf Mnesia.egs*

View File

@ -8,8 +8,7 @@
kernel, kernel,
stdlib, stdlib,
crypto, crypto,
ssl, ssl
mnesia
]}, ]},
{mod, {egs_app, []}}, {mod, {egs_app, []}},
{env, []} {env, []}

View File

@ -37,14 +37,12 @@ start() ->
ensure_started(public_key), ensure_started(public_key),
ensure_started(ssl), ensure_started(ssl),
ssl:seed(crypto:rand_bytes(256)), ssl:seed(crypto:rand_bytes(256)),
ensure_started(mnesia),
application:start(egs). application:start(egs).
%% @spec stop() -> ok %% @spec stop() -> ok
%% @doc Stop the EGS server. %% @doc Stop the EGS server.
stop() -> stop() ->
Res = application:stop(egs), Res = application:stop(egs),
application:stop(mnesia),
application:stop(ssl), application:stop(ssl),
application:stop(public_key), application:stop(public_key),
application:stop(crypto), application:stop(crypto),

View File

@ -26,47 +26,9 @@
%% @spec start(_Type, _StartArgs) -> ServerRet %% @spec start(_Type, _StartArgs) -> ServerRet
%% @doc application start callback for egs. %% @doc application start callback for egs.
start(_Type, _StartArgs) -> 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(). egs_sup:start_link().
%% @spec stop(_State) -> ServerRet %% @spec stop(_State) -> ServerRet
%% @doc application stop callback for egs. %% @doc application stop callback for egs.
stop(_State) -> stop(_State) ->
ok. ok.
%% @spec is_fresh_startup() -> true | false
%% @doc Returns true if mnesia has not been initialized with the egs 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.
%% @spec db_init() -> ok
%% @doc Initialize the database.
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(users, [{attributes, record_info(fields, users)}]),
error_logger:info_report("mnesia tables created"),
ok.

View File

@ -94,7 +94,7 @@ event({char_select_enter, Slot, _BackToPreviousField}, State=#state{gid=GID}) ->
%% Internal. %% Internal.
%% @doc Load the given character's data. %% @doc Load the given character's data.
%% @todo This function is temporary until we get permanent mnesia accounts. %% @todo This function is temporary until we get permanent accounts.
data_load(Folder, Number) -> data_load(Folder, Number) ->
Filename = io_lib:format("save/~s/~b-character", [Folder, Number]), Filename = io_lib:format("save/~s/~b-character", [Folder, Number]),
case file:read_file(Filename) of case file:read_file(Filename) of

View File

@ -36,6 +36,7 @@ init([]) ->
{egs_quests_sup, {egs_quests_sup, start_link, []}, permanent, 5000, supervisor, [egs_quests_sup]}, {egs_quests_sup, {egs_quests_sup, start_link, []}, permanent, 5000, supervisor, [egs_quests_sup]},
{egs_zones_sup, {egs_zones_sup, start_link, []}, permanent, 5000, supervisor, [egs_zones_sup]}, {egs_zones_sup, {egs_zones_sup, start_link, []}, permanent, 5000, supervisor, [egs_zones_sup]},
{egs_accounts, {egs_accounts, start_link, []}, permanent, 5000, worker, dynamic}, {egs_accounts, {egs_accounts, start_link, []}, permanent, 5000, worker, dynamic},
{egs_users, {egs_users, start_link, []}, permanent, 5000, worker, dynamic},
{egs_seasons, {egs_seasons, start_link, []}, permanent, 5000, worker, dynamic}, {egs_seasons, {egs_seasons, start_link, []}, permanent, 5000, worker, dynamic},
{egs_counters_db, {egs_counters_db, start_link, []}, permanent, 5000, worker, dynamic}, {egs_counters_db, {egs_counters_db, start_link, []}, permanent, 5000, worker, dynamic},
{egs_items_db, {egs_items_db, start_link, []}, permanent, 5000, worker, dynamic}, {egs_items_db, {egs_items_db, start_link, []}, permanent, 5000, worker, dynamic},

View File

@ -1,5 +1,5 @@
%% @author Loïc Hoguin <essen@dev-extend.eu> %% @author Loïc Hoguin <essen@dev-extend.eu>
%% @copyright 2010 Loïc Hoguin. %% @copyright 2010-2011 Loïc Hoguin.
%% @doc Users handling. %% @doc Users handling.
%% %%
%% This file is part of EGS. %% This file is part of EGS.
@ -18,70 +18,130 @@
%% along with EGS. If not, see <http://www.gnu.org/licenses/>. %% along with EGS. If not, see <http://www.gnu.org/licenses/>.
-module(egs_users). -module(egs_users).
-export([read/1, select/1, write/1, delete/1, item_nth/2, item_add/3, item_qty_add/3, shop_enter/2, shop_leave/1, shop_get/1, money_add/2, broadcast_spawn/2, broadcast_unspawn/2, set_zone/3]). -behaviour(gen_server).
-define(TABLE, users). -export([start_link/0, stop/0, broadcast_spawn/2, broadcast_unspawn/2, set_zone/3]). %% API.
-export([read/1, select/1, write/1, delete/1, item_nth/2, item_add/3, item_qty_add/3,
shop_enter/2, shop_leave/1, shop_get/1, money_add/2]). %% Deprecated API.
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% gen_server.
-define(SERVER, ?MODULE).
-include("include/records.hrl"). -include("include/records.hrl").
-include_lib("stdlib/include/qlc.hrl").
%% @spec do(Q) -> Record %% @todo We have a conflict with the global state record. Solve it then rename this to state.
%% @doc Perform a mnesia transaction using a QLC query. -record(stateu, {
do(Q) -> users = [] :: list({GID::integer(), #users{}})
F = fun() -> qlc:e(Q) end, }).
{atomic, Val} = mnesia:transaction(F),
Val.
%% -- %% API.
-spec start_link() -> {ok, Pid::pid()}.
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec stop() -> stopped.
stop() ->
gen_server:call(?SERVER, stop).
broadcast_spawn(GID, PlayersGID) ->
gen_server:cast(?SERVER, {broadcast_spawn, GID, PlayersGID}).
broadcast_unspawn(GID, PlayersGID) ->
gen_server:cast(?SERVER, {broadcast_unspawn, GID, PlayersGID}).
set_zone(GID, ZonePid, LID) ->
gen_server:call(?SERVER, {set_zone, GID, ZonePid, LID}).
%% Deprecated API.
%% @spec read({pid, Pid}) -> {ok, User} | {error, badarg} %% @spec read({pid, Pid}) -> {ok, User} | {error, badarg}
%% @spec read(ID) -> {ok, User} | {error, badarg} %% @spec read(ID) -> {ok, User} | {error, badarg}
read({pid, Pid}) ->
List = do(qlc:q([X || X <- mnesia:table(?TABLE), X#?TABLE.pid =:= Pid])),
case List of
[] -> {error, badarg};
[User] -> {ok, User}
end;
read(ID) -> read(ID) ->
case mnesia:transaction(fun() -> mnesia:read({?TABLE, ID}) end) of gen_server:call(?SERVER, {read, ID}).
{atomic, []} -> {error, badarg};
{atomic, [Val]} -> {ok, Val}
end.
%% @spec select(all) -> {ok, List} %% @spec select(all) -> {ok, List}
%% @spec select({neighbors, User}) -> {ok, List} %% @spec select({neighbors, User}) -> {ok, List}
%% @todo state = undefined | {wait_for_authentication, Key} | authenticated | online %% @todo state = undefined | {wait_for_authentication, Key} | authenticated | online
select(all) -> select(What) ->
List = do(qlc:q([X || X <- mnesia:table(?TABLE), gen_server:call(?SERVER, {select, What}).
X#?TABLE.pid /= undefined
])),
{ok, List};
select({neighbors, User}) ->
List = do(qlc:q([X || X <- mnesia:table(?TABLE),
X#?TABLE.gid /= User#?TABLE.gid,
X#?TABLE.pid /= undefined,
X#?TABLE.instancepid =:= User#?TABLE.instancepid,
X#?TABLE.area =:= User#?TABLE.area
])),
{ok, List};
select(UsersGID) ->
L = [read(GID) || GID <- UsersGID],
[User || {ok, User} <- L].
%% @spec write(User) -> ok %% @spec write(User) -> ok
write(User) -> write(User) ->
mnesia:transaction(fun() -> mnesia:write(User) end). gen_server:call(?SERVER, {write, User}).
%% @spec delete(ID) -> ok %% @spec delete(GID) -> ok
delete(ID) -> delete(GID) ->
mnesia:transaction(fun() -> mnesia:delete({?TABLE, ID}) end). gen_server:call(?SERVER, {delete, GID}).
item_nth(GID, ItemIndex) -> item_nth(GID, ItemIndex) ->
{atomic, [User]} = mnesia:transaction(fun() -> mnesia:read({?TABLE, GID}) end), gen_server:call(?SERVER, {item_nth, GID, ItemIndex}).
lists:nth(ItemIndex + 1, (User#users.character)#characters.inventory).
item_add(GID, ItemID, Variables) -> item_add(GID, ItemID, Variables) ->
{atomic, [User]} = mnesia:transaction(fun() -> mnesia:read({?TABLE, GID}) end), gen_server:call(?SERVER, {item_add, GID, ItemID, Variables}).
%% @todo Consumable items.
item_qty_add(GID, ItemIndex, QuantityDiff) ->
gen_server:call(?SERVER, {item_qty_add, GID, ItemIndex, QuantityDiff}).
shop_enter(GID, ShopID) ->
gen_server:call(?SERVER, {shop_enter, GID, ShopID}).
shop_leave(GID) ->
gen_server:call(?SERVER, {shop_leave, GID}).
shop_get(GID) ->
gen_server:call(?SERVER, {shop_get, GID}).
money_add(GID, MoneyDiff) ->
gen_server:call(?SERVER, {money_add, GID, MoneyDiff}).
%% gen_server.
init([]) ->
{ok, #stateu{}}.
handle_call({set_zone, GID, ZonePid, LID}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
Users2 = lists:delete({GID, User}, State#stateu.users),
{reply, ok, State#stateu{users=[{GID, User#users{zonepid=ZonePid, lid=LID}}|Users2]}};
handle_call({read, {pid, Pid}}, _From, State) ->
[User] = [User || {_GID, User} <- State#stateu.users, User#users.pid =:= Pid],
{reply, {ok, User}, State};
handle_call({read, GID}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
{reply, {ok, User}, State};
handle_call({select, all}, _From, State) ->
Users = [User || {_GID, User} <- State#stateu.users],
{reply, {ok, Users}, State};
handle_call({select, {neighbors, #users{gid=FromGID, uni=FromUni, area=FromArea}}}, _From, State) ->
Users = [User || {GID, User = #users{uni=Uni, area=Area}}
<- State#stateu.users, GID =/= FromGID, Uni =:= FromUni, Area =:= FromArea],
{reply, {ok, Users}, State};
handle_call({select, UsersGID}, _From, State) ->
Users = [begin
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
User
end || GID <- UsersGID],
{reply, Users, State};
handle_call({write, User}, _From, State) ->
Users2 = lists:keydelete(User#users.gid, 1, State#stateu.users),
{reply, ok, State#stateu{users=[{User#users.gid, User}|Users2]}};
handle_call({delete, GID}, _From, State) ->
Users2 = lists:keydelete(GID, 1, State#stateu.users),
{reply, ok, State#stateu{users=Users2}};
handle_call({item_nth, GID, ItemIndex}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
Item = lists:nth(ItemIndex + 1, (User#users.character)#characters.inventory),
{reply, Item, State};
handle_call({item_add, GID, ItemID, Variables}, _From, State) ->
{GID, User} = lists:keyfind(GID, 1, State#stateu.users),
Character = User#users.character, Character = User#users.character,
Inventory = Character#characters.inventory, Inventory = Character#characters.inventory,
Inventory2 = case Variables of Inventory2 = case Variables of
@ -112,14 +172,15 @@ item_add(GID, ItemID, Variables) ->
end end
end, end,
Character2 = Character#characters{inventory=Inventory2}, Character2 = Character#characters{inventory=Inventory2},
mnesia:transaction(fun() -> mnesia:write(User#users{character=Character2}) end), Users2 = lists:keydelete(User#users.gid, 1, State#stateu.users),
if New =:= false -> 16#ffffffff; State2 = State#stateu{users=[{GID, User#users{character=Character2}}|Users2]},
true -> length(Inventory2) case New of
end. false -> {reply, 16#ffffffff, State2};
true -> {reply, length(Inventory2), State2}
end;
%% @todo Consumable items. handle_call({item_qty_add, GID, ItemIndex, QuantityDiff}, _From, State) ->
item_qty_add(GID, ItemIndex, QuantityDiff) -> {GID, User} = lists:keyfind(GID, 1, State#stateu.users),
{atomic, [User]} = mnesia:transaction(fun() -> mnesia:read({?TABLE, GID}) end),
Character = User#users.character, Character = User#users.character,
Inventory = Character#characters.inventory, Inventory = Character#characters.inventory,
{ItemID, Variables} = lists:nth(ItemIndex + 1, Inventory), {ItemID, Variables} = lists:nth(ItemIndex + 1, Inventory),
@ -135,49 +196,61 @@ item_qty_add(GID, ItemIndex, QuantityDiff) ->
end end
end, end,
Character2 = Character#characters{inventory=Inventory2}, Character2 = Character#characters{inventory=Inventory2},
mnesia:transaction(fun() -> mnesia:write(User#users{character=Character2}) end). Users2 = lists:keydelete(User#users.gid, 1, State#stateu.users),
{reply, ok, State#stateu{users=[{GID, User#users{character=Character2}}|Users2]}};
shop_enter(GID, ShopID) -> handle_call({shop_enter, GID, ShopID}, _From, State) ->
mnesia:transaction(fun() -> {GID, User} = lists:keyfind(GID, 1, State#stateu.users),
[User] = mnesia:wread({?TABLE, GID}), Users2 = lists:delete({GID, User}, State#stateu.users),
mnesia:write(User#users{shopid=ShopID}) {reply, ok, State#stateu{users=[{GID, User#users{shopid=ShopID}}|Users2]}};
end).
shop_leave(GID) -> handle_call({shop_leave, GID}, _From, State) ->
mnesia:transaction(fun() -> {GID, User} = lists:keyfind(GID, 1, State#stateu.users),
[User] = mnesia:wread({?TABLE, GID}), Users2 = lists:delete({GID, User}, State#stateu.users),
mnesia:write(User#users{shopid=undefined}) {reply, ok, State#stateu{users=[{GID, User#users{shopid=undefined}}|Users2]}};
end).
shop_get(GID) -> handle_call({shop_get, GID}, _From, State) ->
{atomic, [User]} = mnesia:transaction(fun() -> mnesia:read({?TABLE, GID}) end), {GID, User} = lists:keyfind(GID, 1, State#stateu.users),
User#users.shopid. {reply, User#users.shopid, State};
money_add(GID, MoneyDiff) -> handle_call({money_add, GID, MoneyDiff}, _From, State) ->
mnesia:transaction(fun() -> {GID, User} = lists:keyfind(GID, 1, State#stateu.users),
[User] = mnesia:wread({?TABLE, GID}), Character = User#users.character,
Character = User#users.character, Money = Character#characters.money + MoneyDiff,
Money = Character#characters.money + MoneyDiff, if Money >= 0 ->
if Money >= 0 -> Character2 = Character#characters{money=Money},
Character2 = Character#characters{money=Money}, Users2 = lists:delete({GID, User}, State#stateu.users),
mnesia:write(User#users{character=Character2}) {reply, ok, [{GID, User#users{character=Character2}}|Users2]}
end end;
end).
broadcast_spawn(GID, PlayersGID) -> handle_call(stop, _From, State) ->
{ok, OrigUser} = read(GID), {stop, normal, stopped, State};
lists:foreach(fun(DestGID) ->
{ok, DestUser} = read(DestGID),
DestUser#users.pid ! {egs, player_spawn, OrigUser}
end, PlayersGID).
broadcast_unspawn(GID, PlayersGID) -> handle_call(_Request, _From, State) ->
{ok, OrigUser} = read(GID), {reply, ignored, State}.
lists:foreach(fun(DestGID) ->
{ok, DestUser} = read(DestGID),
DestUser#users.pid ! {egs, player_unspawn, OrigUser}
end, PlayersGID).
set_zone(GID, ZonePid, LID) -> handle_cast({broadcast_spawn, GID, PlayersGID}, State) ->
{ok, User} = read(GID), {GID, OrigUser} = lists:keyfind(GID, 1, State#stateu.users),
write(User#users{zonepid=ZonePid, lid=LID}). [begin {_, #users{pid=DestPid}} = lists:keyfind(DestGID, 1, State#stateu.users),
DestPid ! {egs, player_spawn, OrigUser}
end || DestGID <- PlayersGID],
{noreply, State};
handle_cast({broadcast_unspawn, GID, PlayersGID}, State) ->
{GID, OrigUser} = lists:keyfind(GID, 1, State#stateu.users),
[begin {_, #users{pid=DestPid}} = lists:keyfind(DestGID, 1, State#stateu.users),
DestPid ! {egs, player_unspawn, OrigUser}
end || DestGID <- PlayersGID],
{noreply, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.