changes to make joining a friends table possible

This commit is contained in:
Jeena Paradies 2011-05-03 18:34:19 +02:00
parent 726528985b
commit 8a574db360
8 changed files with 120 additions and 132 deletions

View file

@ -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)

View file

@ -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

View file

@ -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 ).

View file

@ -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_call(_Message, _From, State) ->
{noreply, State}.
handle_cast({accepted, _Pid}, State) ->
{noreply, accept(State)}.
handle_cast(_Message, State) ->
{noreply, 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).
handle_info({tcp, _Socket, _Data}, State) ->
io:format("Got connect request! ~n"),
{noreply, State};
% 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_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}.

View file

@ -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}

View file

@ -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,52 +24,22 @@
%% 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.
%% @spec notify(Player::Pid(), From::Pid(),
@ -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}.

View file

@ -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) ->