File size: 2,648 Bytes
a2cd246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/* ============================================================
   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();
    },
  };
}