#pragma once #include #include #include #include #include struct _ENetHost; struct _ENetPeer; // Lockstep networking session for COOPERATE (network) mode. // // Design goals: // - Non-blocking polling (caller drives poll from the main loop) // - Reliable, ordered delivery for inputs and control messages // - Host provides seed + start tick (handshake) // - Only inputs/state hashes are exchanged (no board sync) class NetSession { public: enum class Mode { None, Host, Client, }; enum class ConnState { Disconnected, Connecting, Connected, Error, }; struct Handshake { uint32_t rngSeed = 0; uint32_t startTick = 0; uint8_t startLevel = 0; }; struct InputFrame { uint32_t tick = 0; uint8_t buttons = 0; }; NetSession(); ~NetSession(); NetSession(const NetSession&) = delete; NetSession& operator=(const NetSession&) = delete; // If bindHost is empty or "0.0.0.0", binds to ENET_HOST_ANY. bool host(const std::string& bindHost, uint16_t port); bool join(const std::string& hostNameOrIp, uint16_t port); void shutdown(); void poll(uint32_t timeoutMs = 0); Mode mode() const { return m_mode; } ConnState state() const { return m_state; } bool isConnected() const { return m_state == ConnState::Connected; } // Host-only: send handshake once the peer connects. bool sendHandshake(const Handshake& hs); // Client-only: becomes available once received from host. std::optional takeReceivedHandshake(); // Input exchange -------------------------------------------------------- // Send local input for a given simulation tick. bool sendLocalInput(uint32_t tick, uint8_t buttons); // Returns the last received remote input for a tick (if any). std::optional getRemoteButtons(uint32_t tick) const; // Hash exchange (for desync detection) --------------------------------- bool sendStateHash(uint32_t tick, uint64_t hash); std::optional takeRemoteHash(uint32_t tick); // Diagnostics std::string lastError() const { return m_lastError; } private: enum class MsgType : uint8_t { Handshake = 1, Input = 2, Hash = 3, }; bool ensureEnetInitialized(); void setError(const std::string& msg); bool sendBytesReliable(const void* data, size_t size); void handlePacket(const uint8_t* data, size_t size); Mode m_mode = Mode::None; ConnState m_state = ConnState::Disconnected; _ENetHost* m_host = nullptr; _ENetPeer* m_peer = nullptr; std::string m_lastError; std::optional m_receivedHandshake; std::unordered_map m_remoteInputs; std::unordered_map m_remoteHashes; // Debug logging (rate-limited) uint32_t m_inputsSent = 0; uint32_t m_inputsReceived = 0; uint32_t m_hashesSent = 0; uint32_t m_hashesReceived = 0; uint32_t m_handshakesSent = 0; uint32_t m_handshakesReceived = 0; uint32_t m_lastRecvInputTick = 0xFFFFFFFFu; uint32_t m_lastRecvHashTick = 0xFFFFFFFFu; uint32_t m_lastStatsLogMs = 0; };