added stash
This commit is contained in:
parent
0718031e22
commit
da67b0a977
5 changed files with 192 additions and 73 deletions
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
@implementation GGSNetwork
|
@implementation GGSNetwork
|
||||||
|
|
||||||
#define GGS_HOST @"jeena.net"
|
#define GGS_HOST @"localhost"
|
||||||
#define GGS_PORT 9000
|
#define GGS_PORT 9000
|
||||||
#define NO_TIMEOUT -1
|
#define NO_TIMEOUT -1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,8 @@ handle_info({tcp_closed, Socket}, State) ->
|
||||||
%% and when it does, we accept the connection.
|
%% and when it does, we accept the connection.
|
||||||
handle_info(timeout, LSock) ->
|
handle_info(timeout, LSock) ->
|
||||||
{ok, Sock} = gen_tcp:accept(LSock),
|
{ok, Sock} = gen_tcp:accept(LSock),
|
||||||
spawn(ggs_player, start_link, [Sock]),
|
ggs_player:start(Sock),
|
||||||
|
erlang:display("handle_info"),
|
||||||
{noreply, LSock, 0}.
|
{noreply, LSock, 0}.
|
||||||
|
|
||||||
terminate(normal, _State) ->
|
terminate(normal, _State) ->
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,66 @@
|
||||||
-module(ggs_player).
|
|
||||||
-export([start_link/1, notify/3, get_token/1, stop/2]).
|
|
||||||
-record(pl_state,
|
|
||||||
{token, % Player's token
|
|
||||||
socket, % Player's socket
|
|
||||||
table}). % Player's table
|
|
||||||
|
|
||||||
%% @doc This module handles communication between a player and GGS. This module is
|
%% @doc This module handles communication between a player and GGS. This module is
|
||||||
%%responsible for:
|
%% responsible for:
|
||||||
%% * The storage of the player socket, player token and a table token.
|
%% * The storage of the player socket, player token and a table token.
|
||||||
%% * Ability to fetch a player token.
|
%% * Ability to fetch a player token.
|
||||||
%% * Forwarding messages from players to the game
|
%% * Forwarding messages from players to the game
|
||||||
%% * Remove a player from GGS
|
%% * Remove a player from GGS
|
||||||
|
|
||||||
|
-module(ggs_player).
|
||||||
|
-behaviour(gen_server).
|
||||||
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
-export([start/1, notify/3, notify_game/2, get_token/1, stop/1]).
|
||||||
|
|
||||||
|
-vsn(1.0).
|
||||||
|
|
||||||
|
-record(state, {
|
||||||
|
token,
|
||||||
|
socket,
|
||||||
|
table,
|
||||||
|
protocol }).
|
||||||
|
|
||||||
%% @doc Spawns a process representing the player in GGS. Takes the player socket as
|
%% @doc Spawns a process representing the player in GGS. Takes the player socket as
|
||||||
%% an argument for storage and later usage. Creates a unique player token
|
%% an argument for storage and later usage. Creates a unique player token
|
||||||
%% identifying the player.
|
%% identifying the player.
|
||||||
%% @spec start_link(Socket::socket()) -> {ok, Pid} | {error, Reason}
|
%% @spec start_link(Socket::socket()) -> {ok, Pid} | {error, Reason}
|
||||||
start_link(Socket) ->
|
start(Socket) ->
|
||||||
% The socket is in 'active' mode, and that means we are pushed any data
|
erlang:display("start_link"),
|
||||||
% that arrives on it, we do not need to recv() manually. Since the socket
|
gen_server:start(?MODULE, [Socket], []).
|
||||||
% was opened in our parent process, we need to change the owner of it to
|
|
||||||
% us, otherwise these messages end up in our parent.
|
init([Socket]) ->
|
||||||
erlang:port_connect(Socket, self()),
|
{ok, Protocol} = ggs_protocol:start_link(Socket, self()),
|
||||||
{ok, Token} = ggs_coordinator:join_lobby(),
|
{ok, Token} = ggs_coordinator:join_lobby(),
|
||||||
TableStatus = ggs_coordinator:join_table("1337"),
|
|
||||||
case TableStatus of
|
case ggs_coordinator:join_table("1337") of
|
||||||
{ok, Table} ->
|
{ok, T} ->
|
||||||
notify(self(), self(), {"hello", Token}),
|
Table = T;
|
||||||
loop(#pl_state{socket = Socket, token = Token, table = Table});
|
|
||||||
{error, no_such_table} ->
|
{error, no_such_table} ->
|
||||||
ggs_coordinator:create_table({force, "1337"}),
|
ggs_coordinator:create_table({force, "1337"}),
|
||||||
{ok, Table} = ggs_coordinator:join_table("1337"),
|
{ok, T} = ggs_coordinator:join_table("1337"),
|
||||||
notify(self(), self(), {"hello", Token}),
|
Table = T
|
||||||
loop(#pl_state{socket = Socket, token = Token, table = Table})
|
end,
|
||||||
end.
|
|
||||||
|
State = #state{
|
||||||
|
token = Token,
|
||||||
|
socket = Socket,
|
||||||
|
table = Table,
|
||||||
|
protocol = Protocol
|
||||||
|
},
|
||||||
|
erlang:display(State),
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
%% @doc Handles incoming messages from the GGS and forwards them through the player
|
%% @doc Handles incoming messages from the GGS and forwards them through the player
|
||||||
%% socket to the player.
|
%% socket to the player.
|
||||||
%% @spec notify(Player::Pid(), From::Pid(),
|
%% @spec notify(Player::Pid(), From::Pid(),
|
||||||
%% {Command::String(), Message::string()}) -> ok
|
%% {Command::String(), Message::string()}) -> ok
|
||||||
notify(Player, From, Message) ->
|
notify(Player, _From, Message) ->
|
||||||
{Cmd, Data} = Message,
|
gen_server:cast(Player, {notify, Message}).
|
||||||
Parsed = ggs_protocol:create_message(Cmd, "text","text", Data),
|
|
||||||
Player ! {notify, From, Parsed}.
|
%% @doc Handles incomming messages form a client and forwards them
|
||||||
|
%% through to the game_vm
|
||||||
|
notify_game(Player, Message) ->
|
||||||
|
gen_server:cast(Player, Message).
|
||||||
|
|
||||||
%% @doc Get the player token uniquely representing the player.
|
%% @doc Get the player token uniquely representing the player.
|
||||||
%% @spec get_token() -> string()
|
%% @spec get_token() -> string()
|
||||||
|
|
@ -53,32 +71,33 @@ get_token(_Player) ->
|
||||||
%% together with the table token. It should also close the player socket and the
|
%% together with the table token. It should also close the player socket and the
|
||||||
%% process should return in the end.
|
%% process should return in the end.
|
||||||
%% @spec stop(Table::pid()) -> Reason::string()
|
%% @spec stop(Table::pid()) -> Reason::string()
|
||||||
stop(_Player,_Table) ->
|
stop(Player) ->
|
||||||
ggs_logger:not_implemented().
|
gen_server:cast(Player, stop).
|
||||||
|
|
||||||
%% Internals
|
%% Internals
|
||||||
|
|
||||||
loop(#pl_state{token = _Token, socket = Socket, table = Table} = State) ->
|
handle_call({notify, Message}, _From, #state { protocol = Protocol } = State) ->
|
||||||
receive
|
ggs_protocol:send_command(Protocol, Message),
|
||||||
{tcp, Socket, Data} -> % Just echo for now..
|
{noreplay, State};
|
||||||
Parsed = ggs_protocol:parse(Data),
|
|
||||||
self() ! Parsed,
|
handle_call({game_cmd, Command, _Headers, Data}, _From, #state { table = Table } = State) ->
|
||||||
loop(State);
|
ggs_table:notify(Table, self(), {game, Command, Data}),
|
||||||
{notify, _From, Message} ->
|
{noreplay, State};
|
||||||
gen_tcp:send(Socket, Message),
|
|
||||||
loop(State);
|
handle_call({srv_cmd, "define", _Headers, Data}, _From, #state { table = Table } = State) ->
|
||||||
% Below are messages generated by the parser
|
ggs_table:notify(Table, self(), {server, define, Data}),
|
||||||
{game_cmd,Cmd, _Headers, Data} ->
|
{noreplay, State};
|
||||||
ggs_table:notify(Table, self(), {game, Cmd, Data}),
|
|
||||||
loop(State);
|
handle_call(_Request, _From, St) -> {stop, unimplemented, St}.
|
||||||
{srv_cmd,"define", _Headers, Data} ->
|
handle_cast(_Request, St) -> {stop, unimplemented, St}.
|
||||||
ggs_table:notify(Table, self(), {server, define, Data}),
|
|
||||||
loop(State);
|
handle_info(_Info, St) -> {stop, unimplemented, St}.
|
||||||
{tcp_closed, _Socket} ->
|
|
||||||
io:format("Client disconnected, but THIS IS NOT SUPPORTED YET!~n"),
|
terminate(_Reason, State) ->
|
||||||
loop(State);
|
ggs_protocol:stop(State#state.protocol),
|
||||||
Other ->
|
ggs_table:remove_player(State#state.table, self()),
|
||||||
io:format("Got UNKNOWN message: "),
|
% ggs_coordinator:remove_player(self(), self()), % not implemented yet
|
||||||
erlang:display(Other),
|
% TODO: release Socket
|
||||||
io:format("~n")
|
ok.
|
||||||
end.
|
|
||||||
|
code_change(_OldVsn, St, _Extra) -> {ok, St}.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,110 @@
|
||||||
|
%%% @doc This module handles TCP incomming and outcommint.
|
||||||
|
|
||||||
-module(ggs_protocol).
|
-module(ggs_protocol).
|
||||||
-export([parse/1, getToken/1, create_message/4]).
|
-export([start_link/2,stop/1]).
|
||||||
|
-behaviour(gen_server).
|
||||||
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
terminate/2, code_change/3]).
|
||||||
|
%% Old
|
||||||
|
-export([parse/1, getToken/1, create_message/4, send_command/2]).
|
||||||
|
|
||||||
|
-vsn(1.0).
|
||||||
|
|
||||||
|
-record(state, {
|
||||||
|
player,
|
||||||
|
socket,
|
||||||
|
header_string,
|
||||||
|
header_list,
|
||||||
|
body,
|
||||||
|
content_length}).
|
||||||
|
|
||||||
|
start_link(Socket, Player) ->
|
||||||
|
gen_server:start_link(?MODULE, {Socket, Player}, []).
|
||||||
|
|
||||||
|
stop(Protocol) ->
|
||||||
|
gen_server:cast(Protocol, stop).
|
||||||
|
|
||||||
|
send_command(Protocol, {Command, Data}) ->
|
||||||
|
gen_server:cast(Protocol, {send, Command, Data}).
|
||||||
|
|
||||||
|
init({Socket, Player}) ->
|
||||||
|
erlang:port_connect(Socket, self()),
|
||||||
|
State = #state{
|
||||||
|
socket = Socket,
|
||||||
|
player = Player,
|
||||||
|
header_list = [],
|
||||||
|
header_string = "",
|
||||||
|
body = "",
|
||||||
|
content_length = -1
|
||||||
|
},
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
handle_cast({tcp, _Socket, Data}, State) ->
|
||||||
|
case State#state.content_length of
|
||||||
|
-1 -> % its a header
|
||||||
|
TmpHeader = State#state.header_string ++ Data,
|
||||||
|
case string:str(TmpHeader, "\n\n") of
|
||||||
|
0 -> % still in header
|
||||||
|
{reply, ok, State # state {header_string = TmpHeader}};
|
||||||
|
_ -> % we left the header
|
||||||
|
{Header, Body} = parse(TmpHeader),
|
||||||
|
{_, ContentLengthString} = lists:keyfind(content_len, 1, Header), % find Content-Length
|
||||||
|
{ContentLength, []} = string:to_integer(ContentLengthString),
|
||||||
|
{reply, ok, State#state{
|
||||||
|
header_list = Header,
|
||||||
|
header_string = "",
|
||||||
|
body = Body,
|
||||||
|
content_length = ContentLength}}
|
||||||
|
end;
|
||||||
|
Length -> % its a body
|
||||||
|
LBody = string:len(State#state.body),
|
||||||
|
LData = string:len(Data),
|
||||||
|
NewLength = LBody + LData,
|
||||||
|
if
|
||||||
|
NewLength < Length -> % not enough data
|
||||||
|
Body = State#state.body ++ Data,
|
||||||
|
{reply, ok, State#state {body = Body}};
|
||||||
|
NewLength > Length -> % too much data
|
||||||
|
EndOfMessagePos = LBody + LData - Length,
|
||||||
|
Body = State#state.body ++ string:substr(Data, 0, EndOfMessagePos),
|
||||||
|
NextHeader = string:substr(Data, EndOfMessagePos, LData),
|
||||||
|
Message = prettify(State#state.header_list, Body),
|
||||||
|
gen_player:notify_game(State#state.player, Message),
|
||||||
|
{reply, ok, State#state {
|
||||||
|
header_string = NextHeader,
|
||||||
|
header_list = [],
|
||||||
|
body = "",
|
||||||
|
content_length = -1}};
|
||||||
|
NewLength == Length -> % end of message
|
||||||
|
Message = prettify(State#state.header_list, State#state.body ++ Data),
|
||||||
|
gen_player:notify_game(State#state.player, Message),
|
||||||
|
{reply, ok, State#state {
|
||||||
|
header_string = "",
|
||||||
|
header_list = [],
|
||||||
|
body = "",
|
||||||
|
content_length = -1}}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
|
||||||
|
handle_cast({send, Command, Data}, State) ->
|
||||||
|
Message = create_message(Command, "text", "text", Data),
|
||||||
|
gen_tcp:send(State#state.socket, Message),
|
||||||
|
{noreply, State};
|
||||||
|
|
||||||
|
handle_cast(_Request, St) -> {stop, unimplemented, St}.
|
||||||
|
handle_call(_Request, _From, St) -> {stop, unimplemented, St}.
|
||||||
|
|
||||||
|
handle_info(_Info, St) -> {stop, unimplemented, St}.
|
||||||
|
|
||||||
|
|
||||||
|
terminate(_Reason, _St) -> ok.
|
||||||
|
code_change(_OldVsn, St, _Extra) -> {ok, St}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%% API Functions
|
%% API Functions
|
||||||
parse(Data) ->
|
parse(Data) ->
|
||||||
Parsed = do_parse(Data, []),
|
do_parse(Data, []).
|
||||||
prettify(Parsed).
|
|
||||||
|
|
||||||
getToken(Parsed) ->
|
getToken(Parsed) ->
|
||||||
case lists:keyfind(token, 1, Parsed) of
|
case lists:keyfind(token, 1, Parsed) of
|
||||||
|
|
@ -14,6 +114,8 @@ getToken(Parsed) ->
|
||||||
false
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
create_message(Cmd, Enc, Acc, Data) ->
|
create_message(Cmd, Enc, Acc, Data) ->
|
||||||
Length = integer_to_list(string:len(Data)),
|
Length = integer_to_list(string:len(Data)),
|
||||||
Msg = "Client-Command: " ++ Cmd ++ "\n" ++
|
Msg = "Client-Command: " ++ Cmd ++ "\n" ++
|
||||||
|
|
@ -26,18 +128,15 @@ create_message(Cmd, Enc, Acc, Data) ->
|
||||||
Msg.
|
Msg.
|
||||||
|
|
||||||
%% Internal helpers
|
%% Internal helpers
|
||||||
do_parse(Data, ParsedMessage) ->
|
do_parse(Data, Headers) ->
|
||||||
NewLinePos = string:chr(Data, $\n),
|
NewLinePos = string:chr(Data, $\n),
|
||||||
Line = string:substr(Data, 1, NewLinePos-1),
|
Line = string:substr(Data, 1, NewLinePos-1),
|
||||||
Tokens = re:split(Line, ": ", [{return, list}]),
|
Tokens = re:split(Line, ": ", [{return, list}]),
|
||||||
case handle(Tokens) of
|
case handle(Tokens) of
|
||||||
{Command, more} ->
|
{Command, more} ->
|
||||||
do_parse(string:substr(Data, NewLinePos+1), ParsedMessage ++ [Command]);
|
do_parse(string:substr(Data, NewLinePos+1), Headers ++ [Command]);
|
||||||
{separator, data_next} ->
|
{separator, data_next} ->
|
||||||
{_, Value} = lists:keyfind(content_len, 1, ParsedMessage),
|
{Headers, Data}
|
||||||
{ContentLength, []} = string:to_integer(Value),
|
|
||||||
{data, ArgumentData} = handle_data(string:substr(Data, NewLinePos+1), ContentLength),
|
|
||||||
{ParsedMessage, ArgumentData}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle([[]]) ->
|
handle([[]]) ->
|
||||||
|
|
@ -53,11 +152,11 @@ handle(["Token", Param]) ->
|
||||||
handle(["Content-Type", Param]) ->
|
handle(["Content-Type", Param]) ->
|
||||||
{{content_type, Param}, more}.
|
{{content_type, Param}, more}.
|
||||||
|
|
||||||
handle_data(Data, Length) ->
|
%handle_data(Data, Length) ->
|
||||||
{data, string:substr(Data,1,Length)}.
|
% {data, string:substr(Data,1,Length)}.
|
||||||
|
|
||||||
|
|
||||||
prettify({Args, Data}) ->
|
prettify(Args, Data) ->
|
||||||
case lists:keyfind(srv_cmd, 1, Args) of
|
case lists:keyfind(srv_cmd, 1, Args) of
|
||||||
{_, Value} ->
|
{_, Value} ->
|
||||||
{srv_cmd, Value, Args, Data};
|
{srv_cmd, Value, Args, Data};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
%% @doc start_link should always return ok for any valid socket. A valid socket
|
%% @doc start_link should always return ok for any valid socket. A valid socket
|
||||||
%% should always return {ok, Pid} and {error, Reason} otherwise.
|
%% should always return {ok, Pid} and {error, Reason} otherwise.
|
||||||
start_link_test() ->
|
start_link_test() ->
|
||||||
ggs_logger:not_implemented().
|
{ok, Player} = ggs_player:start_link(Sock).
|
||||||
|
|
||||||
%% @doc Given that start_link returned {ok, Player}. Notify shall always return ok and
|
%% @doc Given that start_link returned {ok, Player}. Notify shall always return ok and
|
||||||
%% deliver a specified message through the socket.
|
%% deliver a specified message through the socket.
|
||||||
|
|
|
||||||
Reference in a new issue