Replaced old protocol with gen_fsm protocol parser
This commit is contained in:
parent
acdcec8dff
commit
cb24e00c6e
5 changed files with 107 additions and 42 deletions
|
@ -1 +1 @@
|
||||||
Subproject commit aad268d03a3c23de917cfecf767d583d3cb32633
|
Subproject commit 00312859714bef6e9a4fdb9931a41fef56eeb89a
|
|
@ -140,7 +140,7 @@ class GGSChat:
|
||||||
"Content-Type: text\n" +
|
"Content-Type: text\n" +
|
||||||
"Content-Length: 0\n"+
|
"Content-Length: 0\n"+
|
||||||
"\n")
|
"\n")
|
||||||
time.sleep(2)
|
#time.sleep(2)
|
||||||
|
|
||||||
def updateUsers(self, text):
|
def updateUsers(self, text):
|
||||||
evalNicks = eval(text)
|
evalNicks = eval(text)
|
||||||
|
|
|
@ -21,6 +21,7 @@ start_link(Socket) ->
|
||||||
% that arrives on it, we do not need to recv() manually. Since the socket
|
% that arrives on it, we do not need to recv() manually. Since the socket
|
||||||
% was opened in our parent process, we need to change the owner of it to
|
% was opened in our parent process, we need to change the owner of it to
|
||||||
% us, otherwise these messages end up in our parent.
|
% us, otherwise these messages end up in our parent.
|
||||||
|
ggs_protocol:start_link(),
|
||||||
erlang:port_connect(Socket, self()),
|
erlang:port_connect(Socket, self()),
|
||||||
{ok, Token} = ggs_coordinator:join_lobby(),
|
{ok, Token} = ggs_coordinator:join_lobby(),
|
||||||
TableStatus = ggs_coordinator:join_table("1337"),
|
TableStatus = ggs_coordinator:join_table("1337"),
|
||||||
|
@ -62,7 +63,6 @@ loop(#pl_state{token = _Token, socket = Socket, table = Table} = State) ->
|
||||||
receive
|
receive
|
||||||
{tcp, Socket, Data} -> % Just echo for now..
|
{tcp, Socket, Data} -> % Just echo for now..
|
||||||
Parsed = ggs_protocol:parse(Data),
|
Parsed = ggs_protocol:parse(Data),
|
||||||
self() ! Parsed,
|
|
||||||
loop(State);
|
loop(State);
|
||||||
{notify, _From, Message} ->
|
{notify, _From, Message} ->
|
||||||
gen_tcp:send(Socket, Message),
|
gen_tcp:send(Socket, Message),
|
||||||
|
|
|
@ -1,12 +1,50 @@
|
||||||
|
%% Parse a string formatted with the GGS protocol using
|
||||||
|
%% an FSM. Each char is put into the FSM, which incrementally
|
||||||
|
%% builds a list of strings which represent the complete
|
||||||
|
%% message.
|
||||||
|
|
||||||
-module(ggs_protocol).
|
-module(ggs_protocol).
|
||||||
-export([parse/1, getToken/1, create_message/4]).
|
-export([parse/1, getToken/1, create_message/4,
|
||||||
|
expect_headers/2, expect_data_section/2,
|
||||||
|
expect_headers/3, expect_data_section/3]).
|
||||||
|
|
||||||
|
%% tests
|
||||||
|
-export([test/0, to_dictionary/2]).
|
||||||
|
|
||||||
|
% gen_fsm callbacks..
|
||||||
|
-export([init/1, handle_info/2, terminate/2, code_change/3, start_link/0]).
|
||||||
|
|
||||||
|
|
||||||
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
|
%% JONTES TESTS
|
||||||
|
test() ->
|
||||||
|
start_link(),
|
||||||
|
parse("Token: %s\nServer-Command: define\nContent-Type: text\nContent-Length: 9\n\nHELLOWORLDToken: %s\nServer-Command: define\nContent-Type: text\nContent-Length: 9\n\nHELLOWORLDToken: %s\nServer-Command: define\n"),
|
||||||
|
to_dictionary(["Hello: world", "Hi: there!"], []).
|
||||||
|
%% END TESTS
|
||||||
|
|
||||||
|
|
||||||
%% API Functions
|
%% API Functions
|
||||||
parse(Data) ->
|
parse(Data) ->
|
||||||
Parsed = do_parse(Data, []),
|
lists:foreach(fun(X) ->
|
||||||
prettify(Parsed).
|
gen_fsm:sync_send_event(?SERVER, {char, X})
|
||||||
|
end, Data).
|
||||||
|
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
gen_fsm:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
% Start state: {[""],0}, meaning:
|
||||||
|
% - Start with no strings parsed
|
||||||
|
% - Start with a data-section-lengths of 0
|
||||||
|
init([]) ->
|
||||||
|
{ok,expect_headers,{[""], 0}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
getToken(Parsed) ->
|
getToken(Parsed) ->
|
||||||
|
|
||||||
case lists:keyfind(token, 1, Parsed) of
|
case lists:keyfind(token, 1, Parsed) of
|
||||||
{_, Value} ->
|
{_, Value} ->
|
||||||
Value;
|
Value;
|
||||||
|
@ -14,6 +52,8 @@ getToken(Parsed) ->
|
||||||
false
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% Assemble a message which can b
|
||||||
|
%e used as a reply to a client
|
||||||
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" ++
|
||||||
|
@ -25,45 +65,64 @@ create_message(Cmd, Enc, Acc, Data) ->
|
||||||
Data,
|
Data,
|
||||||
Msg.
|
Msg.
|
||||||
|
|
||||||
|
%%% Transitions
|
||||||
|
expect_headers(_Event, State) ->
|
||||||
|
{next_state, expect_headers, State}.
|
||||||
|
expect_data_section(_Event, State) ->
|
||||||
|
{next_state, expect_data_section, State}.
|
||||||
|
|
||||||
%% Internal helpers
|
|
||||||
do_parse(Data, ParsedMessage) ->
|
%%% End transitions
|
||||||
NewLinePos = string:chr(Data, $\n),
|
expect_headers({char, $\n}, _From, {Strings, Remains}) ->
|
||||||
Line = string:substr(Data, 1, NewLinePos-1),
|
[LastMessage|_] = Strings,
|
||||||
Tokens = re:split(Line, ": ", [{return, list}]),
|
case LastMessage of
|
||||||
case handle(Tokens) of
|
"" -> % We have a data section.. Last line should thus be the content length.
|
||||||
{Command, more} ->
|
[LastMessage, SecondLast | _] = Strings,
|
||||||
do_parse(string:substr(Data, NewLinePos+1), ParsedMessage ++ [Command]);
|
case re:split(SecondLast, ": ", [{return, list}]) of
|
||||||
{separator, data_next} ->
|
["Content-Length", X] ->
|
||||||
{_, Value} = lists:keyfind(content_len, 1, ParsedMessage),
|
{Int,_} = string:to_integer(X),
|
||||||
{ContentLength, []} = string:to_integer(Value),
|
{reply, ok, expect_data_section, {[""|Strings], Int}};
|
||||||
{data, ArgumentData} = handle_data(string:substr(Data, NewLinePos+1), ContentLength),
|
Other -> ok
|
||||||
{ParsedMessage, ArgumentData}
|
end;
|
||||||
|
Other ->
|
||||||
|
{reply,ok,expect_headers, {[""|Strings], Remains}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
expect_headers({char, Char}, _From, {[Current|Rest], Remains}) ->
|
||||||
|
NewCurrent = Current ++ [Char],
|
||||||
|
{reply,ok, expect_headers, {[NewCurrent|Rest], Remains}}.
|
||||||
|
|
||||||
|
|
||||||
|
expect_data_section({char, Char}, From, {Strings, Remains}) ->
|
||||||
|
case Remains of
|
||||||
|
0 ->
|
||||||
|
[LastMsg,_|Rest] = Strings,
|
||||||
|
NewMsg = LastMsg ++ [Char],
|
||||||
|
Result = [NewMsg|Rest],
|
||||||
|
{Pid,_} = From,
|
||||||
|
erlang:display(From),
|
||||||
|
Pid ! (prettify(to_dictionary(Rest, []), NewMsg)),
|
||||||
|
{reply, ok, expect_headers, {[""], 0}};
|
||||||
|
Other -> [LastMsg|Rest] = Strings,
|
||||||
|
NewMsg = LastMsg ++ [Char],
|
||||||
|
{reply, ok, expect_data_section, {[NewMsg|Rest], Remains-1}}
|
||||||
end.
|
end.
|
||||||
|
handle_call(_Msg, _From, State) ->
|
||||||
handle([[]]) ->
|
{noreply, State}.
|
||||||
{separator, data_next};
|
handle_info(_Msg, State) ->
|
||||||
handle(["Server-Command", Param]) ->
|
{noreply, State}.
|
||||||
{{srv_cmd, Param}, more};
|
terminate(_Reason, _State) ->
|
||||||
handle(["Game-Command", Param]) ->
|
ok.
|
||||||
{{game_cmd, Param}, more};
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
handle(["Content-Length", Param]) ->
|
{ok, State}.
|
||||||
{{content_len, Param}, more};
|
|
||||||
handle(["Token", Param]) ->
|
|
||||||
{{token, Param}, more};
|
|
||||||
handle(["Content-Type", Param]) ->
|
|
||||||
{{content_type, Param}, more}.
|
|
||||||
|
|
||||||
handle_data(Data, Length) ->
|
|
||||||
{data, string:substr(Data,1,Length)}.
|
|
||||||
|
|
||||||
|
|
||||||
prettify({Args, Data}) ->
|
prettify(Args, Data) ->
|
||||||
case lists:keyfind(srv_cmd, 1, Args) of
|
case lists:keyfind("Server-Command", 1, Args) of
|
||||||
{_, Value} ->
|
{_, Value} ->
|
||||||
{srv_cmd, Value, Args, Data};
|
{srv_cmd, Value, Args, Data};
|
||||||
_Other ->
|
_Other ->
|
||||||
case lists:keyfind(game_cmd, 1, Args) of
|
case lists:keyfind("Game-Command", 1, Args) of
|
||||||
{_, Value} ->
|
{_, Value} ->
|
||||||
{game_cmd, Value, Args, Data};
|
{game_cmd, Value, Args, Data};
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -71,3 +130,9 @@ prettify({Args, Data}) ->
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
to_dictionary([], Dict) ->
|
||||||
|
Dict;
|
||||||
|
to_dictionary([S|Strings], Dict) ->
|
||||||
|
[First, Snd] = re:split(S, ": ", [{return, list}]),
|
||||||
|
to_dictionary(Strings, [{First, Snd}|Dict]).
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ send_command(TableToken, PlayerToken, Message) ->
|
||||||
%% @private
|
%% @private
|
||||||
init([TableToken]) ->
|
init([TableToken]) ->
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
GameVM = ggs_gamevm_p:start_link(TableToken),
|
GameVM = ggs_gamevm_e:start_link(TableToken),
|
||||||
{ok, #state {
|
{ok, #state {
|
||||||
game_vm = GameVM,
|
game_vm = GameVM,
|
||||||
players = [] }}.
|
players = [] }}.
|
||||||
|
@ -100,14 +100,14 @@ handle_cast({notify, Player, Message}, #state { game_vm = GameVM } = State) ->
|
||||||
PlayerToken = ggs_coordinator:player_pid_to_token(Player),
|
PlayerToken = ggs_coordinator:player_pid_to_token(Player),
|
||||||
case Message of
|
case Message of
|
||||||
{server, define, Args} ->
|
{server, define, Args} ->
|
||||||
ggs_gamevm_p:define(GameVM, Args);
|
ggs_gamevm_e:define(GameVM, Args);
|
||||||
{game, Command, Args} ->
|
{game, Command, Args} ->
|
||||||
ggs_gamevm_p:player_command(GameVM, PlayerToken, Command, Args)
|
ggs_gamevm_e:player_command(GameVM, PlayerToken, Command, Args)
|
||||||
end,
|
end,
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_cast({notify_game, Message, From}, #state { game_vm = GameVM } = State) ->
|
handle_cast({notify_game, Message, From}, #state { game_vm = GameVM } = State) ->
|
||||||
ggs_gamevm_p:player_command(GameVM, From, Message, ""),
|
ggs_gamevm_e:player_command(GameVM, From, Message, ""),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_cast({notify_all_players, Message}, #state{players = Players} = State) ->
|
handle_cast({notify_all_players, Message}, #state{players = Players} = State) ->
|
||||||
|
|
Reference in a new issue