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>
168 lines
5.7 KiB
C++
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();
|