changes to make joining a friends table possible
This commit is contained in:
parent
726528985b
commit
8a574db360
8 changed files with 120 additions and 132 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ).
|
|
@ -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}.
|
|
@ -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}
|
||||
|
|
|
@ -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}.
|
||||
|
|
|
@ -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) ->
|
||||
|
|
Reference in a new issue