From 8a574db360642fa764d15b30b7652def84884579 Mon Sep 17 00:00:00 2001 From: Jeena Paradies Date: Tue, 3 May 2011 18:34:19 +0200 Subject: [PATCH] changes to make joining a friends table possible --- games/JS-chat/chat.rb | 6 ++- games/JS-chat/chat_server.js | 2 +- games/JS-chat/ggs-network.rb | 18 +++++-- src/ggs_coordinator.erl | 52 +++++++----------- src/ggs_dispatcher.erl | 60 ++++++++++----------- src/ggs_gamevm.erl | 3 +- src/ggs_player.erl | 102 ++++++++++++++++------------------- src/ggs_protocol.erl | 9 +++- 8 files changed, 120 insertions(+), 132 deletions(-) diff --git a/games/JS-chat/chat.rb b/games/JS-chat/chat.rb index 2f2f6f5..3fbcdff 100644 --- a/games/JS-chat/chat.rb +++ b/games/JS-chat/chat.rb @@ -9,8 +9,10 @@ class Chat include GGSDelegate def initialize - @ggs_network = GGSNetwork.new(self) - @ggs_network.connect("localhost") + print "Table token (empty for new): " + table_token = gets.chomp + @ggs_network = GGSNetwork.new(self, table_token) + @ggs_network.connect("ggs.jeena.net", 9000) end def ggsNetworkReady(ggs_network, am_i_host) diff --git a/games/JS-chat/chat_server.js b/games/JS-chat/chat_server.js index 5d95c36..c5d72e7 100644 --- a/games/JS-chat/chat_server.js +++ b/games/JS-chat/chat_server.js @@ -20,4 +20,4 @@ function changeNick(player_id, nick) { function message(player_id, message) { var nick = GGS.localStorage.getItem("nick_" + player_id); GGS.sendCommandToAll('message', nick + "> " + message); -} \ No newline at end of file +} diff --git a/games/JS-chat/ggs-network.rb b/games/JS-chat/ggs-network.rb index 5d77a02..b807062 100644 --- a/games/JS-chat/ggs-network.rb +++ b/games/JS-chat/ggs-network.rb @@ -9,8 +9,10 @@ class GGSNetwork attr_accessor :delegate - def initialize(delegate) + def initialize(delegate, table_token="") + @table_token = table_token @delegate = delegate + @player_token = nil end def define(source_code) @@ -23,12 +25,14 @@ class GGSNetwork def connect(host='localhost', port=9000) @socket = TCPSocket.new(host, port) + write( makeMessage(SERVER, "hello", @table_token) ) read end protected def write(message) + #puts message @socket.write(message) end @@ -67,13 +71,16 @@ class GGSNetwork else @delegate.ggsNetworkReceivedCommandWithArgs(self, command, data) end + else + STDERR.print "ERR: " + [headers, data, @socket.inspect].inspect + "\n" end end def makeMessage(serverOrGame, command, args) - message = "Token: #{@game_token}\n" + - "#{serverOrGame}-Command: #{command}\n" + - "Content-Length: #{args.length}\n\n" + message = "" + message += "Token: #{@player_token}\n" unless @player_token.nil? + message += "#{serverOrGame}-Command: #{command}\n" + + "Content-Length: #{args.length}\n\n" message += args if args.length > 0 @@ -81,8 +88,9 @@ class GGSNetwork end def parse_hello(message) - @game_token, shall_define, @table_token = message.split(",") + @player_token, shall_define, @table_token = message.split(",") @am_i_host = shall_define == "true" + puts "Table-Token: " + @table_token end end diff --git a/src/ggs_coordinator.erl b/src/ggs_coordinator.erl index e90b0bb..99de9b7 100644 --- a/src/ggs_coordinator.erl +++ b/src/ggs_coordinator.erl @@ -4,7 +4,7 @@ -export([ start_link/0, stop/1, join_table/1, - create_table/1, + create_table/0, join_lobby/0, respawn_player/2, respawn_table/1, @@ -43,8 +43,8 @@ join_table(Token) -> gen_server:call(ggs_coordinator, {join_table, Token}). %% @doc Create a new table, return {error, Reason} or {ok, TableToken} -create_table(Params) -> - gen_server:call(ggs_coordinator, {create_table, Params}). +create_table() -> + gen_server:call(ggs_coordinator, create_table). %% @doc This is the first function run by a newly created players. %% Generates a unique token that we use to identify the player. @@ -109,46 +109,28 @@ handle_call(join_lobby, From, State) -> handle_call({join_table, Table}, From, State) -> {FromPlayer, _Ref} = From, - Tables = State#co_state.tables, - case lists:keyfind(Table, 1, Tables) of - {_TableID, TablePID} -> -% TP = TablePID, -% {ok, Players} = (gen_server:call(TP, get_player_list_raw)), % Hack.. deadlock otherwise? -% %Players = [1], -% NumPlayers = length(Players), -% case NumPlayers of -% PN when (PN < 2) -> ggs_table:add_player(TablePID, FromPlayer), -% back_up(State), -% {reply, {ok, TablePID}, State}; -% PN when (PN >= 2) -> {reply, {error, table_full}, State} % TODO: Fix this limit!! -% end; - {TableNum,_} = string:to_integer(Table), - %erlang:display(State#co_state.players), - CurrentPlayers = length(State#co_state.players), - SmallestTable = case (CurrentPlayers rem 2) of - 0 -> CurrentPlayers / 2; - 1 -> (CurrentPlayers / 2)+1 - end, - case (TableNum =< SmallestTable) of - true -> {reply , {error, table_full}, State}; - false -> ggs_table:add_player(TablePID, FromPlayer), - {reply, {ok, TablePID}, State} - end; + case lists:keyfind(Table, 1, State#co_state.tables) of + {TableID, TablePID} -> + % TODO check if table full + ggs_table:add_player(TablePID, FromPlayer), + {reply, {ok, TableID, TablePID}, State}; false -> back_up(State), {reply, {error, no_such_table}, State} end; - -handle_call({create_table, {force, TableToken}}, From, State) -> + +handle_call(create_table, From, State) -> + TableToken = getNewToken(), TableIDMap = State#co_state.player_table_map, Tables = State#co_state.tables, - NewTableProc = ggs_table:start(TableToken), % With start_link, the table dies with the coordinator + TablePid = ggs_table:start(TableToken), % With start_link, the table dies with the coordinator NewState = State#co_state{ player_table_map = [{From, TableToken} | TableIDMap], - tables = [{TableToken, NewTableProc} | Tables] + tables = [{TableToken, TablePid} | Tables] }, back_up(NewState), - {reply, {ok, TableToken}, NewState}; + {reply, {ok, TableToken, TablePid}, NewState}; + handle_call(get_all_players, _From, State) -> {reply, State#co_state.players, State}; @@ -196,3 +178,7 @@ terminate(normal, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. + +% helper +getNewToken() -> + string:strip(os:cmd("uuidgen"), right, $\n ). \ No newline at end of file diff --git a/src/ggs_dispatcher.erl b/src/ggs_dispatcher.erl index 0399c8f..ff4f9e7 100644 --- a/src/ggs_dispatcher.erl +++ b/src/ggs_dispatcher.erl @@ -7,7 +7,7 @@ %% gen_server callback exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, - code_change/3]). + code_change/3, accept_loop/1]). -define(SERVER, ?MODULE). @@ -35,34 +35,32 @@ stop(_Reason) -> ggs_logger:not_implemented(). %% @doc Initiate the dispatcher. This is called from gen_server init([Port]) -> - {ok, LSock} = gen_tcp:listen(Port, [{active, true}, - {reuseaddr, true}]), - {ok, LSock, 0}. + case gen_tcp:listen(Port, [{active, true}, {reuseaddr, true}]) of + {ok, LSock} -> + {ok, accept(LSock), 0}; + {error, Reason} -> + {stop, Reason} + end. + +handle_cast({accepted, _Pid}, State) -> + {noreply, accept(State)}. + +accept_loop({Server, LSocket}) -> + {ok, Socket} = gen_tcp:accept(LSocket), + % Let the server spawn a new process and replace this loop + % with the echo loop, to avoid blocking + gen_server:cast(Server, {accepted, self()}), + {ok, Player} = ggs_player:start(), + gen_tcp:controlling_process(Socket, Player), + ggs_player:save_socket(Player, Socket). + +% To be more robust we should be using spawn_link and trapping exits +accept(LSocket) -> + proc_lib:spawn(?MODULE, accept_loop, [{self(), LSocket}]), + LSocket. -handle_call(_Message, _From, State) -> - {noreply, State}. - -handle_cast(_Message, State) -> - {noreply, State}. - -handle_info({tcp, _Socket, _Data}, State) -> - io:format("Got connect request! ~n"), - {noreply, State}; - -handle_info({tcp_closed, Socket}, State) -> - gen_tcp:close(Socket), - {stop, "Client closed socket", State}; - -%% @doc This is our function for accepting connections. When a client connects, -%% it will immediately time out due to timing settings set in init and here, -%% and when it does, we accept the connection. -handle_info(timeout, LSock) -> - {ok, Socket} = gen_tcp:accept(LSock), - ggs_player:start(Socket), - {noreply, LSock, 0}. - -terminate(normal, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. +% These are just here to suppress warnings. +handle_call(_Msg, _Caller, State) -> {noreply, State}. +handle_info(_Msg, Library) -> {noreply, Library}. +terminate(_Reason, _Library) -> ok. +code_change(_OldVersion, Library, _Extra) -> {ok, Library}. \ No newline at end of file diff --git a/src/ggs_gamevm.erl b/src/ggs_gamevm.erl index 21ba9cd..a6ce71a 100644 --- a/src/ggs_gamevm.erl +++ b/src/ggs_gamevm.erl @@ -60,7 +60,7 @@ stop(GameVM) -> init([Table]) -> process_flag(trap_exit, true), - application:start(erlv8), % Start erlv8 + application:start(erlv8), % Start erlv8 FIXME: don't use a new VM every time, only a context {ok, VM} = erlv8_vm:start(), % Create a JavaScript vm Global = erlv8_vm:global(VM), % Retrieve JS global ggs_db:init(), % Initialize the database @@ -87,7 +87,6 @@ expose(Global, Table) -> {"key", fun(#erlv8_fun_invocation{}, [Position])-> ggs_db:key(Table, "world", Position) end} ])}, {"sendCommand", fun(#erlv8_fun_invocation{}, [Player, Command, Args])-> - erlang:display(Table), ggs_table:send_command(Table, Player, {Command, Args}) end}, {"sendCommandToAll", fun(#erlv8_fun_invocation{}, [Command, Args])-> ggs_table:notify_all_players(Table, {Command, Args}) end} diff --git a/src/ggs_player.erl b/src/ggs_player.erl index cb7723d..a7b9b36 100644 --- a/src/ggs_player.erl +++ b/src/ggs_player.erl @@ -10,7 +10,7 @@ -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]). +-export([start/0, stop/1, notify/3, notify_game/2, get_token/1, save_socket/2]). -vsn(1.0). @@ -24,51 +24,21 @@ %% 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(Socket) -> - gen_server:start(?MODULE, [Socket], []). +start() -> + gen_server:start(?MODULE, [], []). -join_table(Num) -> - case ggs_coordinator:join_table(integer_to_list(Num)) of - {ok, T} -> - %io:format("Joining existing table: ~p~n", [T]), - T; - {error, no_such_table} -> - case ggs_coordinator:create_table({force, integer_to_list(Num)}) of - {ok, _TBToken} -> ok - end, - case ggs_coordinator:join_table(integer_to_list(Num)) of - {ok, T} -> %io:format("Creating new table: ~p~n", [T]), - T; - {error, _E} -> %erlang:display(E), - join_table(Num+1) - end; - {error, table_full} -> - %erlang:display("Table full!"), - join_table(Num+1) - end. - -init([Socket]) -> +init([]) -> {ok, Protocol} = ggs_protocol:start_link(), {ok, Token} = ggs_coordinator:join_lobby(), - - erlang:port_connect(Socket, self()), - Table = join_table(1), State = #state{ token = Token, - socket = Socket, - table = Table, protocol = Protocol }, - - %ggs_protocol:parse(Protocol, Data), - TableToken = ggs_coordinator:table_pid_to_token(Table), - ShallDefine = case ggs_table:already_defined(Table) of - true -> "true"; - false -> "false" - end, - ggs_player:notify(self(), self(), {"hello", Token ++ "," ++ ShallDefine ++ "," ++ TableToken}), % send hello to the client {ok, State}. + +save_socket(Player, Socket) -> + gen_server:cast(Player, {save_socket, Socket}). %% @doc Handles incoming messages from the GGS and forwards them through the player %% socket to the player. @@ -97,48 +67,66 @@ stop(Player) -> %% Internals handle_call(_Request, _From, St) -> {stop, unimplemented, St}. +handle_cast({save_socket, Socket}, State) -> + {noreply, State#state { socket = Socket } }; handle_cast({tcp, _Socket, Data}, #state { protocol = Protocol } = State) -> ggs_protocol:parse(Protocol, Data), {noreply, State}; - -handle_cast({tcp_closed, _Socket}, State) -> - erlang:display("Client disconnected, but THIS IS NOT SUPPORTED YET!~n"), - {noreply, State}; - handle_cast({notify, Message}, #state { socket = Socket } = State) -> gen_tcp:send(Socket, ggs_protocol:create_message(Message)), {noreply, State}; - -handle_cast({srv_cmd, "hello", _Headers, _Data}, #state { token = Token, table = Table } = State) -> - ShallDefine = case ggs_table:already_defined(Table) of - true -> "true"; - false -> "false" +handle_cast({srv_cmd, "hello", _Headers, TableToken}, State) -> + Table = case TableToken of + "" -> + case ggs_coordinator:create_table() of + {ok, NewTableToken, TablePid} -> + ggs_coordinator:join_table(NewTableToken), + {ok, NewTableToken, TablePid}; + E -> + E + end; + _ -> + ggs_coordinator:join_table(TableToken) end, - TableToken = ggs_coordinator:table_pid_to_token(Table), - erlang:display("hello"), - ggs_player:notify(self(), self(), {"hello", "token="++ Token ++ "&define=" ++ ShallDefine ++ "&table_token=" ++ TableToken}), - {noreply, State}; - + erlang:display(Table), + case Table of + {error, Error} -> + ggs_player:notify(self(), self(), {"error", atom_to_list(Error)}), + {noreply, State}; + {ok, TT, TPid} -> + ShallDefine = case ggs_table:already_defined(TPid) of + true -> "true"; + false -> "false" + end, + ggs_player:notify(self(), self(), {"hello", State#state.token ++ "," ++ ShallDefine ++ "," ++ TT}), + {noreply, State#state{ table = TPid } } + end; handle_cast({srv_cmd, "define", _Headers, Data}, #state { table = Table } = State) -> ggs_table:notify(Table, self(), {server, define, Data}), {noreply, State}; - handle_cast({game_cmd, Command, _Headers, Data}, #state { table = Table } = State) -> ggs_table:notify(Table, self(), {game, Command, Data}), {noreply, State}; - +handle_cast(stop, State) -> + {stop, normal, State}; handle_cast(_Request, St) -> {stop, unimplemented1, St}. handle_info({tcp, _Socket, Data}, #state { protocol = Protocol } = State) -> ggs_protocol:parse(Protocol, Data), + {noreply, State}; +handle_info({tcp_closed, _Socket}, State) -> + erlang:display("Client disconnected, but THIS IS NOT SUPPORTED YET!~n"), + gen_server:cast(self(), stop), {noreply, State}. terminate(Reason, State) -> erlang:display(Reason), - ggs_table:remove_player(State#state.table, self()), + ggs_protocol:stop(State#state.protocol), + %ggs_table:remove_player(State#state.table, self()), + gen_tcp:close(State#state.socket), % ggs_coordinator:remove_player(self(), self()), % not implemented yet - % TODO: release Socket ok. -code_change(_OldVsn, St, _Extra) -> {ok, St}. +code_change(_OldVsn, St, _Extra) -> + {ok, St}. diff --git a/src/ggs_protocol.erl b/src/ggs_protocol.erl index c79995f..437a404 100644 --- a/src/ggs_protocol.erl +++ b/src/ggs_protocol.erl @@ -12,7 +12,7 @@ -export([to_dictionary/2]). % gen_fsm callbacks.. --export([init/1, handle_info/2, terminate/2, code_change/3, start_link/0]). +-export([init/1, handle_info/2, handle_event/3, terminate/2, code_change/3, start_link/0, stop/1]). %% API Functions @@ -23,6 +23,10 @@ parse(Protocol, Data) -> start_link() -> gen_fsm:start_link(?MODULE, [], []). +stop(Protocol) -> + gen_fsm:send_all_state_event(Protocol, stop). + + % Start state: {[""],0}, meaning: % - Start with no strings parsed % - Start with a data-section-lengths of 0 @@ -101,6 +105,9 @@ expect_data_section({char, Char}, From, {Strings, Remains}) -> %handle_call(_Msg, _From, State) -> % {noreply, State}. + +handle_event(stop, _StateName, StateData) -> + {stop, normal, StateData}. handle_info(_Msg, State) -> {noreply, State}. terminate(_Reason, _State) ->