--- /dev/null
+#include "networking.h"
+#include <msp/net/inet6.h>
+#include <msp/net/resolve.h>
+#include "director.h"
+
+using namespace std;
+
+namespace Msp::Game {
+
+Networking::Networking(Director &d):
+ event_source(d.get_event_bus()),
+ io_disp(d.get_io_dispatcher())
+{ }
+
+void Networking::start_server(unsigned port)
+{
+ if(state!=DISABLED)
+ throw invalid_state("Networking::start_server");
+
+ Net::Inet6Addr addr = Net::Inet6Addr::wildcard(port);
+ server_socket = make_unique<Net::StreamServerSocket>(addr.get_family());
+ server_socket->listen(addr);
+ server_socket->signal_data_available.connect(sigc::mem_fun(this, &Networking::incoming_connection));
+ io_disp.add(*server_socket);
+
+ state = SERVER;
+ next_id = 1;
+}
+
+void Networking::connect_to_server(const string &host)
+{
+ if(state!=DISABLED)
+ throw invalid_state("Networking::connect_to_server");
+
+ unique_ptr<Net::SockAddr> addr(Net::resolve(host));
+ connection = make_unique<ServerConnection>(*this, *addr);
+ connection->get_communicator().signal_protocol_ready.connect(sigc::mem_fun(this, &Networking::protocol_ready));
+
+ state = CONNECTING;
+ next_id = ID_PENDING+1;
+}
+
+void Networking::disable()
+{
+ server_socket.reset();
+ connection.reset();
+ set_state(DISABLED);
+}
+
+void Networking::reap_connections()
+{
+ if(connection && connection->is_stale())
+ disable();
+
+ for(auto i=clients.begin(); i!=clients.end(); )
+ {
+ if((*i)->is_stale())
+ {
+ for(auto j=players.begin(); j!=players.end(); )
+ {
+ if(j->owner==i->get())
+ {
+ event_source.emit<Events::NetworkPlayerRemoved>(j->id);
+ j = players.erase(j);
+ }
+ else
+ ++j;
+ }
+
+ i = clients.erase(i);
+ }
+ else
+ ++i;
+ }
+}
+
+void Networking::add_player(const string &name, function<void(unsigned)> callback)
+{
+ if(state!=CONNECTED && state!=SERVER)
+ throw invalid_state("Networking::add_player");
+
+ Player &player = create_player(name);
+
+ if(state==SERVER)
+ {
+ callback(player.id);
+
+ Messages::PlayerAdded added;
+ added.id = player.id;
+ added.name = player.name;
+ for(const auto &c: clients)
+ c->get_communicator().send(added);
+
+ event_source.emit<Events::NetworkPlayerAdded>(player.id, player.name);
+ }
+ else
+ {
+ player.add_callback = move(callback);
+
+ Messages::AddPlayerRequest add_req;
+ add_req.token = player.id;
+ add_req.name = player.name;
+ connection->get_communicator().send(add_req);
+ }
+}
+
+Networking::Player &Networking::create_player(const string &name, unsigned id)
+{
+ Player &player = players.emplace_back();
+ player.id = (id ? id : next_id++);
+ player.name = name;
+ return player;
+}
+
+void Networking::set_state(State s)
+{
+ if(s==state)
+ return;
+
+ state = s;
+ event_source.emit<Events::NetworkStateChanged>(state);
+}
+
+void Networking::protocol_ready(const Net::Protocol &p)
+{
+ if(state==CONNECTING && &p==&protocol)
+ set_state(CONNECTED);
+}
+
+void Networking::incoming_connection()
+{
+ unique_ptr<Net::StreamSocket> socket(server_socket->accept());
+ clients.emplace_back(make_unique<ClientConnection>(*this, move(socket)));
+}
+
+
+Networking::Connection::Connection(Networking &n, unique_ptr<Net::StreamSocket> s):
+ networking(n),
+ socket(move(s)),
+ communicator(*socket)
+{
+ networking.io_disp.add(*socket);
+ socket->signal_end_of_file.connect(sigc::mem_fun(this, &Connection::disconnected));
+ communicator.signal_error.connect(sigc::mem_fun(this, &Connection::error));
+}
+
+void Networking::Connection::disconnected()
+{
+ stale = true;
+}
+
+void Networking::Connection::error(const exception &)
+{
+ stale = true;
+}
+
+
+Networking::ServerConnection::ServerConnection(Networking &n, const Net::SockAddr &addr):
+ Connection(n, make_unique<Net::StreamSocket>(addr.get_family()))
+{
+ communicator.add_protocol(networking.protocol, *this);
+ socket->set_block(false);
+ socket->signal_connect_finished.connect(sigc::mem_fun(this, &ServerConnection::connect_finished));
+ socket->connect(addr);
+}
+
+void Networking::ServerConnection::connect_finished(const exception *exc)
+{
+ if(exc)
+ stale = true;
+}
+
+void Networking::ServerConnection::receive(const Messages::AddPlayerResponse &add_resp)
+{
+ auto i = find_member(networking.players, static_cast<unsigned>(add_resp.token), &Player::id);
+ if(i==networking.players.end())
+ throw key_error(add_resp.token);
+
+ i->id = add_resp.id;
+
+ if(i->add_callback)
+ {
+ i->add_callback(i->id);
+ i->add_callback = nullptr;
+ }
+ networking.event_source.emit<Events::NetworkPlayerAdded>(i->id, i->name);
+}
+
+void Networking::ServerConnection::receive(const Messages::PlayerAdded &added)
+{
+ Player &player = networking.create_player(added.name, added.id);
+ networking.event_source.emit<Events::NetworkPlayerAdded>(player.id, player.name);
+}
+
+void Networking::ServerConnection::receive(const Messages::PlayerRemoved &removed)
+{
+ auto i = find_member(networking.players, static_cast<unsigned>(removed.id), &Player::id);
+ if(i!=networking.players.end())
+ {
+ networking.event_source.emit<Events::NetworkPlayerRemoved>(i->id);
+ networking.players.erase(i);
+ }
+}
+
+
+Networking::ClientConnection::ClientConnection(Networking &n, unique_ptr<Net::StreamSocket> s):
+ Connection(n, move(s))
+{
+ communicator.add_protocol(networking.protocol, *this);
+}
+
+void Networking::ClientConnection::receive(const Messages::AddPlayerRequest &add_req)
+{
+ Player &player = networking.create_player(add_req.name);
+ player.owner = this;
+
+ Messages::AddPlayerResponse add_resp;
+ add_resp.token = add_req.token;
+ add_resp.id = player.id;
+ communicator.send(add_resp);
+
+ Messages::PlayerAdded added;
+ added.id = player.id;
+ added.name = player.name;
+ for(const auto &c: networking.clients)
+ if(c.get()!=this)
+ c->get_communicator().send(added);
+
+ networking.event_source.emit<Events::NetworkPlayerAdded>(player.id, player.name);
+}
+
+} // namespace Msp::Game
--- /dev/null
+#ifndef MSP_GAME_NETWORKING_H_
+#define MSP_GAME_NETWORKING_H_
+
+#include <memory>
+#include <msp/io/eventdispatcher.h>
+#include <msp/net/communicator.h>
+#include <msp/net/streamserversocket.h>
+#include "events.h"
+#include "eventsource.h"
+#include "messages.h"
+#include "mspgame_api.h"
+#include "protocol.h"
+
+namespace Msp::Game {
+
+class Director;
+
+class MSPGAME_API Networking
+{
+public:
+ enum State
+ {
+ DISABLED,
+ SERVER,
+ CONNECTING,
+ CONNECTED
+ };
+
+ using EventSource = Game::EventSource<Events::NetworkStateChanged, Events::NetworkPlayerAdded,
+ Events::NetworkPlayerRemoved>;
+
+private:
+ class Connection
+ {
+ protected:
+ Networking &networking;
+ std::unique_ptr<Net::StreamSocket> socket;
+ Net::Communicator communicator;
+ bool stale = false;
+
+ Connection(Networking &, std::unique_ptr<Net::StreamSocket>);
+
+ void disconnected();
+ void error(const std::exception &);
+
+ public:
+ Net::Communicator &get_communicator() { return communicator; }
+ bool is_stale() const { return stale; }
+ };
+
+ class ServerConnection: public Connection,
+ public Net::PacketReceiver<Messages::AddPlayerResponse>,
+ public Net::PacketReceiver<Messages::PlayerAdded>,
+ public Net::PacketReceiver<Messages::PlayerRemoved>
+ {
+ public:
+ ServerConnection(Networking &, const Net::SockAddr &);
+
+ private:
+ void connect_finished(const std::exception *);
+
+ public:
+ void receive(const Messages::AddPlayerResponse &) override;
+ void receive(const Messages::PlayerAdded &) override;
+ void receive(const Messages::PlayerRemoved &) override;
+ };
+
+ class ClientConnection: public Connection,
+ public Net::PacketReceiver<Messages::AddPlayerRequest>
+ {
+ public:
+ ClientConnection(Networking &, std::unique_ptr<Net::StreamSocket>);
+
+ void receive(const Messages::AddPlayerRequest &) override;
+ };
+
+ struct Player
+ {
+ ClientConnection *owner = nullptr;
+ unsigned id = 0;
+ std::string name;
+ std::function<void(unsigned)> add_callback;
+ };
+
+ static constexpr unsigned ID_PENDING = 0x8000;
+
+ EventSource event_source;
+ IO::EventDispatcher &io_disp;
+ Protocol protocol;
+ State state = DISABLED;
+ std::unique_ptr<Net::StreamServerSocket> server_socket;
+ std::unique_ptr<ServerConnection> connection;
+ std::vector<std::unique_ptr<ClientConnection>> clients;
+ std::vector<Player> players;
+ unsigned next_id = 1;
+
+public:
+ Networking(Director &);
+
+ void start_server(unsigned);
+ void connect_to_server(const std::string &);
+ void disable();
+ void reap_connections();
+
+ void add_player(const std::string &, std::function<void(unsigned)>);
+private:
+ Player &create_player(const std::string &, unsigned = 0);
+
+ void set_state(State);
+ void protocol_ready(const Net::Protocol &);
+ void incoming_connection();
+};
+
+
+namespace Events {
+
+struct NetworkStateChanged
+{
+ Networking::State state;
+};
+
+} // namespace Events
+} // namespace Msp::Game
+
+#endif