@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.
package/dist/mod.js CHANGED
@@ -82,16 +82,21 @@ __export2(exports_engine, {
82
82
  SNAPSHOT_HEADER_ENGINE_LEN_OFFSET: () => SNAPSHOT_HEADER_ENGINE_LEN_OFFSET,
83
83
  PlayerInputContext: () => PlayerInputContext,
84
84
  PLAYER_INPUTS_SIZE: () => PLAYER_INPUTS_SIZE,
85
+ PLAYER_INPUTS_MOUSE_CTX_OFFSET: () => PLAYER_INPUTS_MOUSE_CTX_OFFSET,
86
+ PLAYER_INPUTS_KEY_CTX_OFFSET: () => PLAYER_INPUTS_KEY_CTX_OFFSET,
85
87
  NetContext: () => NetContext,
86
88
  NET_CTX_OFFSET: () => NET_CTX_OFFSET,
87
89
  MouseContext: () => MouseContext,
88
- MOUSE_OFFSET: () => MOUSE_OFFSET,
89
- MOUSE_BUTTONS_OFFSET: () => MOUSE_BUTTONS_OFFSET,
90
+ MOUSE_OFFSET: () => PLAYER_INPUTS_MOUSE_CTX_OFFSET,
91
+ MOUSE_CTX_SIZE: () => MOUSE_CTX_SIZE,
92
+ MOUSE_CTX_BUTTON_STATES_OFFSET: () => MOUSE_CTX_BUTTON_STATES_OFFSET,
93
+ MOUSE_BUTTONS_OFFSET: () => MOUSE_CTX_BUTTON_STATES_OFFSET,
90
94
  MAX_ROLLBACK_FRAMES: () => MAX_ROLLBACK_FRAMES,
91
95
  MAX_PLAYERS: () => MAX_PLAYERS,
92
96
  KeyboardContext: () => KeyboardContext,
93
- KEYBOARD_SIZE: () => KEYBOARD_SIZE,
94
- KEYBOARD_OFFSET: () => KEYBOARD_OFFSET,
97
+ KEY_CTX_SIZE: () => KEY_CTX_SIZE,
98
+ KEYBOARD_SIZE: () => KEY_CTX_SIZE,
99
+ KEYBOARD_OFFSET: () => PLAYER_INPUTS_KEY_CTX_OFFSET,
95
100
  InputContext: () => InputContext,
96
101
  INPUT_CTX_SIZE: () => INPUT_CTX_SIZE,
97
102
  INPUT_CTX_OFFSET: () => INPUT_CTX_OFFSET,
@@ -387,33 +392,38 @@ var NetJoinFailReason;
387
392
  NetJoinFailReason2[NetJoinFailReason2["room_not_found"] = 3] = "room_not_found";
388
393
  NetJoinFailReason2[NetJoinFailReason2["already_in_room"] = 4] = "already_in_room";
389
394
  })(NetJoinFailReason ||= {});
390
- var MAX_PLAYERS = 12;
391
- var KEYBOARD_OFFSET = 0;
392
- var KEYBOARD_SIZE = 256;
393
- var MOUSE_OFFSET = 256;
394
- var MOUSE_BUTTONS_OFFSET = 16;
395
+ var TIME_CTX_FRAME_OFFSET = 0;
396
+ var TIME_CTX_DT_MS_OFFSET = 4;
397
+ var TIME_CTX_TOTAL_MS_OFFSET = 8;
398
+ var PEER_CTX_CONNECTED_OFFSET = 0;
399
+ var PEER_CTX_SEQ_OFFSET = 2;
400
+ var PEER_CTX_ACK_OFFSET = 4;
401
+ var PEER_CTX_SIZE = 8;
402
+ var NET_CTX_PEER_COUNT_OFFSET = 0;
403
+ var NET_CTX_LOCAL_PEER_ID_OFFSET = 1;
404
+ var NET_CTX_IN_SESSION_OFFSET = 2;
405
+ var NET_CTX_STATUS_OFFSET = 3;
406
+ var NET_CTX_MATCH_FRAME_OFFSET = 4;
407
+ var NET_CTX_SESSION_START_FRAME_OFFSET = 8;
408
+ var NET_CTX_ROOM_CODE_OFFSET = 12;
409
+ var NET_CTX_WANTS_ROOM_CODE_OFFSET = 20;
410
+ var NET_CTX_WANTS_DISCONNECT_OFFSET = 28;
411
+ var NET_CTX_PEERS_OFFSET = 32;
412
+ var NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET = 128;
413
+ var NET_CTX_TOTAL_ROLLBACKS_OFFSET = 132;
414
+ var NET_CTX_FRAMES_RESIMULATED_OFFSET = 136;
415
+ var MOUSE_CTX_X_OFFSET = 0;
416
+ var MOUSE_CTX_Y_OFFSET = 4;
417
+ var MOUSE_CTX_WHEEL_X_OFFSET = 8;
418
+ var MOUSE_CTX_WHEEL_Y_OFFSET = 12;
419
+ var MOUSE_CTX_BUTTON_STATES_OFFSET = 16;
420
+ var MOUSE_CTX_SIZE = 24;
421
+ var KEY_CTX_SIZE = 256;
422
+ var PLAYER_INPUTS_KEY_CTX_OFFSET = 0;
423
+ var PLAYER_INPUTS_MOUSE_CTX_OFFSET = 256;
395
424
  var PLAYER_INPUTS_SIZE = 280;
396
- var INPUT_CTX_SIZE = MAX_PLAYERS * PLAYER_INPUTS_SIZE;
397
- var EVENT_PAYLOAD_SIZE = 8;
398
- var EVENT_PAYLOAD_ALIGN = 4;
399
- function keyToKeyCode(key) {
400
- return Key[key];
401
- }
402
- function keyCodeToKey(code) {
403
- return Key[code];
404
- }
405
- function mouseButtonToMouseButtonCode(button) {
406
- return MouseButton[button];
407
- }
408
- function mouseButtonCodeToMouseButton(code) {
409
- return MouseButton[code];
410
- }
411
- function inputSourceToInputSourceCode(source) {
412
- return InputSource[source];
413
- }
414
- function inputSourceCodeToInputSource(code) {
415
- return InputSource[code];
416
- }
425
+ var INPUT_CTX_SIZE = 3360;
426
+ var MAX_PLAYERS = 12;
417
427
 
