Files
MetaCrate/docs/network-manager.md
Chili Palmer c8b2317e13
All checks were successful
Native code generation / deterministic (push) Successful in 12m1s
Imaging and meshing gate / native (push) Successful in 4m3s
Native Rust workspace compile / compile (push) Successful in 4m6s
Complete networking integration gate (#58)
2026-08-09 19:45:06 +00:00

8.6 KiB

Native network manager and simulator lifecycle

NetworkManager, Simulator, PacketEventDictionary, and CapsEventDictionary implement the native Rust boundary corresponding to the LibreMetaverse connection and dispatch layer. They use the generated packet codecs and the bounded UDPBase transport; they do not invoke .NET code or start a helper process.

Ownership and execution

A manager owns bounded incoming and outgoing channels plus one keepalive worker. Each simulator owns a UDPBase transport running on a dedicated current-thread Tokio executor so the mapped synchronous connection API does not depend on the caller already having entered a runtime. Async connection methods move blocking compatibility work to short-lived named standard threads and return their result through a runtime-neutral one-shot future. Every worker holds only a weak manager reference between operations. Dropping the last manager and subscription guard therefore releases all manager-owned GridClient handles, cancels the workers, joins their threads, and closes simulator transports.

The public SimulatorCollection is a shared snapshot collection. Lookup, addition, removal, current-simulator selection, and disconnect transitions are synchronized. User callbacks receive cloned handles only after internal locks have been released. Subscription is an RAII guard: dropping or closing it removes exactly its registration through a weak registry reference.

Dispatch semantics

Packet callbacks preserve registration order. PacketType::Default handlers are selected before handlers for the concrete packet type. Once any callback registered for a packet type requests asynchronous dispatch, that packet type remains asynchronous after later removals, matching the conservative C# delegate state. A bounded worker executes each asynchronous default/specific batch in order. Callback panics are isolated and cannot terminate a processor or poison a callback registry.

CAPS dispatch snapshots the default ("") and named callback chains, then invokes them in that order without holding the registry lock. The built-in EnableSimulator handler honors Agent.MultipleSims, ignores duplicate endpoints, validates ports, and connects each newly advertised region with its reported handle and dimensions. Incoming DisableSimulator, KickUser, RegionHandshake, StartPingCheck, and generic-streaming UDP packets perform their reference lifecycle actions before user dispatch. Ping replies preserve the incoming ping identifier and immediately flush pending acknowledgements when the simulator reports a nonzero OldestUnacked sequence.

Connection and shutdown behavior

Connecting reuses an existing endpoint or inserts one simulator atomically, starts the bounded manager workers, raises cancellable SimConnecting, sends a reliable UseCircuitCode, and waits up to Timing.LoginTimeout for its actual protocol ACK. Async compatibility methods use a runtime-neutral blocking bridge, so they can be polled without an ambient Tokio runtime. Circuit-code changes propagate to every tracked simulator. A default connection stores its seed capability, updates CurrentSim, raises SimChanged, and then raises SimConnected. A received region handshake is decoded and answered with the reference RegionHandshakeReply flags before the simulator is marked complete.

The keepalive uses the same two-interval disconnect-candidate transition as the C# timer: traffic clears the candidate, the first silent interval marks it and sends a ping, and the second shuts the manager down with NetworkTimeout. Concurrent disconnect attempts are serialized so one simulator and one global transition are emitted. Per-simulator removal reports NetworkTimeout; losing the last simulator reports SimShutdown. Explicit shutdown preserves the caller-provided reason/message and sends CloseCircuit only for client or network-timeout shutdowns.

Promoting an already-connected simulator is a controlled handoff. The manager first sends a reliable UseCircuitCode followed by the agent/session/circuit identified CompleteAgentMovement, installs the new seed capability, swaps CurrentSim, and finally raises SimChanged with the previous simulator. The old simulator remains tracked until an explicit disconnect so multi-simulator traffic can continue. A completed shutdown leaves the manager reusable: a later connection creates fresh workers and transports rather than retaining canceled ones. Connection attempts made while logout or teardown is still active fail with InvalidOperation, preventing a reentrant reconnect from racing resource cleanup.

RequestLogout, blocking Logout, runtime-neutral LogoutAsync, and nonblocking BeginLogout all send the native LogoutRequest packet at most once per active handshake. A matching LogoutReply is accepted only for the current agent and session, raises LoggedOut with the returned inventory IDs, and then performs a client-initiated shutdown. Blocking calls wait for that ordered teardown; asynchronous calls additionally observe caller cancellation. BeginLogout raises an empty LoggedOut only after a bounded network-timeout teardown when no reply arrives, matching the reference ordering.

Teardown is idempotent and ordered. It cancels active login and logout waits, disconnects non-current simulators before the current simulator, stops manager workers, clears connection/circuit/session/seed state and parsed login secrets, then raises one Disconnected event and marks shutdown complete. Reentrant and repeated shutdown calls do not repeat events. Nonblocking logout waiters observe the lifecycle cancellation and terminate rather than retaining the manager or client; their last detached error remains available through NetworkManager::last_logout_error for diagnostics.

Login behavior

The native login path constructs the reference LLSD request, including the C# MD5 password form, token/MFA fields, viewer identity, platform metadata, requested options, and normalized home/last/region start locations. It uses the client's injectable capability HTTP transport, limits redirects and server delays, observes linked caller/client cancellation, and reports the mapped LoginStatus transitions. Only a successful response invokes registered login callbacks and proceeds to an ACK-gated initial simulator connection.

LoginResponseData parses the session and circuit identifiers, home and look-at vectors, simulator endpoint and dimensions, seed capability, inventory roots and skeletons, buddy rights, account benefits, service URLs, category/config arrays, premium packages, and initial outfit. A rejected, malformed, timed-out, or canceled login leaves no simulator installed. Passwords, login tokens, MFA hashes, session identifiers, seed capabilities, and estate access tokens are excluded from Debug output and diagnostic messages.

Statistics and milestone gate

SimulatorSimStats is the native thread-safe counterpart of the reference Simulator.SimStats. Atomic counters track received/sent packets and bytes, resends, drops, pings, pongs, lag, and bandwidth samples. Server SimStats blocks update the 25 defined simulator measurements with the reference ID and rounding rules; unknown IDs are ignored. Debug snapshots contain aggregate counters only—never packet bytes, endpoints, capability URLs, or credentials.

The offline fake-grid gate composes the completed layers rather than replacing their focused tests. It authenticates through injected LLSD HTTP, establishes a real loopback UDP circuit, discovers seed capabilities, consumes an EventQueueGet message that enables a second region, downloads a capability asset, promotes that region, and completes the logout handshake. It then proves CAPS polling was canceled, download and network workers were joined, sockets were closed, state was cleared, and diagnostic formatting contains none of the fixture tokens. tools/check_milestone_08.py also audits owned source files for failure-only bodies and verifies the explicit queue, payload, event, retry, and concurrency policy markers remain present.

The deterministic tests use loopback fake simulators and require no live grid:

cargo test -p libremetaverse --test network_manager

They cover login hashing, redirects, cancellation and initial handoff alongside ACK-gated circuit setup, typed handshake reply/state, ping response, CAPS enable, UDP disable, current/seed selection, controlled region handoff, reconnect after teardown, all three logout wait modes, reply validation and event order, callback ordering and filtering, RAII unregistration, runtime-neutral async calls, concurrent registration/removal, reentrant/concurrent disconnect, and keepalive timeout reasons.