diff --git a/base/event.hpp b/base/event.hpp
new file mode 100644
index 0000000..f4fcc36
--- /dev/null
+++ b/base/event.hpp
@@ -0,0 +1,49 @@
+/*
+ * This file is a part of Pixelbox - Infinite 2D sandbox game
+ * Evenst for objservers/subscribers patterns
+ * Copyright (C) 2023-2024 UtoECat
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+#include
+#include
+
+namespace pb {
+
+ // subject for "Observers"
+ template
+ class Subject {
+ protected:
+ using function_type = std::function;
+ std::vector handlers;
+ public:
+ Subject() {};
+ Subject(const Subject&) = default;
+ Subject(Subject&&) = default;
+ Subject& operator=(const Subject&) = default;
+ Subject& operator=(Subject&&) = default;
+ void subscribe(const function_type& f) { handlers.emplace_back(f); }
+ void subscribe(function_type&& f) { handlers.emplace_back(std::move(f)); }
+ void notify_all(Args&&... args) {
+ for (auto& f : handlers) {
+ f(std::forward(args)...);
+ }
+ }
+ };
+
+};
+
+
diff --git a/engine/network.cpp b/engine/network.cpp
new file mode 100644
index 0000000..a302258
--- /dev/null
+++ b/engine/network.cpp
@@ -0,0 +1,141 @@
+/*
+ * This file is a part of Pixelbox - Infinite 2D sandbox game
+ * Enet wrapper!
+ * Copyright (C) 2023-2024 UtoECat
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+#include "engine/network.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "enet.h"
+
+/**
+ * @description ENet Wrapper
+ */
+namespace pb {
+
+void ENetBase::on_event_recv(ENetEvent& ev) {
+ auto conn = ENetConnection(ev.peer);
+ switch (ev.type) {
+ case ENET_EVENT_TYPE_CONNECT:
+ enet_peer_set_data(ev.peer, new ConnectionData());
+ h_connect.notify_all(*this, conn);
+ break;
+ case ENET_EVENT_TYPE_DISCONNECT:
+ case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
+ try {
+ h_disconnect.notify_all(*this, conn);
+ } catch (std::exception& e) {
+ std::cerr << "Exception in disconnect handler : " << e.what() << std::endl;
+ } catch (...) {
+ std::cerr << "Unknown exception in disconnect handler" << std::endl;
+ }
+ {
+ auto v = (ConnectionData*)enet_peer_get_data(ev.peer);
+ if (v) delete v;
+ enet_peer_set_data(ev.peer, nullptr);
+ }
+ break;
+ case ENET_EVENT_TYPE_RECEIVE:
+ try {
+ h_recieve.notify_all(*this, conn, ev.channelID,
+ std::string_view((const char*)enet_packet_get_data(ev.packet), enet_packet_get_length(ev.packet)));
+ } catch (std::exception& e) {
+ std::cerr << "Exception in message handler : " << e.what() << std::endl;
+ }
+ enet_packet_destroy(ev.packet); // hehe
+ break;
+ case ENET_EVENT_TYPE_NONE:
+ break;
+ }
+}
+
+void ENetBase::destroy() {
+ /** request disconnection */
+ foreach ([](ENetPeer* peer) {
+ enet_peer_timeout(peer, 2000, 1000, 5000);
+ enet_peer_disconnect(peer, -1); // shutdown
+ })
+ ;
+
+ flush();
+
+ int attempts = host->peerCount * 8; // process as much as *8 events
+ int dummies = host->peerCount;
+
+ ENetEvent ev;
+ while (dummies > 0 && attempts > 0 && enet_host_service(host, &ev, 5000)) {
+ switch (ev.type) {
+ case ENET_EVENT_TYPE_CONNECT:
+ enet_peer_reset(ev.peer); // ignore and kill
+ break;
+ case ENET_EVENT_TYPE_DISCONNECT:
+ case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
+ dummies--;
+ on_event_recv(ev); // default behaviour
+ break;
+ case ENET_EVENT_TYPE_RECEIVE:
+ enet_packet_destroy(ev.packet); // we don't care
+ break;
+ case ENET_EVENT_TYPE_NONE:
+ break;
+ }
+ }
+
+ force_destroy(); // shutdown now!
+};
+
+bool ENetClient::connect(const char* ip, unsigned short port) {
+ ProtocolInfo info;
+ info.ip = NULL;
+ info.port = port;
+ create_client();
+ server = nullptr;
+
+ if (!host) {
+ std::cerr << "ENetClient: can't create client host! (weird)" << std::endl;
+ return false; // :(
+ }
+
+ // connect
+ const ENetAddress addr = info.getAddress();
+ server = enet_host_connect(host, &addr, info.nchannels, 0);
+ if (!server) {
+ std::cerr << "ENetClient : can't create peer!" << std::endl;
+ goto err;
+ }
+
+ // process events
+ ENetEvent event;
+ if (enet_host_service(host, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
+ on_event_recv(event); // do init
+ return true;
+ }
+
+ std::cerr << "ENetClient : can't connect to the server!" << std::endl;
+err:
+ force_destroy();
+ return false;
+}
+
+}; // namespace pb
diff --git a/engine/network.hpp b/engine/network.hpp
index 8bdeae7..1592985 100644
--- a/engine/network.hpp
+++ b/engine/network.hpp
@@ -19,22 +19,30 @@
#pragma once
#include
+#include
+#include
+#include
+#include
#include
#include
#include
+#include
#include "external/enet.h"
#include
+#include "base/event.hpp"
/**
* @description ENet Wrapper
*/
namespace pb {
+ static constexpr unsigned short DEFAULT_PORT = 4792;
+
struct ProtocolInfo {
- const char* ip;
- unsigned short port;
- int nchannels;
- int nconnections;
+ const char* ip = "localhost";
+ unsigned short port = DEFAULT_PORT;
+ int nchannels = 5;
+ int nconnections = 0;
public:
inline ENetAddress getAddress() {
ENetAddress addr;
@@ -53,158 +61,286 @@ namespace pb {
host->maximumWaitingData = 1024 * 128;
}
- class ENetClient : public Default {
- ENetHost* host;
- ENetPeer* server = nullptr;
- ProtocolInfo pinfo;
+ /** represents any data */
+ using ConnectionData = std::shared_ptr;
+
+ class ENetConnection {
+ ENetPeer* peer = nullptr;
public:
+ ENetConnection(const ENetConnection&) = default;
+ ENetConnection(ENetConnection&&) = default;
+ ENetConnection& operator=(const ENetConnection&) = default;
+ ENetConnection& operator=(ENetConnection&&) = default;
- ENetClient(ProtocolInfo info) {
- pinfo = info;
- auto addr = info.getAddress();
- host = enet_host_create(NULL, info.nconnections, info.nchannels,
- 0, 0);
- if (!host) {
- throw std::runtime_error("Can't create client!");
- }
- HostDefaultConfig(host);
+ explicit ENetConnection(ENetPeer* p) { peer = p;}
+ ~ENetConnection() {} // nothing
+ public:
+
+ operator bool() {
+ return peer != nullptr;
}
void disconnect() {
- if (server) {
- enet_peer_reset(server);
- server = nullptr;
- }
+ if (!peer) return;
+ enet_peer_disconnect(peer, 0);
}
- const char* errmsg = nullptr;
- bool service(ENetEvent& ev, int timeout = 1) {
- return enet_host_service(host, &ev, timeout) > 0;
+ void disconnect_later() {
+ if (!peer) return;
+ enet_peer_disconnect_later(peer, 0);
}
- bool connect() {
- if (server) disconnect();
- auto addr = pinfo.getAddress();
- server = enet_host_connect(host, &addr, pinfo.nchannels, 0);
-
- if (!server) {
- errmsg = "can't create peer!";
- return false;
- }
- // process events
- ENetEvent event;
- if (enet_host_service(host, &event, 5000) > 0 &&
- event.type == ENET_EVENT_TYPE_CONNECT) {
- return true;
+ /** @warning you cannot use object methods after this! */
+ void reset() { // forcefully disconnect
+ if (!peer) return;
+ {
+ auto v = (ConnectionData*)enet_peer_get_data(peer);
+ if (v) delete v;
+ enet_peer_set_data(peer, nullptr);
}
+ enet_peer_reset(peer);
+ peer = nullptr;
+ }
- errmsg = "Can't connect to server!";
- enet_peer_reset(server);
- server = nullptr;
- return false;
+ bool send(uint8_t channel, ENetPacket* data) {
+ return enet_peer_send(peer, channel, data) == 0;
}
- ~ENetClient() {
- disconnect();
- enet_host_destroy(host);
+ bool send(uint8_t channel, std::string_view data) {
+ ENetPacket* p = enet_packet_create(data.data(), data.size(), 0);
+ if (!p) return false;
+ enet_peer_send(peer, channel, p);
}
- /* must be called after event is processeed */
- bool is_connected() {
- return server != nullptr;
+ ConnectionData& data() {
+ auto v = (ConnectionData*)enet_peer_get_data(peer);
+ if (!v) std::runtime_error("Connection has no associated data. Use afetr reset()/release_data() or misuse");
+ return *v;
+ }
+ };
+
+ /** @warning NEVER EVER TRY TO DESTROY ENETHOST FROM THE EVENT HANDLER!
+ * PLEASE set defer_destroy to true if you want to do that instead!
+ * @warning NEVER try to reconnect/dsconnect or do any other stuff in a shutdown handler too!
+ * @warning PLEASE DO NOT USE CALLBACKS FOR RESOURCE DEINITIALISATION! Use userdata object destructor for that!
+ */
+ class ENetBase {
+ protected:
+ ENetHost* host = nullptr;
+ ProtocolInfo info; // default
+ bool defer_destroy = false;
+
+ public: // event handlers
+ /** handled every time a client connects. @warning does not triggered on a server disconnection process. */
+ Subject h_connect;
+
+ /** triggered when asuccesfully connected peer is disconnected. Does not work in force_disconnect() case.
+ * also it's not triggered on a peers, that does not respond with a succesful disconnection.
+ */
+ Subject h_disconnect;
+
+ /**
+ * Triggered every time a message is recieved
+ * @param basic
+ * @param basic
+ * @param int specifies channel id where the message was recieved
+ * @param string_view message data
+ */
+ Subject h_recieve; // channel_id, data
+
+ /** server shutdown/disconnection handler. all connections are still valid.
+ * @warning does not called on force_disconnect at all!
+ */
+ Subject h_shutdown;
+
+ protected:
+ // handle events servicing
+ void on_event_recv(ENetEvent& ev);
+
+ protected:
+ /** @warning do not use inside event handers! */
+ void create_server() {
+ if (host) std::runtime_error("host is already exists!");
+ defer_destroy = false;
+ auto addr = info.getAddress();
+ host = enet_host_create(&addr, info.nconnections, info.nchannels, 0, 0);
}
- void releaseEvent(ENetEvent& ev) {
- if (ev.type == ENET_EVENT_TYPE_RECEIVE && ev.packet) {
- enet_packet_destroy(ev.packet);
+ /** @warning do not use inside event handers! */
+ void create_client() {
+ if (host) std::runtime_error("host is already exists!");
+ defer_destroy = false;
+ host = enet_host_create(NULL, info.nconnections, info.nchannels, 0, 0);
+ }
+
+ /** calls function at the first argument on all the peers connected to this host.*/
+ void foreach(std::function cb) {
+ if (!host) return;
+ ENetPeer *currentPeer;
+ for (currentPeer = host->peers;
+ currentPeer < &host->peers[host->peerCount]; ++currentPeer) {
+ cb(currentPeer);
}
}
- /** @warning channel starts FROM ZERO!!!! */
- void send(uint8_t channel, ENetPacket* data) {
- if (server)
- enet_peer_send(server, channel, data);
+ /** @warning do not use inside event handers! Use defer_destroy=true; instead!
+ * Also, this function does not invoke h_shutdown and h_disconnect events AT ALL!
+ */
+ void force_destroy() {
+ // cleanup all userdata
+ foreach([](ENetPeer* peer){
+ auto v = (ConnectionData*)enet_peer_get_data(peer);
+ if (v) delete v;
+ enet_peer_set_data(peer, nullptr);
+ });
+
+ if (host) enet_host_destroy(host);
+ host = nullptr;
+ }
+
+ /** @warning do not use inside event handers! Use defer_destroy=true; instead!
+ * Peacefully disconnects clients once, max 5s delay, 8 times more attempts.
+ * Clients that was not disconnected peacefully will be destroyed without h_desconnect event triggered!
+ */
+ void destroy();
+
+ /** @warning this does not affect already running Host! You should do destroy() and create()... maybe... */
+ void set_address(ProtocolInfo i) {
+ info = i;
+ }
+
+ public:
+ ENetBase() {}
+ ENetBase(const ENetBase&) = delete;
+ ENetBase(ENetBase&&) = default;
+ ENetBase& operator=(const ENetBase&) = delete;
+ ENetBase& operator=(ENetBase&&) = default;
+ ENetBase(ProtocolInfo i) : info(i) {}
+
+ /** use this oeprator in order to check if server is terminated!
+ * You may try to reconnect after that or do delete class instance.
+ */
+ operator bool() const {
+ return !!host;
}
+ /**
+ * Same as operator bool().
+ */
+ bool running() {return !!host;}
+
+ /** does deletes ENet hosts in a way how force_destroy() does, if it wasn't done already. */
+ ~ENetBase() {
+ force_destroy();
+ }
+
+ /** flushes all packages to be finally sended! No need to do if you regulary call a service()! */
void flush() {
enet_host_flush(host);
}
- };
- class ConnectionData {
- public:
- virtual ~ConnectionData() {};
- };
+ /** all work is done internally, but you may optional get event, but cna't use any data!
+ * @param timeout in illiseconds
+ */
+ bool service(ENetEvent* ev = nullptr, uint32_t timeout = 0) {
+ ENetEvent _dummy;
+ if (!ev) ev = &_dummy;
- class ENetConnection {
- ENetPeer* peer;
- public:
- ENetConnection(ENetPeer* p) {
- peer = p;
+ /** runned in case destruction is deferred. that's right :p */
+ if (defer_destroy) destroy();
+ if (!host) {*ev = ENetEvent{ENET_EVENT_TYPE_NONE, 0, 0 , 0, 0}; return false;}
+
+ bool stat = enet_host_service(host, ev, timeout) > 0;
+ on_event_recv(*ev);
+ return stat;
}
- ~ENetConnection() {} // nothing
+
+ };
+
+ class ENetClient : public Default, ENetBase {
+ ENetPeer* server = nullptr;
public:
- void disconnect() {
- enet_peer_disconnect_later(peer, 0);
+
+ ENetClient() {
+ h_disconnect.subscribe([this](ENetBase&, ENetConnection& conn) {
+ defer_destroy = true; // shutdown server
+ server = nullptr;
+ std::cerr << "Client: Server is disconnected!" << std::endl;
+ });
+ };
+
+ ENetClient(ProtocolInfo info) : ENetClient() {
+ set_address(info);
}
- void reset() { // forcefully disconnect
- release_data();
- enet_peer_reset(peer);
+ ~ENetClient() {
+ server = nullptr;
+ // all the other stuff is done in the base class!
}
- bool send(uint8_t channel, ENetPacket* data) {
- return enet_peer_send(peer, channel, data) == 0;
+ /** create AND connect a client to the specified server! */
+ bool connect(const char* ip, unsigned short port = DEFAULT_PORT);
+
+ /** function for a user :) */
+ void disconnect() {
+ if (server) {
+ enet_peer_reset(server);
+ server = nullptr;
+ }
+ destroy(); // destroy connection, all user data, etc.
}
- ConnectionData* data() {
- return (ConnectionData*)enet_peer_get_data(peer);
+ /* same as running() + extra check */
+ bool is_connected() {
+ return running() && server != nullptr;
}
- void set_data(ConnectionData* dat) {
- return enet_peer_set_data(peer, dat);
+ /** @warning channel starts FROM ZERO!!!! */
+ bool send(uint8_t channel, ENetPacket* data) {
+ if (!server || !host) return false;
+ return enet_peer_send(server, channel, data);
}
- void release_data() {
- delete data();
- set_data(nullptr);
+ /** @warning channel starts FROM ZERO!!!! */
+ bool send(uint8_t channel, const std::string_view data) {
+ if (!server || !host) return false;
+ ENetPacket* p = enet_packet_create(data.data(), data.size(), 0);
+ if (!p) return false;
+ return send(channel, p);
}
};
- class ENetServer : public Default {
- ENetHost* host;
+ class ENetServer : public Default,ENetBase {
+ bool shutdown_on_leaving = false;
public:
-
ENetServer(ProtocolInfo info) {
- ENetAddress addr = info.getAddress();
- host = enet_host_create(&addr, info.nconnections, info.nchannels,
- 0, 0);
- if (!host) {
- throw std::runtime_error("Can't create server!");
- }
- HostDefaultConfig(host);
+ set_address(info);
}
- bool service(ENetEvent& ev, int timeout = 1) {
- bool stat = enet_host_service(host, &ev, timeout) > 0;
- if (stat && ev.type == ENET_EVENT_TYPE_CONNECT) {
- enet_peer_set_data(ev.peer, nullptr); // we rely on this behaviour!
- }
- return stat;
+ bool create() {
+ ENetBase::create_server();
+ return !!host;
}
- ~ENetServer() {
- enet_host_destroy(host);
+ void shutdown() {
+ ENetBase::destroy(); // destroy connection, all user data, etc, and fire all callbacks
}
- bool is_valid() {
- return host != nullptr;
+ void destroy() {
+ ENetBase::force_destroy(); // immediate destroy!
}
- void releaseEvent(ENetEvent& ev) {
- if (ev.type == ENET_EVENT_TYPE_RECEIVE && ev.packet) {
- enet_packet_destroy(ev.packet);
- }
+ ~ENetServer() {
+ destroy();
+ }
+
+ /** all work is done internally, but you may optional get event, but cna't use any data! */
+ bool service(ENetEvent* ev = nullptr, int timeout = 1) {
+ ENetEvent _dummy;
+ if (!ev) ev = &_dummy;
+ bool stat = enet_host_service(host, ev, timeout) > 0;
+ on_event_recv(*ev);
+ return stat;
}
void broadcast(uint8_t channel, ENetPacket* data) {
@@ -213,52 +349,9 @@ namespace pb {
}
void foreach(std::function cb) {
- if (!cb) return;
- ENetPeer *currentPeer;
- for (currentPeer = host->peers;
- currentPeer < &host->peers[host->peerCount]; ++currentPeer) {
- cb(currentPeer);
- }
- }
-
- void flush() {
- enet_host_flush(host);
- }
-
- /* shutdown sequence. Behaviour after this is not specified! */
- void shutdown() {
- // send disconnect
- foreach([](ENetConnection c) {
- c.disconnect();
- });
- enet_host_flush(host);
-
- // queue
- ENetEvent ev;
- while (service(ev, 200)) {
- switch (ev.type) {
- case ENET_EVENT_TYPE_DISCONNECT:
- case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: {
- ENetConnection con(ev.peer);
- con.release_data();
- }; break;
- case ENET_EVENT_TYPE_CONNECT:
- enet_peer_disconnect_now(ev.peer, 0);
- break;
- case ENET_EVENT_TYPE_RECEIVE:
- enet_packet_destroy(ev.packet);
- break;
- default:
- break;
- };
- }
-
- // force disconnect and free all data
- foreach([](ENetConnection c) {
- c.reset();
+ ENetBase::foreach([cb](ENetPeer* p){
+ cb(ENetConnection(p));
});
-
- // now we can safely call destructor!
}
};
diff --git a/game/world_view.hpp b/game/world_view.hpp
index 4cd3869..0cc92fe 100644
--- a/game/world_view.hpp
+++ b/game/world_view.hpp
@@ -26,10 +26,10 @@ namespace pb {
public:
WorldViewer();
~WorldViewer();
- WorldViewer(const WorldViewer &) = delete;
+ WorldViewer(const WorldViewer &);
WorldViewer(WorldViewer &&) = delete;
WorldViewer &operator=(const WorldViewer &) = delete;
- WorldViewer &operator=(WorldViewer &&) = delete;
+ WorldViewer &operator=(WorldViewer &&);
};
};
\ No newline at end of file