418
428
  class PlayerInputContext {
419
429
  #dataView;
@@ -424,13 +434,13 @@ class PlayerInputContext {
424
434
  }
425
435
  get keys() {
426
436
  if (!this.#keys) {
427
- this.#keys = new KeyboardContext(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + KEYBOARD_OFFSET));
437
+ this.#keys = new KeyboardContext(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + PLAYER_INPUTS_KEY_CTX_OFFSET));
428
438
  }
429
439
  return this.#keys;
430
440
  }
431
441
  get mouse() {
432
442
  if (!this.#mouse) {
433
- this.#mouse = new MouseContext(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + MOUSE_OFFSET));
443
+ this.#mouse = new MouseContext(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + PLAYER_INPUTS_MOUSE_CTX_OFFSET));
434
444
  }
435
445
  return this.#mouse;
436
446
  }
@@ -455,6 +465,9 @@ class InputContext {
455
465
  this.#dataView = dataView;
456
466
  this.#buildPlayers();
457
467
  }
468
+ hasDataView() {
469
+ return !!this.#dataView;
470
+ }
458
471
  #buildPlayers() {
459
472
  this.#players = [];
460
473
  for (let i = 0;i < MAX_PLAYERS; i++) {
@@ -487,19 +500,19 @@ class MouseContext {
487
500
  this.#dataView = dataView;
488
501
  }
489
502
  get x() {
490
- return this.#dataView.getFloat32(0, true);
503
+ return this.#dataView.getFloat32(MOUSE_CTX_X_OFFSET, true);
491
504
  }
492
505
  get y() {
493
- return this.#dataView.getFloat32(4, true);
506
+ return this.#dataView.getFloat32(MOUSE_CTX_Y_OFFSET, true);
494
507
  }
495
508
  get wheel() {
496
509
  return { x: this.wheelX, y: this.wheelY };
497
510
  }
498
511
  get wheelX() {
499
- return this.#dataView.getFloat32(8, true);
512
+ return this.#dataView.getFloat32(MOUSE_CTX_WHEEL_X_OFFSET, true);
500
513
  }
501
514
  get wheelY() {
502
- return this.#dataView.getFloat32(12, true);
515
+ return this.#dataView.getFloat32(MOUSE_CTX_WHEEL_Y_OFFSET, true);
503
516
  }
504
517
  get left() {
505
518
  return this.#buttonState(1);
@@ -518,7 +531,7 @@ class MouseContext {
518
531
  state = { down: false, held: false, up: false };
519
532
  this.#buttonStates.set(code, state);
520
533
  }
521
- const offset = MOUSE_BUTTONS_OFFSET;
534
+ const offset = MOUSE_CTX_BUTTON_STATES_OFFSET;
522
535
  state.held = !!(this.#dataView.getUint8(offset + code) & 1);
523
536
  state.down = state.held && !(this.#dataView.getUint8(offset + code) & 2);
524
537
  state.up = !state.held && !!(this.#dataView.getUint8(offset + code) & 2);
@@ -1140,14 +1153,6 @@ class KeyboardContext {
1140
1153
  return state;
1141
1154
  }
1142
1155
  }
1143
- var MAX_PEERS = 12;
1144
- var PEERS_ARRAY_OFFSET = 32;
1145
- var PEER_CTX_SIZE = 8;
1146
- var PEER_CONNECTED_OFFSET = 0;
1147
- var PEER_PACKET_COUNT_OFFSET = 1;
1148
- var PEER_SEQ_OFFSET = 2;
1149
- var PEER_ACK_OFFSET = 4;
1150
- var PEER_ACK_COUNT_OFFSET = 6;
1151
1156
  var STATUS_MAP = {
1152
1157
  0: "offline",
1153
1158
  1: "local",
@@ -1158,7 +1163,7 @@ var STATUS_MAP = {
1158
1163
 
1159
1164
  class NetContext {
1160
1165
  dataView;
1161
- #peers = Array.from({ length: MAX_PEERS }, () => ({
1166
+ #peers = Array.from({ length: MAX_PLAYERS }, () => ({
1162
1167
  isLocal: false,
1163
1168
  seq: -1,
1164
1169
  ack: -1
@@ -1176,38 +1181,38 @@ class NetContext {
1176
1181
  if (!this.#hasValidBuffer()) {
1177
1182
  throw new Error("NetContext dataView is not valid");
1178
1183
  }
1179
- return this.dataView.getUint8(0);
1184
+ return this.dataView.getUint8(NET_CTX_PEER_COUNT_OFFSET);
1180
1185
  }
1181
1186
  get localPeerId() {
1182
1187
  if (!this.#hasValidBuffer()) {
1183
1188
  throw new Error("NetContext dataView is not valid");
1184
1189
  }
1185
- return this.dataView.getUint8(1);
1190
+ return this.dataView.getUint8(NET_CTX_LOCAL_PEER_ID_OFFSET);
1186
1191
  }
1187
1192
  get isInSession() {
1188
1193
  if (!this.#hasValidBuffer()) {
1189
1194
  throw new Error("NetContext dataView is not valid");
1190
1195
  }
1191
- return this.dataView.getUint8(2) !== 0;
1196
+ return this.dataView.getUint8(NET_CTX_IN_SESSION_OFFSET) !== 0;
1192
1197
  }
1193
1198
  get status() {
1194
1199
  if (!this.#hasValidBuffer()) {
1195
1200
  throw new Error("NetContext dataView is not valid");
1196
1201
  }
1197
- const statusByte = this.dataView.getUint8(3);
1202
+ const statusByte = this.dataView.getUint8(NET_CTX_STATUS_OFFSET);
1198
1203
  return STATUS_MAP[statusByte] ?? "local";
1199
1204
  }
1200
1205
  get matchFrame() {
1201
1206
  if (!this.#hasValidBuffer()) {
1202
1207
  throw new Error("NetContext dataView is not valid");
1203
1208
  }
1204
- return this.dataView.getUint32(4, true);
1209
+ return this.dataView.getUint32(NET_CTX_MATCH_FRAME_OFFSET, true);
1205
1210
  }
1206
1211
  get sessionStartFrame() {
1207
1212
  if (!this.#hasValidBuffer()) {
1208
1213
  throw new Error("NetContext dataView is not valid");
1209
1214
  }
1210
- return this.dataView.getUint32(8, true);
1215
+ return this.dataView.getUint32(NET_CTX_SESSION_START_FRAME_OFFSET, true);
1211
1216
  }
1212
1217
  get roomCode() {
1213
1218
  if (!this.#hasValidBuffer()) {
@@ -1215,7 +1220,7 @@ class NetContext {
1215
1220
  }
1216
1221
  const bytes = [];
1217
1222
  for (let i = 0;i < 8; i++) {
1218
- const byte = this.dataView.getUint8(12 + i);
1223
+ const byte = this.dataView.getUint8(NET_CTX_ROOM_CODE_OFFSET + i);
1219
1224
  if (byte === 0)
1220
1225
  break;
1221
1226
  bytes.push(byte);
@@ -1228,7 +1233,7 @@ class NetContext {
1228
1233
  }
1229
1234
  const bytes = [];
1230
1235
  for (let i = 0;i < 8; i++) {
1231
- const byte = this.dataView.getUint8(20 + i);
1236
+ const byte = this.dataView.getUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i);
1232
1237
  if (byte === 0)
1233
1238
  break;
1234
1239
  bytes.push(byte);
@@ -1240,11 +1245,11 @@ class NetContext {
1240
1245
  throw new Error("NetContext dataView is not valid");
1241
1246
  }
1242
1247
  for (let i = 0;i < 8; i++) {
1243
- this.dataView.setUint8(20 + i, 0);
1248
+ this.dataView.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i, 0);
1244
1249
  }
1245
1250
  if (code) {
1246
1251
  for (let i = 0;i < Math.min(code.length, 7); i++) {
1247
- this.dataView.setUint8(20 + i, code.charCodeAt(i));
1252
+ this.dataView.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i, code.charCodeAt(i));
1248
1253
  }
1249
1254
  }
1250
1255
  }
@@ -1252,13 +1257,13 @@ class NetContext {
1252
1257
  if (!this.#hasValidBuffer()) {
1253
1258
  return false;
1254
1259
  }
1255
- return this.dataView.getUint8(28) !== 0;
1260
+ return this.dataView.getUint8(NET_CTX_WANTS_DISCONNECT_OFFSET) !== 0;
1256
1261
  }
1257
1262
  set wantsDisconnect(value) {
1258
1263
  if (!this.#hasValidBuffer()) {
1259
1264
  throw new Error("NetContext dataView is not valid");
1260
1265
  }
1261
- this.dataView.setUint8(28, value ? 1 : 0);
1266
+ this.dataView.setUint8(NET_CTX_WANTS_DISCONNECT_OFFSET, value ? 1 : 0);
1262
1267
  }
1263
1268
  get peers() {
1264
1269
  if (!this.#hasValidBuffer()) {
@@ -1268,23 +1273,23 @@ class NetContext {
1268
1273
  const localPeerId = this.localPeerId;
1269
1274
  const matchFrame = this.matchFrame;
1270
1275
  let minRemoteSeq = -1;
1271
- for (let i = 0;i < MAX_PEERS; i++) {
1276
+ for (let i = 0;i < MAX_PLAYERS; i++) {
1272
1277
  if (i === localPeerId)
1273
1278
  continue;
1274
- const peerOffset = PEERS_ARRAY_OFFSET + i * PEER_CTX_SIZE;
1275
- if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET) !== 1)
1279
+ const peerOffset = NET_CTX_PEERS_OFFSET + i * PEER_CTX_SIZE;
1280
+ if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET) !== 1)
1276
1281
  continue;
1277
- if (dv.getUint8(peerOffset + PEER_PACKET_COUNT_OFFSET) === 0)
1282
+ const seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET, true);
1283
+ if (seq < 0)
1278
1284
  continue;
1279
- const seq = dv.getUint16(peerOffset + PEER_SEQ_OFFSET, true);
1280
1285
  if (minRemoteSeq === -1 || seq < minRemoteSeq) {
1281
1286
  minRemoteSeq = seq;
1282
1287
  }
1283
1288
  }
1284
1289
  this.#peersResult.length = 0;
1285
- for (let i = 0;i < MAX_PEERS; i++) {
1286
- const peerOffset = PEERS_ARRAY_OFFSET + i * PEER_CTX_SIZE;
1287
- if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET) !== 1)
1290
+ for (let i = 0;i < MAX_PLAYERS; i++) {
1291
+ const peerOffset = NET_CTX_PEERS_OFFSET + i * PEER_CTX_SIZE;
1292
+ if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET) !== 1)
1288
1293
  continue;
1289
1294
  const peer = this.#peers[i];
1290
1295
  if (!peer) {
@@ -1296,15 +1301,31 @@ class NetContext {
1296
1301
  peer.seq = matchFrame;
1297
1302
  peer.ack = minRemoteSeq;
1298
1303
  } else {
1299
- const packetCount = dv.getUint8(peerOffset + PEER_PACKET_COUNT_OFFSET);
1300
- const ackCount = dv.getUint8(peerOffset + PEER_ACK_COUNT_OFFSET);
1301
- peer.seq = packetCount === 0 ? -1 : dv.getUint16(peerOffset + PEER_SEQ_OFFSET, true);
1302
- peer.ack = ackCount === 0 ? -1 : dv.getUint16(peerOffset + PEER_ACK_OFFSET, true);
1304
+ peer.seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET, true);
1305
+ peer.ack = dv.getInt16(peerOffset + PEER_CTX_ACK_OFFSET, true);
1303
1306
  }
1304
1307
  this.#peersResult.push(peer);
1305
1308
  }
1306
1309
  return this.#peersResult;
1307
1310
  }
1311
+ get lastRollbackDepth() {
1312
+ if (!this.#hasValidBuffer()) {
1313
+ throw new Error("NetContext dataView is not valid");
1314
+ }
1315
+ return this.dataView.getUint32(NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET, true);
1316
+ }
1317
+ get totalRollbacks() {
1318
+ if (!this.#hasValidBuffer()) {
1319
+ throw new Error("NetContext dataView is not valid");
1320
+ }
1321
+ return this.dataView.getUint32(NET_CTX_TOTAL_ROLLBACKS_OFFSET, true);
1322
+ }
1323
+ get framesResimulated() {
1324
+ if (!this.#hasValidBuffer()) {
1325
+ throw new Error("NetContext dataView is not valid");
1326
+ }
1327
+ return Number(this.dataView.getBigUint64(NET_CTX_FRAMES_RESIMULATED_OFFSET, true));
1328
+ }
1308
1329
  }
1309
1330
 
1310
1331
  class TimeContext {
@@ -1316,21 +1337,41 @@ class TimeContext {
1316
1337
  if (!this.dataView) {
1317
1338
  throw new Error("TimeContext DataView is not initialized");
1318
1339
  }
1319
- return this.dataView.getUint32(0, true);
1340
+ return this.dataView.getUint32(TIME_CTX_FRAME_OFFSET, true);
1320
1341
  }
1321
1342
  get dt() {
1322
1343
  if (!this.dataView) {
1323
1344
  throw new Error("TimeContext DataView is not initialized");
1324
1345
  }
1325
- return this.dataView.getUint32(4, true) / 1000;
1346
+ return this.dataView.getUint32(TIME_CTX_DT_MS_OFFSET, true) / 1000;
1326
1347
  }
1327
1348
  get time() {
1328
1349
  if (!this.dataView) {
1329
1350
  throw new Error("TimeContext DataView is not initialized");
1330
1351
  }
1331
- return this.dataView.getUint32(8, true) / 1000;
1352
+ return this.dataView.getUint32(TIME_CTX_TOTAL_MS_OFFSET, true) / 1000;
1332
1353
  }
1333
1354
  }
1355
+ var EVENT_PAYLOAD_SIZE = 8;
1356
+ var EVENT_PAYLOAD_ALIGN = 4;
1357
+ function keyToKeyCode(key) {
1358
+ return Key[key];
1359
+ }
1360
+ function keyCodeToKey(code) {
1361
+ return Key[code];
1362
+ }
1363
+ function mouseButtonToMouseButtonCode(button) {
1364
+ return MouseButton[button];
1365
+ }
1366
+ function mouseButtonCodeToMouseButton(code) {
1367
+ return MouseButton[code];
1368
+ }
1369
+ function inputSourceToInputSourceCode(source) {
1370
+ return InputSource[source];
1371
+ }
1372
+ function inputSourceCodeToInputSource(code) {
1373
+ return InputSource[code];
1374
+ }
1334
1375
  var TAPE_MAGIC = 1413566533;
1335
1376
  function readTapeHeader(tape) {
1336
1377
  const view = new DataView(tape.buffer, tape.byteOffset, tape.byteLength);
@@ -1346,7 +1387,7 @@ function readTapeHeader(tape) {
1346
1387
  eventCount: view.getUint16(14, true)
1347
1388
  };
1348
1389
  }
1349
- var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.87/wasm/bloop.wasm");
1390
+ var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.89/wasm/bloop.wasm");
1350
1391
  var MAX_ROLLBACK_FRAMES = 500;
1351
1392
  var TIME_CTX_OFFSET = 0;
1352
1393
  var INPUT_CTX_OFFSET = TIME_CTX_OFFSET + 4;
@@ -1611,6 +1652,7 @@ async function mount(mountable, options) {
1611
1652
  memory,
1612
1653
  __systems: function(system_handle, ptr) {
1613
1654
  mountable.hooks.setBuffer(memory.buffer);
1655
+ mountable.hooks.setContext(ptr);
1614
1656
  mountable.hooks.systemsCallback(system_handle, ptr);
1615
1657
  },
1616
1658
  __before_frame: function(ptr, frame) {
@@ -1757,9 +1799,15 @@ class Bloop {
1757
1799
  const inputCtxPtr = dv.getUint32(INPUT_CTX_OFFSET, true);
1758
1800
  const netCtxPtr = dv.getUint32(NET_CTX_OFFSET, true);
1759
1801
  this.#context.rawPointer = ptr;
1760
- this.#context.inputs.dataView = new DataView(this.#engineBuffer, inputCtxPtr);
1761
- this.#context.time.dataView = new DataView(this.#engineBuffer, timeCtxPtr);
1762
- this.#context.net.dataView = new DataView(this.#engineBuffer, netCtxPtr);
1802
+ if (!this.#context.inputs.hasDataView() || this.#context.inputs.dataView.buffer !== this.#engineBuffer || this.#context.inputs.dataView.byteOffset !== inputCtxPtr) {
1803
+ this.#context.inputs.dataView = new DataView(this.#engineBuffer, inputCtxPtr);
1804
+ }
1805
+ if (this.#context.time.dataView?.buffer !== this.#engineBuffer || this.#context.time.dataView?.byteOffset !== timeCtxPtr) {
1806
+ this.#context.time.dataView = new DataView(this.#engineBuffer, timeCtxPtr);
1807
+ }
1808
+ if (this.#context.net.dataView?.buffer !== this.#engineBuffer || this.#context.net.dataView?.byteOffset !== netCtxPtr) {
1809
+ this.#context.net.dataView = new DataView(this.#engineBuffer, netCtxPtr);
1810
+ }
1763
1811
  },
1764
1812
  systemsCallback: (system_handle, ptr) => {
1765
1813
  this.hooks.setContext(ptr);
@@ -1864,6 +1912,13 @@ class Bloop {
1864
1912
  system.netcode?.(this.#context);
1865
1913
  break;
1866
1914
  }
1915
+ case exports_enums.EventType.NetSessionInit:
1916
+ console.log("[bloop] NetSessionInit event received");
1917
+ this.#context.event = {
1918
+ type: "session:start"
1919
+ };
1920
+ system.netcode?.(this.#context);
1921
+ break;
1867
1922
  default:
1868
1923
  break;
1869
1924
  }
@@ -2166,15 +2221,33 @@ var NetJoinFailReason2;
2166
2221
  NetJoinFailReason22[NetJoinFailReason22["room_not_found"] = 3] = "room_not_found";
2167
2222
  NetJoinFailReason22[NetJoinFailReason22["already_in_room"] = 4] = "already_in_room";
2168
2223
  })(NetJoinFailReason2 ||= {});
2169
- var MAX_PLAYERS2 = 12;
2170
- var KEYBOARD_OFFSET2 = 0;
2171
- var MOUSE_OFFSET2 = 256;
2172
- var MOUSE_BUTTONS_OFFSET2 = 16;
2224
+ var PEER_CTX_CONNECTED_OFFSET2 = 0;
2225
+ var PEER_CTX_SEQ_OFFSET2 = 2;
2226
+ var PEER_CTX_ACK_OFFSET2 = 4;
2227
+ var PEER_CTX_SIZE2 = 8;
2228
+ var NET_CTX_PEER_COUNT_OFFSET2 = 0;
2229
+ var NET_CTX_LOCAL_PEER_ID_OFFSET2 = 1;
2230
+ var NET_CTX_IN_SESSION_OFFSET2 = 2;
2231
+ var NET_CTX_STATUS_OFFSET2 = 3;
2232
+ var NET_CTX_MATCH_FRAME_OFFSET2 = 4;
2233
+ var NET_CTX_SESSION_START_FRAME_OFFSET2 = 8;
2234
+ var NET_CTX_ROOM_CODE_OFFSET2 = 12;
2235
+ var NET_CTX_WANTS_ROOM_CODE_OFFSET2 = 20;
2236
+ var NET_CTX_WANTS_DISCONNECT_OFFSET2 = 28;
2237
+ var NET_CTX_PEERS_OFFSET2 = 32;
2238
+ var NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET2 = 128;
2239
+ var NET_CTX_TOTAL_ROLLBACKS_OFFSET2 = 132;
2240
+ var NET_CTX_FRAMES_RESIMULATED_OFFSET2 = 136;
2241
+ var MOUSE_CTX_X_OFFSET2 = 0;
2242
+ var MOUSE_CTX_Y_OFFSET2 = 4;
2243
+ var MOUSE_CTX_WHEEL_X_OFFSET2 = 8;
2244
+ var MOUSE_CTX_WHEEL_Y_OFFSET2 = 12;
2245
+ var MOUSE_CTX_BUTTON_STATES_OFFSET2 = 16;
2246
+ var PLAYER_INPUTS_KEY_CTX_OFFSET2 = 0;
2247
+ var PLAYER_INPUTS_MOUSE_CTX_OFFSET2 = 256;
2173
2248
  var PLAYER_INPUTS_SIZE2 = 280;
2174
- var INPUT_CTX_SIZE2 = MAX_PLAYERS2 * PLAYER_INPUTS_SIZE2;
2175
- function mouseButtonCodeToMouseButton2(code) {
2176
- return MouseButton2[code];
2177
- }
2249
+ var MAX_PLAYERS2 = 12;
2250
+
2178
2251
  class PlayerInputContext2 {
2179
2252
  #dataView;
2180
2253
  #keys;
@@ -2184,13 +2257,13 @@ class PlayerInputContext2 {
2184
2257
  }
2185
2258
  get keys() {
2186
2259
  if (!this.#keys) {
2187
- this.#keys = new KeyboardContext2(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + KEYBOARD_OFFSET2));
2260
+ this.#keys = new KeyboardContext2(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + PLAYER_INPUTS_KEY_CTX_OFFSET2));
2188
2261
  }
2189
2262
  return this.#keys;
2190
2263
  }
2191
2264
  get mouse() {
2192
2265
  if (!this.#mouse) {
2193
- this.#mouse = new MouseContext2(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + MOUSE_OFFSET2));
2266
+ this.#mouse = new MouseContext2(new DataView(this.#dataView.buffer, this.#dataView.byteOffset + PLAYER_INPUTS_MOUSE_CTX_OFFSET2));
2194
2267
  }
2195
2268
  return this.#mouse;
2196
2269
  }
@@ -2215,6 +2288,9 @@ class InputContext2 {
2215
2288
  this.#dataView = dataView;
2216
2289
  this.#buildPlayers();
2217
2290
  }
2291
+ hasDataView() {
2292
+ return !!this.#dataView;
2293
+ }
2218
2294
  #buildPlayers() {
2219
2295
  this.#players = [];
2220
2296
  for (let i = 0;i < MAX_PLAYERS2; i++) {
@@ -2247,19 +2323,19 @@ class MouseContext2 {
2247
2323
  this.#dataView = dataView;
2248
2324
  }
2249
2325
  get x() {
2250
- return this.#dataView.getFloat32(0, true);
2326
+ return this.#dataView.getFloat32(MOUSE_CTX_X_OFFSET2, true);
2251
2327
  }
2252
2328
  get y() {
2253
- return this.#dataView.getFloat32(4, true);
2329
+ return this.#dataView.getFloat32(MOUSE_CTX_Y_OFFSET2, true);
2254
2330
  }
2255
2331
  get wheel() {
2256
2332
  return { x: this.wheelX, y: this.wheelY };
2257
2333
  }
2258
2334
  get wheelX() {
2259
- return this.#dataView.getFloat32(8, true);
2335
+ return this.#dataView.getFloat32(MOUSE_CTX_WHEEL_X_OFFSET2, true);
2260
2336
  }
2261
2337
  get wheelY() {
2262
- return this.#dataView.getFloat32(12, true);
2338
+ return this.#dataView.getFloat32(MOUSE_CTX_WHEEL_Y_OFFSET2, true);
2263
2339
  }
2264
2340
  get left() {
2265
2341
  return this.#buttonState(1);
@@ -2278,7 +2354,7 @@ class MouseContext2 {
2278
2354
  state = { down: false, held: false, up: false };
2279
2355
  this.#buttonStates.set(code, state);
2280
2356
  }
2281
- const offset = MOUSE_BUTTONS_OFFSET2;
2357
+ const offset = MOUSE_CTX_BUTTON_STATES_OFFSET2;
2282
2358
  state.held = !!(this.#dataView.getUint8(offset + code) & 1);
2283
2359
  state.down = state.held && !(this.#dataView.getUint8(offset + code) & 2);
2284
2360
  state.up = !state.held && !!(this.#dataView.getUint8(offset + code) & 2);
@@ -2900,14 +2976,6 @@ class KeyboardContext2 {
2900
2976
  return state;
2901
2977
  }
2902
2978
  }
2903
- var MAX_PEERS2 = 12;
2904
- var PEERS_ARRAY_OFFSET2 = 32;
2905
- var PEER_CTX_SIZE2 = 8;
2906
- var PEER_CONNECTED_OFFSET2 = 0;
2907
- var PEER_PACKET_COUNT_OFFSET2 = 1;
2908
- var PEER_SEQ_OFFSET2 = 2;
2909
- var PEER_ACK_OFFSET2 = 4;
2910
- var PEER_ACK_COUNT_OFFSET2 = 6;
2911
2979
  var STATUS_MAP2 = {
2912
2980
  0: "offline",
2913
2981
  1: "local",
@@ -2918,7 +2986,7 @@ var STATUS_MAP2 = {
2918
2986
 
2919
2987
  class NetContext2 {
2920
2988
  dataView;
2921
- #peers = Array.from({ length: MAX_PEERS2 }, () => ({
2989
+ #peers = Array.from({ length: MAX_PLAYERS2 }, () => ({
2922
2990
  isLocal: false,
2923
2991
  seq: -1,
2924
2992
  ack: -1
@@ -2936,38 +3004,38 @@ class NetContext2 {
2936
3004
  if (!this.#hasValidBuffer()) {
2937
3005
  throw new Error("NetContext dataView is not valid");
2938
3006
  }
2939
- return this.dataView.getUint8(0);
3007
+ return this.dataView.getUint8(NET_CTX_PEER_COUNT_OFFSET2);
2940
3008
  }
2941
3009
  get localPeerId() {
2942
3010
  if (!this.#hasValidBuffer()) {
2943
3011
  throw new Error("NetContext dataView is not valid");
2944
3012
  }
2945
- return this.dataView.getUint8(1);
3013
+ return this.dataView.getUint8(NET_CTX_LOCAL_PEER_ID_OFFSET2);
2946
3014
  }
2947
3015
  get isInSession() {
2948
3016
  if (!this.#hasValidBuffer()) {
2949
3017
  throw new Error("NetContext dataView is not valid");
2950
3018
  }
2951
- return this.dataView.getUint8(2) !== 0;
3019
+ return this.dataView.getUint8(NET_CTX_IN_SESSION_OFFSET2) !== 0;
2952
3020
  }
2953
3021
  get status() {
2954
3022
  if (!this.#hasValidBuffer()) {
2955
3023
  throw new Error("NetContext dataView is not valid");
2956
3024
  }
2957
- const statusByte = this.dataView.getUint8(3);
3025
+ const statusByte = this.dataView.getUint8(NET_CTX_STATUS_OFFSET2);
2958
3026
  return STATUS_MAP2[statusByte] ?? "local";
2959
3027
  }
2960
3028
  get matchFrame() {
2961
3029
  if (!this.#hasValidBuffer()) {
2962
3030
  throw new Error("NetContext dataView is not valid");
2963
3031
  }
2964
- return this.dataView.getUint32(4, true);
3032
+ return this.dataView.getUint32(NET_CTX_MATCH_FRAME_OFFSET2, true);
2965
3033
  }
2966
3034
  get sessionStartFrame() {
2967
3035
  if (!this.#hasValidBuffer()) {
2968
3036
  throw new Error("NetContext dataView is not valid");
2969
3037
  }
2970
- return this.dataView.getUint32(8, true);
3038
+ return this.dataView.getUint32(NET_CTX_SESSION_START_FRAME_OFFSET2, true);
2971
3039
  }
2972
3040
  get roomCode() {
2973
3041
  if (!this.#hasValidBuffer()) {
@@ -2975,7 +3043,7 @@ class NetContext2 {
2975
3043
  }
2976
3044
  const bytes = [];
2977
3045
  for (let i = 0;i < 8; i++) {
2978
- const byte = this.dataView.getUint8(12 + i);
3046
+ const byte = this.dataView.getUint8(NET_CTX_ROOM_CODE_OFFSET2 + i);
2979
3047
  if (byte === 0)
2980
3048
  break;
2981
3049
  bytes.push(byte);
@@ -2988,7 +3056,7 @@ class NetContext2 {
2988
3056
  }
2989
3057
  const bytes = [];
2990
3058
  for (let i = 0;i < 8; i++) {
2991
- const byte = this.dataView.getUint8(20 + i);
3059
+ const byte = this.dataView.getUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET2 + i);
2992
3060
  if (byte === 0)
2993
3061
  break;
2994
3062
  bytes.push(byte);
@@ -3000,11 +3068,11 @@ class NetContext2 {
3000
3068
  throw new Error("NetContext dataView is not valid");
3001
3069
  }
3002
3070
  for (let i = 0;i < 8; i++) {
3003
- this.dataView.setUint8(20 + i, 0);
3071
+ this.dataView.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET2 + i, 0);
3004
3072
  }
3005
3073
  if (code) {
3006
3074
  for (let i = 0;i < Math.min(code.length, 7); i++) {
3007
- this.dataView.setUint8(20 + i, code.charCodeAt(i));
3075
+ this.dataView.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET2 + i, code.charCodeAt(i));
3008
3076
  }
3009
3077
  }
3010
3078
  }
@@ -3012,13 +3080,13 @@ class NetContext2 {
3012
3080
  if (!this.#hasValidBuffer()) {
3013
3081
  return false;
3014
3082
  }
3015
- return this.dataView.getUint8(28) !== 0;
3083
+ return this.dataView.getUint8(NET_CTX_WANTS_DISCONNECT_OFFSET2) !== 0;
3016
3084
  }
3017
3085
  set wantsDisconnect(value) {
3018
3086
  if (!this.#hasValidBuffer()) {
3019
3087
  throw new Error("NetContext dataView is not valid");
3020
3088
  }
3021
- this.dataView.setUint8(28, value ? 1 : 0);
3089
+ this.dataView.setUint8(NET_CTX_WANTS_DISCONNECT_OFFSET2, value ? 1 : 0);
3022
3090
  }
3023
3091
  get peers() {
3024
3092
  if (!this.#hasValidBuffer()) {
@@ -3028,23 +3096,23 @@ class NetContext2 {
3028
3096
  const localPeerId = this.localPeerId;
3029
3097
  const matchFrame = this.matchFrame;
3030
3098
  let minRemoteSeq = -1;
3031
- for (let i = 0;i < MAX_PEERS2; i++) {
3099
+ for (let i = 0;i < MAX_PLAYERS2; i++) {
3032
3100
  if (i === localPeerId)
3033
3101
  continue;
3034
- const peerOffset = PEERS_ARRAY_OFFSET2 + i * PEER_CTX_SIZE2;
3035
- if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET2) !== 1)
3102
+ const peerOffset = NET_CTX_PEERS_OFFSET2 + i * PEER_CTX_SIZE2;
3103
+ if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET2) !== 1)
3036
3104
  continue;
3037
- if (dv.getUint8(peerOffset + PEER_PACKET_COUNT_OFFSET2) === 0)
3105
+ const seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET2, true);
3106
+ if (seq < 0)
3038
3107
  continue;
3039
- const seq = dv.getUint16(peerOffset + PEER_SEQ_OFFSET2, true);
3040
3108
  if (minRemoteSeq === -1 || seq < minRemoteSeq) {
3041
3109
  minRemoteSeq = seq;
3042
3110
  }
3043
3111
  }
3044
3112
  this.#peersResult.length = 0;
3045
- for (let i = 0;i < MAX_PEERS2; i++) {
3046
- const peerOffset = PEERS_ARRAY_OFFSET2 + i * PEER_CTX_SIZE2;
3047
- if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET2) !== 1)
3113
+ for (let i = 0;i < MAX_PLAYERS2; i++) {
3114
+ const peerOffset = NET_CTX_PEERS_OFFSET2 + i * PEER_CTX_SIZE2;
3115
+ if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET2) !== 1)
3048
3116
  continue;
3049
3117
  const peer = this.#peers[i];
3050
3118
  if (!peer) {
@@ -3056,15 +3124,34 @@ class NetContext2 {
3056
3124
  peer.seq = matchFrame;
3057
3125
  peer.ack = minRemoteSeq;
3058
3126
  } else {
3059
- const packetCount = dv.getUint8(peerOffset + PEER_PACKET_COUNT_OFFSET2);
3060
- const ackCount = dv.getUint8(peerOffset + PEER_ACK_COUNT_OFFSET2);
3061
- peer.seq = packetCount === 0 ? -1 : dv.getUint16(peerOffset + PEER_SEQ_OFFSET2, true);
3062
- peer.ack = ackCount === 0 ? -1 : dv.getUint16(peerOffset + PEER_ACK_OFFSET2, true);
3127
+ peer.seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET2, true);
3128
+ peer.ack = dv.getInt16(peerOffset + PEER_CTX_ACK_OFFSET2, true);
3063
3129
  }
3064
3130
  this.#peersResult.push(peer);
3065
3131
  }
3066
3132
  return this.#peersResult;
3067
3133
  }
3134
+ get lastRollbackDepth() {
3135
+ if (!this.#hasValidBuffer()) {
3136
+ throw new Error("NetContext dataView is not valid");
3137
+ }
3138
+ return this.dataView.getUint32(NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET2, true);
3139
+ }
3140
+ get totalRollbacks() {
3141
+ if (!this.#hasValidBuffer()) {
3142
+ throw new Error("NetContext dataView is not valid");
3143
+ }
3144
+ return this.dataView.getUint32(NET_CTX_TOTAL_ROLLBACKS_OFFSET2, true);
3145
+ }
3146
+ get framesResimulated() {
3147
+ if (!this.#hasValidBuffer()) {
3148
+ throw new Error("NetContext dataView is not valid");
3149
+ }
3150
+ return Number(this.dataView.getBigUint64(NET_CTX_FRAMES_RESIMULATED_OFFSET2, true));
3151
+ }
3152
+ }
3153
+ function mouseButtonCodeToMouseButton2(code) {
3154
+ return MouseButton2[code];
3068
3155
  }
3069
3156
  var TAPE_MAGIC2 = 1413566533;
3070
3157
  function readTapeHeader2(tape) {
@@ -3081,7 +3168,7 @@ function readTapeHeader2(tape) {
3081
3168
  eventCount: view.getUint16(14, true)
3082
3169
  };
3083
3170
  }
3084
- var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.87/wasm/bloop.wasm");
3171
+ var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.89/wasm/bloop.wasm");
3085
3172
  var TIME_CTX_OFFSET2 = 0;
3086
3173
  var INPUT_CTX_OFFSET2 = TIME_CTX_OFFSET2 + 4;
3087
3174
  var EVENTS_OFFSET2 = INPUT_CTX_OFFSET2 + 4;
@@ -6293,6 +6380,138 @@ function joinRoom(brokerUrl, _roomId, cbs) {
6293
6380
  }
6294
6381
  }
6295
6382
 
6383
+ // src/netcode/reconcile.ts
6384
+ var udp = null;
6385
+ var localPeerId = null;
6386
+ var remotePeerId = null;
6387
+ var localStringPeerId = null;
6388
+ var remoteStringPeerId = null;
6389
+ var incomingPackets = [];
6390
+ var actual = {
6391
+ roomCode: ""
6392
+ };
6393
+ async function reconcile(app, signal) {
6394
+ app.beforeFrame.subscribe((_frame) => {
6395
+ if (!app.game.context.net.isInSession) {
6396
+ return;
6397
+ }
6398
+ try {
6399
+ receivePackets(app);
6400
+ sendPacket(app);
6401
+ } catch (e4) {
6402
+ console.error("Error in beforeFrame:", e4);
6403
+ }
6404
+ });
6405
+ logger.onLog = (log) => {
6406
+ addLog(log);
6407
+ };
6408
+ while (!signal.aborted) {
6409
+ const { net } = app.game.context;
6410
+ if (net.wantsRoomCode && actual.roomCode !== net.wantsRoomCode) {
6411
+ console.log("[netcode] wants a room code", {
6412
+ actual: actual.roomCode,
6413
+ wants: net.wantsRoomCode
6414
+ });
6415
+ actual.roomCode = net.wantsRoomCode;
6416
+ joinRollbackRoom(net.wantsRoomCode, app);
6417
+ }
6418
+ await sleep(150);
6419
+ }
6420
+ }
6421
+ async function sleep(ms) {
6422
+ return new Promise((resolve) => setTimeout(resolve, ms));
6423
+ }
6424
+ function joinRollbackRoom(roomId, app) {
6425
+ app.joinRoom(roomId, {
6426
+ onPeerIdAssign: (peerId) => {
6427
+ localStringPeerId = peerId;
6428
+ },
6429
+ onBrokerMessage: (_message) => {},
6430
+ onMessage(_peerId, data, _reliable) {
6431
+ incomingPackets.push(new Uint8Array(data));
6432
+ },
6433
+ onDataChannelClose(_peerId, reliable) {
6434
+ if (!reliable && remotePeerId !== null) {
6435
+ app.sim.emit.network("peer:leave", { peerId: remotePeerId });
6436
+ }
6437
+ },
6438
+ onDataChannelOpen(peerId, reliable, channel) {
6439
+ if (!reliable) {
6440
+ udp = channel;
6441
+ if (localStringPeerId === null) {
6442
+ console.error("[netcode] Local peer ID not assigned yet!");
6443
+ return;
6444
+ }
6445
+ const ids = assignPeerIds(localStringPeerId, peerId);
6446
+ localPeerId = ids.local;
6447
+ setLocalId(localPeerId);
6448
+ remotePeerId = ids.remote;
6449
+ remoteStringPeerId = peerId;
6450
+ setRemoteId(remotePeerId);
6451
+ app.sim.emit.network("peer:join", { peerId: localPeerId });
6452
+ app.sim.emit.network("peer:join", { peerId: remotePeerId });
6453
+ app.sim.emit.network("peer:assign_local_id", { peerId: localPeerId });
6454
+ app.sim.emit.network("session:start", {});
6455
+ }
6456
+ },
6457
+ onPeerConnected(peerId) {
6458
+ addPeer({
6459
+ id: peerId,
6460
+ nickname: peerId.substring(0, 6),
6461
+ ack: -1,
6462
+ seq: -1,
6463
+ lastPacketTime: performance.now()
6464
+ });
6465
+ console.log(`[netcode] Peer connected: ${peerId}. Total peers: ${debugState.netStatus.value.peers.length}`);
6466
+ },
6467
+ onPeerDisconnected(peerId) {
6468
+ removePeer(peerId);
6469
+ if (remotePeerId !== null && peerId === remoteStringPeerId) {
6470
+ app.sim.emit.network("peer:leave", { peerId: remotePeerId });
6471
+ app.sim.emit.network("session:end", {});
6472
+ }
6473
+ }
6474
+ });
6475
+ }
6476
+ function assignPeerIds(localId, remoteId) {
6477
+ if (localId < remoteId) {
6478
+ return { local: 0, remote: 1 };
6479
+ } else {
6480
+ return { local: 1, remote: 0 };
6481
+ }
6482
+ }
6483
+ function receivePackets(app) {
6484
+ for (const packetData of incomingPackets) {
6485
+ app.sim.emit.packet(packetData);
6486
+ if (remotePeerId == null) {
6487
+ return;
6488
+ }
6489
+ const peerState = unwrap(app.sim.net.peers[remotePeerId], `Remote peer state not found for peerId ${remotePeerId}`);
6490
+ updatePeer(remoteStringPeerId, {
6491
+ ack: peerState.ack,
6492
+ seq: peerState.seq,
6493
+ lastPacketTime: performance.now()
6494
+ });
6495
+ }
6496
+ incomingPackets.length = 0;
6497
+ }
6498
+ function sendPacket(app) {
6499
+ if (!udp || remotePeerId === null) {
6500
+ console.warn("[netcode] Cannot send packet, udp or remotePeerId is null");
6501
+ return;
6502
+ }
6503
+ if (udp.readyState !== "open") {
6504
+ console.warn("[netcode] Data channel not open, cannot send packet. readyState=", udp.readyState);
6505
+ return;
6506
+ }
6507
+ const packet = app.sim.getOutboundPacket(remotePeerId);
6508
+ if (!packet) {
6509
+ console.warn("[netcode] No packet to send");
6510
+ return;
6511
+ }
6512
+ udp.send(packet);
6513
+ }
6514
+
6296
6515
  // src/App.ts
6297
6516
  var DEFAULT_BROKER_URL = "wss://webrtc-divine-glade-8064.fly.dev/ws";
6298
6517
  async function start(opts) {
@@ -6316,6 +6535,7 @@ class App {
6316
6535
  #unsubscribe = null;
6317
6536
  #now = performance.now();
6318
6537
  #debugUi = null;
6538
+ #abortController = new AbortController;
6319
6539
  constructor(sim, game, brokerUrl, debugUiOpts) {
6320
6540
  this.#sim = sim;
6321
6541
  this.game = game;
@@ -6329,6 +6549,9 @@ class App {
6329
6549
  this.beforeFrame.notify(frame);
6330
6550
  };
6331
6551
  this.subscribe();
6552
+ reconcile(this, this.#abortController.signal).catch((err) => {
6553
+ console.error("Error in lemmyloop:", err);
6554
+ });
6332
6555
  }
6333
6556
  get sim() {
6334
6557
  return this.#sim;
@@ -6534,6 +6757,7 @@ class App {
6534
6757
  this.afterFrame.unsubscribeAll();
6535
6758
  this.onHmr.unsubscribeAll();
6536
6759
  this.#debugUi?.unmount();
6760
+ this.#abortController.abort();
6537
6761
  }
6538
6762
  async acceptHmr(module, opts) {
6539
6763
  const game = module.game ?? module;
@@ -6578,131 +6802,13 @@ var PacketType;
6578
6802
  PacketType2[PacketType2["None"] = 0] = "None";
6579
6803
  PacketType2[PacketType2["Inputs"] = 1] = "Inputs";
6580
6804
  })(PacketType ||= {});
6581
- // src/netcode/scaffold.ts
6582
- function joinRollbackRoom(roomId, app, opts) {
6583
- let udp = null;
6584
- let sessionActive = false;
6585
- let localPeerId = null;
6586
- let remotePeerId = null;
6587
- let localStringPeerId = null;
6588
- let remoteStringPeerId = null;
6589
- const incomingPackets = [];
6590
- function assignPeerIds(localId, remoteId) {
6591
- if (localId < remoteId) {
6592
- return { local: 0, remote: 1 };
6593
- } else {
6594
- return { local: 1, remote: 0 };
6595
- }
6596
- }
6597
- function receivePackets() {
6598
- for (const packetData of incomingPackets) {
6599
- app.sim.emit.packet(packetData);
6600
- if (remotePeerId == null) {
6601
- return;
6602
- }
6603
- const peerState = unwrap(app.sim.net.peers[remotePeerId], `Remote peer state not found for peerId ${remotePeerId}`);
6604
- updatePeer(remoteStringPeerId, {
6605
- ack: peerState.ack,
6606
- seq: peerState.seq,
6607
- lastPacketTime: performance.now()
6608
- });
6609
- }
6610
- incomingPackets.length = 0;
6611
- }
6612
- function sendPacket() {
6613
- if (!udp || remotePeerId === null) {
6614
- console.warn("[netcode] Cannot send packet, udp or remotePeerId is null");
6615
- return;
6616
- }
6617
- if (udp.readyState !== "open") {
6618
- console.warn("[netcode] Data channel not open, cannot send packet. readyState=", udp.readyState);
6619
- return;
6620
- }
6621
- const packet = app.sim.getOutboundPacket(remotePeerId);
6622
- if (!packet) {
6623
- console.warn("[netcode] No packet to send");
6624
- return;
6625
- }
6626
- udp.send(packet);
6627
- }
6628
- logger.onLog = (log) => {
6629
- addLog(log);
6630
- };
6631
- app.joinRoom(roomId, {
6632
- onPeerIdAssign: (peerId) => {
6633
- localStringPeerId = peerId;
6634
- },
6635
- onBrokerMessage: (_message) => {},
6636
- onMessage(_peerId, data, _reliable) {
6637
- incomingPackets.push(new Uint8Array(data));
6638
- },
6639
- onDataChannelClose(peerId, reliable) {
6640
- if (!reliable && remotePeerId !== null) {
6641
- app.sim.emit.network("peer:leave", { peerId: remotePeerId });
6642
- sessionActive = false;
6643
- opts?.onSessionEnd?.();
6644
- }
6645
- },
6646
- onDataChannelOpen(peerId, reliable, channel) {
6647
- if (!reliable) {
6648
- udp = channel;
6649
- if (localStringPeerId === null) {
6650
- console.error("[netcode] Local peer ID not assigned yet!");
6651
- return;
6652
- }
6653
- const ids = assignPeerIds(localStringPeerId, peerId);
6654
- localPeerId = ids.local;
6655
- setLocalId(localPeerId);
6656
- remotePeerId = ids.remote;
6657
- remoteStringPeerId = peerId;
6658
- setRemoteId(remotePeerId);
6659
- app.sim.emit.network("peer:join", { peerId: localPeerId });
6660
- app.sim.emit.network("peer:join", { peerId: remotePeerId });
6661
- app.sim.emit.network("peer:assign_local_id", { peerId: localPeerId });
6662
- app.sim.emit.network("session:start", {});
6663
- sessionActive = true;
6664
- opts?.onSessionStart?.();
6665
- }
6666
- },
6667
- onPeerConnected(peerId) {
6668
- addPeer({
6669
- id: peerId,
6670
- nickname: peerId.substring(0, 6),
6671
- ack: -1,
6672
- seq: -1,
6673
- lastPacketTime: performance.now()
6674
- });
6675
- console.log(`[netcode] Peer connected: ${peerId}. Total peers: ${debugState.netStatus.value.peers.length}`);
6676
- },
6677
- onPeerDisconnected(peerId) {
6678
- removePeer(peerId);
6679
- if (remotePeerId !== null && peerId === remoteStringPeerId) {
6680
- app.sim.emit.network("peer:leave", { peerId: remotePeerId });
6681
- sessionActive = false;
6682
- opts?.onSessionEnd?.();
6683
- }
6684
- }
6685
- });
6686
- app.beforeFrame.subscribe((_frame) => {
6687
- if (!sessionActive || !udp || remotePeerId === null) {
6688
- return;
6689
- }
6690
- try {
6691
- receivePackets();
6692
- sendPacket();
6693
- } catch (e4) {
6694
- console.error("Error in beforeFrame:", e4);
6695
- }
6696
- });
6697
- }
6698
6805
  export {
6699
6806
  start,
6700
6807
  logger,
6701
- joinRollbackRoom,
6702
6808
  PacketType,
6703
6809
  exports_mod as Debug,
6704
6810
  App
6705
6811
  };
6706
6812
 
6707
- //# debugId=B9819EC38DD29BBA64756E2164756E21
6813
+ //# debugId=164BA35FD7424E1864756E2164756E21
6708
6814
  //# sourceMappingURL=mod.js.map