#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 #include #include #include namespace ydi::client { struct Connection { std::optional server; std::optional context{ std::nullopt }; std::optional socket{ std::nullopt }; std::recursive_mutex mtx; std::atomic status; std::atomic stop_threads{ false }; }; std::optional connection{ std::nullopt }; std::optional 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 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 ? NetworkData::ConnectionStatus(connection->status) : NetworkData::CONNECTION_DISCONNECTED; } namespace send { void reveal_clue(NetworkData::ClueID id) { if (connection) { 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