Files
RP412/MUNGA_L4/L4NETTRANSPORT.h
T
CydandClaude Opus 5 94e1cf2cf0 A window that answers while it waits
Launching a network race connects to each machine in turn and retries
while one is not listening yet, because they finish loading at different
moments. That part is deliberate and stays. What was not deliberate is
that the wait slept without pumping messages, so Windows saw a process
that had stopped answering and painted the whole thing "Not responding" -
for up to two minutes, with no indication of which peer was missing, how
long remained, or any way out. It runs before the engine block, so there
is no render loop keeping the window alive either.

Three changes, both transports:

Pump while waiting. Every sleep on the connect path goes through
NetTransport_PumpAndSleep, so the window keeps painting and can be moved.
It is re-entrancy guarded, because dispatching a message can run
application code that reaches a connect of its own, and nested pumping
would deliver messages twice and let an inner wait swallow the escape
meant for the outer one.

Shorten the deadline. Two minutes suited the arcade, where a pod that was
still booting would always answer eventually on a LAN with nothing else to
go wrong. Over the internet a machine silent for twenty seconds is not
coming. RP412CONNECTWAIT, 2 to 300, default 20, documented in environ.ini.

Say what is happening. The title bar names the peer and counts down, and
ESC gives up at once - the title being the one surface guaranteed to exist
this early, since there is no renderer yet to draw a progress screen with.
The cancel latch is cleared when a connect sequence begins so that an
escape pressed during one race cannot cancel the next.

The Winsock path is only partly fixed and the code now says so: connect()
there is still blocking, since the socket is only made nonblocking after
it succeeds, so an unreachable host - filtered rather than refused - still
sits in the OS SYN retry for around twenty seconds. Fixing that needs
FIONBIO before connect() and a select() on our own timeout. Left for when
LAN play comes up; a Steam host goes through SteamNetTransport::Connect,
which is fully covered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:36:46 -05:00

168 lines
5.7 KiB
C++

//===========================================================================//
// File: l4nettransport.h //
// Project: MUNGA Brick: Network Transport Seam //
// Contents: The wire interface under the network manager //
//---------------------------------------------------------------------------//
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
// PROPRIETARY AND CONFIDENTIAL //
//===========================================================================//
#pragma once
#include "..\munga\style.h"
#include <Winsock2.h>
//########################################################################
// NetTransport - the seam between the network manager's mesh/console
// logic and the wire (docs/RP412-FRONTEND-DESIGN.md section 3). Mirrors
// the RIOBase pattern: L4NET keeps hosts, message queues, and the
// deterministic mesh ordering; only connect/listen/send/recv live
// behind this interface.
//
// Implementations:
// WinsockNetTransport (l4nettransport.cpp) - TCP; the arcade/LAN wire
// SteamNetTransport (future) - ISteamNetworkingSockets
// P2P over SDR
//
// Addresses stay SOCKADDR_IN-shaped on purpose: Steam's FakeIP system
// hands out fake IPv4 addresses for exactly this kind of engine, so
// the [pilots] list keeps working as ip[:port] strings in both worlds.
//########################################################################
class NetTransport
{
public:
// Opaque connection handle. SOCKET for Winsock (SOCKET is UINT_PTR),
// HSteamNetConnection for Steam - both fit; callers must not
// interpret it.
typedef UINT_PTR Connection;
static const Connection InvalidConnection = (Connection) ~0; // == INVALID_SOCKET
// Receive() results when no payload came back
enum
{
ReceiveDisconnected = 0, // orderly close or connection reset
ReceiveNoData = -1 // nothing pending (connections are nonblocking)
};
virtual ~NetTransport()
{
}
//---------------------------------------------------------------
// Lifecycle
//---------------------------------------------------------------
virtual Logical
Startup() = 0;
virtual void
Cleanup() = 0;
//---------------------------------------------------------------
// Addressing: the local interface list (the mesh identifies
// "which [pilots] entry is me" against it) and numeric ip[:port]
// parsing. Port 0 in the result means "caller applies default".
//---------------------------------------------------------------
virtual int
GetLocalAddresses(
unsigned long *addresses,
int max_count
) = 0;
virtual Logical
Resolve(
const char *host_name,
SOCKADDR_IN *address
) = 0;
//---------------------------------------------------------------
// Connections (the deterministic mesh + the console channel).
// Connect blocks until the remote end accepts (the mesh relies
// on retry-until-up ordering), then goes nonblocking. Listeners
// are nonblocking from the start; Accept polls one.
//---------------------------------------------------------------
virtual Connection
Connect(
const SOCKADDR_IN *remote,
int local_port
) = 0;
virtual Connection
Listen(
int local_port,
int backlog
) = 0;
virtual Connection
Accept(Connection listener) = 0;
virtual void
Close(Connection connection) = 0;
//---------------------------------------------------------------
// Data plane (nonblocking)
//---------------------------------------------------------------
virtual int
Send(
Connection connection,
const void *data,
int size
) = 0;
virtual int
Receive(
Connection connection,
void *buffer,
int size
) = 0;
// remote endpoint of a live connection (mesh identity checks)
virtual Logical
GetRemoteAddress(
Connection connection,
SOCKADDR_IN *address
) = 0;
};
// The process-wide transport. Defaults to Winsock TCP; a Steam build
// installs its transport BEFORE the network manager comes up.
NetTransport *
NetTransport_Get();
void
NetTransport_Set(NetTransport *transport);
//########################################################################
// Waiting for a peer without hanging the window.
//
// Connect retries until the peer answers, because the egg-ACK ordering
// means it may still be booting - that part is deliberate. What was not
// deliberate is that the wait slept without pumping messages, so Windows
// saw a process that had stopped answering and painted the whole thing
// "Not responding" for up to two minutes. It happens before the engine
// block, so there is no render loop keeping the window alive either.
//
// Sleep through this instead. It pumps, so the window keeps drawing and
// can be moved; it watches for a cancel; and it is re-entrancy guarded,
// because dispatching a message can run application code that reaches a
// connect of its own.
//########################################################################
enum NetWaitResult
{
NetWaitContinue, // carry on waiting
NetWaitCancelled, // the user pressed escape
NetWaitQuit // WM_QUIT arrived; the caller must unwind
};
NetWaitResult
NetTransport_PumpAndSleep(int milliseconds);
// How long Connect keeps redialling, in seconds. RP412CONNECTWAIT
// overrides; see the definition for why the default came down from 120.
int
NetTransport_ConnectWaitSeconds();
// Progress, shown in the window title while a connect is outstanding.
// Pass NULL to put the original title back.
void
NetTransport_SetWaitProgress(const char *text);
// Forget a previous escape, so one race's cancel cannot cancel the next.
// Call before starting a connect sequence.
void
NetTransport_ClearWaitCancel();