Steam transport: status callbacks now dispatch (CCallback, not config ptr)

Root cause of the three-machine failure found and fixed. The
connection-status callback was registered as a per-connection config
value pointer - which the steam_api flavor of the library never
dispatches (only the standalone lib does). So members never saw the
incoming connection request, never accepted, and the owner timed out
after 120s of state-5 retries. Valve''s own SpaceWar example registers
SteamNetConnectionStatusChangedCallback_t with STEAM_CALLBACK; the
transport now does the same through a CCallback listener constructed
after SteamAPI_Init.

Proven with the new RP412STEAMSELFTEST=1 loopback: one machine
listens on its fake console port and dials its own FakeIP - the log
now shows incoming/accept/connected on both ends and a verified data
round trip, connect succeeded on attempt 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-12 22:45:45 -05:00
co-authored by Claude Fable 5
parent d2836d195f
commit 7eb765696d
+90 -15
View File
@@ -157,10 +157,13 @@ namespace
}
//---------------------------------------------------------------
// Status-changed callback (fires inside SteamAPI_RunCallbacks on
// the game thread): accept incoming, queue connected, mark drops
// Status-changed handler (fires inside SteamAPI_RunCallbacks on
// the game thread): accept incoming, queue connected, mark drops.
// Registered through the CCallback dispatcher below - the
// config-value function pointer is NOT dispatched by the steam_api
// flavor of the library (Valve's own example uses STEAM_CALLBACK).
//---------------------------------------------------------------
void __cdecl
void
OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *status)
{
switch (status->m_info.m_eState)
@@ -219,6 +222,25 @@ namespace
}
}
//---------------------------------------------------------------
// The callback listener: constructed after SteamAPI_Init so the
// CCallback registration lands in a live callback manager
//---------------------------------------------------------------
class SteamTransportCallbacks
{
public:
STEAM_CALLBACK(SteamTransportCallbacks, OnStatusChanged,
SteamNetConnectionStatusChangedCallback_t);
};
void SteamTransportCallbacks::OnStatusChanged(
SteamNetConnectionStatusChangedCallback_t *status)
{
OnConnectionStatusChanged(status);
}
SteamTransportCallbacks *gCallbacks = NULL;
//---------------------------------------------------------------
// The transport
//---------------------------------------------------------------
@@ -306,11 +328,6 @@ namespace
<< inet_ntoa(remote->sin_addr) << ":" << ntohs(remote->sin_port)
<< " (fake port " << fake_port << ")...\n" << std::flush;
SteamNetworkingConfigValue_t option;
option.SetPtr(
k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged,
(void *) &OnConnectionStatusChanged);
SteamNetworkingIPAddr target;
target.Clear();
target.SetIPv4(remote_fake_ip, fake_port);
@@ -323,7 +340,7 @@ namespace
{
++attempt;
HSteamNetConnection handle =
SteamNetworkingSockets()->ConnectByIPAddress(target, 1, &option);
SteamNetworkingSockets()->ConnectByIPAddress(target, 0, NULL);
if (handle == k_HSteamNetConnection_Invalid)
{
DEBUG_STREAM << "SteamNetTransport: ConnectByIPAddress refused the call\n" << std::flush;
@@ -419,13 +436,8 @@ namespace
<< local_port << " (fake port " << gLocalFakePorts[index]
<< ")...\n" << std::flush;
SteamNetworkingConfigValue_t option;
option.SetPtr(
k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged,
(void *) &OnConnectionStatusChanged);
HSteamListenSocket handle =
SteamNetworkingSockets()->CreateListenSocketP2PFakeIP(index, 1, &option);
SteamNetworkingSockets()->CreateListenSocketP2PFakeIP(index, 0, NULL);
if (handle == k_HSteamListenSocket_Invalid)
{
return InvalidConnection;
@@ -615,6 +627,12 @@ Logical
return False;
}
// connection status arrives through the CCallback dispatcher
if (gCallbacks == NULL)
{
gCallbacks = new SteamTransportCallbacks;
}
SteamNetworkingUtils()->InitRelayNetworkAccess();
if (!SteamNetworkingSockets()->BeginAsyncRequestFakeIP(2))
@@ -664,6 +682,63 @@ Logical
gSteamReady = True;
NetTransport_Set(&gSteamTransport);
//
// dev: RP412STEAMSELFTEST=1 loops a connection back to ourselves,
// proving the listen/accept/connect/send/receive machinery (and
// the status-callback dispatch) without a second machine
//
const char *self_test = getenv("RP412STEAMSELFTEST");
if (self_test != NULL && atoi(self_test) != 0)
{
DEBUG_STREAM << "SteamNetTransport: SELF TEST starting\n" << std::flush;
SteamNetTransport_RegisterPeer(
gLocalFakeAddressString, gLocalFakePorts[0], gLocalFakePorts[1]);
NetTransport *transport = &gSteamTransport;
NetTransport::Connection listener = transport->Listen(1501, 1);
SOCKADDR_IN self_address;
transport->Resolve(gLocalFakeAddressString, &self_address);
self_address.sin_port = htons(1501);
NetTransport::Connection outgoing = transport->Connect(&self_address, 0);
NetTransport::Connection incoming = NetTransport::InvalidConnection;
DWORD accept_deadline = GetTickCount() + 10 * 1000;
while (incoming == NetTransport::InvalidConnection &&
(LONG)(GetTickCount() - accept_deadline) < 0)
{
incoming = transport->Accept(listener);
Sleep(50);
}
Logical ok = False;
if (outgoing != NetTransport::InvalidConnection &&
incoming != NetTransport::InvalidConnection)
{
transport->Send(outgoing, "PING", 4);
char buffer[16];
DWORD recv_deadline = GetTickCount() + 5 * 1000;
while ((LONG)(GetTickCount() - recv_deadline) < 0)
{
if (transport->Receive(incoming, buffer, sizeof(buffer)) == 4)
{
ok = (memcmp(buffer, "PING", 4) == 0);
break;
}
Sleep(25);
}
}
DEBUG_STREAM << "SteamNetTransport: SELF TEST "
<< (ok ? "PASSED" : "FAILED")
<< " (out " << (outgoing != NetTransport::InvalidConnection)
<< ", in " << (incoming != NetTransport::InvalidConnection)
<< ")\n" << std::flush;
// leave the session pristine for the real race
gSteamTransport.Cleanup();
gPeerCount = 0;
}
return True;
}