@bloopjs/web 0.0.80 → 0.0.81
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 +489 -96
- package/dist/mod.js.map +5 -5
- package/dist/netcode/scaffold.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/netcode/scaffold.ts +13 -10
package/dist/mod.js
CHANGED
|
@@ -113,6 +113,7 @@ var __export22 = (target, all) => {
|
|
|
113
113
|
};
|
|
114
114
|
var exports_enums = {};
|
|
115
115
|
__export22(exports_enums, {
|
|
116
|
+
NetJoinFailReason: () => NetJoinFailReason,
|
|
116
117
|
MouseButton: () => MouseButton,
|
|
117
118
|
Key: () => Key,
|
|
118
119
|
InputSource: () => InputSource,
|
|
@@ -128,11 +129,13 @@ var EventType;
|
|
|
128
129
|
EventType2[EventType2["MouseUp"] = 5] = "MouseUp";
|
|
129
130
|
EventType2[EventType2["MouseWheel"] = 6] = "MouseWheel";
|
|
130
131
|
EventType2[EventType2["FrameStart"] = 7] = "FrameStart";
|
|
131
|
-
EventType2[EventType2["
|
|
132
|
-
EventType2[EventType2["
|
|
133
|
-
EventType2[EventType2["
|
|
134
|
-
EventType2[EventType2["
|
|
135
|
-
EventType2[EventType2["
|
|
132
|
+
EventType2[EventType2["NetJoinOk"] = 8] = "NetJoinOk";
|
|
133
|
+
EventType2[EventType2["NetJoinFail"] = 9] = "NetJoinFail";
|
|
134
|
+
EventType2[EventType2["NetPeerJoin"] = 10] = "NetPeerJoin";
|
|
135
|
+
EventType2[EventType2["NetPeerLeave"] = 11] = "NetPeerLeave";
|
|
136
|
+
EventType2[EventType2["NetPeerAssignLocalId"] = 12] = "NetPeerAssignLocalId";
|
|
137
|
+
EventType2[EventType2["NetPacketReceived"] = 13] = "NetPacketReceived";
|
|
138
|
+
EventType2[EventType2["NetSessionInit"] = 14] = "NetSessionInit";
|
|
136
139
|
})(EventType ||= {});
|
|
137
140
|
var MouseButton;
|
|
138
141
|
((MouseButton2) => {
|
|
@@ -376,6 +379,14 @@ var InputSource;
|
|
|
376
379
|
InputSource2[InputSource2["RemotePeer11"] = 139] = "RemotePeer11";
|
|
377
380
|
InputSource2[InputSource2["Unmapped"] = 255] = "Unmapped";
|
|
378
381
|
})(InputSource ||= {});
|
|
382
|
+
var NetJoinFailReason;
|
|
383
|
+
((NetJoinFailReason2) => {
|
|
384
|
+
NetJoinFailReason2[NetJoinFailReason2["unknown"] = 0] = "unknown";
|
|
385
|
+
NetJoinFailReason2[NetJoinFailReason2["timeout"] = 1] = "timeout";
|
|
386
|
+
NetJoinFailReason2[NetJoinFailReason2["room_full"] = 2] = "room_full";
|
|
387
|
+
NetJoinFailReason2[NetJoinFailReason2["room_not_found"] = 3] = "room_not_found";
|
|
388
|
+
NetJoinFailReason2[NetJoinFailReason2["already_in_room"] = 4] = "already_in_room";
|
|
389
|
+
})(NetJoinFailReason ||= {});
|
|
379
390
|
var MAX_PLAYERS = 12;
|
|
380
391
|
var KEYBOARD_OFFSET = 0;
|
|
381
392
|
var KEYBOARD_SIZE = 256;
|
|
@@ -1129,30 +1140,171 @@ class KeyboardContext {
|
|
|
1129
1140
|
return state;
|
|
1130
1141
|
}
|
|
1131
1142
|
}
|
|
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
|
+
var STATUS_MAP = {
|
|
1152
|
+
0: "offline",
|
|
1153
|
+
1: "local",
|
|
1154
|
+
2: "join:pending",
|
|
1155
|
+
3: "connected",
|
|
1156
|
+
4: "disconnected"
|
|
1157
|
+
};
|
|
1132
1158
|
|
|
1133
1159
|
class NetContext {
|
|
1134
1160
|
dataView;
|
|
1161
|
+
#peers = Array.from({ length: MAX_PEERS }, () => ({
|
|
1162
|
+
isLocal: false,
|
|
1163
|
+
seq: -1,
|
|
1164
|
+
ack: -1
|
|
1165
|
+
}));
|
|
1166
|
+
#peersResult = [];
|
|
1135
1167
|
constructor(dataView) {
|
|
1136
1168
|
this.dataView = dataView;
|
|
1137
1169
|
}
|
|
1170
|
+
#hasValidBuffer() {
|
|
1171
|
+
if (!this.dataView)
|
|
1172
|
+
return false;
|
|
1173
|
+
return this.dataView.buffer.byteLength > 0;
|
|
1174
|
+
}
|
|
1138
1175
|
get peerCount() {
|
|
1139
|
-
if (!this
|
|
1140
|
-
throw new Error("NetContext
|
|
1176
|
+
if (!this.#hasValidBuffer()) {
|
|
1177
|
+
throw new Error("NetContext dataView is not valid");
|
|
1141
1178
|
}
|
|
1142
1179
|
return this.dataView.getUint8(0);
|
|
1143
1180
|
}
|
|
1181
|
+
get localPeerId() {
|
|
1182
|
+
if (!this.#hasValidBuffer()) {
|
|
1183
|
+
throw new Error("NetContext dataView is not valid");
|
|
1184
|
+
}
|
|
1185
|
+
return this.dataView.getUint8(1);
|
|
1186
|
+
}
|
|
1144
1187
|
get isInSession() {
|
|
1145
|
-
if (!this
|
|
1146
|
-
throw new Error("NetContext
|
|
1188
|
+
if (!this.#hasValidBuffer()) {
|
|
1189
|
+
throw new Error("NetContext dataView is not valid");
|
|
1147
1190
|
}
|
|
1148
1191
|
return this.dataView.getUint8(2) !== 0;
|
|
1149
1192
|
}
|
|
1193
|
+
get status() {
|
|
1194
|
+
if (!this.#hasValidBuffer()) {
|
|
1195
|
+
throw new Error("NetContext dataView is not valid");
|
|
1196
|
+
}
|
|
1197
|
+
const statusByte = this.dataView.getUint8(3);
|
|
1198
|
+
return STATUS_MAP[statusByte] ?? "local";
|
|
1199
|
+
}
|
|
1150
1200
|
get matchFrame() {
|
|
1151
|
-
if (!this
|
|
1152
|
-
throw new Error("NetContext
|
|
1201
|
+
if (!this.#hasValidBuffer()) {
|
|
1202
|
+
throw new Error("NetContext dataView is not valid");
|
|
1153
1203
|
}
|
|
1154
1204
|
return this.dataView.getUint32(4, true);
|
|
1155
1205
|
}
|
|
1206
|
+
get sessionStartFrame() {
|
|
1207
|
+
if (!this.#hasValidBuffer()) {
|
|
1208
|
+
throw new Error("NetContext dataView is not valid");
|
|
1209
|
+
}
|
|
1210
|
+
return this.dataView.getUint32(8, true);
|
|
1211
|
+
}
|
|
1212
|
+
get roomCode() {
|
|
1213
|
+
if (!this.#hasValidBuffer()) {
|
|
1214
|
+
throw new Error("NetContext dataView is not valid");
|
|
1215
|
+
}
|
|
1216
|
+
const bytes = [];
|
|
1217
|
+
for (let i = 0;i < 8; i++) {
|
|
1218
|
+
const byte = this.dataView.getUint8(12 + i);
|
|
1219
|
+
if (byte === 0)
|
|
1220
|
+
break;
|
|
1221
|
+
bytes.push(byte);
|
|
1222
|
+
}
|
|
1223
|
+
return String.fromCharCode(...bytes);
|
|
1224
|
+
}
|
|
1225
|
+
get wantsRoomCode() {
|
|
1226
|
+
if (!this.#hasValidBuffer()) {
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
const bytes = [];
|
|
1230
|
+
for (let i = 0;i < 8; i++) {
|
|
1231
|
+
const byte = this.dataView.getUint8(20 + i);
|
|
1232
|
+
if (byte === 0)
|
|
1233
|
+
break;
|
|
1234
|
+
bytes.push(byte);
|
|
1235
|
+
}
|
|
1236
|
+
return bytes.length > 0 ? String.fromCharCode(...bytes) : undefined;
|
|
1237
|
+
}
|
|
1238
|
+
set wantsRoomCode(code) {
|
|
1239
|
+
if (!this.#hasValidBuffer()) {
|
|
1240
|
+
throw new Error("NetContext dataView is not valid");
|
|
1241
|
+
}
|
|
1242
|
+
for (let i = 0;i < 8; i++) {
|
|
1243
|
+
this.dataView.setUint8(20 + i, 0);
|
|
1244
|
+
}
|
|
1245
|
+
if (code) {
|
|
1246
|
+
for (let i = 0;i < Math.min(code.length, 7); i++) {
|
|
1247
|
+
this.dataView.setUint8(20 + i, code.charCodeAt(i));
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
get wantsDisconnect() {
|
|
1252
|
+
if (!this.#hasValidBuffer()) {
|
|
1253
|
+
return false;
|
|
1254
|
+
}
|
|
1255
|
+
return this.dataView.getUint8(28) !== 0;
|
|
1256
|
+
}
|
|
1257
|
+
set wantsDisconnect(value) {
|
|
1258
|
+
if (!this.#hasValidBuffer()) {
|
|
1259
|
+
throw new Error("NetContext dataView is not valid");
|
|
1260
|
+
}
|
|
1261
|
+
this.dataView.setUint8(28, value ? 1 : 0);
|
|
1262
|
+
}
|
|
1263
|
+
get peers() {
|
|
1264
|
+
if (!this.#hasValidBuffer()) {
|
|
1265
|
+
throw new Error("NetContext dataView is not valid");
|
|
1266
|
+
}
|
|
1267
|
+
const dv = this.dataView;
|
|
1268
|
+
const localPeerId = this.localPeerId;
|
|
1269
|
+
const matchFrame = this.matchFrame;
|
|
1270
|
+
let minRemoteSeq = -1;
|
|
1271
|
+
for (let i = 0;i < MAX_PEERS; i++) {
|
|
1272
|
+
if (i === localPeerId)
|
|
1273
|
+
continue;
|
|
1274
|
+
const peerOffset = PEERS_ARRAY_OFFSET + i * PEER_CTX_SIZE;
|
|
1275
|
+
if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET) !== 1)
|
|
1276
|
+
continue;
|
|
1277
|
+
if (dv.getUint8(peerOffset + PEER_PACKET_COUNT_OFFSET) === 0)
|
|
1278
|
+
continue;
|
|
1279
|
+
const seq = dv.getUint16(peerOffset + PEER_SEQ_OFFSET, true);
|
|
1280
|
+
if (minRemoteSeq === -1 || seq < minRemoteSeq) {
|
|
1281
|
+
minRemoteSeq = seq;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
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)
|
|
1288
|
+
continue;
|
|
1289
|
+
const peer = this.#peers[i];
|
|
1290
|
+
if (!peer) {
|
|
1291
|
+
throw new Error(`Unexpected missing peer object at index ${i}`);
|
|
1292
|
+
}
|
|
1293
|
+
const isLocal = i === localPeerId;
|
|
1294
|
+
peer.isLocal = isLocal;
|
|
1295
|
+
if (isLocal) {
|
|
1296
|
+
peer.seq = matchFrame;
|
|
1297
|
+
peer.ack = minRemoteSeq;
|
|
1298
|
+
} 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);
|
|
1303
|
+
}
|
|
1304
|
+
this.#peersResult.push(peer);
|
|
1305
|
+
}
|
|
1306
|
+
return this.#peersResult;
|
|
1307
|
+
}
|
|
1156
1308
|
}
|
|
1157
1309
|
|
|
1158
1310
|
class TimeContext {
|
|
@@ -1194,7 +1346,7 @@ function readTapeHeader(tape) {
|
|
|
1194
1346
|
eventCount: view.getUint16(14, true)
|
|
1195
1347
|
};
|
|
1196
1348
|
}
|
|
1197
|
-
var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.
|
|
1349
|
+
var DEFAULT_WASM_URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.81/wasm/bloop.wasm");
|
|
1198
1350
|
var MAX_ROLLBACK_FRAMES = 500;
|
|
1199
1351
|
var TIME_CTX_OFFSET = 0;
|
|
1200
1352
|
var INPUT_CTX_OFFSET = TIME_CTX_OFFSET + 4;
|
|
@@ -1203,63 +1355,6 @@ var NET_CTX_OFFSET = EVENTS_OFFSET + 4;
|
|
|
1203
1355
|
var SNAPSHOT_HEADER_LEN = 16;
|
|
1204
1356
|
var SNAPSHOT_HEADER_USER_LEN_OFFSET = 4;
|
|
1205
1357
|
var SNAPSHOT_HEADER_ENGINE_LEN_OFFSET = 8;
|
|
1206
|
-
|
|
1207
|
-
class Net {
|
|
1208
|
-
#wasm;
|
|
1209
|
-
#memory;
|
|
1210
|
-
constructor(wasm, memory) {
|
|
1211
|
-
this.#wasm = wasm;
|
|
1212
|
-
this.#memory = memory;
|
|
1213
|
-
}
|
|
1214
|
-
setLocalPeer(peerId) {
|
|
1215
|
-
this.#wasm.session_set_local_peer(peerId);
|
|
1216
|
-
}
|
|
1217
|
-
connectPeer(peerId) {
|
|
1218
|
-
this.#wasm.session_peer_connect(peerId);
|
|
1219
|
-
}
|
|
1220
|
-
disconnectPeer(peerId) {
|
|
1221
|
-
this.#wasm.session_peer_disconnect(peerId);
|
|
1222
|
-
}
|
|
1223
|
-
getOutboundPacket(targetPeer) {
|
|
1224
|
-
this.#wasm.build_outbound_packet(targetPeer);
|
|
1225
|
-
const len = this.#wasm.get_outbound_packet_len();
|
|
1226
|
-
if (len === 0) {
|
|
1227
|
-
return null;
|
|
1228
|
-
}
|
|
1229
|
-
const ptr = this.#wasm.get_outbound_packet();
|
|
1230
|
-
assert(ptr > 0, `Invalid outbound packet pointer: ${ptr}`);
|
|
1231
|
-
const memoryView = new Uint8Array(this.#memory.buffer, ptr, len);
|
|
1232
|
-
const copy = new Uint8Array(len);
|
|
1233
|
-
copy.set(memoryView);
|
|
1234
|
-
return copy;
|
|
1235
|
-
}
|
|
1236
|
-
receivePacket(data) {
|
|
1237
|
-
if (data.length === 0) {
|
|
1238
|
-
return;
|
|
1239
|
-
}
|
|
1240
|
-
const ptr = this.#wasm.alloc(data.byteLength);
|
|
1241
|
-
assert(ptr > 0, `Failed to allocate ${data.byteLength} bytes for received packet`);
|
|
1242
|
-
const memoryView = new Uint8Array(this.#memory.buffer, ptr, data.byteLength);
|
|
1243
|
-
memoryView.set(data);
|
|
1244
|
-
const result = this.#wasm.receive_packet(ptr, data.byteLength);
|
|
1245
|
-
this.#wasm.free(ptr, data.byteLength);
|
|
1246
|
-
if (result !== 0) {
|
|
1247
|
-
const errorMessages = {
|
|
1248
|
-
1: "No active session",
|
|
1249
|
-
2: "Buffer too small",
|
|
1250
|
-
3: "Unsupported packet version",
|
|
1251
|
-
4: "Invalid event count"
|
|
1252
|
-
};
|
|
1253
|
-
throw new Error(errorMessages[result] ?? `Unknown packet error: ${result}`);
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
getPeerState(peer) {
|
|
1257
|
-
return {
|
|
1258
|
-
seq: this.#wasm.get_peer_seq(peer),
|
|
1259
|
-
ack: this.#wasm.get_peer_ack(peer)
|
|
1260
|
-
};
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
1358
|
var originalConsole = globalThis.console;
|
|
1264
1359
|
var noop = () => {};
|
|
1265
1360
|
var stubConsole = Object.fromEntries(Object.keys(originalConsole).map((key) => [key, noop]));
|
|
@@ -1277,7 +1372,7 @@ class Sim {
|
|
|
1277
1372
|
#time;
|
|
1278
1373
|
#serialize;
|
|
1279
1374
|
#isPaused = false;
|
|
1280
|
-
net;
|
|
1375
|
+
#net;
|
|
1281
1376
|
onTapeFull;
|
|
1282
1377
|
constructor(wasm, memory, opts) {
|
|
1283
1378
|
this.wasm = wasm;
|
|
@@ -1285,7 +1380,7 @@ class Sim {
|
|
|
1285
1380
|
this.#time = new TimeContext(new DataView(this.#memory.buffer, this.wasm.get_time_ctx()));
|
|
1286
1381
|
this.id = `${Math.floor(Math.random() * 1e6)}`;
|
|
1287
1382
|
this.#serialize = opts?.serialize;
|
|
1288
|
-
this
|
|
1383
|
+
this.#net = new NetContext;
|
|
1289
1384
|
}
|
|
1290
1385
|
step(ms) {
|
|
1291
1386
|
if (this.#isPaused) {
|
|
@@ -1366,7 +1461,9 @@ class Sim {
|
|
|
1366
1461
|
assert(tapePtr > 0, `failed to allocate ${tape.byteLength} bytes for tape load, pointer=${tapePtr}`);
|
|
1367
1462
|
const memoryView = new Uint8Array(this.#memory.buffer, tapePtr, tape.byteLength);
|
|
1368
1463
|
memoryView.set(tape);
|
|
1369
|
-
this.
|
|
1464
|
+
if (this.isRecording) {
|
|
1465
|
+
this.wasm.stop_recording();
|
|
1466
|
+
}
|
|
1370
1467
|
const result = this.wasm.load_tape(tapePtr, tape.byteLength);
|
|
1371
1468
|
assert(result === 0, `failed to load tape, error code=${result}`);
|
|
1372
1469
|
this.wasm.free(tapePtr, tape.byteLength);
|
|
@@ -1388,6 +1485,12 @@ class Sim {
|
|
|
1388
1485
|
}
|
|
1389
1486
|
return this.#time;
|
|
1390
1487
|
}
|
|
1488
|
+
get net() {
|
|
1489
|
+
if (!this.#net.dataView || this.#net.dataView.buffer !== this.#memory.buffer) {
|
|
1490
|
+
this.#net.dataView = new DataView(this.#memory.buffer, this.wasm.get_net_ctx());
|
|
1491
|
+
}
|
|
1492
|
+
return this.#net;
|
|
1493
|
+
}
|
|
1391
1494
|
get buffer() {
|
|
1392
1495
|
return this.#memory.buffer;
|
|
1393
1496
|
}
|
|
@@ -1418,16 +1521,77 @@ class Sim {
|
|
|
1418
1521
|
},
|
|
1419
1522
|
mousewheel: (x, y, peerId = 0) => {
|
|
1420
1523
|
this.wasm.emit_mousewheel(x, y, peerId);
|
|
1524
|
+
},
|
|
1525
|
+
network: (type, data) => {
|
|
1526
|
+
switch (type) {
|
|
1527
|
+
case "join:ok": {
|
|
1528
|
+
const roomCode = data.roomCode;
|
|
1529
|
+
const encoded = new TextEncoder().encode(roomCode);
|
|
1530
|
+
const ptr = this.wasm.alloc(encoded.length);
|
|
1531
|
+
new Uint8Array(this.#memory.buffer, ptr, encoded.length).set(encoded);
|
|
1532
|
+
this.wasm.emit_net_join_ok(ptr, encoded.length);
|
|
1533
|
+
this.wasm.free(ptr, encoded.length);
|
|
1534
|
+
break;
|
|
1535
|
+
}
|
|
1536
|
+
case "join:fail":
|
|
1537
|
+
this.wasm.emit_net_join_fail(0);
|
|
1538
|
+
break;
|
|
1539
|
+
case "peer:join": {
|
|
1540
|
+
const peerId = data.peerId;
|
|
1541
|
+
this.wasm.emit_net_peer_join(peerId);
|
|
1542
|
+
break;
|
|
1543
|
+
}
|
|
1544
|
+
case "peer:leave": {
|
|
1545
|
+
const peerId = data.peerId;
|
|
1546
|
+
this.wasm.emit_net_peer_leave(peerId);
|
|
1547
|
+
break;
|
|
1548
|
+
}
|
|
1549
|
+
case "peer:assign_local_id": {
|
|
1550
|
+
const peerId = data.peerId;
|
|
1551
|
+
this.wasm.emit_net_peer_assign_local_id(peerId);
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
case "session:start":
|
|
1555
|
+
this.wasm.emit_net_session_init();
|
|
1556
|
+
break;
|
|
1557
|
+
case "session:end":
|
|
1558
|
+
this.wasm.emit_net_session_end();
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
},
|
|
1562
|
+
packet: (data) => {
|
|
1563
|
+
if (data.length === 0) {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
const ptr = this.wasm.alloc(data.byteLength);
|
|
1567
|
+
assert(ptr > 0, `Failed to allocate ${data.byteLength} bytes for received packet`);
|
|
1568
|
+
const memoryView = new Uint8Array(this.#memory.buffer, ptr, data.byteLength);
|
|
1569
|
+
memoryView.set(data);
|
|
1570
|
+
const result = this.wasm.emit_receive_packet(ptr, data.byteLength);
|
|
1571
|
+
this.wasm.free(ptr, data.byteLength);
|
|
1572
|
+
if (result !== 0) {
|
|
1573
|
+
const errorMessages = {
|
|
1574
|
+
1: "No active session",
|
|
1575
|
+
2: "Buffer too small",
|
|
1576
|
+
3: "Unsupported packet version",
|
|
1577
|
+
4: "Invalid event count"
|
|
1578
|
+
};
|
|
1579
|
+
throw new Error(errorMessages[result] ?? `Unknown packet error: ${result}`);
|
|
1580
|
+
}
|
|
1421
1581
|
}
|
|
1422
1582
|
};
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
const
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1583
|
+
getOutboundPacket(targetPeer) {
|
|
1584
|
+
this.wasm.build_outbound_packet(targetPeer);
|
|
1585
|
+
const len = this.wasm.get_outbound_packet_len();
|
|
1586
|
+
if (len === 0) {
|
|
1587
|
+
throw new Error(`No outbound packet available for peer ${targetPeer}`);
|
|
1588
|
+
}
|
|
1589
|
+
const ptr = this.wasm.get_outbound_packet();
|
|
1590
|
+
assert(ptr > 0, `Invalid outbound packet pointer: ${ptr}`);
|
|
1591
|
+
const memoryView = new Uint8Array(this.#memory.buffer, ptr, len);
|
|
1592
|
+
const copy = new Uint8Array(len);
|
|
1593
|
+
copy.set(memoryView);
|
|
1594
|
+
return copy;
|
|
1431
1595
|
}
|
|
1432
1596
|
}
|
|
1433
1597
|
async function mount(mountable, options) {
|
|
@@ -1646,8 +1810,59 @@ class Bloop {
|
|
|
1646
1810
|
y: payloadVec2.y
|
|
1647
1811
|
}));
|
|
1648
1812
|
break;
|
|
1813
|
+
case exports_enums.EventType.NetJoinOk: {
|
|
1814
|
+
const roomCodeBytes = [];
|
|
1815
|
+
for (let j = 0;j < 8; j++) {
|
|
1816
|
+
const byte = eventsDataView.getUint8(payloadOffset + j);
|
|
1817
|
+
if (byte === 0)
|
|
1818
|
+
break;
|
|
1819
|
+
roomCodeBytes.push(byte);
|
|
1820
|
+
}
|
|
1821
|
+
const roomCode = String.fromCharCode(...roomCodeBytes);
|
|
1822
|
+
this.#context.event = {
|
|
1823
|
+
type: "join:ok",
|
|
1824
|
+
data: { roomCode }
|
|
1825
|
+
};
|
|
1826
|
+
system.netcode?.(this.#context);
|
|
1827
|
+
break;
|
|
1828
|
+
}
|
|
1829
|
+
case exports_enums.EventType.NetJoinFail: {
|
|
1830
|
+
const reasonCode = eventsDataView.getUint8(payloadOffset);
|
|
1831
|
+
const reasons = [
|
|
1832
|
+
"unknown",
|
|
1833
|
+
"timeout",
|
|
1834
|
+
"room_full",
|
|
1835
|
+
"room_not_found",
|
|
1836
|
+
"already_in_room"
|
|
1837
|
+
];
|
|
1838
|
+
const reason = reasons[reasonCode] ?? "unknown";
|
|
1839
|
+
this.#context.event = {
|
|
1840
|
+
type: "join:fail",
|
|
1841
|
+
data: { reason }
|
|
1842
|
+
};
|
|
1843
|
+
system.netcode?.(this.#context);
|
|
1844
|
+
break;
|
|
1845
|
+
}
|
|
1846
|
+
case exports_enums.EventType.NetPeerJoin: {
|
|
1847
|
+
const peerId = eventsDataView.getUint8(payloadOffset);
|
|
1848
|
+
this.#context.event = {
|
|
1849
|
+
type: "peer:join",
|
|
1850
|
+
data: { peerId }
|
|
1851
|
+
};
|
|
1852
|
+
system.netcode?.(this.#context);
|
|
1853
|
+
break;
|
|
1854
|
+
}
|
|
1855
|
+
case exports_enums.EventType.NetPeerLeave: {
|
|
1856
|
+
const peerId = eventsDataView.getUint8(payloadOffset);
|
|
1857
|
+
this.#context.event = {
|
|
1858
|
+
type: "peer:leave",
|
|
1859
|
+
data: { peerId }
|
|
1860
|
+
};
|
|
1861
|
+
system.netcode?.(this.#context);
|
|
1862
|
+
break;
|
|
1863
|
+
}
|
|
1649
1864
|
default:
|
|
1650
|
-
|
|
1865
|
+
break;
|
|
1651
1866
|
}
|
|
1652
1867
|
offset += 12;
|
|
1653
1868
|
}
|
|
@@ -1674,6 +1889,7 @@ var __export3 = (target, all) => {
|
|
|
1674
1889
|
};
|
|
1675
1890
|
var exports_enums2 = {};
|
|
1676
1891
|
__export3(exports_enums2, {
|
|
1892
|
+
NetJoinFailReason: () => NetJoinFailReason2,
|
|
1677
1893
|
MouseButton: () => MouseButton2,
|
|
1678
1894
|
Key: () => Key2,
|
|
1679
1895
|
InputSource: () => InputSource2,
|
|
@@ -1689,11 +1905,13 @@ var EventType2;
|
|
|
1689
1905
|
EventType22[EventType22["MouseUp"] = 5] = "MouseUp";
|
|
1690
1906
|
EventType22[EventType22["MouseWheel"] = 6] = "MouseWheel";
|
|
1691
1907
|
EventType22[EventType22["FrameStart"] = 7] = "FrameStart";
|
|
1692
|
-
EventType22[EventType22["
|
|
1693
|
-
EventType22[EventType22["
|
|
1694
|
-
EventType22[EventType22["
|
|
1695
|
-
EventType22[EventType22["
|
|
1696
|
-
EventType22[EventType22["
|
|
1908
|
+
EventType22[EventType22["NetJoinOk"] = 8] = "NetJoinOk";
|
|
1909
|
+
EventType22[EventType22["NetJoinFail"] = 9] = "NetJoinFail";
|
|
1910
|
+
EventType22[EventType22["NetPeerJoin"] = 10] = "NetPeerJoin";
|
|
1911
|
+
EventType22[EventType22["NetPeerLeave"] = 11] = "NetPeerLeave";
|
|
1912
|
+
EventType22[EventType22["NetPeerAssignLocalId"] = 12] = "NetPeerAssignLocalId";
|
|
1913
|
+
EventType22[EventType22["NetPacketReceived"] = 13] = "NetPacketReceived";
|
|
1914
|
+
EventType22[EventType22["NetSessionInit"] = 14] = "NetSessionInit";
|
|
1697
1915
|
})(EventType2 ||= {});
|
|
1698
1916
|
var MouseButton2;
|
|
1699
1917
|
((MouseButton22) => {
|
|
@@ -1937,6 +2155,14 @@ var InputSource2;
|
|
|
1937
2155
|
InputSource22[InputSource22["RemotePeer11"] = 139] = "RemotePeer11";
|
|
1938
2156
|
InputSource22[InputSource22["Unmapped"] = 255] = "Unmapped";
|
|
1939
2157
|
})(InputSource2 ||= {});
|
|
2158
|
+
var NetJoinFailReason2;
|
|
2159
|
+
((NetJoinFailReason22) => {
|
|
2160
|
+
NetJoinFailReason22[NetJoinFailReason22["unknown"] = 0] = "unknown";
|
|
2161
|
+
NetJoinFailReason22[NetJoinFailReason22["timeout"] = 1] = "timeout";
|
|
2162
|
+
NetJoinFailReason22[NetJoinFailReason22["room_full"] = 2] = "room_full";
|
|
2163
|
+
NetJoinFailReason22[NetJoinFailReason22["room_not_found"] = 3] = "room_not_found";
|
|
2164
|
+
NetJoinFailReason22[NetJoinFailReason22["already_in_room"] = 4] = "already_in_room";
|
|
2165
|
+
})(NetJoinFailReason2 ||= {});
|
|
1940
2166
|
var MAX_PLAYERS2 = 12;
|
|
1941
2167
|
var KEYBOARD_OFFSET2 = 0;
|
|
1942
2168
|
var MOUSE_OFFSET2 = 256;
|
|
@@ -2671,6 +2897,172 @@ class KeyboardContext2 {
|
|
|
2671
2897
|
return state;
|
|
2672
2898
|
}
|
|
2673
2899
|
}
|
|
2900
|
+
var MAX_PEERS2 = 12;
|
|
2901
|
+
var PEERS_ARRAY_OFFSET2 = 32;
|
|
2902
|
+
var PEER_CTX_SIZE2 = 8;
|
|
2903
|
+
var PEER_CONNECTED_OFFSET2 = 0;
|
|
2904
|
+
var PEER_PACKET_COUNT_OFFSET2 = 1;
|
|
2905
|
+
var PEER_SEQ_OFFSET2 = 2;
|
|
2906
|
+
var PEER_ACK_OFFSET2 = 4;
|
|
2907
|
+
var PEER_ACK_COUNT_OFFSET2 = 6;
|
|
2908
|
+
var STATUS_MAP2 = {
|
|
2909
|
+
0: "offline",
|
|
2910
|
+
1: "local",
|
|
2911
|
+
2: "join:pending",
|
|
2912
|
+
3: "connected",
|
|
2913
|
+
4: "disconnected"
|
|
2914
|
+
};
|
|
2915
|
+
|
|
2916
|
+
class NetContext2 {
|
|
2917
|
+
dataView;
|
|
2918
|
+
#peers = Array.from({ length: MAX_PEERS2 }, () => ({
|
|
2919
|
+
isLocal: false,
|
|
2920
|
+
seq: -1,
|
|
2921
|
+
ack: -1
|
|
2922
|
+
}));
|
|
2923
|
+
#peersResult = [];
|
|
2924
|
+
constructor(dataView) {
|
|
2925
|
+
this.dataView = dataView;
|
|
2926
|
+
}
|
|
2927
|
+
#hasValidBuffer() {
|
|
2928
|
+
if (!this.dataView)
|
|
2929
|
+
return false;
|
|
2930
|
+
return this.dataView.buffer.byteLength > 0;
|
|
2931
|
+
}
|
|
2932
|
+
get peerCount() {
|
|
2933
|
+
if (!this.#hasValidBuffer()) {
|
|
2934
|
+
throw new Error("NetContext dataView is not valid");
|
|
2935
|
+
}
|
|
2936
|
+
return this.dataView.getUint8(0);
|
|
2937
|
+
}
|
|
2938
|
+
get localPeerId() {
|
|
2939
|
+
if (!this.#hasValidBuffer()) {
|
|
2940
|
+
throw new Error("NetContext dataView is not valid");
|
|
2941
|
+
}
|
|
2942
|
+
return this.dataView.getUint8(1);
|
|
2943
|
+
}
|
|
2944
|
+
get isInSession() {
|
|
2945
|
+
if (!this.#hasValidBuffer()) {
|
|
2946
|
+
throw new Error("NetContext dataView is not valid");
|
|
2947
|
+
}
|
|
2948
|
+
return this.dataView.getUint8(2) !== 0;
|
|
2949
|
+
}
|
|
2950
|
+
get status() {
|
|
2951
|
+
if (!this.#hasValidBuffer()) {
|
|
2952
|
+
throw new Error("NetContext dataView is not valid");
|
|
2953
|
+
}
|
|
2954
|
+
const statusByte = this.dataView.getUint8(3);
|
|
2955
|
+
return STATUS_MAP2[statusByte] ?? "local";
|
|
2956
|
+
}
|
|
2957
|
+
get matchFrame() {
|
|
2958
|
+
if (!this.#hasValidBuffer()) {
|
|
2959
|
+
throw new Error("NetContext dataView is not valid");
|
|
2960
|
+
}
|
|
2961
|
+
return this.dataView.getUint32(4, true);
|
|
2962
|
+
}
|
|
2963
|
+
get sessionStartFrame() {
|
|
2964
|
+
if (!this.#hasValidBuffer()) {
|
|
2965
|
+
throw new Error("NetContext dataView is not valid");
|
|
2966
|
+
}
|
|
2967
|
+
return this.dataView.getUint32(8, true);
|
|
2968
|
+
}
|
|
2969
|
+
get roomCode() {
|
|
2970
|
+
if (!this.#hasValidBuffer()) {
|
|
2971
|
+
throw new Error("NetContext dataView is not valid");
|
|
2972
|
+
}
|
|
2973
|
+
const bytes = [];
|
|
2974
|
+
for (let i = 0;i < 8; i++) {
|
|
2975
|
+
const byte = this.dataView.getUint8(12 + i);
|
|
2976
|
+
if (byte === 0)
|
|
2977
|
+
break;
|
|
2978
|
+
bytes.push(byte);
|
|
2979
|
+
}
|
|
2980
|
+
return String.fromCharCode(...bytes);
|
|
2981
|
+
}
|
|
2982
|
+
get wantsRoomCode() {
|
|
2983
|
+
if (!this.#hasValidBuffer()) {
|
|
2984
|
+
return;
|
|
2985
|
+
}
|
|
2986
|
+
const bytes = [];
|
|
2987
|
+
for (let i = 0;i < 8; i++) {
|
|
2988
|
+
const byte = this.dataView.getUint8(20 + i);
|
|
2989
|
+
if (byte === 0)
|
|
2990
|
+
break;
|
|
2991
|
+
bytes.push(byte);
|
|
2992
|
+
}
|
|
2993
|
+
return bytes.length > 0 ? String.fromCharCode(...bytes) : undefined;
|
|
2994
|
+
}
|
|
2995
|
+
set wantsRoomCode(code) {
|
|
2996
|
+
if (!this.#hasValidBuffer()) {
|
|
2997
|
+
throw new Error("NetContext dataView is not valid");
|
|
2998
|
+
}
|
|
2999
|
+
for (let i = 0;i < 8; i++) {
|
|
3000
|
+
this.dataView.setUint8(20 + i, 0);
|
|
3001
|
+
}
|
|
3002
|
+
if (code) {
|
|
3003
|
+
for (let i = 0;i < Math.min(code.length, 7); i++) {
|
|
3004
|
+
this.dataView.setUint8(20 + i, code.charCodeAt(i));
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
get wantsDisconnect() {
|
|
3009
|
+
if (!this.#hasValidBuffer()) {
|
|
3010
|
+
return false;
|
|
3011
|
+
}
|
|
3012
|
+
return this.dataView.getUint8(28) !== 0;
|
|
3013
|
+
}
|
|
3014
|
+
set wantsDisconnect(value) {
|
|
3015
|
+
if (!this.#hasValidBuffer()) {
|
|
3016
|
+
throw new Error("NetContext dataView is not valid");
|
|
3017
|
+
}
|
|
3018
|
+
this.dataView.setUint8(28, value ? 1 : 0);
|
|
3019
|
+
}
|
|
3020
|
+
get peers() {
|
|
3021
|
+
if (!this.#hasValidBuffer()) {
|
|
3022
|
+
throw new Error("NetContext dataView is not valid");
|
|
3023
|
+
}
|
|
3024
|
+
const dv = this.dataView;
|
|
3025
|
+
const localPeerId = this.localPeerId;
|
|
3026
|
+
const matchFrame = this.matchFrame;
|
|
3027
|
+
let minRemoteSeq = -1;
|
|
3028
|
+
for (let i = 0;i < MAX_PEERS2; i++) {
|
|
3029
|
+
if (i === localPeerId)
|
|
3030
|
+
continue;
|
|
3031
|
+
const peerOffset = PEERS_ARRAY_OFFSET2 + i * PEER_CTX_SIZE2;
|
|
3032
|
+
if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET2) !== 1)
|
|
3033
|
+
continue;
|
|
3034
|
+
if (dv.getUint8(peerOffset + PEER_PACKET_COUNT_OFFSET2) === 0)
|
|
3035
|
+
continue;
|
|
3036
|
+
const seq = dv.getUint16(peerOffset + PEER_SEQ_OFFSET2, true);
|
|
3037
|
+
if (minRemoteSeq === -1 || seq < minRemoteSeq) {
|
|
3038
|
+
minRemoteSeq = seq;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
this.#peersResult.length = 0;
|
|
3042
|
+
for (let i = 0;i < MAX_PEERS2; i++) {
|
|
3043
|
+
const peerOffset = PEERS_ARRAY_OFFSET2 + i * PEER_CTX_SIZE2;
|
|
3044
|
+
if (dv.getUint8(peerOffset + PEER_CONNECTED_OFFSET2) !== 1)
|
|
3045
|
+
continue;
|
|
3046
|
+
const peer = this.#peers[i];
|
|
3047
|
+
if (!peer) {
|
|
3048
|
+
throw new Error(`Unexpected missing peer object at index ${i}`);
|
|
3049
|
+
}
|
|
3050
|
+
const isLocal = i === localPeerId;
|
|
3051
|
+
peer.isLocal = isLocal;
|
|
3052
|
+
if (isLocal) {
|
|
3053
|
+
peer.seq = matchFrame;
|
|
3054
|
+
peer.ack = minRemoteSeq;
|
|
3055
|
+
} else {
|
|
3056
|
+
const packetCount = dv.getUint8(peerOffset + PEER_PACKET_COUNT_OFFSET2);
|
|
3057
|
+
const ackCount = dv.getUint8(peerOffset + PEER_ACK_COUNT_OFFSET2);
|
|
3058
|
+
peer.seq = packetCount === 0 ? -1 : dv.getUint16(peerOffset + PEER_SEQ_OFFSET2, true);
|
|
3059
|
+
peer.ack = ackCount === 0 ? -1 : dv.getUint16(peerOffset + PEER_ACK_OFFSET2, true);
|
|
3060
|
+
}
|
|
3061
|
+
this.#peersResult.push(peer);
|
|
3062
|
+
}
|
|
3063
|
+
return this.#peersResult;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
2674
3066
|
var TAPE_MAGIC2 = 1413566533;
|
|
2675
3067
|
function readTapeHeader2(tape) {
|
|
2676
3068
|
const view = new DataView(tape.buffer, tape.byteOffset, tape.byteLength);
|
|
@@ -2686,7 +3078,7 @@ function readTapeHeader2(tape) {
|
|
|
2686
3078
|
eventCount: view.getUint16(14, true)
|
|
2687
3079
|
};
|
|
2688
3080
|
}
|
|
2689
|
-
var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.
|
|
3081
|
+
var DEFAULT_WASM_URL2 = new URL("https://unpkg.com/@bloopjs/engine@0.0.81/wasm/bloop.wasm");
|
|
2690
3082
|
var TIME_CTX_OFFSET2 = 0;
|
|
2691
3083
|
var INPUT_CTX_OFFSET2 = TIME_CTX_OFFSET2 + 4;
|
|
2692
3084
|
var EVENTS_OFFSET2 = INPUT_CTX_OFFSET2 + 4;
|
|
@@ -6201,11 +6593,11 @@ function joinRollbackRoom(roomId, app, opts) {
|
|
|
6201
6593
|
}
|
|
6202
6594
|
function receivePackets() {
|
|
6203
6595
|
for (const packetData of incomingPackets) {
|
|
6204
|
-
app.sim.
|
|
6596
|
+
app.sim.emit.packet(packetData);
|
|
6205
6597
|
if (remotePeerId == null) {
|
|
6206
6598
|
return;
|
|
6207
6599
|
}
|
|
6208
|
-
const peerState = app.sim.net.
|
|
6600
|
+
const peerState = unwrap(app.sim.net.peers[remotePeerId], `Remote peer state not found for peerId ${remotePeerId}`);
|
|
6209
6601
|
updatePeer(remoteStringPeerId, {
|
|
6210
6602
|
ack: peerState.ack,
|
|
6211
6603
|
seq: peerState.seq,
|
|
@@ -6223,7 +6615,7 @@ function joinRollbackRoom(roomId, app, opts) {
|
|
|
6223
6615
|
console.warn("[netcode] Data channel not open, cannot send packet. readyState=", udp.readyState);
|
|
6224
6616
|
return;
|
|
6225
6617
|
}
|
|
6226
|
-
const packet = app.sim.
|
|
6618
|
+
const packet = app.sim.getOutboundPacket(remotePeerId);
|
|
6227
6619
|
if (!packet) {
|
|
6228
6620
|
console.warn("[netcode] No packet to send");
|
|
6229
6621
|
return;
|
|
@@ -6245,7 +6637,7 @@ function joinRollbackRoom(roomId, app, opts) {
|
|
|
6245
6637
|
onDataChannelClose(peerId, reliable) {
|
|
6246
6638
|
console.log(`Data channel closed: ${peerId} (reliable: ${reliable})`);
|
|
6247
6639
|
if (!reliable && remotePeerId !== null) {
|
|
6248
|
-
app.sim.
|
|
6640
|
+
app.sim.emit.network("peer:leave", { peerId: remotePeerId });
|
|
6249
6641
|
sessionActive = false;
|
|
6250
6642
|
opts?.onSessionEnd?.();
|
|
6251
6643
|
}
|
|
@@ -6264,9 +6656,10 @@ function joinRollbackRoom(roomId, app, opts) {
|
|
|
6264
6656
|
remotePeerId = ids.remote;
|
|
6265
6657
|
remoteStringPeerId = peerId;
|
|
6266
6658
|
setRemoteId(remotePeerId);
|
|
6267
|
-
app.sim.
|
|
6268
|
-
app.sim.
|
|
6269
|
-
app.sim.
|
|
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", {});
|
|
6270
6663
|
sessionActive = true;
|
|
6271
6664
|
console.log(`[netcode] Session started at frame ${app.sim.time.frame}`);
|
|
6272
6665
|
opts?.onSessionStart?.();
|
|
@@ -6285,7 +6678,7 @@ function joinRollbackRoom(roomId, app, opts) {
|
|
|
6285
6678
|
onPeerDisconnected(peerId) {
|
|
6286
6679
|
removePeer(peerId);
|
|
6287
6680
|
if (remotePeerId !== null && peerId === remoteStringPeerId) {
|
|
6288
|
-
app.sim.
|
|
6681
|
+
app.sim.emit.network("peer:leave", { peerId: remotePeerId });
|
|
6289
6682
|
sessionActive = false;
|
|
6290
6683
|
opts?.onSessionEnd?.();
|
|
6291
6684
|
}
|
|
@@ -6312,5 +6705,5 @@ export {
|
|
|
6312
6705
|
App
|
|
6313
6706
|
};
|
|
6314
6707
|
|
|
6315
|
-
//# debugId=
|
|
6708
|
+
//# debugId=A73DA4EECDE34BD764756E2164756E21
|
|
6316
6709
|
//# sourceMappingURL=mod.js.map
|