216 lines
7.9 KiB
C++
216 lines
7.9 KiB
C++
#include "co_gns_server_bridge.h"
|
|
|
|
#include <steam/steamnetworkingsockets.h>
|
|
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#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{};
|
|
assert(co_gns_server_create("127.0.0.1", 0, &server, error.data(), error.size()) == 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;
|
|
}
|