Commit 86e58010 authored by Jonathan Michalon's avatar Jonathan Michalon

Created system for networked signals and used it.

A new class called SignalHandle can now listen to defined signals, send
them over a client <-> server connection and reemit them on the other side
provided incoming SignalMessages are transmitted to her.

BTW now the bot listens to messages too, new message utils handle buffers
(currently used both for signal data and string data), the server
"publishes" a table with a Game reading XML data, the bot "joins" it
and player-added signals are transmitted.
parent 9d7e5150
......@@ -10,12 +10,13 @@ public class Bot
private MainLoop loop = null;
private OVCC.Player player = null;
private OVCCClient.Client client = null;
private OVCCClient.Game game = null;
public OVCC.Network.SignalHandle signal_handle = null;
public bool join (OVCCClient.Server server)
throws Error
{
uint player_suffix = 7;
OVCCClient.Game game = null;
/* connect to the server */
debug ("Trying to connect to server...");
......@@ -26,7 +27,7 @@ public class Bot
debug ("Trying to login...");
do {
/* Create our player instance */
player = new OVCC.Player ("""Clever bot %3u""".printf (player_suffix));
player = new OVCC.Player ("""Clever bot %03u""".printf (player_suffix));
try {
client = new OVCCClient.Client (player);
......@@ -56,6 +57,9 @@ public class Bot
throw e;
}
debug ("Joined");
/* start listening to the server */
server.start_listening (game, player);
/* react on game state changes */
game.notify["state"].connect ((s, p) => {
......
......@@ -22,6 +22,7 @@ libovcc_la_SOURCES = board.vala \
ovccnetworkemptymessage.vala \
ovccnetworkstringmessage.vala \
ovccnetworkloginmessage.vala \
ovccnetworksignals.vala \
ovccnetworkdisconnectmessage.vala \
ovccnetworkwelcomemessage.vala \
ovccnetworkerrormessage.vala
......
......@@ -250,7 +250,7 @@ namespace OVCC
bool success = false;
/* can a player leave a running game? */
if (players.find (player) != null) {
if (players.find (player) == null) {
warning ("Player is not on this game");
} else {
_players.remove (player);
......
......@@ -42,6 +42,7 @@ namespace OVCC.Network
ERROR,
WELCOME,
LOGIN,
SIGNAL,
DISCONNECT,
INVALID
}
......@@ -53,6 +54,7 @@ namespace OVCC.Network
typeof (ErrorMessage),
typeof (WelcomeMessage),
typeof (LoginMessage),
typeof (SignalMessage),
typeof (DisconnectMessage)
};
......@@ -136,5 +138,29 @@ namespace OVCC.Network
}
return result;
}
public uint8[] read_buffer (DataInputStream stream,
Cancellable? cancel = null)
throws Error
{
var len = stream.read_uint16 (cancel);
if (len < 1 || len > 0x400 /* arbitrary limit not to fulfill memory */) {
throw new MessageError.MALFORMED_MESSAGE ("Invalid message length %u",
len);
}
var buf = new uint8[len];
stream.read (buf, cancel);
return buf;
}
public bool write_buffer (DataOutputStream stream,
uint8[] buf,
Cancellable? cancel = null)
throws Error
{
stream.put_uint16 ((uint16)buf.length, cancel);
return stream.write (buf, cancel) == buf.length;
}
}
}
/*
*
* Copyright (C) 2011 Colomban Wendling <ban@herbesfolles.org>
* Jonathan Michalon <studios.chalmion@no-log.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* Communicate signals over network
*/
namespace OVCC.Network
{
public errordomain SignalError
{
UNMAPPED_SIGNAL,
FAILED
}
public enum SignalType
{
PLAYER_ADDED,
PLAYER_REMOVED,
GAME_STATE,
PLACE_TILE,
PLACE_PAWN,
CURRENT_TILE_CHANGED,
CURRENT_PLAYER_CHANGED,
TYPES_N;
}
public class SignalHandle: Object
{
/* objects on which the signals take effect */
public Game game_object { get; construct; }
public Player player_object { get; construct; }
private DataInputStream input;
private DataOutputStream output;
private int[] ignore_next = new int[SignalType.TYPES_N];
public SignalHandle (Game game, Player player,
DataInputStream input_p, DataOutputStream output_p)
{
Object (game_object: game, player_object: player);
input = input_p;
output = output_p;
game_object.player_added.connect ((p) => {
send_to_peer (SignalType.PLAYER_ADDED, {1, 2});
});
}
private bool send_to_peer (SignalType s, uint8[] data)
{
if (ignore_next[s] > 0) {
ignore_next[s]--;
return false;
}
SignalMessage msg = new SignalMessage ();
msg.stype = s;
msg.data = data;
debug ("Transmitting signal %s", s.to_string());
try {
msg.send (output);
} catch (Error e) {
warning ("Cannot transmit signal %s: %s", s.to_string(), e.message);
}
return true;
}
public bool signal_received (SignalMessage msg)
{
debug ("Received signal %s", msg.stype.to_string());
switch (msg.stype) {
case SignalType.PLAYER_ADDED:
debug ("Adding player %s", "new");
ignore_next[SignalType.PLAYER_ADDED]++;
game_object.add_player (new Player("new"));
break;
}
return true;
}
}
public class SignalMessage: Message
{
public override MessageType message_type { get { return MessageType.SIGNAL; } }
public SignalType stype { get; set; default = 0; }
public uint8[] data { get; set; default = null; }
public override bool serialize (DataOutputStream stream,
Cancellable? cancel = null)
throws Error
{
stream.put_uint16 ((uint16)stype, cancel);
return write_buffer (stream, data, cancel);
}
public override bool deserialize (DataInputStream stream,
Cancellable? cancel = null)
throws Error
{
stype = (SignalType)stream.read_uint16 (cancel);
data = read_buffer (stream, cancel);
return true;
}
}
}
......@@ -35,25 +35,18 @@ public abstract class OVCC.Network.StringMessage : Message
throws Error
{
var len = message.length;
stream.put_uint16 ((uint16)len, cancel);
var data = new uint8[len];
Memory.copy (data, message, len);
return stream.write (data, cancel) == len;
return write_buffer (stream, data, cancel);
}
public override bool deserialize (DataInputStream stream,
Cancellable? cancel = null)
throws Error
{
var len = stream.read_uint16 (cancel);
if (len < 1 || len > 0x400 /* arbitrary limit not to fulfill memory */) {
throw new MessageError.MALFORMED_MESSAGE ("Invalid message length %u",
len);
}
var buf = new uint8[len];
stream.read (buf, cancel);
var msg = "%.*s".printf (len, buf);
if (! msg.validate (len)) {
var buf = read_buffer (stream, cancel);
var msg = "%.*s".printf (buf.length, buf);
if (! msg.validate (buf.length)) {
throw new MessageError.MALFORMED_MESSAGE ("Invalid string message");
}
this.message = msg;
......
......@@ -4,5 +4,9 @@ namespace OVCCClient {
public class Game : OVCC.Game
{
/* network wrapper around OVCC Game */
public Game.from_tileset (OVCC.TileSet ts) {
base.from_tileset (ts);
}
}
}
using OVCC;
using OVCC.Network;
static const string TILES_FILE = "tiles_original.xml";
static const string TILESET_FILE = "tileset_original.xml";
namespace OVCCClient
{
public errordomain ServerError
......@@ -30,6 +33,7 @@ namespace OVCCClient
private SocketConnection connection = null;
private DataInputStream input;
private DataOutputStream output;
private SignalHandle signal_handle = null;
public Server (string host,
uint16 port)
......@@ -146,8 +150,52 @@ namespace OVCCClient
throws ServerError
requires (connection != null)
{
/* FIXME load right tileset and do something with stack... */
TilesDef tiles = new TilesDef ();
TileSet tileset = new TileSet ();
tiles.load (File.new_for_path (TILES_FILE));
tileset.load (tiles, File.new_for_path (TILESET_FILE));
Game game = new Game.from_tileset(tileset);
return game;
throw new ServerError.NO_OPEN_TABLE ("Not implemented");
}
private void listen_loop ()
{
var running = true;
while (running) {
Message? msg;
try {
msg = Message.receive (input);
} catch (Error e) {
warning ("error receiving data: %s", e.message);
break;
}
if (msg == null) {
debug ("Invalid message received");
continue;
}
debug ("Message type %s received", msg.get_type ().name ());
switch (msg.message_type) {
case MessageType.SIGNAL:
signal_handle.signal_received ((msg as SignalMessage));
break;
case MessageType.DISCONNECT: running = false; break;
}
}
signal_handle = null;
}
public void start_listening (Game game, Player player)
{
signal_handle = new SignalHandle (game, player, input, output);
Thread.create<void> (listen_loop, false);
}
}
}
......@@ -18,6 +18,7 @@
*
*/
using OVCC;
using OVCC.Network;
......@@ -27,8 +28,13 @@ public class Client: Object
protected DataInputStream input;
protected DataOutputStream output;
public string login {get; set; default = null;}
public string password {get; set; default = null;}
public string login {get; set; default = null;}
public string password {get; set; default = null;}
public Player player {get; set; default = null;}
public Game game {get; set; default = null;}
private SignalHandle signal_handle = null;
public Client ()
{
......@@ -42,7 +48,6 @@ public class Client: Object
connection = conn;
input = new DataInputStream (connection.input_stream);
output = new DataOutputStream (connection.output_stream);
/* here, may authenticate */
return true;
}
......@@ -50,6 +55,9 @@ public class Client: Object
throws Error
requires (connection != null)
{
game.remove_player (player);
signal_handle = null; /* to break circular reference */
return connection.socket.close ();
}
......@@ -67,4 +75,15 @@ public class Client: Object
{
return Message.receive (input, cancel);
}
public bool join (Game game_p)
{
player = new Player (login);
game = game_p;
signal_handle = new SignalHandle (game, player, input, output);
game.add_player (player);
return true;
}
}
......@@ -18,14 +18,18 @@
*
*/
using OVCC;
using OVCC.Network;
static const string TILES_FILE = "tiles_original.xml";
static const string TILESET_FILE = "tileset_original.xml";
/* TODO: add a Source to be notified of incoming messages asynchronously */
public class Server: ThreadedSocketService
{
private List<Client> clients = null;
private List<Game> tables = null;
public string name { get; construct; }
public uint port { get; construct; default = 0xdead; }
......@@ -128,10 +132,16 @@ public class Server: ThreadedSocketService
}
switch (msg.message_type) {
case MessageType.LOGIN:
bool logged;
debug ("Login for '%s' with '%s'", (msg as LoginMessage).login,
(msg as LoginMessage).password);
handle_login ((msg as LoginMessage), client);
logged = handle_login ((msg as LoginMessage), client);
if (logged) {
client.join(tables.first().data);
}
break;
case MessageType.SIGNAL:
break;
case MessageType.DISCONNECT: running = false; break;
}
......@@ -156,8 +166,17 @@ public class Server: ThreadedSocketService
public Server (string name,
uint16 port = 0xdead)
throws Error
{
Object (name: name, port: port);
TilesDef tiles = new TilesDef ();
TileSet tileset = new TileSet ();
tiles.load (File.new_for_path (TILES_FILE));
tileset.load (tiles, File.new_for_path (TILESET_FILE));
Game table = new Game.from_tileset(tileset);
this.tables.prepend (table);
}
/* a bit ugly since a direct call to parent method will not trigger the
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment