Merge branch 'development' of https://git.objectionable.solutions/Team-Immerse-Yourself/YouDunIt into development
This commit is contained in:
commit
b764992b93
19 changed files with 728 additions and 96 deletions
9
.clangd
9
.clangd
|
|
@ -6,6 +6,8 @@ Diagnostics:
|
|||
Includes:
|
||||
IgnoreHeader:
|
||||
- \.compat\.inc
|
||||
CompileFlags:
|
||||
CompilationDatabase: engine/
|
||||
---
|
||||
# Header-specific conditions.
|
||||
|
||||
|
|
@ -20,11 +22,4 @@ CompileFlags:
|
|||
- -Wno-unused-const-variable
|
||||
- -Wno-unused-function
|
||||
- -Wno-unused-variable
|
||||
---
|
||||
# Suppress all third-party warnings.
|
||||
|
||||
If:
|
||||
PathMatch: thirdparty/.*
|
||||
|
||||
Diagnostics:
|
||||
Suppress: "*"
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -22,3 +22,4 @@ compile_commands.json
|
|||
*__pycache__
|
||||
|
||||
*.blend1
|
||||
/.dir-locals.el
|
||||
|
|
|
|||
72
flatscreen-project/network_test.tscn
Normal file
72
flatscreen-project/network_test.tscn
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://bbvpj46frv0ho"]
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_78ugl"]
|
||||
script/source = "extends ClientNode
|
||||
|
||||
@onready
|
||||
var client_status := %ClientStatus
|
||||
|
||||
func _ready():
|
||||
connect_to_server(\"localhost\")
|
||||
|
||||
func _on_connection_changed(connected: int) -> void:
|
||||
if (connected == NetworkData.CONNECTION_DISCONNECTED):
|
||||
client_status.text = \"DISCONNECTED\"
|
||||
elif (connected == NetworkData.CONNECTION_CONNECTED):
|
||||
client_status.text = \"CONNECTED\"
|
||||
elif (connected == NetworkData.CONNECTION_AUTHENTICATED):
|
||||
client_status.text = \"AUTHENTICATED\"
|
||||
"
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="ServerNode" type="ServerNode" parent="."]
|
||||
|
||||
[node name="ClientNode" type="ClientNode" parent="."]
|
||||
script = SubResource("GDScript_78ugl")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="ServerInfo" type="PanelContainer" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Header" type="Label" parent="HBoxContainer/ServerInfo"]
|
||||
layout_mode = 2
|
||||
text = "Server: OFF"
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/ServerInfo/Header"]
|
||||
layout_mode = 0
|
||||
offset_top = -312.0
|
||||
offset_right = 574.0
|
||||
offset_bottom = 336.0
|
||||
|
||||
[node name="ClientInfo" type="PanelContainer" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/ClientInfo"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Header" type="Label" parent="HBoxContainer/ClientInfo/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Client:"
|
||||
|
||||
[node name="ClientStatus" type="Label" parent="HBoxContainer/ClientInfo/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "DISCONNECTED"
|
||||
|
||||
[connection signal="connection_changed" from="ClientNode" to="ClientNode" method="_on_connection_changed"]
|
||||
|
|
@ -9,6 +9,8 @@
|
|||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="you_done_it"
|
||||
run/main_scene="uid://bbvpj46frv0ho"
|
||||
config/features=PackedStringArray("4.5", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
|
|
|||
3
flatscreen-project/scenes/flatscreen_root.tscn
Normal file
3
flatscreen-project/scenes/flatscreen_root.tscn
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[gd_scene format=3 uid="uid://dosb4sb7pvss4"]
|
||||
|
||||
[node name="ServerNode" type="ServerNode"]
|
||||
9
justfile
9
justfile
|
|
@ -6,7 +6,7 @@ compiledb := if tree_hash == `cat .tree.hash` { "no" } else { "yes" }
|
|||
|
||||
build: format
|
||||
# Compiling Editor
|
||||
cd engine/ && scons target=editor symbols=yes optimization=debug dev_build=yes linker=mold use_llvm=yes compiledb={{compiledb}} custom_modules="../modules"
|
||||
cd engine/ && scons disable_exceptions=false target=editor symbols=yes optimization=debug dev_build=yes linker=mold use_llvm=yes compiledb={{compiledb}} custom_modules="../modules"
|
||||
echo {{tree_hash}} > .tree.hash
|
||||
|
||||
run: build
|
||||
|
|
@ -15,7 +15,7 @@ run: build
|
|||
|
||||
release-linux: build
|
||||
# Compiling Linux Release
|
||||
cd engine/ && scons platform=linuxbsd target=template_release arch=x86_64 linker=mold use_llvm=yes custom_modules="../modules"
|
||||
cd engine/ && scons disable_exceptions=false platform=linuxbsd target=template_release arch=x86_64 linker=mold use_llvm=yes custom_modules="../modules"
|
||||
# Preparing Build Environment
|
||||
sed -i "s!templatepath!{{`realpath engine/bin/godot.linuxbsd.template_release.x86_64.llvm`}}!" project/export_presets.cfg
|
||||
rm -rf build && mkdir build
|
||||
|
|
@ -27,7 +27,7 @@ release-linux: build
|
|||
|
||||
release-windows: build
|
||||
# Compiling Windows Release
|
||||
cd engine/ && scons platform=windows target=template_release arch=x86_64 linker=mold custom_modules="../modules"
|
||||
cd engine/ && scons disable_exceptions=false platform=windows target=template_release arch=x86_64 linker=mold custom_modules="../modules"
|
||||
# Preparing Build Environment
|
||||
sed -i "s!templatepath!{{`realpath engine/bin/godot.windows.template_release.x86_64`}}!" project/export_presets.cfg
|
||||
rm -rf build && mkdir build
|
||||
|
|
@ -46,4 +46,5 @@ initialize-template projectname:
|
|||
|
||||
format:
|
||||
# Formatting Custom Modules
|
||||
clang-format -i $(find modules/ -iname '*.h' -o -iname '*.c' -o -iname '*.hpp' -o -iname '*.cpp' -o -iname '*.hxx' -o -iname '*.cxx')
|
||||
clang-format -i modules/you_done_it/*.h modules/you_done_it/*.cpp
|
||||
# $(find modules/ -iname '*.h' -o -iname '*.c' -o -iname '*.hpp' -o -iname '*.cpp' -o -iname '*.hxx' -o -iname '*.cxx')
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ ydi_env = env.Clone()
|
|||
ydi_env.Append(CPPPATH=["libzmq/include", "cppzmq/"])
|
||||
ydi_env.add_source_files(env.modules_sources, "*.cpp")
|
||||
|
||||
if not os.path.isdir(Dir('libzmq/build/lib').abspath):
|
||||
call(["cmake", "-Slibzmq", "-B" + Dir('libzmq/build').abspath, "-DZMQ_BUILD_TESTS=OFF"])
|
||||
call(["cmake", "-Slibzmq", "-B" + Dir('libzmq/build').abspath, "-DZMQ_BUILD_TESTS=OFF", "-DZMQ_BUILD_STATIC=ON", "-DZMQ_BUILD_SHARED=OFF"])
|
||||
call(["cmake", "--build", Dir('libzmq/build').abspath])
|
||||
|
||||
env.Append(LIBPATH=[Dir('libzmq/build/lib/').abspath])
|
||||
ydi_env.Append(LIBS=['libzmq'])
|
||||
ydi_env.Append(LIBPATH=[Dir('libzmq/build/lib/').abspath])
|
||||
env.Append(LIBS=['libzmq'])
|
||||
|
|
|
|||
55
modules/you_done_it/client_node.cpp
Normal file
55
modules/you_done_it/client_node.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#include "client_node.h"
|
||||
#include "ydi_client.h"
|
||||
|
||||
ClientNode *ClientNode::singleton_instance{ nullptr };
|
||||
String const ClientNode::sig_connection_changed{ "connection_changed" };
|
||||
|
||||
void ClientNode::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("connect_to_server", "server_url"), &self_type::connect_to_server);
|
||||
ADD_SIGNAL(MethodInfo(sig_connection_changed, PropertyInfo(Variant::INT, "connected", PROPERTY_HINT_ENUM, "Disconnected,Connected,Authenticated")));
|
||||
}
|
||||
|
||||
void ClientNode::enter_tree() {
|
||||
if (singleton_instance) {
|
||||
print_error("Attempt to create duplicate ClientNode, aborting");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void ClientNode::process() {
|
||||
NetworkData::ConnectionStatus const new_status{ ydi::client::status() };
|
||||
if (new_status != this->state) {
|
||||
this->state = new_status;
|
||||
emit_signal(sig_connection_changed, new_status);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientNode::exit_tree() {
|
||||
ydi::client::disconnect();
|
||||
if (singleton_instance == this) {
|
||||
singleton_instance = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientNode::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
set_process(true);
|
||||
return;
|
||||
case NOTIFICATION_PROCESS:
|
||||
process();
|
||||
return;
|
||||
case NOTIFICATION_EXIT_TREE:
|
||||
exit_tree();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientNode::connect_to_server(String const &server) {
|
||||
ydi::client::connect(server);
|
||||
}
|
||||
25
modules/you_done_it/client_node.h
Normal file
25
modules/you_done_it/client_node.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "ydi_networking.h"
|
||||
#include <scene/main/node.h>
|
||||
|
||||
class ClientNode : Node {
|
||||
GDCLASS(ClientNode, Node);
|
||||
static ClientNode *singleton_instance;
|
||||
static void _bind_methods();
|
||||
void enter_tree();
|
||||
void process();
|
||||
void exit_tree();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
void connect_to_server(String const &url);
|
||||
|
||||
private:
|
||||
NetworkData::ConnectionStatus state{ NetworkData::CONNECTION_DISCONNECTED };
|
||||
|
||||
public:
|
||||
static String const sig_connection_changed;
|
||||
};
|
||||
|
|
@ -1,12 +1,17 @@
|
|||
#include "register_types.h"
|
||||
|
||||
#include "core/object/class_db.h"
|
||||
#include "zmq.hpp"
|
||||
#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) {
|
||||
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
|
||||
return;
|
||||
}
|
||||
ClassDB::register_class<ServerNode>();
|
||||
ClassDB::register_class<ClientNode>();
|
||||
ClassDB::register_abstract_class<NetworkData>();
|
||||
}
|
||||
|
||||
void uninitialize_you_done_it_module(ModuleInitializationLevel p_level) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
#ifndef you_done_it_REGISTER_TYPES_H
|
||||
#define you_done_it_REGISTER_TYPES_H
|
||||
#pragma once
|
||||
|
||||
#include "modules/register_module_types.h"
|
||||
|
||||
void initialize_you_done_it_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_you_done_it_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif // !you_done_it_REGISTER_TYPES_H
|
||||
|
|
|
|||
64
modules/you_done_it/server_node.cpp
Normal file
64
modules/you_done_it/server_node.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include "server_node.h"
|
||||
#include "ydi_server.h"
|
||||
#include <core/config/engine.h>
|
||||
|
||||
ServerNode *ServerNode::singleton_instance{ nullptr };
|
||||
String const ServerNode::sig_clue_revealed{ "clue_revealed" };
|
||||
String const ServerNode::sig_connection_established{ "connection_established" };
|
||||
String const ServerNode::sig_connection_lost{ "connection_lost" };
|
||||
|
||||
void ServerNode::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo(sig_clue_revealed, PropertyInfo(Variant::INT, "id")));
|
||||
ADD_SIGNAL(MethodInfo(sig_connection_established));
|
||||
ADD_SIGNAL(MethodInfo(sig_connection_lost));
|
||||
}
|
||||
|
||||
void ServerNode::enter_tree() {
|
||||
if (singleton_instance) {
|
||||
print_error("Attempt to create duplicate ServerNode, aborting");
|
||||
abort();
|
||||
}
|
||||
singleton_instance = this;
|
||||
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(sig_clue_revealed, clue);
|
||||
}
|
||||
}
|
||||
bool new_is_connected{ ydi::server::has_client() };
|
||||
if (this->is_connected != new_is_connected) {
|
||||
this->is_connected = new_is_connected;
|
||||
emit_signal(this->is_connected ? sig_connection_established : sig_connection_lost);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerNode::exit_tree() {
|
||||
ydi::server::close();
|
||||
if (singleton_instance == this) {
|
||||
singleton_instance = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerNode::_notification(int what) {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return;
|
||||
}
|
||||
switch (what) {
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
set_process(true);
|
||||
enter_tree();
|
||||
return;
|
||||
case NOTIFICATION_PROCESS:
|
||||
process(get_process_delta_time());
|
||||
return;
|
||||
case NOTIFICATION_EXIT_TREE:
|
||||
exit_tree();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
23
modules/you_done_it/server_node.h
Normal file
23
modules/you_done_it/server_node.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <scene/main/node.h>
|
||||
|
||||
class ServerNode : public Node {
|
||||
GDCLASS(ServerNode, Node);
|
||||
static ServerNode *singleton_instance;
|
||||
static void _bind_methods();
|
||||
void enter_tree();
|
||||
void process(double delta);
|
||||
void exit_tree();
|
||||
|
||||
protected:
|
||||
void _notification(int what);
|
||||
|
||||
public:
|
||||
static String const sig_clue_revealed;
|
||||
static String const sig_connection_established;
|
||||
static String const sig_connection_lost;
|
||||
|
||||
private:
|
||||
bool is_connected{ false };
|
||||
};
|
||||
144
modules/you_done_it/ydi_client.cpp
Normal file
144
modules/you_done_it/ydi_client.cpp
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#include "ydi_client.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "you_done_it/ydi_networking.h"
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <zmq.hpp>
|
||||
#include <zmq_addon.hpp>
|
||||
|
||||
namespace ydi::client {
|
||||
struct Connection {
|
||||
std::optional<std::string> server;
|
||||
std::optional<zmq::context_t> context{ std::nullopt };
|
||||
std::optional<zmq::socket_t> socket{ std::nullopt };
|
||||
std::recursive_mutex mtx;
|
||||
std::atomic<NetworkData::ConnectionStatus> status;
|
||||
std::atomic<bool> stop_threads{ false };
|
||||
};
|
||||
|
||||
std::optional<Connection> connection{ std::nullopt };
|
||||
|
||||
std::optional<std::thread> receive_thread{ std::nullopt };
|
||||
|
||||
void handle_ok(zmq::multipart_t const &message) {
|
||||
NetworkData::MessageType type{ to_message_type(message[1]) };
|
||||
switch (type) {
|
||||
case NetworkData::MSG_CONNECT:
|
||||
connection->status = NetworkData::CONNECTION_AUTHENTICATED;
|
||||
return;
|
||||
default: // no need to handle every OK, just some relevant ones
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_message(zmq::multipart_t const &message) {
|
||||
print_line("Client handle_message:");
|
||||
print_message_contents(message);
|
||||
NetworkData::MessageType type{ to_message_type(message[0]) };
|
||||
switch (type) {
|
||||
case NetworkData::MSG_OK:
|
||||
print_line("Client: received OK");
|
||||
handle_ok(message);
|
||||
return;
|
||||
case NetworkData::MSG_HEART:
|
||||
print_line("Client: Received HEART, sending BEAT");
|
||||
multipart(NetworkData::MSG_BEAT).send(*connection->socket);
|
||||
return;
|
||||
default:
|
||||
print_line("Client: Message not handled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void receive_thread_entry() {
|
||||
{
|
||||
std::scoped_lock lock{ connection->mtx };
|
||||
multipart(NetworkData::MSG_CONNECT).send(*connection->socket);
|
||||
}
|
||||
zmq::multipart_t message{};
|
||||
while (!connection->stop_threads) {
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(10ms);
|
||||
std::scoped_lock lock{ connection->mtx };
|
||||
if (message.recv(*connection->socket, (int)zmq::recv_flags::dontwait)) {
|
||||
handle_message(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void connect(String const &url) {
|
||||
if (connection) {
|
||||
print_line("Client: Detected attempt to open duplicate client connection, exiting without action");
|
||||
return;
|
||||
}
|
||||
connection.emplace();
|
||||
print_line("Client: Connecting to ", url);
|
||||
try {
|
||||
connection->context.emplace(1);
|
||||
} catch (...) {
|
||||
connection.reset();
|
||||
print_line("Client: Failed to create context");
|
||||
return;
|
||||
}
|
||||
print_line("Client: Created context");
|
||||
try {
|
||||
connection->socket.emplace(*connection->context, zmq::socket_type::dealer);
|
||||
} catch (...) {
|
||||
connection->context->close();
|
||||
connection->context.reset();
|
||||
connection.reset();
|
||||
print_line("Client: Failed to create socket");
|
||||
return;
|
||||
}
|
||||
print_line("Client: Created socket");
|
||||
try {
|
||||
CharStringT<char> cstrurl{ url.ascii() };
|
||||
std::string server{ cstrurl.get_data() };
|
||||
server = "tcp://" + server + ":6667";
|
||||
connection->socket->connect(server);
|
||||
connection->server = server;
|
||||
} catch (...) {
|
||||
connection->socket->close();
|
||||
connection->socket.reset();
|
||||
connection->context->close();
|
||||
connection->context.reset();
|
||||
connection.reset();
|
||||
print_line("Client: Failed to connect to server");
|
||||
}
|
||||
print_line("Client: connected to server");
|
||||
connection->status = NetworkData::CONNECTION_CONNECTED;
|
||||
receive_thread.emplace(receive_thread_entry);
|
||||
print_line("Client: Connection complete!");
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
if (connection) {
|
||||
connection->stop_threads = true;
|
||||
if (receive_thread && receive_thread->joinable()) {
|
||||
receive_thread->join();
|
||||
}
|
||||
if (connection->socket) {
|
||||
connection->socket->close();
|
||||
connection->socket.reset();
|
||||
}
|
||||
if (connection->context) {
|
||||
connection->context->shutdown();
|
||||
connection->context.reset();
|
||||
}
|
||||
}
|
||||
connection.reset();
|
||||
}
|
||||
|
||||
NetworkData::ConnectionStatus status() {
|
||||
return connection->status;
|
||||
}
|
||||
|
||||
namespace send {
|
||||
void reveal_clue(NetworkData::ClueID id) {
|
||||
std::scoped_lock lock{ connection->mtx };
|
||||
multipart(NetworkData::MSG_REVEAL, id).send(*connection->socket);
|
||||
}
|
||||
} //namespace send
|
||||
} //namespace ydi::client
|
||||
13
modules/you_done_it/ydi_client.h
Normal file
13
modules/you_done_it/ydi_client.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "ydi_networking.h"
|
||||
#include <core/string/ustring.h>
|
||||
|
||||
namespace ydi::client {
|
||||
void connect(String const &url);
|
||||
void disconnect();
|
||||
NetworkData::ConnectionStatus status();
|
||||
namespace send {
|
||||
void reveal_clue(NetworkData::ClueID id);
|
||||
} //namespace send
|
||||
} //namespace ydi::client
|
||||
86
modules/you_done_it/ydi_networking.cpp
Normal file
86
modules/you_done_it/ydi_networking.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#include "ydi_networking.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include <core/core_bind.h>
|
||||
#include <core/object/class_db.h>
|
||||
|
||||
MAKE_TYPE_INFO(NetworkData::ClueID, Variant::INT);
|
||||
MAKE_TYPE_INFO(NetworkData::ConnectionStatus, Variant::INT);
|
||||
|
||||
void NetworkData::_bind_methods() {
|
||||
BIND_ENUM_CONSTANT(CLUE_FIRST);
|
||||
BIND_ENUM_CONSTANT(CLUE_SECOND);
|
||||
BIND_ENUM_CONSTANT(CLUE_MAX);
|
||||
|
||||
BIND_ENUM_CONSTANT(CONNECTION_DISCONNECTED);
|
||||
BIND_ENUM_CONSTANT(CONNECTION_CONNECTED);
|
||||
BIND_ENUM_CONSTANT(CONNECTION_AUTHENTICATED);
|
||||
}
|
||||
|
||||
namespace ydi {
|
||||
int to_int(zmq::message_t const &msg, int failure) {
|
||||
try {
|
||||
return std::stoi(msg.to_string());
|
||||
} catch (...) {
|
||||
return failure;
|
||||
}
|
||||
}
|
||||
NetworkData::MessageType to_message_type(zmq::message_t const &msg) {
|
||||
return (NetworkData::MessageType)to_int(msg, (int)NetworkData::MSG_INVALID);
|
||||
}
|
||||
|
||||
NetworkData::NOKReason to_nok_reason(zmq::message_t const &msg) {
|
||||
return (NetworkData::NOKReason)to_int(msg, (int)NetworkData::NOK_INVALID_REASON);
|
||||
}
|
||||
|
||||
NetworkData::ClueID to_clue_id(zmq::message_t const &msg) {
|
||||
return (NetworkData::ClueID)to_int(msg, NetworkData::CLUE_MAX);
|
||||
}
|
||||
|
||||
void print_message_contents(zmq::multipart_t const &mpart) {
|
||||
print_line("multipart message:");
|
||||
for (zmq::message_t const &msg : mpart) {
|
||||
print_line(" - ", msg.to_string().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void extend_multipart(zmq::multipart_t &mpart, NetworkData::MessageType type) {
|
||||
mpart.addstr(std::to_string((int)type));
|
||||
}
|
||||
|
||||
void extend_multipart(zmq::multipart_t &mpart, NetworkData::ClueID id) {
|
||||
mpart.addstr(std::to_string(id));
|
||||
}
|
||||
|
||||
void extend_multipart(zmq::multipart_t &mpart, std::string const &string) {
|
||||
mpart.addstr(string);
|
||||
}
|
||||
|
||||
void extend_multipart(zmq::multipart_t &mpart, std::string_view const &strv) {
|
||||
mpart.addmem(strv.data(), strv.size());
|
||||
}
|
||||
|
||||
void extend_multipart(zmq::multipart_t &mpart, char const *cstr) {
|
||||
mpart.addstr(cstr);
|
||||
}
|
||||
|
||||
void extend_multipart(zmq::multipart_t &mpart, int const &arg) {
|
||||
std::string as_string{ std::to_string(arg) };
|
||||
mpart.addstr(as_string);
|
||||
}
|
||||
|
||||
void extend_multipart(zmq::multipart_t &mpart, zmq::multipart_t const &right) {
|
||||
mpart.append(right.clone());
|
||||
}
|
||||
|
||||
void extend_multipart(zmq::multipart_t &msg, std::pair<zmq::multipart_t::iterator, zmq::multipart_t::iterator> range) {
|
||||
for (; range.first != range.second; ++range.first) {
|
||||
msg.addstr(range.first->to_string());
|
||||
}
|
||||
}
|
||||
|
||||
void extend_multipart(zmq::multipart_t &msg, std::pair<zmq::multipart_t::const_iterator, zmq::multipart_t::const_iterator> range) {
|
||||
for (; range.first != range.second; ++range.first) {
|
||||
msg.addstr(range.first->to_string());
|
||||
}
|
||||
}
|
||||
} //namespace ydi
|
||||
|
|
@ -1,16 +1,93 @@
|
|||
#ifndef YDI_NETWORKING_H
|
||||
#define YDI_NETWORKING_H
|
||||
#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_OUT_OF_CONTEXT, //!< this means a received message is not valid in the recipient's current context
|
||||
NOK_INVALID_REASON //!< this means the value could not be parsed as a NOK reason, not that the reason is an invalid message.
|
||||
};
|
||||
};
|
||||
|
||||
namespace ydi {
|
||||
enum MessageType {
|
||||
NONE = 0u,
|
||||
CONNECTION_REQUEST = 1u,
|
||||
CONNECTION_RESPONSE = 2u,
|
||||
};
|
||||
int to_int(zmq::message_t const &msg, int failure = 0);
|
||||
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,
|
||||
};
|
||||
void print_message_contents(zmq::multipart_t const &mpart);
|
||||
|
||||
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);
|
||||
void extend_multipart(zmq::multipart_t &mpart, char const *cstr);
|
||||
void extend_multipart(zmq::multipart_t &mpart, int const &arg);
|
||||
void extend_multipart(zmq::multipart_t &mpart, zmq::multipart_t const &right);
|
||||
void extend_multipart(zmq::multipart_t &mpart);
|
||||
void extend_multipart(zmq::multipart_t &mpart, std::pair<zmq::multipart_t::iterator, zmq::multipart_t::iterator> range);
|
||||
void extend_multipart(zmq::multipart_t &mpart, std::pair<zmq::multipart_t::const_iterator, zmq::multipart_t::const_iterator> range);
|
||||
|
||||
template <typename TArg>
|
||||
void extend_multipart_r(zmq::multipart_t &mpart, TArg const &arg) {
|
||||
extend_multipart(mpart, arg);
|
||||
}
|
||||
|
||||
#endif // !YDI_NETWORKING_H
|
||||
template <typename TArg, typename... TArgs>
|
||||
void extend_multipart_r(zmq::multipart_t &mpart, TArg const &arg, TArgs const &...args) {
|
||||
extend_multipart(mpart, arg);
|
||||
extend_multipart_r(mpart, args...);
|
||||
}
|
||||
|
||||
template <typename TArg>
|
||||
zmq::multipart_t multipart(TArg const &arg) {
|
||||
zmq::multipart_t mpart{};
|
||||
extend_multipart(mpart, arg);
|
||||
return mpart;
|
||||
}
|
||||
|
||||
template <typename TArg, typename... TArgs>
|
||||
zmq::multipart_t multipart(TArg const &arg, TArgs const &...args) {
|
||||
zmq::multipart_t mpart{ multipart(arg) };
|
||||
extend_multipart_r(mpart, args...);
|
||||
return mpart;
|
||||
}
|
||||
} //namespace ydi
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
#include "ydi_server.h"
|
||||
#include "ydi_networking.h"
|
||||
#include <core/os/time.h>
|
||||
#include <core/templates/vector.h>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stop_token>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <zmq.hpp>
|
||||
|
|
@ -13,78 +14,179 @@ namespace ydi::server {
|
|||
struct Service {
|
||||
std::optional<zmq::context_t> context{ std::nullopt };
|
||||
std::optional<zmq::socket_t> socket{ std::nullopt };
|
||||
std::unordered_set<ClueID> revealedClues{};
|
||||
std::unordered_set<NetworkData::ClueID> revealed_clues{};
|
||||
std::recursive_mutex mtx{};
|
||||
std::atomic<bool> stop_threads{ false };
|
||||
std::optional<std::string> client{ std::nullopt };
|
||||
Vector<NetworkData::ClueID> new_clues{};
|
||||
double lastHeart{ 0.0 };
|
||||
double lastBeat{ 0.0 };
|
||||
|
||||
std::atomic<bool> stop_threads{ false };
|
||||
};
|
||||
|
||||
std::optional<std::thread> receive_thread{ std::nullopt };
|
||||
std::optional<std::thread> ping_thread{ std::nullopt };
|
||||
std::optional<Service> service{ std::nullopt };
|
||||
thread_local std::optional<std::thread> receiveThread{ std::nullopt };
|
||||
thread_local std::optional<std::thread> pingThread{ std::nullopt };
|
||||
|
||||
void receive_thread_entry() {
|
||||
using namespace std::chrono_literals;
|
||||
zmq::multipart_t incoming{};
|
||||
while (service->stop_threads) {
|
||||
std::this_thread::sleep_for(20ms);
|
||||
std::scoped_lock lock{ service->mtx };
|
||||
if (incoming.recv(*service->socket)) {
|
||||
}
|
||||
void handle_reveal_clue(zmq::multipart_t const &message) {
|
||||
service->new_clues.push_back(to_clue_id(message.at(2)));
|
||||
}
|
||||
|
||||
void handle_authorised_message(std::string_view const &sender, NetworkData::MessageType type, zmq::multipart_t &message) {
|
||||
switch (type) {
|
||||
case NetworkData::MSG_BEAT:
|
||||
print_line("Server: Received beat, storing timestamp");
|
||||
service->lastBeat = Time::get_singleton()->get_unix_time_from_system();
|
||||
return;
|
||||
case NetworkData::MSG_REVEAL:
|
||||
print_line("Server: Received revealed clue, ", (int)to_clue_id(message.at(2)));
|
||||
handle_reveal_clue(message);
|
||||
return;
|
||||
default:
|
||||
print_line("Server: Encountered unknown message type, sending NOK_UNKNOWN_MSG response");
|
||||
multipart(sender, NetworkData::MSG_NOK, NetworkData::NOK_UNKNOWN_MSG, message).send(*service->socket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ping_thread_entry() {
|
||||
using namespace std::chrono_literals;
|
||||
{ std::scoped_lock lock{ service->mtx };
|
||||
{
|
||||
std::scoped_lock lock{ service->mtx };
|
||||
if (!service->client) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
static zmq::multipart_t ping{ make_multipart(*service->client, std::string("PING")) };
|
||||
while (!service->stop_threads) {
|
||||
std::this_thread::sleep_for(1s);
|
||||
std::scoped_lock lock{ service->mtx };
|
||||
ping.send(*service->socket);
|
||||
print_line("Server: Send HEART");
|
||||
multipart(*service->client, NetworkData::MSG_HEART).send(*service->socket);
|
||||
service->lastHeart = Time::get_singleton()->get_unix_time_from_system();
|
||||
}
|
||||
}
|
||||
|
||||
void handle_message(zmq::multipart_t &message) {
|
||||
print_line("Server handle_message:");
|
||||
print_message_contents(message);
|
||||
std::string_view const sender{ message.at(0).to_string_view() };
|
||||
NetworkData::MessageType type{ to_message_type(message.at(1)) };
|
||||
std::scoped_lock lock{ service->mtx };
|
||||
if (service->client) {
|
||||
if (sender == service->client) {
|
||||
handle_authorised_message(sender, type, message);
|
||||
} else {
|
||||
multipart(sender, NetworkData::MSG_NOK, NetworkData::NOK_UNAUTHENTICATED, std::pair{ message.begin() + 1, message.end() }).send(*service->socket);
|
||||
}
|
||||
} else if (type == NetworkData::MSG_CONNECT) {
|
||||
service->client.emplace(sender);
|
||||
multipart(sender, NetworkData::MSG_OK, std::pair{ message.begin() + 1, message.end() }).send(*service->socket);
|
||||
ping_thread.emplace(ping_thread_entry);
|
||||
} else {
|
||||
multipart(sender, NetworkData::MSG_NOK, NetworkData::NOK_OUT_OF_CONTEXT, std::pair{ message.begin() + 1, message.end() }).send(*service->socket);
|
||||
}
|
||||
}
|
||||
|
||||
void receive_thread_entry() {
|
||||
using namespace std::chrono_literals;
|
||||
zmq::multipart_t incoming{};
|
||||
while (!service->stop_threads) {
|
||||
std::this_thread::sleep_for(20ms);
|
||||
std::scoped_lock lock{ service->mtx };
|
||||
if (incoming.recv(*service->socket, (int)zmq::recv_flags::dontwait)) {
|
||||
handle_message(incoming);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void open() {
|
||||
if (service) {
|
||||
print_error("Server: Detected attempt to open duplicate Server, exiting without action.");
|
||||
return;
|
||||
}
|
||||
print_line("Server: Starting");
|
||||
service.emplace();
|
||||
try {
|
||||
service->context.emplace(1);
|
||||
} catch (...) {
|
||||
service.reset();
|
||||
print_line("Server: Failed to create context");
|
||||
return;
|
||||
}
|
||||
print_line("Server: Created zmq context");
|
||||
try {
|
||||
service->socket.emplace(*service->context, zmq::socket_type::router);
|
||||
} catch (...) {
|
||||
service->context->close();
|
||||
service->context.reset();
|
||||
service.reset();
|
||||
print_line("Server: Failed to create socket");
|
||||
return;
|
||||
}
|
||||
print_line("Server: Created socket");
|
||||
try {
|
||||
service->socket->connect("tcp://*:6667");
|
||||
service->socket->bind("tcp://*:6667");
|
||||
} catch (...) {
|
||||
service->socket->close();
|
||||
service->socket.reset();
|
||||
service->context->close();
|
||||
service->context.reset();
|
||||
service.reset();
|
||||
print_line("Server: Failed to bind socket");
|
||||
return;
|
||||
}
|
||||
receiveThread.emplace(receive_thread_entry);
|
||||
print_line("Server: Bound socket");
|
||||
receive_thread.emplace(receive_thread_entry);
|
||||
assert(receive_thread->joinable());
|
||||
print_line("Server: Startup complete!");
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (service) {
|
||||
std::scoped_lock lock{ service->mtx };
|
||||
service->socket->close();
|
||||
service->socket.reset();
|
||||
service->context->close();
|
||||
service->context.reset();
|
||||
print_line("Server: Shutting down...");
|
||||
service->stop_threads = true;
|
||||
print_line("Server: Stopping Threads...");
|
||||
if (receive_thread && receive_thread->joinable()) {
|
||||
receive_thread->join();
|
||||
}
|
||||
print_line("Server: Receive thread stopped");
|
||||
if (ping_thread && ping_thread->joinable()) {
|
||||
ping_thread->join();
|
||||
}
|
||||
print_line("Server: Ping thread stopped");
|
||||
if (service->socket) {
|
||||
service->socket->close();
|
||||
service->socket.reset();
|
||||
}
|
||||
print_line("Server: Socket closed");
|
||||
if (service->context) {
|
||||
service->context->close();
|
||||
service->context.reset();
|
||||
}
|
||||
print_line("Server: Context closed");
|
||||
service.reset();
|
||||
print_line("Server: Shutdown complete!");
|
||||
}
|
||||
}
|
||||
|
||||
bool has_client() {
|
||||
if (service) {
|
||||
std::scoped_lock lock{ service->mtx };
|
||||
return service->client.has_value();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace receive {
|
||||
bool new_clues(Vector<NetworkData::ClueID> &out) {
|
||||
std::scoped_lock lock{ service->mtx };
|
||||
bool const 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
|
||||
|
|
|
|||
|
|
@ -1,51 +1,18 @@
|
|||
#ifndef YDI_SERVER_H
|
||||
#define YDI_SERVER_H
|
||||
#pragma once
|
||||
|
||||
#include "ydi_networking.h"
|
||||
#include <cassert>
|
||||
#include <core/io/image.h>
|
||||
#include <core/object/ref_counted.h>
|
||||
#include <core/string/ustring.h>
|
||||
#include <core/templates/vector.h>
|
||||
#include <zmq_addon.hpp>
|
||||
#include <cassert>
|
||||
|
||||
namespace ydi::server {
|
||||
void open();
|
||||
void close();
|
||||
bool has_client();
|
||||
|
||||
namespace receive {
|
||||
bool isRevealed(ClueID id);
|
||||
Ref<Image> clueImage(ClueID id);
|
||||
}
|
||||
namespace send {
|
||||
void announceConclusion(ClueID method, ClueID motive, ClueID murderer);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TArg>
|
||||
void extend_multipart(zmq::multipart_t &, TArg const &arg);
|
||||
|
||||
template <>
|
||||
void extend_multipart(zmq::multipart_t &mpart, std::string const &string) {
|
||||
mpart.addstr(string);
|
||||
}
|
||||
|
||||
template <typename TArg>
|
||||
void extend_multipart(zmq::multipart_t &mpart, TArg const &arg) {
|
||||
std::string as_string{ std::to_string(arg) };
|
||||
mpart.addstr(as_string);
|
||||
}
|
||||
|
||||
template <typename TArg, typename... TArgs>
|
||||
void extend_multipart(zmq::multipart_t &mpart, TArg const &arg, TArgs const &... args) {
|
||||
extend_multipart(mpart, arg);
|
||||
extend_multipart(mpart, arg, args...);
|
||||
}
|
||||
|
||||
template <typename TArg, typename... TArgs>
|
||||
zmq::multipart_t make_multipart(std::string const &target, TArg const &arg, TArgs const &... args) {
|
||||
zmq::multipart_t mpart{};
|
||||
mpart.addstr(target);
|
||||
extend_multipart(mpart, arg, args...);
|
||||
return mpart;
|
||||
}
|
||||
|
||||
#endif // !YDI_SERVER_H
|
||||
bool new_clues(Vector<NetworkData::ClueID> &out);
|
||||
} //namespace receive
|
||||
} //namespace ydi::server
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue