SpaceCities / net /loopback.js
Almaatla's picture
Claude Sonnet 5
T-011: net/loopback.js β€” in-process transport
a2cd246 unverified
Raw History Blame Contribute Delete
2.65 kB
/* ============================================================
The in-process Transport β€” same tab, same process, no socket (ADR-0004).
Wraps a server/session.js session directly. Single-player wires a client
to a session through THIS, exactly as multiplayer will wire a client to a
session through a (Phase 3) WebSocketTransport β€” same interface
(net/transport.js), so client code written against Transport never knows
which one it has.
submitCommand returns a Promise even though the work underneath is
genuinely synchronous β€” the mutation happens and is visible to the caller
BEFORE the promise ever resolves (see test/loopback.test.js's own test for
this). That is deliberate: client code written against a Promise-returning
submitCommand is already correct for a transport where the promise takes
real time to resolve, so nothing about the client changes when
WebSocketTransport replaces this. It is also what T-013's fault-injection
layer needs β€” there is a real async gap here to delay.
tick() and getState() are LOOPBACK-SPECIFIC EXTENSIONS beyond the generic
Transport contract β€” a real remote transport cannot offer either (ticking
happens server-side, invisible to a remote client; state arrives only as
pushed events, never by synchronous pull). T-012 decides how much of the
client actually leans on these vs. driving purely off onEvent; both are
valid against this shape. Do not add either to net/transport.js's
documented interface β€” that would silently make it a contract only
loopback can satisfy.
============================================================ */
"use strict";
/**
* @param {ReturnType<import("../server/session.js").createSession>} session
* @returns {Transport & { tick: (dt: number) => void, getState: () => State }}
*/
export function createLoopbackTransport(session) {
const handlers = new Set();
let closed = false;
function emit(event) {
if (closed) return;
for (const h of handlers) h(event);
}
return {
submitCommand(cmd) {
if (closed) return Promise.resolve({ ok: false, code: "closed" });
const result = session.submitCommand(cmd); // synchronous β€” see file header
emit({ type: "commandResult", result });
return Promise.resolve(result);
},
onEvent(handler) {
handlers.add(handler);
},
close() {
closed = true;
handlers.clear();
},
tick(dt) {
if (closed) return;
session.tick(dt);
emit({ type: "state", state: session.getState() });
},
getState() {
return session.getState();
},
};
}