@bloopjs/web 0.0.87 → 0.0.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,8 +2,6 @@ export type { PeerId, BrokerMessage, PeerMessage } from "./protocol.ts";
2
2
  export type { Log, LogOpts, LogDirection, LogSeverity, OnLogCallback, } from "./logs.ts";
3
3
  export type { WebRtcPipe } from "./transport.ts";
4
4
  export type { RoomEvents } from "./broker.ts";
5
- export type { JoinRollbackRoomOptions } from "./scaffold.ts";
6
5
  export { PacketType } from "./protocol.ts";
7
6
  export { logger } from "./logs.ts";
8
- export { joinRollbackRoom } from "./scaffold.ts";
9
7
  //# sourceMappingURL=mod.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/netcode/mod.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACxE,YAAY,EACV,GAAG,EACH,OAAO,EACP,YAAY,EACZ,WAAW,EACX,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAE7D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/netcode/mod.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACxE,YAAY,EACV,GAAG,EACH,OAAO,EACP,YAAY,EACZ,WAAW,EACX,aAAa,GACd,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { App } from "../App.ts";
2
+ export declare function reconcile(app: App, signal: AbortSignal): Promise<void>;
3
+ //# sourceMappingURL=reconcile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/netcode/reconcile.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAiBrC,wBAAsB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAiC5E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloopjs/web",
3
- "version": "0.0.87",
3
+ "version": "0.0.89",
4
4
  "author": "Neil Sarkar",
5
5
  "type": "module",
6
6
  "repository": {
@@ -33,8 +33,8 @@
33
33
  "typescript": "^5"
34
34
  },
35
35
  "dependencies": {
36
- "@bloopjs/bloop": "0.0.87",
37
- "@bloopjs/engine": "0.0.87",
36
+ "@bloopjs/bloop": "0.0.89",
37
+ "@bloopjs/engine": "0.0.89",
38
38
  "@preact/signals": "^1.3.1",
39
39
  "partysocket": "^1.1.6",
40
40
  "preact": "^10.25.4"
package/src/App.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  type RoomEvents,
20
20
  } from "./netcode/broker";
21
21
  import { logger } from "./netcode/logs.ts";
22
+ import { reconcile } from "./netcode/reconcile.ts";
22
23
 
