#include "co_gns_server_bridge.h" #include #include #include #include #include #include #include #include #include namespace { using namespace std::chrono_literals; HSteamNetConnection g_clientConnection = k_HSteamNetConnection_Invalid; bool g_clientConnected = false; bool g_clientFailed = false; void ClientConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t* info) { if (info == nullptr || info->m_hConn != g_clientConnection) { return; } switch (info->m_info.m_eState) { case k_ESteamNetworkingConnectionState_Connected: g_clientConnected = true; break; case k_ESteamNetworkingConnectionState_ClosedByPeer: case k_ESteamNetworkingConnectionState_ProblemDetectedLocally: g_clientFailed = info->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally; if (auto* networking = SteamNetworkingSockets(); networking != nullptr) { networking->CloseConnection(info->m_hConn, 0, nullptr, false); } g_clientConnection = k_HSteamNetConnection_Invalid; g_clientConnected = false; break; default: break; } } struct PolledEvent { co_gns_event event{}; std::string payload; }; std::optional PollBridge(co_gns_server_handle server) { std::array payload{}; co_gns_event event{}; const auto result = co_gns_server_poll( server, &event, payload.data(), static_cast(payload.size())); assert(result >= 0); if (result == 0) { return std::nullopt; } PolledEvent out{}; out.event = event; if (event.type == CO_GNS_EVENT_MESSAGE) { out.payload.assign(payload.data(), event.payload_size); } return out; } template bool WaitUntil(co_gns_server_handle server, Predicate&& predicate, std::chrono::milliseconds timeout = 3s) { const auto deadline = std::chrono::steady_clock::now() + timeout; while (std::chrono::steady_clock::now() < deadline) { PollBridge(server); if (predicate()) { return true; } std::this_thread::sleep_for(2ms); } return false; } std::optional WaitForEvent( co_gns_server_handle server, std::uint32_t eventType, std::chrono::milliseconds timeout = 3s) { const auto deadline = std::chrono::steady_clock::now() + timeout; while (std::chrono::steady_clock::now() < deadline) { auto event = PollBridge(server); if (event && event->event.type == eventType) { return event; } std::this_thread::sleep_for(2ms); } return std::nullopt; } std::optional WaitForClientMessage( co_gns_server_handle server, ISteamNetworkingSockets* networking, std::chrono::milliseconds timeout = 3s) { const auto deadline = std::chrono::steady_clock::now() + timeout; while (std::chrono::steady_clock::now() < deadline) { PollBridge(server); ISteamNetworkingMessage* message = nullptr; const auto count = networking->ReceiveMessagesOnConnection(g_clientConnection, &message, 1); if (count > 0 && message != nullptr) { std::string payload; if (message->m_cbSize > 0 && message->m_pData != nullptr) { payload.assign( static_cast(message->m_pData), static_cast(message->m_cbSize)); } message->Release(); return payload; } assert(count >= 0); std::this_thread::sleep_for(2ms); } return std::nullopt; } } int main() { co_gns_server_handle server = nullptr; std::array error{}; const int createResult = co_gns_server_create("127.0.0.1", 0, &server, error.data(), error.size()); if (createResult != 1) { std::fprintf(stderr, "co_gns_server_create failed (rc=%d): %s\n", createResult, error.data()); } assert(createResult == 1); assert(server != nullptr); const auto port = co_gns_server_local_port(server); assert(port != 0); auto* networking = SteamNetworkingSockets(); assert(networking != nullptr); SteamNetworkingIPAddr serverAddress{}; serverAddress.Clear(); serverAddress.SetIPv4(0x7f000001U, port); SteamNetworkingConfigValue_t option{}; option.SetPtr( k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged, reinterpret_cast(+ClientConnectionStatusChanged)); g_clientConnection = networking->ConnectByIPAddress(serverAddress, 1, &option); assert(g_clientConnection != k_HSteamNetConnection_Invalid); const auto connectedEvent = WaitForEvent(server, CO_GNS_EVENT_CONNECTED); assert(connectedEvent.has_value()); const auto serverConnectionId = connectedEvent->event.connection_id; assert(serverConnectionId != 0); assert(WaitUntil(server, []() { return g_clientConnected && !g_clientFailed; })); assert(co_gns_server_connection_count(server) == 1); const std::string reliable = R"({"type":"playerState","characterName":"Nomad"})"; assert(networking->SendMessageToConnection( g_clientConnection, reliable.data(), static_cast(reliable.size()), k_nSteamNetworkingSend_ReliableNoNagle, nullptr) == k_EResultOK); const auto messageEvent = WaitForEvent(server, CO_GNS_EVENT_MESSAGE); assert(messageEvent.has_value()); assert(messageEvent->event.connection_id == serverConnectionId); assert(messageEvent->payload == reliable); assert(messageEvent->payload.find('\n') == std::string::npos); const std::string reliableResponse = R"({"type":"sessionReady","playerId":1})"; assert(co_gns_server_send( server, serverConnectionId, reliableResponse.data(), static_cast(reliableResponse.size()), CO_GNS_DELIVERY_RELIABLE_ORDERED) == CO_GNS_SEND_SENT); const auto clientReliable = WaitForClientMessage(server, networking); assert(clientReliable.has_value()); assert(*clientReliable == reliableResponse); const std::string snapshotResponse = R"({"type":"transform","snapshotSequence":8,"x":1})"; assert(co_gns_server_send( server, serverConnectionId, snapshotResponse.data(), static_cast(snapshotResponse.size()), CO_GNS_DELIVERY_UNRELIABLE_SEQUENCED) == CO_GNS_SEND_SENT); const auto clientSnapshot = WaitForClientMessage(server, networking); assert(clientSnapshot.has_value()); assert(*clientSnapshot == snapshotResponse); const std::string tooLarge((64 * 1024) + 1, 'x'); assert(co_gns_server_send( server, serverConnectionId, tooLarge.data(), static_cast(tooLarge.size()), CO_GNS_DELIVERY_RELIABLE_ORDERED) == CO_GNS_SEND_TOO_LARGE); assert(networking->SendMessageToConnection( g_clientConnection, tooLarge.data(), static_cast(tooLarge.size()), k_nSteamNetworkingSend_ReliableNoNagle, nullptr) == k_EResultOK); const auto oversizeEvent = WaitForEvent(server, CO_GNS_EVENT_OVERSIZE_MESSAGE); assert(oversizeEvent.has_value()); assert(oversizeEvent->event.connection_id == serverConnectionId); assert(oversizeEvent->event.payload_size == tooLarge.size()); assert(co_gns_server_disconnect(server, serverConnectionId, 1000, "test complete") == 1); assert(WaitUntil(server, []() { return g_clientConnection == k_HSteamNetConnection_Invalid; })); assert(!g_clientFailed); co_gns_server_destroy(server); return 0; }