feat: added client node and registered clue id

This commit is contained in:
Sara Gerretsen 2025-10-12 12:54:06 +02:00
parent 38081a7fd0
commit acb7351ea7
11 changed files with 167 additions and 82 deletions

View file

@ -0,0 +1,27 @@
#include "client_node.h"
#include "ydi_client.h"
void ClientNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("connect_to_server"), &self_type::connect_to_server);
}
void ClientNode::exit_tree() {
ydi::client::disconnect();
}
void ClientNode::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
switch (what) {
case NOTIFICATION_EXIT_TREE:
exit_tree();
return;
default:
return;
}
}
void ClientNode::connect_to_server(String const &server) {
ydi::client::connect(server);
}

View file

@ -0,0 +1,15 @@
#pragma once
#include <scene/main/node.h>
class ClientNode : Node {
GDCLASS(ClientNode, Node);
static void _bind_methods();
void exit_tree();
protected:
void _notification(int what);
public:
void connect_to_server(String const &url);
};

View file

@ -1,6 +1,8 @@
#include "register_types.h"
#include "client_node.h"
#include "server_node.h"
#include "ydi_networking.h"
#include <core/object/class_db.h>
void initialize_you_done_it_module(ModuleInitializationLevel p_level) {
@ -8,6 +10,8 @@ void initialize_you_done_it_module(ModuleInitializationLevel p_level) {
return;
}
ClassDB::register_class<ServerNode>();
ClassDB::register_class<ClientNode>();
ClassDB::register_abstract_class<NetworkData>();
}
void uninitialize_you_done_it_module(ModuleInitializationLevel p_level) {

View file

@ -2,12 +2,22 @@
#include "ydi_server.h"
#include <core/config/engine.h>
void ServerNode::_bind_methods() {}
void ServerNode::_bind_methods() {
ADD_SIGNAL(MethodInfo("new_clue", PropertyInfo(Variant::INT, "id")));
}
void ServerNode::enter_tree() {
ydi::server::open();
}
void ServerNode::process(double delta) {
Vector<NetworkData::ClueID> new_clues{};
if (ydi::server::receive::new_clues(new_clues)) {
for (NetworkData::ClueID clue : new_clues) {
emit_signal("new_clue", clue);
}
}
}
void ServerNode::exit_tree() {
ydi::server::close();
}
@ -20,6 +30,9 @@ void ServerNode::_notification(int what) {
case NOTIFICATION_ENTER_TREE:
enter_tree();
return;
case NOTIFICATION_PROCESS:
process(get_process_delta_time());
return;
case NOTIFICATION_EXIT_TREE:
exit_tree();
return;

View file

@ -6,6 +6,7 @@ class ServerNode : public Node {
GDCLASS(ServerNode, Node);
static void _bind_methods();
void enter_tree();
void process(double delta);
void exit_tree();
protected:

View file

@ -14,7 +14,7 @@ struct Connection {
std::optional<zmq::context_t> context{ std::nullopt };
std::optional<zmq::socket_t> socket{ std::nullopt };
std::recursive_mutex mtx;
std::atomic<ConnectionStatus> status;
std::atomic<NetworkData::ConnectionStatus> status;
std::atomic<bool> stop_threads;
};
@ -23,22 +23,25 @@ std::optional<Connection> connection{ std::nullopt };
std::optional<std::thread> receive_thread{ std::nullopt };
void handle_ok(zmq::multipart_t const &message) {
MessageType type{ to_message_type(message[1]) };
NetworkData::MessageType type{ to_message_type(message[1]) };
switch (type) {
default: // no need to handle every OK, just some relevant ones
return;
case CONNECT:
connection->status = AUTHENTICATED;
case NetworkData::MSG_CONNECT:
connection->status = NetworkData::CONNECTION_AUTHENTICATED;
return;
}
}
void handle_message(zmq::multipart_t const &message) {
MessageType type{ to_message_type(message[0]) };
NetworkData::MessageType type{ to_message_type(message[0]) };
switch (type) {
case OK:
case NetworkData::MSG_OK:
handle_ok(message);
case NetworkData::MSG_HEART:
multipart(NetworkData::MSG_BEAT).send(*connection->socket);
default:
print_error(vformat("Client: Unhandled message type received: ", type, message[0].to_string().c_str()));
print_error(vformat("Client: Received unhandled message: ", type, message[0].to_string().c_str()));
return;
}
}

View file

@ -7,6 +7,6 @@ namespace ydi::client {
void connect(String const &url);
void disconnect();
namespace send {
void reveal_clue(ClueID id);
void reveal_clue(NetworkData::ClueID id);
} //namespace send
} //namespace ydi::client

View file

@ -1,38 +1,48 @@
#include "ydi_networking.h"
#include <core/core_bind.h>
#include <core/object/class_db.h>
MAKE_TYPE_INFO(NetworkData::ClueID, Variant::INT);
void NetworkData::_bind_methods() {
BIND_ENUM_CONSTANT(CLUE_FIRST);
BIND_ENUM_CONSTANT(CLUE_SECOND);
BIND_ENUM_CONSTANT(CLUE_MAX);
}
namespace ydi {
MessageType to_message_type(zmq::message_t const &msg) {
NetworkData::MessageType to_message_type(zmq::message_t const &msg) {
int as_int{ std::stoi(msg.str()) };
if (as_int >= 0 && as_int < MESSAGE_TYPE_INVALID) {
return (MessageType)as_int;
if (as_int >= 0 && as_int < NetworkData::MSG_INVALID) {
return (NetworkData::MessageType)as_int;
} else {
return MESSAGE_TYPE_INVALID;
return NetworkData::MSG_INVALID;
}
}
NOKReason to_nok_reason(zmq::message_t const &msg) {
NetworkData::NOKReason to_nok_reason(zmq::message_t const &msg) {
int as_int{ std::stoi(msg.str()) };
if (as_int >= 0 && as_int < NOK_REASON_INVALID) {
return (NOKReason)as_int;
if (as_int >= 0 && as_int < NetworkData::NOK_REASON_INVALID) {
return (NetworkData::NOKReason)as_int;
} else {
return NOK_REASON_INVALID;
return NetworkData::NOK_REASON_INVALID;
}
}
ClueID to_clue_id(zmq::message_t const &msg) {
NetworkData::ClueID to_clue_id(zmq::message_t const &msg) {
int as_int{ std::stoi(msg.str()) };
if (as_int >= 0 && as_int < CLUE_MAX) {
return (ClueID)as_int;
if (as_int >= 0 && as_int < NetworkData::CLUE_MAX) {
return (NetworkData::ClueID)as_int;
} else {
return CLUE_MAX;
return NetworkData::CLUE_MAX;
}
}
void extend_multipart(zmq::multipart_t &mpart, MessageType type) {
void extend_multipart(zmq::multipart_t &mpart, NetworkData::MessageType type) {
mpart.addstr(std::to_string(type));
}
void extend_multipart(zmq::multipart_t &mpart, ClueID id) {
void extend_multipart(zmq::multipart_t &mpart, NetworkData::ClueID id) {
mpart.addstr(std::to_string(id));
}

View file

@ -1,47 +1,57 @@
#pragma once
#include <core/object/class_db.h>
#include <core/object/object.h>
#include <zmq.h>
#include <string>
#include <string_view>
#include <zmq.hpp>
#include <zmq_addon.hpp>
class NetworkData : Object {
GDCLASS(NetworkData, Object);
static void _bind_methods();
public:
enum MessageType {
// connection management messages
MSG_CONNECT = 1u,
MSG_OK,
MSG_NOK,
MSG_HEART,
MSG_BEAT,
// gameplay messages
MSG_REVEAL,
// end of messages
MSG_INVALID
};
enum ClueID {
CLUE_FIRST,
CLUE_SECOND,
CLUE_MAX
};
enum ConnectionStatus {
CONNECTION_DISCONNECTED,
CONNECTION_CONNECTED,
CONNECTION_AUTHENTICATED
};
enum NOKReason {
NOK_UNAUTHENTICATED,
NOK_UNKNOWN_MSG,
NOK_REASON_INVALID //!< this means the value could not be parsed as a NOK reason, not that the reason is an invalid message.INVALID
};
};
namespace ydi {
enum MessageType {
// connection management messages
CONNECT = 1u,
OK,
NOK,
HEART,
BEAT,
// gameplay messages
REVEAL,
// end of messages
MESSAGE_TYPE_INVALID
};
NetworkData::MessageType to_message_type(zmq::message_t const &msg);
NetworkData::NOKReason to_nok_reason(zmq::message_t const &msg);
NetworkData::ClueID to_clue_id(zmq::message_t const &msg);
enum ClueID {
FIRST_CLUE,
CLUE_MAX
};
enum ConnectionStatus {
DISCONNECTED,
CONNECTED,
AUTHENTICATED
};
enum NOKReason {
CLIENT_UNAUTHENTICATED,
NOK_REASON_INVALID //!< this means the value could not be parsed as a NOK reason, not that the reason is an invalid message.INVALID
};
MessageType to_message_type(zmq::message_t const &msg);
NOKReason to_nok_reason(zmq::message_t const &msg);
ClueID to_clue_id(zmq::message_t const &msg);
void extend_multipart(zmq::multipart_t &mpart, MessageType type);
void extend_multipart(zmq::multipart_t &mpart, ClueID type);
void extend_multipart(zmq::multipart_t &mpart, NetworkData::MessageType type);
void extend_multipart(zmq::multipart_t &mpart, NetworkData::ClueID type);
void extend_multipart(zmq::multipart_t &mpart, std::string const &string);
void extend_multipart(zmq::multipart_t &mpart, std::string_view const &strv);

View file

@ -5,26 +5,19 @@
#include <atomic>
#include <mutex>
#include <optional>
#include <queue>
#include <thread>
#include <unordered_set>
#include <zmq.hpp>
#include <zmq_addon.hpp>
namespace ydi::server {
struct Event {
MessageType type;
union {
ClueID clue;
};
};
struct Service {
std::optional<zmq::context_t> context{ std::nullopt };
std::optional<zmq::socket_t> socket{ std::nullopt };
std::unordered_set<ClueID> revealed_clues{};
std::unordered_set<NetworkData::ClueID> revealed_clues{};
std::recursive_mutex mtx{};
std::optional<std::string> client{ std::nullopt };
std::queue<Event> unhandled_events{};
Vector<NetworkData::ClueID> new_clues{};
double lastHeart{ 0.0 };
double lastBeat{ 0.0 };
@ -36,23 +29,22 @@ std::optional<std::thread> ping_thread{ std::nullopt };
std::optional<Service> service{ std::nullopt };
void handle_reveal_clue(zmq::multipart_t const &message) {
Event evt{ .type = REVEAL, .clue = to_clue_id(message.at(2)) };
service->unhandled_events.push(evt);
service->new_clues.push_back(to_clue_id(message.at(2)));
}
void handle_authorised_message(std::string_view const &sender, MessageType type, zmq::multipart_t &message) {
if (type == BEAT) {
void handle_authorised_message(std::string_view const &sender, NetworkData::MessageType type, zmq::multipart_t &message) {
if (type == NetworkData::MSG_BEAT) {
service->lastBeat = Time::get_singleton()->get_unix_time_from_system();
} else if (type == REVEAL) {
} else if (type == NetworkData::MSG_REVEAL) {
handle_reveal_clue(message);
} else {
multipart(sender, "NOK", "UNKOWN_COMMAND", message).send(*service->socket);
multipart(sender, NetworkData::MSG_NOK, NetworkData::NOK_UNKNOWN_MSG, message).send(*service->socket);
}
}
void handle_message(zmq::multipart_t &message) {
std::string_view const sender{ message.at(0).to_string_view() };
MessageType type{ to_message_type(message.at(1)) };
NetworkData::MessageType type{ to_message_type(message.at(1)) };
std::scoped_lock lock{ service->mtx };
if (service->client) {
if (sender == service->client) {
@ -60,11 +52,11 @@ void handle_message(zmq::multipart_t &message) {
} else {
multipart(sender, "NOK", "UNAUTHORIZED_REQUEST").send(*service->socket);
}
} else if (type == CONNECT) {
} else if (type == NetworkData::MSG_CONNECT) {
service->client.emplace(sender);
multipart(sender, OK, message).send(*service->socket);
multipart(sender, NetworkData::MSG_OK, message).send(*service->socket);
} else {
multipart(sender, NOK, "UNAUTHORIZED_REQUEST", message).send(*service->socket);
multipart(sender, NetworkData::MSG_NOK, "UNAUTHORIZED_REQUEST", message).send(*service->socket);
}
}
@ -161,4 +153,16 @@ void close() {
print_line("Server: Shutdown complete!");
}
}
namespace receive {
bool new_clues(Vector<NetworkData::ClueID> &out) {
std::scoped_lock lock{ service->mtx };
bool has_new{ !service->new_clues.is_empty() };
if (has_new) {
out.append_array(service->new_clues);
service->new_clues.clear();
}
return has_new;
}
} //namespace receive
} //namespace ydi::server

View file

@ -2,6 +2,7 @@
#include "ydi_networking.h"
#include <core/io/image.h>
#include <core/object/ref_counted.h>
#include <core/string/ustring.h>
#include <core/templates/vector.h>
#include <cassert>
@ -9,11 +10,8 @@
namespace ydi::server {
void open();
void close();
namespace receive {
bool isRevealed(ClueID id);
Ref<Image> clueImage(ClueID id);
bool new_clues(Vector<NetworkData::ClueID> &out);
} //namespace receive
namespace send {
void announceConclusion(ClueID method, ClueID motive, ClueID murderer);
}
} //namespace ydi::server