The bare assert hid why server creation fails on the CI runner. Emit the error buffer to stderr before asserting so the cause is visible in logs.
221 lines
8.0 KiB
C++
221 lines
8.0 KiB
C++
#include "co_gns_server_bridge.h"
|
|
|
|
#include <steam/steamnetworkingsockets.h>
|
|
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
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<PolledEvent> PollBridge(co_gns_server_handle server)
|
|
{
|
|
std::array<char, 64 * 1024> payload{};
|
|
co_gns_event event{};
|
|
const auto result = co_gns_server_poll(
|
|
server,
|
|
&event,
|
|
payload.data(),
|
|
static_cast<std::uint32_t>(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 <class Predicate>
|
|
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<PolledEvent> 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<std::string> 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<const char*>(message->m_pData),
|
|
static_cast<std::size_t>(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<char, 256> 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<void*>(+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<std::uint32_t>(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<std::uint32_t>(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<std::uint32_t>(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<std::uint32_t>(tooLarge.size()),
|
|
CO_GNS_DELIVERY_RELIABLE_ORDERED) == CO_GNS_SEND_TOO_LARGE);
|
|
assert(networking->SendMessageToConnection(
|
|
g_clientConnection,
|
|
tooLarge.data(),
|
|
static_cast<std::uint32_t>(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;
|
|
}
|