23
24
  export type StartOptions = {
24
25
  /** A bloop game instance */
@@ -85,6 +86,8 @@ export class App {
85
86
  #now: number = performance.now();
86
87
  #debugUi: DebugUi | null = null;
87
88
 
89
+ #abortController: AbortController = new AbortController();
90
+
88
91
  constructor(
89
92
  sim: Sim,
90
93
  game: Bloop<any>,
@@ -106,6 +109,10 @@ export class App {
106
109
  };
107
110
 
108
111
  this.subscribe();
112
+
113
+ reconcile(this, this.#abortController.signal).catch((err) => {
114
+ console.error("Error in lemmyloop:", err);
115
+ });
109
116
  }
110
117
 
111
118
  /** The simulation instance associated with this app */
@@ -367,6 +374,7 @@ export class App {
367
374
  this.afterFrame.unsubscribeAll();
368
375
  this.onHmr.unsubscribeAll();
369
376
  this.#debugUi?.unmount();
377
+ this.#abortController.abort();
370
378
  }
371
379
 
372
380
  /**
@@ -9,8 +9,6 @@ export type {
9
9
  } from "./logs.ts";
10
10
  export type { WebRtcPipe } from "./transport.ts";
11
11
  export type { RoomEvents } from "./broker.ts";
12
- export type { JoinRollbackRoomOptions } from "./scaffold.ts";
13
12
 
14
13
  export { PacketType } from "./protocol.ts";
15
14
  export { logger } from "./logs.ts";
16
- export { joinRollbackRoom } from "./scaffold.ts";
@@ -0,0 +1,171 @@
1
+ import { unwrap } from "@bloopjs/bloop";
2
+ import type { App } from "../App.ts";
3
+ import * as Debug from "../debugui/mod.ts";
4
+ import { logger } from "./logs.ts";
5
+
6
+ // State
7
+ let udp: RTCDataChannel | null = null;
8
+ let localPeerId: number | null = null;
9
+ let remotePeerId: number | null = null;
10
+ let localStringPeerId: string | null = null;
11
+ let remoteStringPeerId: string | null = null;
12
+ const incomingPackets: Uint8Array[] = [];
13
+
14
+ // actual netcode state (vs desired)
15
+ const actual = {
16
+ roomCode: "",
17
+ };
18
+
19
+ export async function reconcile(app: App, signal: AbortSignal): Promise<void> {
20
+ // Process packets and send our state each frame
21
+ app.beforeFrame.subscribe((_frame) => {
22
+ if (!app.game.context.net.isInSession) {
23
+ return;
24
+ }
25
+
26
+ try {
27
+ receivePackets(app);
28
+ sendPacket(app);
29
+ } catch (e) {
30
+ console.error("Error in beforeFrame:", e);
31
+ }
32
+ });
33
+
34
+ // Wire up logger to debug state
35
+ logger.onLog = (log) => {
36
+ Debug.addLog(log);
37
+ };
38
+
39
+ while (!signal.aborted) {
40
+ const { net } = app.game.context;
41
+ if (net.wantsRoomCode && actual.roomCode !== net.wantsRoomCode) {
42
+ console.log("[netcode] wants a room code", {
43
+ actual: actual.roomCode,
44
+ wants: net.wantsRoomCode,
45
+ });
46
+ actual.roomCode = net.wantsRoomCode;
47
+ joinRollbackRoom(net.wantsRoomCode, app);
48
+ }
49
+
50
+ await sleep(150);
51
+ }
52
+ }
53
+
54
+ async function sleep(ms: number) {
55
+ return new Promise((resolve) => setTimeout(resolve, ms));
56
+ }
57
+
58
+ function joinRollbackRoom(roomId: string, app: App): void {
59
+ app.joinRoom(roomId, {
60
+ onPeerIdAssign: (peerId) => {
61
+ localStringPeerId = peerId;
62
+ },
63
+ onBrokerMessage: (_message) => {},
64
+ onMessage(_peerId, data, _reliable) {
65
+ incomingPackets.push(new Uint8Array(data));
66
+ },
67
+ onDataChannelClose(_peerId, reliable) {
68
+ if (!reliable && remotePeerId !== null) {
69
+ app.sim.emit.network("peer:leave", { peerId: remotePeerId });
70
+ }
71
+ },
72
+ onDataChannelOpen(peerId, reliable, channel) {
73
+ if (!reliable) {
74
+ udp = channel;
75
+
76
+ if (localStringPeerId === null) {
77
+ console.error("[netcode] Local peer ID not assigned yet!");
78
+ return;
79
+ }
80
+
81
+ const ids = assignPeerIds(localStringPeerId, peerId);
82
+ localPeerId = ids.local;
83
+ Debug.setLocalId(localPeerId);
84
+ remotePeerId = ids.remote;
85
+ remoteStringPeerId = peerId;
86
+ Debug.setRemoteId(remotePeerId);
87
+
88
+ // Set up local and remote peers in net state
89
+ app.sim.emit.network("peer:join", { peerId: localPeerId });
90
+ app.sim.emit.network("peer:join", { peerId: remotePeerId });
91
+ app.sim.emit.network("peer:assign_local_id", { peerId: localPeerId });
92
+ app.sim.emit.network("session:start", {});
93
+ }
94
+ },
95
+ onPeerConnected(peerId) {
96
+ Debug.addPeer({
97
+ id: peerId,
98
+ nickname: peerId.substring(0, 6),
99
+ ack: -1,
100
+ seq: -1,
101
+ lastPacketTime: performance.now(),
102
+ });
103
+ console.log(
104
+ `[netcode] Peer connected: ${peerId}. Total peers: ${Debug.debugState.netStatus.value.peers.length}`,
105
+ );
106
+ },
107
+ onPeerDisconnected(peerId) {
108
+ Debug.removePeer(peerId);
109
+ if (remotePeerId !== null && peerId === remoteStringPeerId) {
110
+ app.sim.emit.network("peer:leave", { peerId: remotePeerId });
111
+ app.sim.emit.network("session:end", {});
112
+ }
113
+ },
114
+ });
115
+ }
116
+
117
+ function assignPeerIds(
118
+ localId: string,
119
+ remoteId: string,
120
+ ): { local: number; remote: number } {
121
+ if (localId < remoteId) {
122
+ return { local: 0, remote: 1 };
123
+ } else {
124
+ return { local: 1, remote: 0 };
125
+ }
126
+ }
127
+
128
+ function receivePackets(app: App) {
129
+ for (const packetData of incomingPackets) {
130
+ app.sim.emit.packet(packetData);
131
+
132
+ if (remotePeerId == null) {
133
+ return;
134
+ }
135
+
136
+ const peerState = unwrap(
137
+ app.sim.net.peers[remotePeerId],
138
+ `Remote peer state not found for peerId ${remotePeerId}`,
139
+ );
140
+ Debug.updatePeer(remoteStringPeerId!, {
141
+ ack: peerState.ack,
142
+ seq: peerState.seq,
143
+ lastPacketTime: performance.now(),
144
+ });
145
+ }
146
+ incomingPackets.length = 0;
147
+ }
148
+
149
+ function sendPacket(app: App) {
150
+ if (!udp || remotePeerId === null) {
151
+ console.warn("[netcode] Cannot send packet, udp or remotePeerId is null");
152
+ return;
153
+ }
154
+
155
+ if (udp.readyState !== "open") {
156
+ console.warn(
157
+ "[netcode] Data channel not open, cannot send packet. readyState=",
158
+ udp.readyState,
159
+ );
160
+ return;
161
+ }
162
+
163
+ const packet = app.sim.getOutboundPacket(remotePeerId);
164
+
165
+ if (!packet) {
166
+ console.warn("[netcode] No packet to send");
167
+ return;
168
+ }
169
+
170
+ udp.send(packet);
171
+ }
@@ -1,13 +0,0 @@
1
- import type { App } from "../App.ts";
2
- export type JoinRollbackRoomOptions = {
3
- /** Called when session becomes active */
4
- onSessionStart?: () => void;
5
- /** Called when session ends */
6
- onSessionEnd?: () => void;
7
- };
8
- /**
9
- * Join a rollback netcode room and wire up packet processing.
10
- * This is a scaffold/stopgap - not the final architecture.
11
- */
12
- export declare function joinRollbackRoom(roomId: string, app: App, opts?: JoinRollbackRoomOptions): void;
13
- //# sourceMappingURL=scaffold.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/netcode/scaffold.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAIrC,MAAM,MAAM,uBAAuB,GAAG;IACpC,yCAAyC;IACzC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,EACR,IAAI,CAAC,EAAE,uBAAuB,GAC7B,IAAI,CAmJN"}
@@ -1,168 +0,0 @@
1
- import { unwrap } from "@bloopjs/bloop";
2
- import type { App } from "../App.ts";
3
- import * as Debug from "../debugui/mod.ts";
4
- import { logger } from "./logs.ts";
5
-
6
- export type JoinRollbackRoomOptions = {
7
- /** Called when session becomes active */
8
- onSessionStart?: () => void;
9
- /** Called when session ends */
10
- onSessionEnd?: () => void;
11
- };
12
-
13
- /**
14
- * Join a rollback netcode room and wire up packet processing.
15
- * This is a scaffold/stopgap - not the final architecture.
16
- */
17
- export function joinRollbackRoom(
18
- roomId: string,
19
- app: App,
20
- opts?: JoinRollbackRoomOptions,
21
- ): void {
22
- // State
23
- let udp: RTCDataChannel | null = null;
24
- let sessionActive = false;
25
- let localPeerId: number | null = null;
26
- let remotePeerId: number | null = null;
27
- let localStringPeerId: string | null = null;
28
- let remoteStringPeerId: string | null = null;
29
- const incomingPackets: Uint8Array[] = [];
30
-
31
- function assignPeerIds(
32
- localId: string,
33
- remoteId: string,
34
- ): { local: number; remote: number } {
35
- if (localId < remoteId) {
36
- return { local: 0, remote: 1 };
37
- } else {
38
- return { local: 1, remote: 0 };
39
- }
40
- }
41
-
42
- function receivePackets() {
43
- for (const packetData of incomingPackets) {
44
- app.sim.emit.packet(packetData);
45
-
46
- if (remotePeerId == null) {
47
- return;
48
- }
49
-
50
- const peerState = unwrap(
51
- app.sim.net.peers[remotePeerId],
52
- `Remote peer state not found for peerId ${remotePeerId}`,
53
- );
54
- Debug.updatePeer(remoteStringPeerId!, {
55
- ack: peerState.ack,
56
- seq: peerState.seq,
57
- lastPacketTime: performance.now(),
58
- });
59
- }
60
- incomingPackets.length = 0;
61
- }
62
-
63
- function sendPacket() {
64
- if (!udp || remotePeerId === null) {
65
- console.warn("[netcode] Cannot send packet, udp or remotePeerId is null");
66
- return;
67
- }
68
-
69
- if (udp.readyState !== "open") {
70
- console.warn(
71
- "[netcode] Data channel not open, cannot send packet. readyState=",
72
- udp.readyState,
73
- );
74
- return;
75
- }
76
-
77
- const packet = app.sim.getOutboundPacket(remotePeerId);
78
-
79
- if (!packet) {
80
- console.warn("[netcode] No packet to send");
81
- return;
82
- }
83
-
84
- udp.send(packet);
85
- }
86
-
87
- // Wire up logger to debug state
88
- logger.onLog = (log) => {
89
- Debug.addLog(log);
90
- };
91
-
92
- app.joinRoom(roomId, {
93
- onPeerIdAssign: (peerId) => {
94
- localStringPeerId = peerId;
95
- },
96
- onBrokerMessage: (_message) => {},
97
- onMessage(_peerId, data, _reliable) {
98
- incomingPackets.push(new Uint8Array(data));
99
- },
100
- onDataChannelClose(peerId, reliable) {
101
- if (!reliable && remotePeerId !== null) {
102
- app.sim.emit.network("peer:leave", { peerId: remotePeerId });
103
- sessionActive = false;
104
- opts?.onSessionEnd?.();
105
- }
106
- },
107
- onDataChannelOpen(peerId, reliable, channel) {
108
- if (!reliable) {
109
- udp = channel;
110
-
111
- if (localStringPeerId === null) {
112
- console.error("[netcode] Local peer ID not assigned yet!");
113
- return;
114
- }
115
-
116
- const ids = assignPeerIds(localStringPeerId, peerId);
117
- localPeerId = ids.local;
118
- Debug.setLocalId(localPeerId);
119
- remotePeerId = ids.remote;
120
- remoteStringPeerId = peerId;
121
- Debug.setRemoteId(remotePeerId);
122
-
123
- // Set up local and remote peers in net state
124
- app.sim.emit.network("peer:join", { peerId: localPeerId });
125
- app.sim.emit.network("peer:join", { peerId: remotePeerId });
126
- app.sim.emit.network("peer:assign_local_id", { peerId: localPeerId });
127
- app.sim.emit.network("session:start", {});
128
-
129
- sessionActive = true;
130
- opts?.onSessionStart?.();
131
- }
132
- },
133
- onPeerConnected(peerId) {
134
- Debug.addPeer({
135
- id: peerId,
136
- nickname: peerId.substring(0, 6),
137
- ack: -1,
138
- seq: -1,
139
- lastPacketTime: performance.now(),
140
- });
141
- console.log(
142
- `[netcode] Peer connected: ${peerId}. Total peers: ${Debug.debugState.netStatus.value.peers.length}`,
143
- );
144
- },
145
- onPeerDisconnected(peerId) {
146
- Debug.removePeer(peerId);
147
- if (remotePeerId !== null && peerId === remoteStringPeerId) {
148
- app.sim.emit.network("peer:leave", { peerId: remotePeerId });
149
- sessionActive = false;
150
- opts?.onSessionEnd?.();
151
- }
152
- },
153
- });
154
-
155
- // Process packets and send our state each frame
156
- app.beforeFrame.subscribe((_frame) => {
157
- if (!sessionActive || !udp || remotePeerId === null) {
158
- return;
159
- }
160
-
161
- try {
162
- receivePackets();
163
- sendPacket();
164
- } catch (e) {
165
- console.error("Error in beforeFrame:", e);
166
- }
167
- });
168
- }