fixed pong with new protocol
This commit is contained in:
commit
865c963ad6
19 changed files with 3974 additions and 110 deletions
1
Makefile
1
Makefile
|
|
@ -26,6 +26,7 @@ endif
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(BEAMDIR)/*.beam ;
|
rm -rf $(BEAMDIR)/*.beam ;
|
||||||
|
rm -rf $(SRCDIR)/*.beam ;
|
||||||
rm -rf erl_crush.dump ;
|
rm -rf erl_crush.dump ;
|
||||||
echo "==> clean ggs" ;
|
echo "==> clean ggs" ;
|
||||||
$(MAKE) -C erlang_js/ clean
|
$(MAKE) -C erlang_js/ clean
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ class GGSChat:
|
||||||
"Content-Type: text\n" +
|
"Content-Type: text\n" +
|
||||||
"Content-Length: 0\n"+
|
"Content-Length: 0\n"+
|
||||||
"\n")
|
"\n")
|
||||||
time.sleep(2)
|
#time.sleep(2)
|
||||||
|
|
||||||
def updateUsers(self, text):
|
def updateUsers(self, text):
|
||||||
evalNicks = eval(text)
|
evalNicks = eval(text)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
@implementation GGSNetwork
|
@implementation GGSNetwork
|
||||||
|
|
||||||
#define GGS_HOST @"jeena.net"
|
#define GGS_HOST @"home.jeena.net"
|
||||||
#define GGS_PORT 9000
|
#define GGS_PORT 9000
|
||||||
#define NO_TIMEOUT -1
|
#define NO_TIMEOUT -1
|
||||||
|
|
||||||
|
|
@ -32,13 +32,14 @@
|
||||||
@synthesize asyncSocket, delegate, gameToken, currentHeaders;
|
@synthesize asyncSocket, delegate, gameToken, currentHeaders;
|
||||||
|
|
||||||
- (id)initWithDelegate:(id<GGSDelegate>)_delegate {
|
- (id)initWithDelegate:(id<GGSDelegate>)_delegate {
|
||||||
if (self = [super init]) {
|
if ((self = [super init])) {
|
||||||
delegate = _delegate;
|
delegate = _delegate;
|
||||||
asyncSocket = [[AsyncSocket alloc] initWithDelegate:self];
|
asyncSocket = [[AsyncSocket alloc] initWithDelegate:self];
|
||||||
|
|
||||||
[asyncSocket connectToHost:GGS_HOST onPort:GGS_PORT error:nil];
|
[asyncSocket connectToHost:GGS_HOST onPort:GGS_PORT error:nil];
|
||||||
|
|
||||||
[asyncSocket readDataToData:HEADER_DELIMITER withTimeout:NO_TIMEOUT tag:HEAD];
|
[asyncSocket readDataToData:HEADER_DELIMITER withTimeout:NO_TIMEOUT tag:HEAD];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
|
@ -62,11 +63,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
|
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
|
||||||
|
//[asyncSocket writeData:[self makeMessageFor:@"Server" withCommand:@"hello" andArgs:@""] withTimeout:NO_TIMEOUT tag:NO_TAG];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onSocket:(AsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag {
|
- (void)onSocket:(AsyncSocket *)sender didReadData:(NSData *)data withTag:(long)tag {
|
||||||
|
|
||||||
if (tag == HEAD) {
|
if (tag == HEAD) {
|
||||||
[self parseAndSetHeader:data];
|
[self parseAndSetHeader:data];
|
||||||
|
|
||||||
|
|
@ -96,6 +97,7 @@
|
||||||
self.gameToken = response;
|
self.gameToken = response;
|
||||||
|
|
||||||
[delegate GGSNetwork:self ready:YES];
|
[delegate GGSNetwork:self ready:YES];
|
||||||
|
NSLog(@"%@", self.gameToken);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
[delegate GGSNetwork:self receivedCommand:command withArgs:response];
|
[delegate GGSNetwork:self receivedCommand:command withArgs:response];
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@
|
||||||
#pragma mark GGSNetwork Delegate
|
#pragma mark GGSNetwork Delegate
|
||||||
|
|
||||||
- (void)GGSNetwork:(GGSNetwork *)_ggsNetwork ready:(BOOL)ready {
|
- (void)GGSNetwork:(GGSNetwork *)_ggsNetwork ready:(BOOL)ready {
|
||||||
|
NSLog(@"ready");
|
||||||
[ggsNetwork sendCommand:@"ready" withArgs:@""];
|
[ggsNetwork sendCommand:@"ready" withArgs:@""];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
3150
games/Pong/Pong.xcodeproj/project.xcworkspace/xcuserdata/jeena.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
3150
games/Pong/Pong.xcodeproj/project.xcworkspace/xcuserdata/jeena.xcuserdatad/UserInterfaceState.xcuserstate
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
|
||||||
|
BuildableName = "Pong.app"
|
||||||
|
BlueprintName = "Pong"
|
||||||
|
ReferencedContainer = "container:Pong.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.GDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.GDB"
|
||||||
|
displayScaleIsEnabled = "NO"
|
||||||
|
displayScale = "1.00"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
<BuildableProductRunnable>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
|
||||||
|
BuildableName = "Pong.app"
|
||||||
|
BlueprintName = "Pong"
|
||||||
|
ReferencedContainer = "container:Pong.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
displayScaleIsEnabled = "NO"
|
||||||
|
displayScale = "1.00"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
buildConfiguration = "Release">
|
||||||
|
<BuildableProductRunnable>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "1D6058900D05DD3D006BFB54"
|
||||||
|
BuildableName = "Pong.app"
|
||||||
|
BlueprintName = "Pong"
|
||||||
|
ReferencedContainer = "container:Pong.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>Pong.xcscheme</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
<dict>
|
||||||
|
<key>1D6058900D05DD3D006BFB54</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
|
@ -60,7 +60,8 @@ while True:
|
||||||
|
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
"""
|
time.sleep(2)
|
||||||
|
|
||||||
HOST = 'localhost' # The remote host
|
HOST = 'localhost' # The remote host
|
||||||
PORT = int(sys.argv[1]) # The same port as used by the server
|
PORT = int(sys.argv[1]) # The same port as used by the server
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
@ -81,4 +82,3 @@ print "Token:", token
|
||||||
print "Data: ", ' '.join(data.split(" ")[1:])
|
print "Data: ", ' '.join(data.split(" ")[1:])
|
||||||
|
|
||||||
s.close()
|
s.close()
|
||||||
"""
|
|
||||||
|
|
|
||||||
23
python_client_reconnect
Executable file
23
python_client_reconnect
Executable file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import sys, time, socket
|
||||||
|
|
||||||
|
HOST = 'localhost' # The remote host
|
||||||
|
PORT = int(sys.argv[1]) # The same port as used by the server
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.connect((HOST, PORT))
|
||||||
|
# Call that function!
|
||||||
|
|
||||||
|
print "Calling myFun"
|
||||||
|
s.send(
|
||||||
|
"Token: %s\n\
|
||||||
|
Server-Command: call\n\
|
||||||
|
Content-Type: text\n\
|
||||||
|
Content-Length: 6\n\
|
||||||
|
\n\
|
||||||
|
myFun" % raw_input("Token >> "))
|
||||||
|
fs = s.makefile()
|
||||||
|
data = fs.readline()
|
||||||
|
print "Data: ", ' '.join(data.split(" ")[1:])
|
||||||
|
|
||||||
|
s.close()
|
||||||
|
|
@ -61,10 +61,8 @@ getItem(GameToken,Ns,Key) ->
|
||||||
mnesia:read(data, {GameToken,Ns,Key})
|
mnesia:read(data, {GameToken,Ns,Key})
|
||||||
end,
|
end,
|
||||||
case mnesia:transaction(Fun) of
|
case mnesia:transaction(Fun) of
|
||||||
{atomic, []} ->
|
{atomic, []} -> {error};
|
||||||
{error};
|
{atomic, [Ret]} -> Ret#data.value
|
||||||
{atomic, [Ret]} ->
|
|
||||||
Ret#data.value
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
length(GameToken,Ns) ->
|
length(GameToken,Ns) ->
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ handle_call(_Message, _From, State) ->
|
||||||
handle_cast(_Message, State) ->
|
handle_cast(_Message, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({tcp, _Socket, _RawData}, State) ->
|
handle_info({tcp, _Socket, _Data}, State) ->
|
||||||
io:format("Got connect request!~n"),
|
io:format("Got connect request! ~n"),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_info({tcp_closed, Socket}, State) ->
|
handle_info({tcp_closed, Socket}, State) ->
|
||||||
|
|
@ -57,8 +57,8 @@ handle_info({tcp_closed, Socket}, State) ->
|
||||||
%% it will immediately time out due to timing settings set in init and here,
|
%% it will immediately time out due to timing settings set in init and here,
|
||||||
%% and when it does, we accept the connection.
|
%% and when it does, we accept the connection.
|
||||||
handle_info(timeout, LSock) ->
|
handle_info(timeout, LSock) ->
|
||||||
{ok, Sock} = gen_tcp:accept(LSock),
|
{ok, Socket} = gen_tcp:accept(LSock),
|
||||||
spawn(ggs_player, start_link, [Sock]),
|
ggs_player:start(Socket),
|
||||||
{noreply, LSock, 0}.
|
{noreply, LSock, 0}.
|
||||||
|
|
||||||
terminate(normal, _State) ->
|
terminate(normal, _State) ->
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ define(GameVM, SourceCode) ->
|
||||||
%% Command = a game command to run
|
%% Command = a game command to run
|
||||||
%% Args = arguments for the Command parameter
|
%% Args = arguments for the Command parameter
|
||||||
player_command(GameVM, Player, Command, Args) ->
|
player_command(GameVM, Player, Command, Args) ->
|
||||||
|
erlang:display(Command),
|
||||||
gen_server:cast(GameVM, {player_command, Player, Command, Args}).
|
gen_server:cast(GameVM, {player_command, Player, Command, Args}).
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
|
|
@ -117,16 +118,19 @@ intern_player_command(Table, Player, Command, _Args) ->
|
||||||
|
|
||||||
intern_add_player(Table, Player) ->
|
intern_add_player(Table, Player) ->
|
||||||
{ok, PlayerList} = ggs_table:get_player_list(Table),
|
{ok, PlayerList} = ggs_table:get_player_list(Table),
|
||||||
|
erlang:display(PlayerList),
|
||||||
case length(PlayerList) of
|
case length(PlayerList) of
|
||||||
1 ->
|
1 ->
|
||||||
erlang:display("P1: joining"),
|
erlang:display("P1: joining"),
|
||||||
ggs_db:setItem(Table, local_storage, Player, player1),
|
ggs_db:setItem(Table, local_storage, Player, player1),
|
||||||
|
erlang:display(ggs_db:getItem(Table, local_storage, Player)),
|
||||||
ggs_db:setItem(Table, local_storage, player1_y, 50),
|
ggs_db:setItem(Table, local_storage, player1_y, 50),
|
||||||
ggs_table:send_command(Table, Player, {"welcome", int2str(1)}),
|
ggs_table:send_command(Table, Player, {"welcome", int2str(1)}),
|
||||||
ggs_table:notify_all_players(Table, {"player1_y", int2str(50)});
|
ggs_table:notify_all_players(Table, {"player1_y", int2str(50)});
|
||||||
2 ->
|
2 ->
|
||||||
erlang:display("P2: joining"),
|
erlang:display("P2: joining"),
|
||||||
ggs_db:setItem(Table, local_storage, Player, player2),
|
ggs_db:setItem(Table, local_storage, Player, player2),
|
||||||
|
erlang:display(ggs_db:getItem(Table, local_storage, Player)),
|
||||||
ggs_db:setItem(Table, local_storage, player2_y, 50),
|
ggs_db:setItem(Table, local_storage, player2_y, 50),
|
||||||
ggs_table:send_command(Table, Player, {"welcome", int2str(2)}),
|
ggs_table:send_command(Table, Player, {"welcome", int2str(2)}),
|
||||||
ggs_table:send_command(Table, Player, {"player1_y", int2str(50)}),
|
ggs_table:send_command(Table, Player, {"player1_y", int2str(50)}),
|
||||||
|
|
@ -189,6 +193,8 @@ intern_down(Table, Player) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
intern_start(Table, Player) ->
|
intern_start(Table, Player) ->
|
||||||
|
erlang:display(Player),
|
||||||
|
erlang:display(ggs_db:getItem(Table, local_storage, Player)),
|
||||||
case ggs_db:getItem(Table, local_storage, Player) of
|
case ggs_db:getItem(Table, local_storage, Player) of
|
||||||
player1 ->
|
player1 ->
|
||||||
ggs_db:setItem(Table, local_storage, player1_ready, true),
|
ggs_db:setItem(Table, local_storage, player1_ready, true),
|
||||||
|
|
@ -217,7 +223,9 @@ intern_start(Table, Player) ->
|
||||||
_Other ->
|
_Other ->
|
||||||
erlang:display("P2 ready, waiting."),
|
erlang:display("P2 ready, waiting."),
|
||||||
ggs_table:send_command(Table, Player, {"game", "wait"})
|
ggs_table:send_command(Table, Player, {"game", "wait"})
|
||||||
end
|
end;
|
||||||
|
Other ->
|
||||||
|
erlang:display(Other)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
game_loop([Table]) ->
|
game_loop([Table]) ->
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,69 @@
|
||||||
-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
|
%% @doc This module handles communication between a player and GGS. This module is
|
||||||
%%responsible for:
|
%% responsible for:
|
||||||
%% * The storage of the player socket, player token and a table token.
|
%% * The storage of the player socket, player token and a table token.
|
||||||
%% * Ability to fetch a player token.
|
%% * Ability to fetch a player token.
|
||||||
%% * Forwarding messages from players to the game
|
%% * Forwarding messages from players to the game
|
||||||
%% * Remove a player from GGS
|
%% * 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
|
%% @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
|
%% an argument for storage and later usage. Creates a unique player token
|
||||||
%% identifying the player.
|
%% identifying the player.
|
||||||
%% @spec start_link(Socket::socket()) -> {ok, Pid} | {error, Reason}
|
%% @spec start_link(Socket::socket()) -> {ok, Pid} | {error, Reason}
|
||||||
start_link(Socket) ->
|
start(Socket) ->
|
||||||
% The socket is in 'active' mode, and that means we are pushed any data
|
gen_server:start(?MODULE, [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
|
init([Socket]) ->
|
||||||
% us, otherwise these messages end up in our parent.
|
{ok, Protocol} = ggs_protocol:start_link(),
|
||||||
erlang:port_connect(Socket, self()),
|
|
||||||
{ok, Token} = ggs_coordinator:join_lobby(),
|
{ok, Token} = ggs_coordinator:join_lobby(),
|
||||||
TableStatus = ggs_coordinator:join_table("1337"),
|
|
||||||
case TableStatus of
|
erlang:port_connect(Socket, self()),
|
||||||
{ok, Table} ->
|
|
||||||
notify(self(), self(), {"hello", Token}),
|
case ggs_coordinator:join_table("1337") of
|
||||||
loop(#pl_state{socket = Socket, token = Token, table = Table});
|
{ok, T} ->
|
||||||
|
Table = T;
|
||||||
{error, no_such_table} ->
|
{error, no_such_table} ->
|
||||||
ggs_coordinator:create_table({force, "1337"}),
|
ggs_coordinator:create_table({force, "1337"}),
|
||||||
{ok, Table} = ggs_coordinator:join_table("1337"),
|
{ok, T} = ggs_coordinator:join_table("1337"),
|
||||||
notify(self(), self(), {"hello", Token}),
|
Table = T
|
||||||
loop(#pl_state{socket = Socket, token = Token, table = Table})
|
end,
|
||||||
end.
|
|
||||||
|
State = #state{
|
||||||
|
token = Token,
|
||||||
|
socket = Socket,
|
||||||
|
table = Table,
|
||||||
|
protocol = Protocol
|
||||||
|
},
|
||||||
|
|
||||||
|
%ggs_protocol:parse(Protocol, Data),
|
||||||
|
ggs_player:notify(self(), self(), {"hello", Token}), % send hello to the client
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
%% @doc Handles incoming messages from the GGS and forwards them through the player
|
%% @doc Handles incoming messages from the GGS and forwards them through the player
|
||||||
%% socket to the player.
|
%% socket to the player.
|
||||||
%% @spec notify(Player::Pid(), From::Pid(),
|
%% @spec notify(Player::Pid(), From::Pid(),
|
||||||
%% {Command::String(), Message::string()}) -> ok
|
%% {Command::String(), Message::string()}) -> ok
|
||||||
notify(Player, From, Message) ->
|
notify(Player, _From, Message) ->
|
||||||
{Cmd, Data} = Message,
|
gen_server:cast(Player, {notify, Message}).
|
||||||
Parsed = ggs_protocol:create_message(Cmd, "text","text", Data),
|
|
||||||
Player ! {notify, From, Parsed}.
|
%% @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.
|
%% @doc Get the player token uniquely representing the player.
|
||||||
%% @spec get_token() -> string()
|
%% @spec get_token() -> string()
|
||||||
|
|
@ -53,32 +74,46 @@ get_token(_Player) ->
|
||||||
%% together with the table token. It should also close the player socket and the
|
%% together with the table token. It should also close the player socket and the
|
||||||
%% process should return in the end.
|
%% process should return in the end.
|
||||||
%% @spec stop(Table::pid()) -> Reason::string()
|
%% @spec stop(Table::pid()) -> Reason::string()
|
||||||
stop(_Player,_Table) ->
|
stop(Player) ->
|
||||||
ggs_logger:not_implemented().
|
gen_server:cast(Player, stop).
|
||||||
|
|
||||||
%% Internals
|
%% Internals
|
||||||
|
handle_call(_Request, _From, St) -> {stop, unimplemented, St}.
|
||||||
|
|
||||||
loop(#pl_state{token = _Token, socket = Socket, table = Table} = State) ->
|
handle_cast({tcp, _Socket, Data}, #state { protocol = Protocol } = _State) ->
|
||||||
receive
|
ggs_protocol:parse(Protocol, Data);
|
||||||
{tcp, Socket, Data} -> % Just echo for now..
|
|
||||||
Parsed = ggs_protocol:parse(Data),
|
handle_cast({tcp_closed, _Socket}, _State) ->
|
||||||
self() ! Parsed,
|
erlang:display("Client disconnected, but THIS IS NOT SUPPORTED YET!~n");
|
||||||
loop(State);
|
|
||||||
{notify, _From, Message} ->
|
handle_cast({notify, Message}, #state { socket = Socket } = State) ->
|
||||||
gen_tcp:send(Socket, Message),
|
gen_tcp:send(Socket, ggs_protocol:create_message(Message)),
|
||||||
loop(State);
|
{noreply, State};
|
||||||
% Below are messages generated by the parser
|
|
||||||
{game_cmd,Cmd, _Headers, Data} ->
|
handle_cast({srv_cmd, "hello", _Headers, Data}, #state { token = Token } = State) ->
|
||||||
ggs_table:notify(Table, self(), {game, Cmd, Data}),
|
ggs_player:notify(self(), self(), {"hello", Token}),
|
||||||
loop(State);
|
{noreply, State};
|
||||||
{srv_cmd,"define", _Headers, Data} ->
|
|
||||||
ggs_table:notify(Table, self(), {server, define, Data}),
|
handle_cast({srv_cmd, "define", _Headers, Data}, #state { table = Table } = State) ->
|
||||||
loop(State);
|
ggs_table:notify(Table, self(), {server, define, Data}),
|
||||||
{tcp_closed, _Socket} ->
|
{noreply, State};
|
||||||
io:format("Client disconnected, but THIS IS NOT SUPPORTED YET!~n"),
|
|
||||||
loop(State);
|
handle_cast({game_cmd, Command, _Headers, Data}, #state { table = Table } = State) ->
|
||||||
Other ->
|
ggs_table:notify(Table, self(), {game, Command, Data}),
|
||||||
io:format("Got UNKNOWN message: "),
|
{noreply, State};
|
||||||
erlang:display(Other),
|
|
||||||
io:format("~n")
|
handle_cast(_Request, St) ->
|
||||||
end.
|
{stop, unimplemented1, St}.
|
||||||
|
|
||||||
|
handle_info({tcp, _Socket, Data}, #state { protocol = Protocol } = State) ->
|
||||||
|
ggs_protocol:parse(Protocol, Data),
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
terminate(Reason, State) ->
|
||||||
|
erlang:display(Reason),
|
||||||
|
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}.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,33 @@
|
||||||
|
%% Parse a string formatted with the GGS protocol using
|
||||||
|
%% an FSM. Each char is put into the FSM, which incrementally
|
||||||
|
%% builds a list of strings which represent the complete
|
||||||
|
%% message.
|
||||||
|
|
||||||
-module(ggs_protocol).
|
-module(ggs_protocol).
|
||||||
-export([parse/1, getToken/1, create_message/4]).
|
-export([parse/2, getToken/1, create_message/4, create_message/1,
|
||||||
|
expect_headers/2, expect_data_section/2,
|
||||||
|
expect_headers/3, expect_data_section/3]).
|
||||||
|
|
||||||
|
%% tests
|
||||||
|
-export([to_dictionary/2]).
|
||||||
|
|
||||||
|
% gen_fsm callbacks..
|
||||||
|
-export([init/1, handle_info/2, terminate/2, code_change/3, start_link/0]).
|
||||||
|
|
||||||
|
|
||||||
%% API Functions
|
%% API Functions
|
||||||
parse(Data) ->
|
parse(Protocol, Data) ->
|
||||||
Parsed = do_parse(Data, []),
|
lists:foreach(fun(X) -> gen_fsm:sync_send_event(Protocol, {char, X}) end, Data).
|
||||||
prettify(Parsed).
|
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
gen_fsm:start_link(?MODULE, [], []).
|
||||||
|
|
||||||
|
% Start state: {[""],0}, meaning:
|
||||||
|
% - Start with no strings parsed
|
||||||
|
% - Start with a data-section-lengths of 0
|
||||||
|
init([]) ->
|
||||||
|
{ok, expect_headers, {[""], 0}}.
|
||||||
|
|
||||||
getToken(Parsed) ->
|
getToken(Parsed) ->
|
||||||
case lists:keyfind(token, 1, Parsed) of
|
case lists:keyfind(token, 1, Parsed) of
|
||||||
|
|
@ -14,6 +37,11 @@ getToken(Parsed) ->
|
||||||
false
|
false
|
||||||
end.
|
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) ->
|
create_message(Cmd, Enc, Acc, Data) ->
|
||||||
Length = integer_to_list(string:len(Data)),
|
Length = integer_to_list(string:len(Data)),
|
||||||
Msg = "Client-Command: " ++ Cmd ++ "\n" ++
|
Msg = "Client-Command: " ++ Cmd ++ "\n" ++
|
||||||
|
|
@ -25,45 +53,66 @@ create_message(Cmd, Enc, Acc, Data) ->
|
||||||
Data,
|
Data,
|
||||||
Msg.
|
Msg.
|
||||||
|
|
||||||
|
%%% Transitions
|
||||||
|
expect_headers(_Event, State) ->
|
||||||
|
{next_state, expect_headers, State}.
|
||||||
|
expect_data_section(_Event, State) ->
|
||||||
|
{next_state, expect_data_section, State}.
|
||||||
|
|
||||||
%% Internal helpers
|
|
||||||
do_parse(Data, ParsedMessage) ->
|
%%% End transitions
|
||||||
NewLinePos = string:chr(Data, $\n),
|
expect_headers({char, $\n}, {Pid,_}, {Strings, Remains}) ->
|
||||||
Line = string:substr(Data, 1, NewLinePos-1),
|
[LastMessage|_] = Strings,
|
||||||
Tokens = re:split(Line, ": ", [{return, list}]),
|
case LastMessage of
|
||||||
case handle(Tokens) of
|
"" -> % We have a data section.. Last line should thus be the content length.
|
||||||
{Command, more} ->
|
[LastMessage, SecondLast | Rest] = Strings,
|
||||||
do_parse(string:substr(Data, NewLinePos+1), ParsedMessage ++ [Command]);
|
case re:split(SecondLast, ": ", [{return, list}]) of
|
||||||
{separator, data_next} ->
|
["Content-Length", X] ->
|
||||||
{_, Value} = lists:keyfind(content_len, 1, ParsedMessage),
|
{Int,_} = string:to_integer(X),
|
||||||
{ContentLength, []} = string:to_integer(Value),
|
case Int of
|
||||||
{data, ArgumentData} = handle_data(string:substr(Data, NewLinePos+1), ContentLength),
|
0 -> ggs_player:notify_game(Pid, prettify(to_dictionary([SecondLast|Rest], []), [])),
|
||||||
{ParsedMessage, ArgumentData}
|
{reply, ok, expect_headers, {[""], 0}};
|
||||||
|
_ -> {reply, ok, expect_data_section, {[""|Strings], Int}}
|
||||||
|
end;
|
||||||
|
_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,
|
||||||
|
{Pid,_} = From,
|
||||||
|
ggs_player:notify_game(Pid, prettify(to_dictionary(Rest, []), LastMsg)),
|
||||||
|
{reply, ok, expect_headers, {[[Char]], 0}};
|
||||||
|
_Other -> [LastMsg|Rest] = Strings,
|
||||||
|
NewMsg = LastMsg ++ [Char],
|
||||||
|
{reply, ok, expect_data_section, {[NewMsg|Rest], Remains-1}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
handle([[]]) ->
|
%handle_call(_Msg, _From, State) ->
|
||||||
{separator, data_next};
|
% {noreply, State}.
|
||||||
handle(["Server-Command", Param]) ->
|
handle_info(_Msg, State) ->
|
||||||
{{srv_cmd, Param}, more};
|
{noreply, State}.
|
||||||
handle(["Game-Command", Param]) ->
|
terminate(_Reason, _State) ->
|
||||||
{{game_cmd, Param}, more};
|
ok.
|
||||||
handle(["Content-Length", Param]) ->
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
{{content_len, Param}, more};
|
{ok, State}.
|
||||||
handle(["Token", Param]) ->
|
|
||||||
{{token, Param}, more};
|
|
||||||
handle(["Content-Type", Param]) ->
|
|
||||||
{{content_type, Param}, more}.
|
|
||||||
|
|
||||||
handle_data(Data, Length) ->
|
|
||||||
{data, string:substr(Data,1,Length)}.
|
|
||||||
|
|
||||||
|
|
||||||
prettify({Args, Data}) ->
|
prettify(Args, Data) ->
|
||||||
case lists:keyfind(srv_cmd, 1, Args) of
|
case lists:keyfind("Server-Command", 1, Args) of
|
||||||
{_, Value} ->
|
{_, Value} ->
|
||||||
{srv_cmd, Value, Args, Data};
|
{srv_cmd, Value, Args, Data};
|
||||||
_Other ->
|
_Other ->
|
||||||
case lists:keyfind(game_cmd, 1, Args) of
|
case lists:keyfind("Game-Command", 1, Args) of
|
||||||
{_, Value} ->
|
{_, Value} ->
|
||||||
{game_cmd, Value, Args, Data};
|
{game_cmd, Value, Args, Data};
|
||||||
_ ->
|
_ ->
|
||||||
|
|
@ -71,3 +120,9 @@ prettify({Args, Data}) ->
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
to_dictionary([], Dict) ->
|
||||||
|
Dict;
|
||||||
|
to_dictionary([S|Strings], Dict) ->
|
||||||
|
[First, Snd] = re:split(S, ": ", [{return, list}]),
|
||||||
|
to_dictionary(Strings, [{First, Snd}|Dict]).
|
||||||
|
|
||||||
|
|
|
||||||
163
src/ggs_protocol.erl.bk2
Normal file
163
src/ggs_protocol.erl.bk2
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
%%% @doc This module handles TCP incomming and outcommint.
|
||||||
|
|
||||||
|
-module(ggs_protocol).
|
||||||
|
-export([start_link/1,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,
|
||||||
|
header_string,
|
||||||
|
header_list,
|
||||||
|
body,
|
||||||
|
content_length}).
|
||||||
|
|
||||||
|
start_link(Player) ->
|
||||||
|
gen_server:start_link(?MODULE, [Player], []).
|
||||||
|
|
||||||
|
stop(Protocol) ->
|
||||||
|
gen_server:cast(Protocol, stop).
|
||||||
|
|
||||||
|
send_command(Protocol, {Command, Data}) ->
|
||||||
|
gen_server:cast(Protocol, {send, Command, Data}).
|
||||||
|
|
||||||
|
init([Player]) ->
|
||||||
|
State = #state{
|
||||||
|
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(_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) ->
|
||||||
|
do_parse(Data, []).
|
||||||
|
|
||||||
|
getToken(Parsed) ->
|
||||||
|
case lists:keyfind(token, 1, Parsed) of
|
||||||
|
{_, Value} ->
|
||||||
|
Value;
|
||||||
|
false ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
%% 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}
|
||||||
|
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)}.
|
||||||
|
|
||||||
|
|
||||||
|
prettify(Args, Data) ->
|
||||||
|
case lists:keyfind(srv_cmd, 1, Args) of
|
||||||
|
{_, Value} ->
|
||||||
|
{srv_cmd, Value, Args, Data};
|
||||||
|
_Other ->
|
||||||
|
case lists:keyfind(game_cmd, 1, Args) of
|
||||||
|
{_, Value} ->
|
||||||
|
{game_cmd, Value, Args, Data};
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
146
src/ggs_protocol.erl.orig
Normal file
146
src/ggs_protocol.erl.orig
Normal file
|
|
@ -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]).
|
||||||
|
|
||||||
142
src/ggs_server.erl
Normal file
142
src/ggs_server.erl
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
-module(ggs_server).
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([start_link/1,
|
||||||
|
start_link/0,
|
||||||
|
stop/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% gen_server callbacks
|
||||||
|
-export([init/1, handle_call/3, handle_cast/2,
|
||||||
|
handle_info/2, terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
|
||||||
|
-define(SERVER, ?MODULE).
|
||||||
|
-define(DEFAULT_PORT, 1055).
|
||||||
|
|
||||||
|
-record(state, {port, lsock, client_vm_map = []}).
|
||||||
|
|
||||||
|
%%%====================================================
|
||||||
|
%%% API
|
||||||
|
%%%====================================================
|
||||||
|
|
||||||
|
%%-----------------------------------------------------
|
||||||
|
%% @doc Starts the server
|
||||||
|
%% @end
|
||||||
|
%%-----------------------------------------------------
|
||||||
|
start_link(Port) ->
|
||||||
|
gen_server:start_link({local, ?SERVER}, ?MODULE, [Port], []).
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
start_link(?DEFAULT_PORT).
|
||||||
|
|
||||||
|
%%-----------------------------------------------------
|
||||||
|
%% @doc Stops the server.
|
||||||
|
%% @spec stop() -> ok
|
||||||
|
%% @end
|
||||||
|
%%-----------------------------------------------------
|
||||||
|
stop() ->
|
||||||
|
gen_server:cast(?SERVER, stop).
|
||||||
|
|
||||||
|
%%-----------------------------------------------------
|
||||||
|
%% gen_server callbacks
|
||||||
|
%%-----------------------------------------------------
|
||||||
|
|
||||||
|
init([Port]) ->
|
||||||
|
case gen_server:call(ggs_backup, get_backup) of
|
||||||
|
{backup_state, not_initialized} ->
|
||||||
|
{ok, LSock} = gen_tcp:listen(Port, [{active, true},
|
||||||
|
{reuseaddr, true}]),
|
||||||
|
{ok, #state{port = Port, lsock = LSock}, 0};
|
||||||
|
{backup_state, State} ->
|
||||||
|
{ok, LSock} = gen_tcp:listen(Port, [{active, true},
|
||||||
|
{reuseaddr, true}]),
|
||||||
|
{ok, State#state{lsock = LSock}, 0}
|
||||||
|
end.
|
||||||
|
|
||||||
|
handle_call({backup_state, OldState}, _From, _State) ->
|
||||||
|
io:format("Received old state from backup~n"),
|
||||||
|
{noreply, OldState}.
|
||||||
|
|
||||||
|
|
||||||
|
handle_info({tcp, Socket, RawData}, State) ->
|
||||||
|
ggs_protocol:parse(RawData),
|
||||||
|
{noreply, State#state{lsock = Socket}};
|
||||||
|
|
||||||
|
handle_info({tcp_closed, Socket}, State) ->
|
||||||
|
gen_tcp:close(Socket),
|
||||||
|
{stop, "Client closed socket", State};
|
||||||
|
|
||||||
|
handle_info(timeout, #state{lsock = LSock} = State) ->
|
||||||
|
{ok, _Sock} = gen_tcp:accept(LSock),
|
||||||
|
{noreply, State};
|
||||||
|
|
||||||
|
handle_info(Other, _State) ->
|
||||||
|
erlang:display(Other).
|
||||||
|
|
||||||
|
terminate(_Reason, _State) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
%%-----------------------------------------------------
|
||||||
|
%% Internal functions
|
||||||
|
%%-----------------------------------------------------
|
||||||
|
handle_cast(stop, State) ->
|
||||||
|
{stop, normal, State};
|
||||||
|
|
||||||
|
% Handle javascript defines
|
||||||
|
handle_cast({srv_cmd, "define", Headers, Data}, State) ->
|
||||||
|
Token = ggs_protocol:getToken(Headers),
|
||||||
|
GameVM = getJSVM(Token, State),
|
||||||
|
ggs_vm_runner:define(GameVM, Data),
|
||||||
|
send(State#state.lsock, "Token", "Okay, defined that for you!"),
|
||||||
|
{noreply, State};
|
||||||
|
|
||||||
|
% Handle javascript calls
|
||||||
|
handle_cast({srv_cmd, "call", Headers, Data}, State) ->
|
||||||
|
Token = ggs_protocol:getToken(Headers),
|
||||||
|
io:format("Got call request: ~p~n", [Data]),
|
||||||
|
GameVM = getJSVM(Token, State),
|
||||||
|
Ret = ggs_vm_runner:user_command(GameVM, "User", Data, []),
|
||||||
|
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({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
|
||||||
|
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}.
|
||||||
|
|
||||||
|
|
||||||
|
%%-----------------------------------------------------
|
||||||
|
%% Helpers
|
||||||
|
%%-----------------------------------------------------
|
||||||
|
getNewToken() ->
|
||||||
|
string:strip(os:cmd("uuidgen"), right, $\n ).
|
||||||
|
|
||||||
|
getJSVM(ClientToken, State) ->
|
||||||
|
VMs = State#state.client_vm_map,
|
||||||
|
{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) ->
|
||||||
|
gen_tcp:send(Socket, string:join([RefID,String,"\n"], " ")).
|
||||||
|
|
||||||
|
send(Socket, RefID, String1, String2) ->
|
||||||
|
gen_tcp:send(Socket, string:join([RefID,String1, String2,"\n"], " ")).
|
||||||
42
src/ggs_vm_runner.erl
Normal file
42
src/ggs_vm_runner.erl
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
-module(ggs_vm_runner).
|
||||||
|
-export([start_link/0, define/2, user_command/4]).
|
||||||
|
|
||||||
|
%Mattias
|
||||||
|
start_link() ->
|
||||||
|
erlang_js:start(),
|
||||||
|
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, 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) ->
|
||||||
|
Ref = make_ref(),
|
||||||
|
GameVM ! {user_command, User, Command, Args, self(), Ref},
|
||||||
|
receive
|
||||||
|
{Ref, RetVal} ->
|
||||||
|
RetVal;
|
||||||
|
Other -> Other
|
||||||
|
end.
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
%% @doc start_link should always return ok for any valid socket. A valid socket
|
%% @doc start_link should always return ok for any valid socket. A valid socket
|
||||||
%% should always return {ok, Pid} and {error, Reason} otherwise.
|
%% should always return {ok, Pid} and {error, Reason} otherwise.
|
||||||
start_link_test() ->
|
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
|
%% @doc Given that start_link returned {ok, Player}. Notify shall always return ok and
|
||||||
%% deliver a specified message through the socket.
|
%% deliver a specified message through the socket.
|
||||||
|
|
|
||||||
Reference in a new issue