From: Mikko Rasa Date: Tue, 17 Jan 2023 07:52:25 +0000 (+0200) Subject: Add basic networking infrastructure X-Git-Url: https://git.tdb.fi/?a=commitdiff_plain;h=ef6ea3a070df769f99d891ff236133065820d7b2;p=libs%2Fgame.git Add basic networking infrastructure Currently it only allows running a server, connecting to it and creating players, but no synchronization yet. --- diff --git a/Build b/Build index f7385be..e2a9e8c 100644 --- a/Build +++ b/Build @@ -15,6 +15,7 @@ package "mspgame" library "mspgame" { source "source/game"; + require "mspnet"; install true; install_map { diff --git a/source/game/director.cpp b/source/game/director.cpp index 53b2e7f..31d20a9 100644 --- a/source/game/director.cpp +++ b/source/game/director.cpp @@ -11,6 +11,7 @@ namespace Msp::Game { Director::Director(DataFile::Collection &r): resources(r), + networking(*this), event_source(event_bus) { #if DEBUG @@ -42,6 +43,8 @@ void Director::activate_stage(Stage &s) void Director::tick() { + io_dispatcher.tick(Time::zero); + Time::TimeStamp now = Time::now(); Time::TimeDelta dt = (last_tick ? now-last_tick : Time::zero); last_tick = now; @@ -50,6 +53,8 @@ void Director::tick() for(unsigned i=0; (i=stepsize); ++i, backlog-=stepsize) for(const auto &s: stages) s->tick(stepsize); + + networking.reap_connections(); } } // namespace Msp::Game diff --git a/source/game/director.h b/source/game/director.h index 5aeddea..dc6e483 100644 --- a/source/game/director.h +++ b/source/game/director.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "accessguard.h" @@ -12,6 +13,7 @@ #include "events.h" #include "eventsource.h" #include "mspgame_api.h" +#include "networking.h" #include "reflection.h" namespace Msp::Game { @@ -27,6 +29,8 @@ private: std::optional access_guard; Reflection::Reflector reflector; DataFile::Collection &resources; + IO::EventDispatcher io_dispatcher; + Networking networking; EventBus event_bus; EventSource event_source; std::vector> stages; @@ -42,6 +46,8 @@ public: ~Director(); DataFile::Collection &get_resources() const { return resources; } + Networking &get_networking() { return networking; } + IO::EventDispatcher &get_io_dispatcher() { return io_dispatcher; } EventBus &get_event_bus() { return event_bus; } EventSource &get_event_source() { return event_source; } const std::vector> &get_stages() const { return stages; } diff --git a/source/game/events.h b/source/game/events.h index 106d3c4..ea903ec 100644 --- a/source/game/events.h +++ b/source/game/events.h @@ -12,6 +12,20 @@ class Stage; namespace Events { +// Defined in networking.h +struct NetworkStateChanged; + +struct NetworkPlayerAdded +{ + unsigned id; + std::string name; +}; + +struct NetworkPlayerRemoved +{ + unsigned id; +}; + struct EntityCreated { Handle entity; diff --git a/source/game/messages.h b/source/game/messages.h new file mode 100644 index 0000000..3b18c33 --- /dev/null +++ b/source/game/messages.h @@ -0,0 +1,33 @@ +#ifndef MSP_GAME_MESSAGES_H_ +#define MSP_GAME_MESSAGES_H_ + +namespace Msp::Game { +namespace Messages { + +struct AddPlayerRequest +{ + std::uint16_t token; + std::string name; +}; + +struct AddPlayerResponse +{ + std::uint16_t token; + std::uint16_t id; +}; + +struct PlayerAdded +{ + std::uint16_t id; + std::string name; +}; + +struct PlayerRemoved +{ + std::uint16_t id; +}; + +} // namespace Messages +} // namespace Msp::Game + +#endif diff --git a/source/game/networking.cpp b/source/game/networking.cpp new file mode 100644 index 0000000..30b5287 --- /dev/null +++ b/source/game/networking.cpp @@ -0,0 +1,232 @@ +#include "networking.h" +#include +#include +#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(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 addr(Net::resolve(host)); + connection = make_unique(*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(j->id); + j = players.erase(j); + } + else + ++j; + } + + i = clients.erase(i); + } + else + ++i; + } +} + +void Networking::add_player(const string &name, function 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(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(state); +} + +void Networking::protocol_ready(const Net::Protocol &p) +{ + if(state==CONNECTING && &p==&protocol) + set_state(CONNECTED); +} + +void Networking::incoming_connection() +{ + unique_ptr socket(server_socket->accept()); + clients.emplace_back(make_unique(*this, move(socket))); +} + + +Networking::Connection::Connection(Networking &n, unique_ptr 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(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(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(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(player.id, player.name); +} + +void Networking::ServerConnection::receive(const Messages::PlayerRemoved &removed) +{ + auto i = find_member(networking.players, static_cast(removed.id), &Player::id); + if(i!=networking.players.end()) + { + networking.event_source.emit(i->id); + networking.players.erase(i); + } +} + + +Networking::ClientConnection::ClientConnection(Networking &n, unique_ptr 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(player.id, player.name); +} + +} // namespace Msp::Game diff --git a/source/game/networking.h b/source/game/networking.h new file mode 100644 index 0000000..478b347 --- /dev/null +++ b/source/game/networking.h @@ -0,0 +1,125 @@ +#ifndef MSP_GAME_NETWORKING_H_ +#define MSP_GAME_NETWORKING_H_ + +#include +#include +#include +#include +#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; + +private: + class Connection + { + protected: + Networking &networking; + std::unique_ptr socket; + Net::Communicator communicator; + bool stale = false; + + Connection(Networking &, std::unique_ptr); + + 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, + public Net::PacketReceiver, + public Net::PacketReceiver + { + 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 + { + public: + ClientConnection(Networking &, std::unique_ptr); + + void receive(const Messages::AddPlayerRequest &) override; + }; + + struct Player + { + ClientConnection *owner = nullptr; + unsigned id = 0; + std::string name; + std::function add_callback; + }; + + static constexpr unsigned ID_PENDING = 0x8000; + + EventSource event_source; + IO::EventDispatcher &io_disp; + Protocol protocol; + State state = DISABLED; + std::unique_ptr server_socket; + std::unique_ptr connection; + std::vector> clients; + std::vector 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); +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 diff --git a/source/game/protocol.cpp b/source/game/protocol.cpp new file mode 100644 index 0000000..1226521 --- /dev/null +++ b/source/game/protocol.cpp @@ -0,0 +1,15 @@ +#include "protocol.h" +#include "messages.h" + +namespace Msp::Game { + +Protocol::Protocol() +{ + using namespace Messages; + + add(&AddPlayerRequest::token, &AddPlayerRequest::name); + add(&AddPlayerResponse::token, &AddPlayerResponse::id); + add(&PlayerAdded::id, &PlayerAdded::name); +} + +} // namespace Msp::Game diff --git a/source/game/protocol.h b/source/game/protocol.h new file mode 100644 index 0000000..efeca1f --- /dev/null +++ b/source/game/protocol.h @@ -0,0 +1,16 @@ +#ifndef MSP_GAME_PROTOCOL_H_ +#define MSP_GAME_PROTOCOL_H_ + +#include + +namespace Msp::Game { + +class Protocol: public Net::Protocol +{ +public: + Protocol(); +}; + +} // namespace Msp::Game + +#endif