152 lines
4.1 KiB
C++
152 lines
4.1 KiB
C++
#include "ydi_client.h"
|
|
#include "core/string/print_string.h"
|
|
#include "you_done_it/ydi_networking.h"
|
|
#include "zmq.hpp"
|
|
#include "zmq_addon.hpp"
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <thread>
|
|
|
|
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) {
|
|
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:
|
|
multipart(NetworkData::MSG_BEAT).send(*connection->socket);
|
|
return;
|
|
default:
|
|
print_line("Client: Message not handled:");
|
|
print_message_contents(message);
|
|
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() {
|
|
if (!connection) {
|
|
return NetworkData::CONNECTION_DISCONNECTED;
|
|
} else {
|
|
std::scoped_lock lock{ connection->mtx };
|
|
return connection->status;
|
|
}
|
|
}
|
|
|
|
namespace send {
|
|
void reveal_clue(NetworkData::ClueID id) {
|
|
if (connection) {
|
|
print_line("Sending Clue ", id);
|
|
std::scoped_lock lock{ connection->mtx };
|
|
multipart(NetworkData::MSG_REVEAL, id).send(*connection->socket);
|
|
} else {
|
|
print_error("ydi::client::send::reveal_clue: No connection, exiting without action, call connect and try again");
|
|
}
|
|
}
|
|
} //namespace send
|
|
} //namespace ydi::client
|