From df197dab408e09910a6cb2aa09fe92990c7d6d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Mon, 14 Feb 2011 17:45:56 +0100 Subject: [PATCH 1/7] Switched over to new protocol module --- src/ggs_protocol.erl | 16 +++++++++++++--- src/ggs_server.erl | 40 ++++++++++++++++------------------------ 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/ggs_protocol.erl b/src/ggs_protocol.erl index 46c1ba1..35da585 100644 --- a/src/ggs_protocol.erl +++ b/src/ggs_protocol.erl @@ -1,10 +1,20 @@ -module(ggs_protocol). --export([parse/1]). +-export([parse/1, getToken/1]). +%% API Functions parse(Data) -> Parsed = do_parse(Data, []), prettify(Parsed). +getToken(Parsed) -> + case lists:keyfind(token, 1, Parsed) of + {_, Value} -> + Value; + false -> + false + end. + +%% Internal helpers do_parse(Data, ParsedMessage) -> NewLinePos = string:chr(Data, $\n), Line = string:substr(Data, 1, NewLinePos-1), @@ -15,7 +25,8 @@ do_parse(Data, ParsedMessage) -> {separator, data_next} -> {_, Value} = lists:keyfind(content_len, 1, ParsedMessage), {ContentLength, []} = string:to_integer(Value), - {ParsedMessage, handle_data(string:substr(Data, NewLinePos+1), ContentLength)} + {data, ArgumentData} = handle_data(string:substr(Data, NewLinePos+1), ContentLength), + {ParsedMessage, ArgumentData} end. handle([[]]) -> @@ -33,7 +44,6 @@ handle_data(Data, Length) -> {data, string:substr(Data,1,Length)}. -%% Helpers prettify({Args, Data}) -> case lists:keyfind(srv_cmd, 1, Args) of {_, Value} -> diff --git a/src/ggs_server.erl b/src/ggs_server.erl index 4a116fa..f8eae1e 100644 --- a/src/ggs_server.erl +++ b/src/ggs_server.erl @@ -1,10 +1,3 @@ -%%%---------------------------------------------------- -%%% @author Jonatan Pålsson -%%% @copyright 2010 Jonatan Pålsson -%%% @doc RPC over TCP server -%%% @end -%%%---------------------------------------------------- - -module(ggs_server). -behaviour(gen_server). @@ -95,37 +88,36 @@ handle_cast(stop, State) -> {stop, normal, State}; % Handle javascript defines -handle_cast({srv_cmd, "define", Args, Data}, State) -> - %JSVM = getJSVM(Token, State), - %js_runner:define(JSVM, Payload), +handle_cast({srv_cmd, "define", Headers, Data}, State) -> + Token = ggs_protocol:getToken(Headers), + JSVM = getJSVM(Token, State), + js_runner:define(JSVM, Data), send(State#state.lsock, "Token", "Okay, defined that for you!"), {noreply, State}; % Handle javascript calls -handle_cast({srv_cmd, "call", Args, Data}, State) -> - %io:format("Got call request: ~p~n", [Payload]), - %JSVM = getJSVM(Token, State), - %erlang:display(erlang:port_info(JSVM)), - %{ok, Ret} = js_runner:call(JSVM, Payload, []),%Payload, []), - %send(State#state.lsock, Token, "JS says:", binary_to_list(Ret)), +handle_cast({srv_cmd, "call", Headers, Data}, State) -> + Token = ggs_protocol:getToken(Headers), + io:format("Got call request: ~p~n", [Data]), + JSVM = getJSVM(Token, State), + erlang:display(erlang:port_info(JSVM)), + {ok, Ret} = js_runner:call(JSVM, Data, []),%Payload, []), + send(State#state.lsock, Token, "JS says:", binary_to_list(Ret)), {noreply, State}; % Set the new state to the reference generated, and JSVM associated handle_cast({srv_cmd, "hello", Headers, Data}, State) -> - %JSVM = js_runner:boot(), + JSVM = js_runner:boot(), Client = getRef(), send(State#state.lsock, Client, "This is your refID"), - %OldMap = State#state.client_vm_map, - %NewState = State#state{client_vm_map = OldMap ++ [{Client, JSVM}]}, - %gen_server:cast(ggs_backup, {set_backup, NewState}), - {noreply, State}. %NewState + OldMap = State#state.client_vm_map, + NewState = State#state{client_vm_map = OldMap ++ [{Client, JSVM}]}, + gen_server:cast(ggs_backup, {set_backup, NewState}), + {noreply, NewState}. %%----------------------------------------------------- %% Helpers %%----------------------------------------------------- getRef() -> - %{A1,A2,A3} = now(), - %#random:seed(A1, A2, A3), - %random:uniform(1000). string:strip(os:cmd("uuidgen"), right, $\n ). getJSVM(RefID, State) -> From bf98e1fee2c9338466ea58b2d08c9a277cf370ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Mon, 14 Feb 2011 19:32:46 +0100 Subject: [PATCH 2/7] Fixed ggs_vm_runner --- src/ggs_server.erl | 4 ++-- src/ggs_vm_runner.erl | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/ggs_server.erl b/src/ggs_server.erl index 34d297f..c695fd6 100644 --- a/src/ggs_server.erl +++ b/src/ggs_server.erl @@ -104,8 +104,8 @@ handle_cast({define, Token, SourceCode}, State) -> % Handle javascript calls handle_cast({call, Token, Command}, State) -> GameVM = getJSVM(Token, State), - ggs_vm_runner:user_command(GameVM, "User", Command, []), - %send(State#state.lsock, Token, "JS says:", binary_to_list(Ret)), Unessecary + Ret = ggs_vm_runner:user_command(GameVM, "User", Command, []), + send(State#state.lsock, Token, "JS says:", binary_to_list(Ret)), {noreply, State}; % Set the new state to the reference generated, and JSVM associated diff --git a/src/ggs_vm_runner.erl b/src/ggs_vm_runner.erl index cf633f0..6c66378 100644 --- a/src/ggs_vm_runner.erl +++ b/src/ggs_vm_runner.erl @@ -4,25 +4,41 @@ %Mattias start_link() -> erlang_js:start(), - {ok, Port} = js_driver:new(), - js:define(Port, <<"function userCommand(user, command, args){}">>), - PortPid = spawn_link(fun() -> loop(Port) end ), + PortPid = spawn_link( fun() -> + process_flag(trap_exit, true), + {ok, Port} = js_driver:new(), + js:define(Port, <<"function userCommand(user, command, args){return 'Hello world';}">>), + loop(Port) + end ), PortPid. loop(Port) -> -receive - {define, SourceCode} -> - ok = js:define(Port, list_to_binary(SourceCode)), - loop(Port); - {user_command, User, Command, Args} -> - {ok, Ret} = js:call(Port, <<"userCommand">>, list_to_binary([User,Command,Args])), - loop(Port) -end. + io:format("I am PID"), + erlang:display(self()), + receive + {define, SourceCode} -> + ok = js:define(Port, list_to_binary(SourceCode)), + loop(Port); + {user_command, User, Command, Args, From, Ref} -> + {ok, Ret} = js:call(Port, <<"userCommand">>, + [ list_to_binary(User), + list_to_binary(Command), + list_to_binary(Args) + ]), + From ! {Ref, Ret}, + loop(Port) + end. define(GameVM, SourceCode) -> GameVM ! {define,SourceCode}. user_command(GameVM, User, Command, Args) -> - GameVM ! {user_command, User, Command, Args}. + Ref = make_ref(), + GameVM ! {user_command, User, Command, Args, self(), Ref}, + receive + {Ref, RetVal} -> + RetVal; + Other -> Other + end. From 66aa7f4bf123e6157a0c6fa8679dde63ce397542 Mon Sep 17 00:00:00 2001 From: Jeena Paradies Date: Tue, 15 Feb 2011 07:57:44 +0100 Subject: [PATCH 3/7] some more interface compatibility --- src/ggs_server.erl | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/ggs_server.erl b/src/ggs_server.erl index ab9b8f8..833feac 100644 --- a/src/ggs_server.erl +++ b/src/ggs_server.erl @@ -105,26 +105,33 @@ handle_cast({srv_cmd, "call", Headers, Data}, State) -> {noreply, State}; % Set the new state to the reference generated, and JSVM associated +%handle_cast({server, hello, Headers}, State) -> handle_cast({srv_cmd, "hello", Headers, Data}, State) -> - GameVM = ggs_vm_runner:start_link(), - Client = getRef(), - send(State#state.lsock, Client, "This is your refID"), + GameToken = case proplist:get_value(game_token, Headers) of -> + undefined -> getNewToken(); + GT -> GT; + end, + ClientToken = getNewToken(), OldMap = State#state.client_vm_map, - NewState = State#state{client_vm_map = OldMap ++ [{Client, GameVM}]}, + NewState = State#state{client_vm_map = OldMap ++ [{ClientToken, GameVM, GameToken}]}, gen_server:cast(ggs_backup, {set_backup, NewState}), {noreply, NewState}. + %%----------------------------------------------------- %% Helpers %%----------------------------------------------------- -getRef() -> +getNewToken() -> string:strip(os:cmd("uuidgen"), right, $\n ). -getJSVM(RefID, State) -> +getJSVM(ClientToken, State) -> VMs = State#state.client_vm_map, - erlang:display(RefID), - erlang:display(VMs), - {value, {_,VM}} = lists:keysearch(RefID, 1, VMs), + {value, {_,VM}} = lists:keysearch(ClientToken, 1, VMs), + VM. + +getGameVMByGameToken(GameToken, State) -> + VMs = State#state.client_vm_map, + {value, {_,VM}} = lists:keysearch(GameToken, 3, VMs), VM. send(Socket, RefID, String) -> From da67b0a9773fc8c5784a6caaa661cd4d1bd493ab Mon Sep 17 00:00:00 2001 From: Jeena Paradies Date: Mon, 11 Apr 2011 10:54:18 +0200 Subject: [PATCH 4/7] added stash --- games/Pong/Classes/GGSNetwork.m | 2 +- src/ggs_dispatcher.erl | 3 +- src/ggs_player.erl | 133 ++++++++++++++++++-------------- src/ggs_protocol.erl | 125 ++++++++++++++++++++++++++---- tests/ggs_player_test.erl | 2 +- 5 files changed, 192 insertions(+), 73 deletions(-) diff --git a/games/Pong/Classes/GGSNetwork.m b/games/Pong/Classes/GGSNetwork.m index a436f13..ecc00d0 100644 --- a/games/Pong/Classes/GGSNetwork.m +++ b/games/Pong/Classes/GGSNetwork.m @@ -11,7 +11,7 @@ @implementation GGSNetwork -#define GGS_HOST @"jeena.net" +#define GGS_HOST @"localhost" #define GGS_PORT 9000 #define NO_TIMEOUT -1 diff --git a/src/ggs_dispatcher.erl b/src/ggs_dispatcher.erl index 11dd729..fdd28c6 100644 --- a/src/ggs_dispatcher.erl +++ b/src/ggs_dispatcher.erl @@ -58,7 +58,8 @@ handle_info({tcp_closed, Socket}, State) -> %% and when it does, we accept the connection. handle_info(timeout, LSock) -> {ok, Sock} = gen_tcp:accept(LSock), - spawn(ggs_player, start_link, [Sock]), + ggs_player:start(Sock), + erlang:display("handle_info"), {noreply, LSock, 0}. terminate(normal, _State) -> diff --git a/src/ggs_player.erl b/src/ggs_player.erl index d6e939e..e80aeac 100644 --- a/src/ggs_player.erl +++ b/src/ggs_player.erl @@ -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 -%%responsible for: -%% * The storage of the player socket, player token and a table token. -%% * Ability to fetch a player token. -%% * Forwarding messages from players to the game -%% * Remove a player from GGS +%% responsible for: +%% * The storage of the player socket, player token and a table token. +%% * Ability to fetch a player token. +%% * Forwarding messages from players to the game +%% * 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 %% an argument for storage and later usage. Creates a unique player token %% identifying the player. %% @spec start_link(Socket::socket()) -> {ok, Pid} | {error, Reason} -start_link(Socket) -> - % The socket is in 'active' mode, and that means we are pushed any data - % 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 - % us, otherwise these messages end up in our parent. - erlang:port_connect(Socket, self()), +start(Socket) -> + erlang:display("start_link"), + gen_server:start(?MODULE, [Socket], []). + +init([Socket]) -> + {ok, Protocol} = ggs_protocol:start_link(Socket, self()), {ok, Token} = ggs_coordinator:join_lobby(), - TableStatus = ggs_coordinator:join_table("1337"), - case TableStatus of - {ok, Table} -> - notify(self(), self(), {"hello", Token}), - loop(#pl_state{socket = Socket, token = Token, table = Table}); + + case ggs_coordinator:join_table("1337") of + {ok, T} -> + Table = T; {error, no_such_table} -> ggs_coordinator:create_table({force, "1337"}), - {ok, Table} = ggs_coordinator:join_table("1337"), - notify(self(), self(), {"hello", Token}), - loop(#pl_state{socket = Socket, token = Token, table = Table}) - end. + {ok, T} = ggs_coordinator:join_table("1337"), + Table = T + 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 %% socket to the player. %% @spec notify(Player::Pid(), From::Pid(), %% {Command::String(), Message::string()}) -> ok -notify(Player, From, Message) -> - {Cmd, Data} = Message, - Parsed = ggs_protocol:create_message(Cmd, "text","text", Data), - Player ! {notify, From, Parsed}. +notify(Player, _From, Message) -> + gen_server:cast(Player, {notify, Message}). + +%% @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. %% @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 %% process should return in the end. %% @spec stop(Table::pid()) -> Reason::string() -stop(_Player,_Table) -> - ggs_logger:not_implemented(). +stop(Player) -> + gen_server:cast(Player, stop). %% Internals -loop(#pl_state{token = _Token, socket = Socket, table = Table} = State) -> - receive - {tcp, Socket, Data} -> % Just echo for now.. - Parsed = ggs_protocol:parse(Data), - self() ! Parsed, - loop(State); - {notify, _From, Message} -> - gen_tcp:send(Socket, Message), - loop(State); - % Below are messages generated by the parser - {game_cmd,Cmd, _Headers, Data} -> - ggs_table:notify(Table, self(), {game, Cmd, Data}), - loop(State); - {srv_cmd,"define", _Headers, Data} -> - ggs_table:notify(Table, self(), {server, define, Data}), - loop(State); - {tcp_closed, _Socket} -> - io:format("Client disconnected, but THIS IS NOT SUPPORTED YET!~n"), - loop(State); - Other -> - io:format("Got UNKNOWN message: "), - erlang:display(Other), - io:format("~n") - end. +handle_call({notify, Message}, _From, #state { protocol = Protocol } = State) -> + ggs_protocol:send_command(Protocol, Message), + {noreplay, State}; + +handle_call({game_cmd, Command, _Headers, Data}, _From, #state { table = Table } = State) -> + ggs_table:notify(Table, self(), {game, Command, Data}), + {noreplay, State}; + +handle_call({srv_cmd, "define", _Headers, Data}, _From, #state { table = Table } = State) -> + ggs_table:notify(Table, self(), {server, define, Data}), + {noreplay, State}; + +handle_call(_Request, _From, St) -> {stop, unimplemented, St}. +handle_cast(_Request, St) -> {stop, unimplemented, St}. + +handle_info(_Info, St) -> {stop, unimplemented, St}. + +terminate(_Reason, State) -> + ggs_protocol:stop(State#state.protocol), + ggs_table:remove_player(State#state.table, self()), + % ggs_coordinator:remove_player(self(), self()), % not implemented yet + % TODO: release Socket + ok. + +code_change(_OldVsn, St, _Extra) -> {ok, St}. diff --git a/src/ggs_protocol.erl b/src/ggs_protocol.erl index 11cb2d0..f561ff7 100644 --- a/src/ggs_protocol.erl +++ b/src/ggs_protocol.erl @@ -1,11 +1,111 @@ +%%% @doc This module handles TCP incomming and outcommint. + -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 parse(Data) -> - Parsed = do_parse(Data, []), - prettify(Parsed). - + do_parse(Data, []). + getToken(Parsed) -> case lists:keyfind(token, 1, Parsed) of {_, Value} -> @@ -13,6 +113,8 @@ getToken(Parsed) -> false -> false end. + + create_message(Cmd, Enc, Acc, Data) -> Length = integer_to_list(string:len(Data)), @@ -26,18 +128,15 @@ create_message(Cmd, Enc, Acc, Data) -> Msg. %% Internal helpers -do_parse(Data, ParsedMessage) -> +do_parse(Data, Headers) -> NewLinePos = string:chr(Data, $\n), Line = string:substr(Data, 1, NewLinePos-1), Tokens = re:split(Line, ": ", [{return, list}]), case handle(Tokens) of {Command, more} -> - do_parse(string:substr(Data, NewLinePos+1), ParsedMessage ++ [Command]); + do_parse(string:substr(Data, NewLinePos+1), Headers ++ [Command]); {separator, data_next} -> - {_, Value} = lists:keyfind(content_len, 1, ParsedMessage), - {ContentLength, []} = string:to_integer(Value), - {data, ArgumentData} = handle_data(string:substr(Data, NewLinePos+1), ContentLength), - {ParsedMessage, ArgumentData} + {Headers, Data} end. handle([[]]) -> @@ -53,11 +152,11 @@ handle(["Token", Param]) -> handle(["Content-Type", Param]) -> {{content_type, Param}, more}. -handle_data(Data, Length) -> - {data, string:substr(Data,1,Length)}. +%handle_data(Data, Length) -> +% {data, string:substr(Data,1,Length)}. -prettify({Args, Data}) -> +prettify(Args, Data) -> case lists:keyfind(srv_cmd, 1, Args) of {_, Value} -> {srv_cmd, Value, Args, Data}; diff --git a/tests/ggs_player_test.erl b/tests/ggs_player_test.erl index efe7530..1782842 100644 --- a/tests/ggs_player_test.erl +++ b/tests/ggs_player_test.erl @@ -4,7 +4,7 @@ %% @doc start_link should always return ok for any valid socket. A valid socket %% should always return {ok, Pid} and {error, Reason} otherwise. 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 %% deliver a specified message through the socket. From cb24e00c6eb0b2fd23a2c1e89d2e18dea5d4d40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20P=C3=A5lsson?= Date: Mon, 11 Apr 2011 16:27:53 +0200 Subject: [PATCH 5/7] Replaced old protocol with gen_fsm protocol parser --- doc/report | 2 +- games/GGSChat/chat.py | 2 +- src/ggs_player.erl | 2 +- src/ggs_protocol.erl | 135 +++++++++++++++++++++++++++++++----------- src/ggs_table.erl | 8 +-- 5 files changed, 107 insertions(+), 42 deletions(-) diff --git a/doc/report b/doc/report index aad268d..0031285 160000 --- a/doc/report +++ b/doc/report @@ -1 +1 @@ -Subproject commit aad268d03a3c23de917cfecf767d583d3cb32633 +Subproject commit 00312859714bef6e9a4fdb9931a41fef56eeb89a diff --git a/games/GGSChat/chat.py b/games/GGSChat/chat.py index e587570..aadf5d9 100644 --- a/games/GGSChat/chat.py +++ b/games/GGSChat/chat.py @@ -140,7 +140,7 @@ class GGSChat: "Content-Type: text\n" + "Content-Length: 0\n"+ "\n") - time.sleep(2) + #time.sleep(2) def updateUsers(self, text): evalNicks = eval(text) diff --git a/src/ggs_player.erl b/src/ggs_player.erl index d6e939e..0323bcc 100644 --- a/src/ggs_player.erl +++ b/src/ggs_player.erl @@ -21,6 +21,7 @@ start_link(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 % us, otherwise these messages end up in our parent. + ggs_protocol:start_link(), erlang:port_connect(Socket, self()), {ok, Token} = ggs_coordinator:join_lobby(), TableStatus = ggs_coordinator:join_table("1337"), @@ -62,7 +63,6 @@ loop(#pl_state{token = _Token, socket = Socket, table = Table} = State) -> receive {tcp, Socket, Data} -> % Just echo for now.. Parsed = ggs_protocol:parse(Data), - self() ! Parsed, loop(State); {notify, _From, Message} -> gen_tcp:send(Socket, Message), diff --git a/src/ggs_protocol.erl b/src/ggs_protocol.erl index d61dfc0..36a96d6 100644 --- a/src/ggs_protocol.erl +++ b/src/ggs_protocol.erl @@ -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). --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 parse(Data) -> - Parsed = do_parse(Data, []), - prettify(Parsed). + lists:foreach(fun(X) -> + 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) -> + case lists:keyfind(token, 1, Parsed) of {_, Value} -> Value; @@ -14,6 +52,8 @@ getToken(Parsed) -> false end. +%% Assemble a message which can b +%e used as a reply to a client create_message(Cmd, Enc, Acc, Data) -> Length = integer_to_list(string:len(Data)), Msg = "Client-Command: " ++ Cmd ++ "\n" ++ @@ -25,45 +65,64 @@ create_message(Cmd, Enc, Acc, Data) -> Data, 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) -> - NewLinePos = string:chr(Data, $\n), - Line = string:substr(Data, 1, NewLinePos-1), - Tokens = re:split(Line, ": ", [{return, list}]), - case handle(Tokens) of - {Command, more} -> - do_parse(string:substr(Data, NewLinePos+1), ParsedMessage ++ [Command]); - {separator, data_next} -> - {_, Value} = lists:keyfind(content_len, 1, ParsedMessage), - {ContentLength, []} = string:to_integer(Value), - {data, ArgumentData} = handle_data(string:substr(Data, NewLinePos+1), ContentLength), - {ParsedMessage, ArgumentData} + +%%% End transitions +expect_headers({char, $\n}, _From, {Strings, Remains}) -> + [LastMessage|_] = Strings, + case LastMessage of + "" -> % We have a data section.. Last line should thus be the content length. + [LastMessage, SecondLast | _] = Strings, + case re:split(SecondLast, ": ", [{return, list}]) of + ["Content-Length", X] -> + {Int,_} = string:to_integer(X), + {reply, ok, expect_data_section, {[""|Strings], Int}}; + Other -> ok + 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. - -handle([[]]) -> - {separator, data_next}; -handle(["Server-Command", Param]) -> - {{srv_cmd, Param}, more}; -handle(["Game-Command", Param]) -> - {{game_cmd, Param}, more}; -handle(["Content-Length", Param]) -> - {{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)}. +handle_call(_Msg, _From, State) -> + {noreply, State}. +handle_info(_Msg, State) -> + {noreply, State}. +terminate(_Reason, _State) -> + ok. +code_change(_OldVsn, State, _Extra) -> + {ok, State}. -prettify({Args, Data}) -> - case lists:keyfind(srv_cmd, 1, Args) of +prettify(Args, Data) -> + case lists:keyfind("Server-Command", 1, Args) of {_, Value} -> {srv_cmd, Value, Args, Data}; _Other -> - case lists:keyfind(game_cmd, 1, Args) of + case lists:keyfind("Game-Command", 1, Args) of {_, Value} -> {game_cmd, Value, Args, Data}; _ -> @@ -71,3 +130,9 @@ prettify({Args, Data}) -> end end. +to_dictionary([], Dict) -> + Dict; +to_dictionary([S|Strings], Dict) -> + [First, Snd] = re:split(S, ": ", [{return, list}]), + to_dictionary(Strings, [{First, Snd}|Dict]). + diff --git a/src/ggs_table.erl b/src/ggs_table.erl index 909f789..7cdef80 100644 --- a/src/ggs_table.erl +++ b/src/ggs_table.erl @@ -74,7 +74,7 @@ send_command(TableToken, PlayerToken, Message) -> %% @private init([TableToken]) -> process_flag(trap_exit, true), - GameVM = ggs_gamevm_p:start_link(TableToken), + GameVM = ggs_gamevm_e:start_link(TableToken), {ok, #state { game_vm = GameVM, players = [] }}. @@ -100,14 +100,14 @@ handle_cast({notify, Player, Message}, #state { game_vm = GameVM } = State) -> PlayerToken = ggs_coordinator:player_pid_to_token(Player), case Message of {server, define, Args} -> - ggs_gamevm_p:define(GameVM, Args); + ggs_gamevm_e:define(GameVM, Args); {game, Command, Args} -> - ggs_gamevm_p:player_command(GameVM, PlayerToken, Command, Args) + ggs_gamevm_e:player_command(GameVM, PlayerToken, Command, Args) end, {noreply, 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}; handle_cast({notify_all_players, Message}, #state{players = Players} = State) -> From 135caff0e9b82693459cb8fedf95cdbabac2e310 Mon Sep 17 00:00:00 2001 From: Jeena Paradies Date: Mon, 11 Apr 2011 16:48:40 +0200 Subject: [PATCH 6/7] changed player to gen_server --- Makefile | 1 + games/Pong/Classes/GGSNetwork.m | 2 +- .../UserInterfaceState.xcuserstate | 2713 +++++++++-------- src/ggs_player.erl | 19 +- src/ggs_protocol.erl | 224 +- src/ggs_protocol.erl.orig | 146 + src/ggs_server.erl | 13 +- 7 files changed, 1673 insertions(+), 1445 deletions(-) create mode 100644 src/ggs_protocol.erl.orig diff --git a/Makefile b/Makefile index a4934f9..eb5bf0f 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ endif clean: rm -rf $(BEAMDIR)/*.beam ; + rm -rf $(SRCDIR)/*.beam ; rm -rf erl_crush.dump ; echo "==> clean ggs" ; $(MAKE) -C erlang_js/ clean diff --git a/games/Pong/Classes/GGSNetwork.m b/games/Pong/Classes/GGSNetwork.m index ecc00d0..7b68fdc 100644 --- a/games/Pong/Classes/GGSNetwork.m +++ b/games/Pong/Classes/GGSNetwork.m @@ -32,7 +32,7 @@ @synthesize asyncSocket, delegate, gameToken, currentHeaders; - (id)initWithDelegate:(id)_delegate { - if (self = [super init]) { + if ((self = [super init])) { delegate = _delegate; asyncSocket = [[AsyncSocket alloc] initWithDelegate:self]; diff --git a/games/Pong/Pong.xcodeproj/project.xcworkspace/xcuserdata/jeena.xcuserdatad/UserInterfaceState.xcuserstate b/games/Pong/Pong.xcodeproj/project.xcworkspace/xcuserdata/jeena.xcuserdatad/UserInterfaceState.xcuserstate index 7578947..b1bf0bc 100644 --- a/games/Pong/Pong.xcodeproj/project.xcworkspace/xcuserdata/jeena.xcuserdatad/UserInterfaceState.xcuserstate +++ b/games/Pong/Pong.xcodeproj/project.xcworkspace/xcuserdata/jeena.xcuserdatad/UserInterfaceState.xcuserstate @@ -11,7 +11,7 @@ $class CF$UID - 126 + 184 NS.keys @@ -32,7 +32,7 @@ CF$UID - 100 + 112 @@ -42,7 +42,7 @@ $class CF$UID - 37 + 39 NS.keys @@ -97,29 +97,29 @@ CF$UID 16 - - CF$UID - 48 - - - CF$UID - 49 - - - CF$UID - 54 - CF$UID 57 CF$UID - 90 + 58 CF$UID - 91 + 63 + + + CF$UID + 66 + + + CF$UID + 100 + + + CF$UID + 101 CF$UID @@ -146,7 +146,7 @@ $class CF$UID - 37 + 39 NS.keys @@ -168,7 +168,7 @@ $class CF$UID - 37 + 39 NS.keys @@ -188,24 +188,32 @@ CF$UID 27 + + CF$UID + 29 + NS.objects CF$UID - 29 + 31 CF$UID - 38 + 40 CF$UID - 42 + 47 CF$UID - 45 + 51 + + + CF$UID + 54 @@ -278,7 +286,7 @@ 21 NS.string - file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/GGSNetwork.m + file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/PongViewController.m $class @@ -304,7 +312,7 @@ 21 NS.string - file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/PongAppDelegate.m + file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/GGSNetwork.m $class @@ -323,6 +331,32 @@ 28 + + $class + + CF$UID + 21 + + NS.string + file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/PongAppDelegate.m + + + $class + + CF$UID + 22 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 30 + + $class @@ -336,18 +370,10 @@ $class CF$UID - 37 + 39 NS.keys - - CF$UID - 30 - - - CF$UID - 31 - CF$UID 32 @@ -356,9 +382,6 @@ CF$UID 33 - - NS.objects - CF$UID 34 @@ -367,13 +390,24 @@ CF$UID 35 + + NS.objects + + + CF$UID + 36 + + + CF$UID + 37 + CF$UID 15 CF$UID - 36 + 38 @@ -398,77 +432,18 @@ $class CF$UID - 37 + 39 NS.keys - - CF$UID - 30 - - - CF$UID - 31 - - - CF$UID - 32 - - - CF$UID - 33 - - - NS.objects - - - CF$UID - 39 - - - CF$UID - 40 - - - CF$UID - 15 - CF$UID 41 - - - 322690356.257366 - {369, 1381} - {207, 0} - - $class - - CF$UID - 37 - - NS.keys - CF$UID - 30 + 42 - - CF$UID - 31 - - - CF$UID - 32 - - - CF$UID - 33 - - - NS.objects - CF$UID 43 @@ -477,34 +452,41 @@ CF$UID 44 + + NS.objects + + + CF$UID + 45 + + + CF$UID + 46 + CF$UID 15 CF$UID - 36 + 38 - 322646166.07745898 - {1704, 1033} + PrimaryDocumentTimestamp + PrimaryDocumentVisibleCharacterRange + HideAllIssues + PrimaryDocumentSelectedCharacterRange + 324225931.14446598 + {362, 1156} $class CF$UID - 37 + 39 NS.keys - - CF$UID - 30 - - - CF$UID - 31 - CF$UID 32 @@ -513,16 +495,24 @@ CF$UID 33 + + CF$UID + 34 + + + CF$UID + 35 + NS.objects CF$UID - 46 + 48 CF$UID - 47 + 49 CF$UID @@ -530,28 +520,36 @@ CF$UID - 36 + 50 - 322646160.93821901 - {0, 455} - 0 + 324221134.73035002 + {506, 1336} + {2711, 5} $class CF$UID - 37 + 39 NS.keys CF$UID - 50 + 32 CF$UID - 51 + 33 + + + CF$UID + 34 + + + CF$UID + 35 NS.objects @@ -564,47 +562,74 @@ CF$UID 53 + + CF$UID + 15 + + + CF$UID + 38 + - IDEDeviceLocation - IDEDeviceArchitecture - dvtdevice-iphonesimulator:/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.0.sdk-iPhone - i386 + 324221136.37834799 + {1697, 1040} $class CF$UID - 37 + 39 NS.keys CF$UID - 55 + 32 + + + CF$UID + 33 + + + CF$UID + 34 + + + CF$UID + 35 NS.objects + + CF$UID + 55 + CF$UID 56 + + CF$UID + 15 + + + CF$UID + 38 + - IDENameString - Pong + 322646160.93821901 + {0, 455} + 0 $class CF$UID - 37 + 39 NS.keys - - CF$UID - 58 - CF$UID 59 @@ -622,12 +647,72 @@ CF$UID - 89 + 62 + + + + IDEDeviceLocation + IDEDeviceArchitecture + dvtdevice-iphonesimulator:/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator4.0.sdk-iPhone + i386 + + $class + + CF$UID + 39 + + NS.keys + + + CF$UID + 64 + + + NS.objects + + + CF$UID + 65 + + + + IDENameString + Pong + + $class + + CF$UID + 39 + + NS.keys + + + CF$UID + 67 CF$UID 68 + + CF$UID + 69 + + + NS.objects + + + CF$UID + 70 + + + CF$UID + 98 + + + CF$UID + 99 + IDEActivityReportCompletionSummaryStringSegments @@ -637,62 +722,62 @@ $class CF$UID - 88 + 97 NS.objects CF$UID - 62 - - - CF$UID - 69 - - - CF$UID - 73 + 71 CF$UID 78 + + CF$UID + 82 + + + CF$UID + 87 + $class CF$UID - 37 + 39 NS.keys CF$UID - 63 + 72 CF$UID - 64 + 73 CF$UID - 65 + 74 NS.objects CF$UID - 66 + 75 CF$UID - 67 + 76 CF$UID - 68 + 77 @@ -706,36 +791,36 @@ $class CF$UID - 37 + 39 NS.keys CF$UID - 63 + 72 CF$UID - 64 + 73 CF$UID - 65 + 74 NS.objects CF$UID - 70 + 79 CF$UID - 71 + 80 CF$UID - 72 + 81 @@ -746,36 +831,36 @@ $class CF$UID - 37 + 39 NS.keys CF$UID - 63 + 72 CF$UID - 64 + 73 CF$UID - 65 + 74 NS.objects CF$UID - 74 + 83 CF$UID - 75 + 84 CF$UID - 76 + 85 @@ -785,7 +870,7 @@ $class CF$UID - 77 + 86 NS.data @@ -821,60 +906,60 @@ $class CF$UID - 37 + 39 NS.keys CF$UID - 63 + 72 CF$UID - 79 + 88 CF$UID - 80 + 89 CF$UID - 65 + 74 CF$UID - 81 + 90 CF$UID - 82 + 91 NS.objects CF$UID - 83 + 92 CF$UID - 84 + 93 CF$UID - 85 + 94 CF$UID - 87 + 96 CF$UID - 84 + 93 CF$UID - 84 + 93 @@ -888,10 +973,10 @@ $class CF$UID - 86 + 95 NS.time - 322656379.44809198 + 324225926.14380801 $classes @@ -902,7 +987,7 @@ $classname NSDate - Today at 11:46 + Today at 16:45 $classes @@ -914,11 +999,12 @@ NSMutableArray 106 + Pong $class CF$UID - 88 + 97 NS.objects @@ -932,25 +1018,29 @@ $class CF$UID - 88 + 97 NS.objects CF$UID - 92 + 102 CF$UID - 94 + 104 CF$UID - 96 + 106 CF$UID - 98 + 108 + + + CF$UID + 110 @@ -968,10 +1058,10 @@ NS.relative CF$UID - 93 + 103 - file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/GGSNetwork.m + file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/PongViewController.m $class @@ -986,25 +1076,7 @@ NS.relative CF$UID - 95 - - - file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/GGSNetwork.h - - $class - - CF$UID - 22 - - NS.base - - CF$UID - 0 - - NS.relative - - CF$UID - 97 + 105 file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/PongAppDelegate.m @@ -1022,7 +1094,43 @@ NS.relative CF$UID - 99 + 107 + + + file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/GGSNetwork.m + + $class + + CF$UID + 22 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 109 + + + file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/GGSNetwork.h + + $class + + CF$UID + 22 + + NS.base + + CF$UID + 0 + + NS.relative + + CF$UID + 111 file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/PongAppDelegate.h @@ -1030,44 +1138,21 @@ $class CF$UID - 37 + 39 NS.keys CF$UID - 101 + 113 CF$UID - 102 + 114 CF$UID - 103 - - - CF$UID - 104 - - - CF$UID - 105 - - - CF$UID - 106 - - - NS.objects - - - CF$UID - 107 - - - CF$UID - 274 + 115 CF$UID @@ -1075,11 +1160,34 @@ CF$UID - 101 + 117 CF$UID - 275 + 118 + + + NS.objects + + + CF$UID + 119 + + + CF$UID + 285 + + + CF$UID + 219 + + + CF$UID + 113 + + + CF$UID + 286 CF$UID @@ -1097,137 +1205,18 @@ $class CF$UID - 37 + 39 NS.keys - - - CF$UID - 108 - - - CF$UID - 109 - - - CF$UID - 110 - - - CF$UID - 111 - - - CF$UID - 112 - - - CF$UID - 113 - - - CF$UID - 114 - - - CF$UID - 115 - - - NS.objects - - - CF$UID - 48 - - - CF$UID - 116 - - - CF$UID - 117 - - - CF$UID - 118 - - - CF$UID - 129 - - - CF$UID - 171 - - - CF$UID - 15 - - - CF$UID - 180 - - - - AssistantEditorsLayout - IDEShowNavigator - IDETabLabel - IDEWorkspaceTabControllerUtilityAreaSplitView - IDENavigatorArea - IDEWorkspaceTabControllerDesignAreaSplitView - IDEShowUtilities - IDEEditorArea - - GGSNetwork.m - - $class - - CF$UID - 37 - - NS.keys - - - CF$UID - 119 - - - NS.objects CF$UID 120 - - - DVTSplitViewItems - - $class - - CF$UID - 88 - - NS.objects - CF$UID 121 - - CF$UID - 127 - - - - - $class - - CF$UID - 126 - - NS.keys - CF$UID 122 @@ -1236,9 +1225,6 @@ CF$UID 123 - - NS.objects - CF$UID 124 @@ -1247,59 +1233,71 @@ CF$UID 125 - - - DVTIdentifier - DVTViewMagnitude - - 478 - - $classes - - NSDictionary - NSObject - - $classname - NSDictionary - - - $class - - CF$UID - 126 - - NS.keys - CF$UID - 122 + 126 CF$UID - 123 + 127 NS.objects CF$UID - 124 + 128 CF$UID - 128 + 219 + + + CF$UID + 57 + + + CF$UID + 229 + + + CF$UID + 236 + + + CF$UID + 275 + + + CF$UID + 15 + + + CF$UID + 284 - 224 + IDEEditorArea + IDEShowNavigator + AssistantEditorsLayout + IDEWorkspaceTabControllerUtilityAreaSplitView + IDENavigatorArea + IDEWorkspaceTabControllerDesignAreaSplitView + IDEShowUtilities + IDETabLabel $class CF$UID - 37 + 39 NS.keys + + CF$UID + 129 + CF$UID 130 @@ -1312,34 +1310,10 @@ CF$UID 132 - - NS.objects - CF$UID 133 - - CF$UID - 132 - - - CF$UID - 151 - - - - Xcode.IDEKit.Navigator.Issues - SelectedNavigator - Xcode.IDEKit.Navigator.Structure - - $class - - CF$UID - 37 - - NS.keys - CF$UID 134 @@ -1352,546 +1326,40 @@ CF$UID 136 + + NS.objects + CF$UID 137 - - CF$UID - 138 - - - CF$UID - 139 - - - CF$UID - 140 - - - CF$UID - 141 - - - CF$UID - 142 - - - CF$UID - 143 - - - NS.objects - - - CF$UID - 15 - - - CF$UID - 144 - - - CF$UID - 145 - - - CF$UID - 147 - - - CF$UID - 148 - - - CF$UID - 15 - - - CF$UID - 15 - - - CF$UID - 149 - - - CF$UID - 15 - - - CF$UID - 150 - - - - IDEErrorFilteringEnabled - IDEVisibleRect - IDECollapsedFiles - IDEExpandedIssues - IDESelectedNavigables - IDEShowsByType - IDESchemeFilteringEnabled - IDECollapsedTypes - IDERecentFilteringEnabled - IDECollapsedGroups - {{0, 0}, {259, 636}} - - $class - - CF$UID - 146 - - NS.objects - - - - $classes - - NSMutableSet - NSSet - NSObject - - $classname - NSMutableSet - - - $class - - CF$UID - 146 - - NS.objects - - - - $class - - CF$UID - 88 - - NS.objects - - - - $class - - CF$UID - 146 - - NS.objects - - - - $class - - CF$UID - 146 - - NS.objects - - - - $class - - CF$UID - 37 - - NS.keys - - - CF$UID - 152 - - - CF$UID - 153 - - - CF$UID - 154 - - - CF$UID - 155 - - - CF$UID - 156 - CF$UID 157 CF$UID - 158 - - - NS.objects - - - CF$UID - 159 + 190 CF$UID - 15 + 219 CF$UID - 160 + 57 CF$UID - 15 + 220 CF$UID - 15 + 228 CF$UID - 162 - - - CF$UID - 168 - - - - IDEVisibleRect - IDEUnsavedDocumentFilteringEnabled - IDENavigatorExpandedItemsBeforeFilteringSet - IDERecentDocumentFilteringEnabled - IDESCMStatusFilteringEnabled - IDESelectedObjects - IDEExpandedItemsSet - {{0, 0}, {259, 658}} - - $class - - CF$UID - 161 - - NS.objects - - - - $classes - - NSSet - NSObject - - $classname - NSSet - - - $class - - CF$UID - 167 - - NS.objects - - - CF$UID - 163 - - - - - $class - - CF$UID - 88 - - NS.objects - - - CF$UID - 164 - - - CF$UID - 165 - - - CF$UID - 166 - - - - Pong - Classes - GGSNetwork.m - - $classes - - NSArray - NSObject - - $classname - NSArray - - - $class - - CF$UID - 161 - - NS.objects - - - CF$UID - 169 - - - CF$UID - 170 - - - - - $class - - CF$UID - 88 - - NS.objects - - - CF$UID - 164 - - - - - $class - - CF$UID - 88 - - NS.objects - - - CF$UID - 164 - - - CF$UID - 165 - - - - - $class - - CF$UID - 37 - - NS.keys - - - CF$UID - 119 - - - NS.objects - - - CF$UID - 172 - - - - - $class - - CF$UID - 88 - - NS.objects - - - CF$UID - 173 - - - CF$UID - 175 - - - CF$UID - 177 - - - - - $class - - CF$UID - 126 - - NS.keys - - - CF$UID - 122 - - - CF$UID - 123 - - - NS.objects - - - CF$UID - 112 - - - CF$UID - 174 - - - - 260 - - $class - - CF$UID - 126 - - NS.keys - - - CF$UID - 122 - - - CF$UID - 123 - - - NS.objects - - - CF$UID - 115 - - - CF$UID - 176 - - - - 1016 - - $class - - CF$UID - 126 - - NS.keys - - - CF$UID - 122 - - - CF$UID - 123 - - - NS.objects - - - CF$UID - 178 - - - CF$UID - 179 - - - - IDEUtilitiesArea - 260 - - $class - - CF$UID - 37 - - NS.keys - - - CF$UID - 181 - - - CF$UID - 182 - - - CF$UID - 183 - - - CF$UID - 184 - - - CF$UID - 185 - - - CF$UID - 186 - - - CF$UID - 187 - - - CF$UID - 188 - - - NS.objects - - - CF$UID - 189 - - - CF$UID - 206 - - - CF$UID - 239 - - - CF$UID - 116 - - - CF$UID - 48 - - - CF$UID - 265 - - - CF$UID - 273 - - - CF$UID - 116 + 219 @@ -1907,7 +1375,7 @@ $class CF$UID - 205 + 156 geniusEditorContextNode @@ -1917,19 +1385,19 @@ primaryEditorContextNode CF$UID - 190 + 138 rootLayoutTreeNode CF$UID - 202 + 153 $class CF$UID - 204 + 155 children @@ -1941,41 +1409,41 @@ documentArchivableRepresentation CF$UID - 191 + 139 orientation 0 parent CF$UID - 202 + 153 $class CF$UID - 201 + 152 DocumentLocation CF$UID - 199 + 150 DomainIdentifier CF$UID - 192 + 140 IdentifierPath CF$UID - 193 + 141 IndexOfDocumentIdentifier CF$UID - 48 + 57 Xcode.IDENavigableItemDomain.WorkspaceStructure @@ -1983,21 +1451,21 @@ $class CF$UID - 167 + 149 NS.objects CF$UID - 194 + 142 CF$UID - 196 + 145 CF$UID - 197 + 147 @@ -2005,14 +1473,15 @@ $class CF$UID - 195 + 144 Identifier CF$UID - 166 + 143 + PongViewController.m $classes @@ -2026,32 +1495,42 @@ $class CF$UID - 195 + 144 Identifier CF$UID - 165 + 146 + Classes $class CF$UID - 195 + 144 Identifier CF$UID - 198 + 148 Pong + + $classes + + NSArray + NSObject + + $classname + NSArray + $class CF$UID - 200 + 151 documentURL @@ -2086,12 +1565,12 @@ $class CF$UID - 204 + 155 children CF$UID - 203 + 154 contentType 0 @@ -2112,13 +1591,13 @@ $class CF$UID - 167 + 149 NS.objects CF$UID - 190 + 138 @@ -2144,20 +1623,20 @@ $class CF$UID - 37 + 39 NS.keys CF$UID - 207 + 158 NS.objects CF$UID - 208 + 159 @@ -2166,20 +1645,20 @@ $class CF$UID - 37 + 39 NS.keys CF$UID - 209 + 160 NS.objects CF$UID - 210 + 161 @@ -2188,36 +1667,36 @@ $class CF$UID - 126 + 184 NS.keys CF$UID - 211 + 162 CF$UID - 212 + 163 CF$UID - 213 + 164 NS.objects CF$UID - 214 + 165 CF$UID - 48 + 57 CF$UID - 237 + 188 @@ -2228,13 +1707,13 @@ $class CF$UID - 167 + 149 NS.objects CF$UID - 215 + 166 @@ -2242,68 +1721,68 @@ $class CF$UID - 37 + 39 NS.keys CF$UID - 216 + 167 CF$UID - 217 + 168 CF$UID - 218 + 169 CF$UID - 219 + 170 CF$UID - 220 + 171 CF$UID - 221 + 172 CF$UID - 222 + 173 NS.objects CF$UID - 223 + 174 CF$UID - 224 + 175 CF$UID - 230 + 181 CF$UID - 234 + 143 CF$UID - 166 + 143 CF$UID - 17 + 185 CF$UID - 235 + 186 @@ -2319,48 +1798,48 @@ $class CF$UID - 201 + 152 DocumentLocation CF$UID - 199 + 150 DomainIdentifier CF$UID - 192 + 140 IdentifierPath CF$UID - 225 + 176 IndexOfDocumentIdentifier CF$UID - 48 + 57 $class CF$UID - 167 + 149 NS.objects CF$UID - 226 + 177 CF$UID - 227 + 178 CF$UID - 228 + 179 @@ -2368,36 +1847,36 @@ $class CF$UID - 195 + 144 Identifier CF$UID - 166 + 143 $class CF$UID - 195 + 144 Identifier CF$UID - 165 + 146 $class CF$UID - 195 + 144 Identifier CF$UID - 229 + 180 Pong @@ -2405,36 +1884,36 @@ $class CF$UID - 126 + 184 NS.keys CF$UID - 30 + 41 CF$UID - 31 + 42 CF$UID - 32 + 43 CF$UID - 33 + 44 NS.objects CF$UID - 231 + 182 CF$UID - 232 + 183 CF$UID @@ -2442,14 +1921,22 @@ CF$UID - 233 + 38 - 322690356.25804502 - {369, 1381} - {207, 0} - @implementation GGSNetwork + 324225931.14565301 + {362, 1156} + + $classes + + NSDictionary + NSObject + + $classname + NSDictionary + + Xcode.IDEKit.EditorDocument.SourceCode $class @@ -2464,21 +1951,21 @@ NS.relative CF$UID - 236 + 187 - file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/GGSNetwork.m + file://localhost/Users/jeena/Student/GGS/games/Pong/Classes/PongViewController.m $class CF$UID - 167 + 149 NS.objects CF$UID - 238 + 189 @@ -2487,14 +1974,578 @@ $class CF$UID - 37 + 39 NS.keys + + + CF$UID + 191 + + + CF$UID + 192 + + + CF$UID + 193 + + + CF$UID + 194 + + + CF$UID + 195 + + + CF$UID + 196 + + + NS.objects + + + CF$UID + 93 + + + CF$UID + 197 + + + CF$UID + 199 + + + CF$UID + 93 + + + CF$UID + 202 + + + CF$UID + 213 + + + + LayoutFocusMode + console + variables + LayoutMode + IDEDebuggerAreaSplitView + IDEDebugArea_SplitView + + $class + + CF$UID + 39 + + NS.keys + + + CF$UID + 198 + + + NS.objects + + + CF$UID + 57 + + + + ConsoleFilterMode + + $class + + CF$UID + 39 + + NS.keys + + + CF$UID + 200 + + + NS.objects + + + CF$UID + 201 + + + + DBGVariablesViewFilterMode + 2 + + $class + + CF$UID + 39 + + NS.keys + + + CF$UID + 203 + + + NS.objects + + + CF$UID + 204 + + + + DVTSplitViewItems + + $class + + CF$UID + 97 + + NS.objects + + + CF$UID + 205 + + + CF$UID + 210 + + + + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 208 + + + CF$UID + 209 + + + + DVTIdentifier + DVTViewMagnitude + VariablesView + 298 + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 211 + + + CF$UID + 212 + + + + ConsoleArea + 717 + + $class + + CF$UID + 39 + + NS.keys + + + CF$UID + 203 + + + NS.objects + + + CF$UID + 214 + + + + + $class + + CF$UID + 97 + + NS.objects + + + CF$UID + 215 + + + CF$UID + 217 + + + + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 208 + + + CF$UID + 216 + + + + 298 + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 211 + + + CF$UID + 218 + + + + 717 + + + $class + + CF$UID + 39 + + NS.keys + + + CF$UID + 203 + + + NS.objects + + + CF$UID + 221 + + + + + $class + + CF$UID + 97 + + NS.objects + + + CF$UID + 222 + + + CF$UID + 225 + + + + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 223 + + + CF$UID + 224 + + + + IDEEditor + 587 + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 226 + + + CF$UID + 227 + + + + IDEDebuggerArea + 115 + + $class + + CF$UID + 39 + + NS.keys + + NS.objects + + + + $class + + CF$UID + 39 + + NS.keys + + + CF$UID + 203 + + + NS.objects + + + CF$UID + 230 + + + + + $class + + CF$UID + 97 + + NS.objects + + + CF$UID + 231 + + + CF$UID + 234 + + + + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 232 + + + CF$UID + 233 + + + + + 478 + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 232 + + + CF$UID + 235 + + + + 224 + + $class + + CF$UID + 39 + + NS.keys + + + CF$UID + 237 + + + CF$UID + 238 + + + CF$UID + 239 + + + NS.objects CF$UID 240 + + CF$UID + 239 + + + CF$UID + 258 + + + + Xcode.IDEKit.Navigator.Issues + SelectedNavigator + Xcode.IDEKit.Navigator.Structure + + $class + + CF$UID + 39 + + NS.keys + CF$UID 241 @@ -2515,144 +2566,41 @@ CF$UID 245 - - NS.objects - - - CF$UID - 84 - CF$UID 246 + + CF$UID + 247 + CF$UID 248 CF$UID - 84 + 249 + + + CF$UID + 250 + + + NS.objects + + + CF$UID + 15 CF$UID 251 - - CF$UID - 259 - - - - LayoutFocusMode - console - variables - LayoutMode - IDEDebugArea_SplitView - IDEDebuggerAreaSplitView - - $class - - CF$UID - 37 - - NS.keys - - - CF$UID - 247 - - - NS.objects - - - CF$UID - 48 - - - - ConsoleFilterMode - - $class - - CF$UID - 37 - - NS.keys - - - CF$UID - 249 - - - NS.objects - - - CF$UID - 250 - - - - DBGVariablesViewFilterMode - 2 - - $class - - CF$UID - 37 - - NS.keys - - - CF$UID - 119 - - - NS.objects - CF$UID 252 - - - - $class - - CF$UID - 88 - - NS.objects - - - CF$UID - 253 - - - CF$UID - 256 - - - - - $class - - CF$UID - 126 - - NS.keys - - - CF$UID - 122 - - - CF$UID - 123 - - - NS.objects - CF$UID 254 @@ -2661,151 +2609,129 @@ CF$UID 255 - - - VariablesView - 298 - - $class - - CF$UID - 126 - - NS.keys - CF$UID - 122 + 15 CF$UID - 123 + 15 + + + CF$UID + 256 + + + CF$UID + 15 - - NS.objects - CF$UID 257 - - CF$UID - 258 - - ConsoleArea - 717 + IDEErrorFilteringEnabled + IDEVisibleRect + IDECollapsedFiles + IDEExpandedIssues + IDESelectedNavigables + IDEShowsByType + IDESchemeFilteringEnabled + IDECollapsedTypes + IDERecentFilteringEnabled + IDECollapsedGroups + {{0, 0}, {259, 636}} $class CF$UID - 37 + 253 + + NS.objects + + + + $classes + + NSMutableSet + NSSet + NSObject + + $classname + NSMutableSet + + + $class + + CF$UID + 253 + + NS.objects + + + + $class + + CF$UID + 97 + + NS.objects + + + + $class + + CF$UID + 253 + + NS.objects + + + + $class + + CF$UID + 253 + + NS.objects + + + + $class + + CF$UID + 39 NS.keys CF$UID - 119 + 259 - - NS.objects - CF$UID 260 - - - - $class - - CF$UID - 88 - - NS.objects - CF$UID 261 - - CF$UID - 263 - - - - - $class - - CF$UID - 126 - - NS.keys - - - CF$UID - 122 - - - CF$UID - 123 - - - NS.objects - - - CF$UID - 254 - CF$UID 262 - - - 298 - - $class - - CF$UID - 126 - - NS.keys - CF$UID - 122 - - - CF$UID - 123 - - - NS.objects - - - CF$UID - 257 + 263 CF$UID 264 - - - 717 - - $class - - CF$UID - 37 - - NS.keys - CF$UID - 119 + 265 NS.objects @@ -2814,20 +2740,66 @@ CF$UID 266 + + CF$UID + 15 + + + CF$UID + 267 + + + CF$UID + 15 + + + CF$UID + 15 + + + CF$UID + 269 + + + CF$UID + 272 + + IDEVisibleRect + IDEUnsavedDocumentFilteringEnabled + IDENavigatorExpandedItemsBeforeFilteringSet + IDERecentDocumentFilteringEnabled + IDESCMStatusFilteringEnabled + IDESelectedObjects + IDEExpandedItemsSet + {{0, 0}, {259, 658}} + + $class + + CF$UID + 268 + + NS.objects + + + + $classes + + NSSet + NSObject + + $classname + NSSet + $class CF$UID - 88 + 149 NS.objects - - CF$UID - 267 - CF$UID 270 @@ -2838,50 +2810,8 @@ $class CF$UID - 126 + 97 - NS.keys - - - CF$UID - 122 - - - CF$UID - 123 - - - NS.objects - - - CF$UID - 268 - - - CF$UID - 269 - - - - IDEEditor - 587 - - $class - - CF$UID - 126 - - NS.keys - - - CF$UID - 122 - - - CF$UID - 123 - - NS.objects @@ -2890,34 +2820,211 @@ CF$UID - 272 + 146 + + + CF$UID + 143 - IDEDebuggerArea - 115 + Pong $class CF$UID - 37 - - NS.keys - - NS.objects - - - - $class - - CF$UID - 167 + 268 NS.objects CF$UID - 101 + 273 + + + CF$UID + 274 + + + + + $class + + CF$UID + 97 + + NS.objects + + + CF$UID + 271 + + + + + $class + + CF$UID + 97 + + NS.objects + + + CF$UID + 271 + + + CF$UID + 146 + + + + + $class + + CF$UID + 39 + + NS.keys + + + CF$UID + 203 + + + NS.objects + + + CF$UID + 276 + + + + + $class + + CF$UID + 97 + + NS.objects + + + CF$UID + 277 + + + CF$UID + 279 + + + CF$UID + 281 + + + + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 124 + + + CF$UID + 278 + + + + 260 + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 120 + + + CF$UID + 280 + + + + 1016 + + $class + + CF$UID + 184 + + NS.keys + + + CF$UID + 206 + + + CF$UID + 207 + + + NS.objects + + + CF$UID + 282 + + + CF$UID + 283 + + + + IDEUtilitiesArea + 260 + PongViewController.m + + $class + + CF$UID + 149 + + NS.objects + + + CF$UID + 113 diff --git a/src/ggs_player.erl b/src/ggs_player.erl index e80aeac..9b31d3d 100644 --- a/src/ggs_player.erl +++ b/src/ggs_player.erl @@ -25,11 +25,10 @@ %% identifying the player. %% @spec start_link(Socket::socket()) -> {ok, Pid} | {error, Reason} start(Socket) -> - erlang:display("start_link"), gen_server:start(?MODULE, [Socket], []). init([Socket]) -> - {ok, Protocol} = ggs_protocol:start_link(Socket, self()), + {ok, Protocol} = ggs_protocol:start_link(), {ok, Token} = ggs_coordinator:join_lobby(), case ggs_coordinator:join_table("1337") of @@ -75,20 +74,24 @@ stop(Player) -> gen_server:cast(Player, stop). %% Internals - -handle_call({notify, Message}, _From, #state { protocol = Protocol } = State) -> - ggs_protocol:send_command(Protocol, Message), - {noreplay, State}; +handle_call({notify, Message}, _From, #state { protocol = Protocol, socket = Socket } = State) -> + gen_tcp:send(Socket, ggs_protocol:create_message(Protocol, Message)), + {noreply, State}; handle_call({game_cmd, Command, _Headers, Data}, _From, #state { table = Table } = State) -> ggs_table:notify(Table, self(), {game, Command, Data}), - {noreplay, State}; + {noreply, State}; handle_call({srv_cmd, "define", _Headers, Data}, _From, #state { table = Table } = State) -> ggs_table:notify(Table, self(), {server, define, Data}), - {noreplay, State}; + {noreply, State}; + handle_call(_Request, _From, St) -> {stop, unimplemented, St}. + +handle_cast({tcp, _Socket, Data}, #state { protocol = Protocol } = _State) -> + ggs_protocol:parse(Protocol, Data); + handle_cast(_Request, St) -> {stop, unimplemented, St}. handle_info(_Info, St) -> {stop, unimplemented, St}. diff --git a/src/ggs_protocol.erl b/src/ggs_protocol.erl index f561ff7..b0c16e8 100644 --- a/src/ggs_protocol.erl +++ b/src/ggs_protocol.erl @@ -1,121 +1,62 @@ -%%% @doc This module handles TCP incomming and outcommint. +%% 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). --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]). +-export([parse/2, getToken/1, create_message/4, + expect_headers/2, expect_data_section/2, + expect_headers/3, expect_data_section/3]). --vsn(1.0). +%% tests +-export([to_dictionary/2]). --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}. +% gen_fsm callbacks.. +-export([init/1, handle_info/2, terminate/2, code_change/3, start_link/0]). -terminate(_Reason, _St) -> ok. -code_change(_OldVsn, St, _Extra) -> {ok, St}. +-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 -parse(Data) -> - do_parse(Data, []). - +parse(Protocol, Data) -> + lists:foreach(fun(X) -> + gen_fsm:sync_send_event(Protocol, {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) -> + case lists:keyfind(token, 1, Parsed) of {_, Value} -> Value; false -> false end. - +create_message({Command, Data}) -> + create_message(Command, "text", "text", Data). +%% Assemble a message which can b +%e used as a reply to a client create_message(Cmd, Enc, Acc, Data) -> Length = integer_to_list(string:len(Data)), Msg = "Client-Command: " ++ Cmd ++ "\n" ++ @@ -127,41 +68,64 @@ create_message(Cmd, Enc, Acc, Data) -> Data, Msg. -%% Internal helpers -do_parse(Data, Headers) -> - NewLinePos = string:chr(Data, $\n), - Line = string:substr(Data, 1, NewLinePos-1), - Tokens = re:split(Line, ": ", [{return, list}]), - case handle(Tokens) of - {Command, more} -> - do_parse(string:substr(Data, NewLinePos+1), Headers ++ [Command]); - {separator, data_next} -> - {Headers, Data} +%%% Transitions +expect_headers(_Event, State) -> + {next_state, expect_headers, State}. +expect_data_section(_Event, State) -> + {next_state, expect_data_section, State}. + + +%%% End transitions +expect_headers({char, $\n}, _From, {Strings, Remains}) -> + [LastMessage|_] = Strings, + case LastMessage of + "" -> % We have a data section.. Last line should thus be the content length. + [LastMessage, SecondLast | _] = Strings, + case re:split(SecondLast, ": ", [{return, list}]) of + ["Content-Length", X] -> + {Int,_} = string:to_integer(X), + {reply, ok, expect_data_section, {[""|Strings], Int}}; + Other -> ok + 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. - -handle([[]]) -> - {separator, data_next}; -handle(["Server-Command", Param]) -> - {{srv_cmd, Param}, more}; -handle(["Game-Command", Param]) -> - {{game_cmd, Param}, more}; -handle(["Content-Length", Param]) -> - {{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)}. +handle_call(_Msg, _From, State) -> + {noreply, State}. +handle_info(_Msg, State) -> + {noreply, State}. +terminate(_Reason, _State) -> + ok. +code_change(_OldVsn, State, _Extra) -> + {ok, State}. prettify(Args, Data) -> - case lists:keyfind(srv_cmd, 1, Args) of + case lists:keyfind("Server-Command", 1, Args) of {_, Value} -> {srv_cmd, Value, Args, Data}; _Other -> - case lists:keyfind(game_cmd, 1, Args) of + case lists:keyfind("Game-Command", 1, Args) of {_, Value} -> {game_cmd, Value, Args, Data}; _ -> @@ -169,3 +133,9 @@ prettify(Args, Data) -> end end. +to_dictionary([], Dict) -> + Dict; +to_dictionary([S|Strings], Dict) -> + [First, Snd] = re:split(S, ": ", [{return, list}]), + to_dictionary(Strings, [{First, Snd}|Dict]). + diff --git a/src/ggs_protocol.erl.orig b/src/ggs_protocol.erl.orig new file mode 100644 index 0000000..61d424b --- /dev/null +++ b/src/ggs_protocol.erl.orig @@ -0,0 +1,146 @@ +%% 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). +-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 +parse(Data) -> + lists:foreach(fun(X) -> + 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) -> + + case lists:keyfind(token, 1, Parsed) of + {_, Value} -> + Value; + false -> + false + end. +<<<<<<< Updated upstream +======= + + +create_message(Protocol, {Command, Data}) { + create_message(Command, "text", "text", Data), +} +>>>>>>> Stashed changes + +%% Assemble a message which can b +%e used as a reply to a client +create_message(Cmd, Enc, Acc, Data) -> + Length = integer_to_list(string:len(Data)), + Msg = "Client-Command: " ++ Cmd ++ "\n" ++ + "Client-Encoding: " ++ Enc ++ "\n" ++ + "Content-Size: " ++ Length ++ "\n" ++ + "GGS-Version: 1.0\n" ++ + "Accept: " ++ Acc ++ "\n" ++ + "\n" ++ + Data, + Msg. + +%%% Transitions +expect_headers(_Event, State) -> + {next_state, expect_headers, State}. +expect_data_section(_Event, State) -> + {next_state, expect_data_section, State}. + + +%%% End transitions +expect_headers({char, $\n}, _From, {Strings, Remains}) -> + [LastMessage|_] = Strings, + case LastMessage of + "" -> % We have a data section.. Last line should thus be the content length. + [LastMessage, SecondLast | _] = Strings, + case re:split(SecondLast, ": ", [{return, list}]) of + ["Content-Length", X] -> + {Int,_} = string:to_integer(X), + {reply, ok, expect_data_section, {[""|Strings], Int}}; + Other -> ok + 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. +handle_call(_Msg, _From, State) -> + {noreply, State}. +handle_info(_Msg, State) -> + {noreply, State}. +terminate(_Reason, _State) -> + ok. +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +prettify(Args, Data) -> + case lists:keyfind("Server-Command", 1, Args) of + {_, Value} -> + {srv_cmd, Value, Args, Data}; + _Other -> + case lists:keyfind("Game-Command", 1, Args) of + {_, Value} -> + {game_cmd, Value, Args, Data}; + _ -> + ok + end + end. + +to_dictionary([], Dict) -> + Dict; +to_dictionary([S|Strings], Dict) -> + [First, Snd] = re:split(S, ": ", [{return, list}]), + to_dictionary(Strings, [{First, Snd}|Dict]). + diff --git a/src/ggs_server.erl b/src/ggs_server.erl index 833feac..40bb88f 100644 --- a/src/ggs_server.erl +++ b/src/ggs_server.erl @@ -55,7 +55,7 @@ init([Port]) -> {ok, State#state{lsock = LSock}, 0} end. -handle_call({backup_state, OldState}, _From, State) -> +handle_call({backup_state, OldState}, _From, _State) -> io:format("Received old state from backup~n"), {noreply, OldState}. @@ -72,7 +72,7 @@ handle_info(timeout, #state{lsock = LSock} = State) -> {ok, _Sock} = gen_tcp:accept(LSock), {noreply, State}; -handle_info(Other, State) -> +handle_info(Other, _State) -> erlang:display(Other). terminate(_Reason, _State) -> @@ -106,13 +106,14 @@ handle_cast({srv_cmd, "call", Headers, Data}, State) -> % Set the new state to the reference generated, and JSVM associated %handle_cast({server, hello, Headers}, State) -> -handle_cast({srv_cmd, "hello", Headers, Data}, State) -> - GameToken = case proplist:get_value(game_token, Headers) of -> - undefined -> getNewToken(); - GT -> GT; +handle_cast({srv_cmd, "hello", Headers, _Data}, State) -> + GameToken = case proplist:get_value(game_token, Headers) of + undefined -> getNewToken(); + GT -> GT end, ClientToken = getNewToken(), OldMap = State#state.client_vm_map, + GameVM = getJSVM(ClientToken, State), NewState = State#state{client_vm_map = OldMap ++ [{ClientToken, GameVM, GameToken}]}, gen_server:cast(ggs_backup, {set_backup, NewState}), {noreply, NewState}. From 087dde8916de3d345550e3f87e772244af929dec Mon Sep 17 00:00:00 2001 From: Jeena Paradies Date: Mon, 11 Apr 2011 17:33:33 +0200 Subject: [PATCH 7/7] removed fsm name --- src/ggs_protocol.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ggs_protocol.erl b/src/ggs_protocol.erl index b0c16e8..03acddd 100644 --- a/src/ggs_protocol.erl +++ b/src/ggs_protocol.erl @@ -33,8 +33,8 @@ parse(Protocol, Data) -> start_link() -> - gen_fsm:start_link({local, ?SERVER}, ?MODULE, [], []). - + gen_fsm:start_link(?MODULE, [], []). + % Start state: {[""],0}, meaning: % - Start with no strings parsed % - Start with a data-section-lengths of 0