@bloopjs/engine 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/codegen/offsets.d.ts +40 -0
- package/dist/codegen/offsets.d.ts.map +1 -0
- package/dist/contexts/inputContext.d.ts +1 -0
- package/dist/contexts/inputContext.d.ts.map +1 -1
- package/dist/contexts/netContext.d.ts +8 -8
- package/dist/contexts/netContext.d.ts.map +1 -1
- package/dist/contexts/timeContext.d.ts.map +1 -1
- package/dist/engine.js +118 -76
- package/dist/engine.js.map +9 -8
- package/dist/inputs.d.ts +2 -7
- package/dist/inputs.d.ts.map +1 -1
- package/js/codegen/offsets.ts +48 -0
- package/js/contexts/inputContext.ts +20 -12
- package/js/contexts/netContext.ts +76 -47
- package/js/contexts/timeContext.ts +9 -3
- package/js/defaultUrl.ts +1 -1
- package/js/inputs.ts +18 -14
- package/package.json +1 -1
- package/wasm/bloop.wasm +0 -0
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_PLAYERS,
|
|
3
|
+
NET_CTX_PEERS_OFFSET,
|
|
4
|
+
NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET,
|
|
5
|
+
NET_CTX_TOTAL_ROLLBACKS_OFFSET,
|
|
6
|
+
NET_CTX_FRAMES_RESIMULATED_OFFSET,
|
|
7
|
+
NET_CTX_PEER_COUNT_OFFSET,
|
|
8
|
+
NET_CTX_LOCAL_PEER_ID_OFFSET,
|
|
9
|
+
NET_CTX_IN_SESSION_OFFSET,
|
|
10
|
+
NET_CTX_STATUS_OFFSET,
|
|
11
|
+
NET_CTX_MATCH_FRAME_OFFSET,
|
|
12
|
+
NET_CTX_SESSION_START_FRAME_OFFSET,
|
|
13
|
+
NET_CTX_ROOM_CODE_OFFSET,
|
|
14
|
+
NET_CTX_WANTS_ROOM_CODE_OFFSET,
|
|
15
|
+
NET_CTX_WANTS_DISCONNECT_OFFSET,
|
|
16
|
+
PEER_CTX_SIZE,
|
|
17
|
+
PEER_CTX_CONNECTED_OFFSET,
|
|
18
|
+
PEER_CTX_SEQ_OFFSET,
|
|
19
|
+
PEER_CTX_ACK_OFFSET,
|
|
20
|
+
} from "../codegen/offsets";
|
|
21
|
+
|
|
1
22
|
/**
|
|
2
23
|
* Network status values matching Zig NetStatus enum
|
|
3
24
|
*/
|
|
@@ -28,17 +49,6 @@ export type PeerInfo = {
|
|
|
28
49
|
ack: number; // -1 if no packets received
|
|
29
50
|
};
|
|
30
51
|
|
|
31
|
-
const MAX_PEERS = 12;
|
|
32
|
-
const PEERS_ARRAY_OFFSET = 32; // After _pad at offset 29-31
|
|
33
|
-
const PEER_CTX_SIZE = 8; // connected(1) + packet_count(1) + seq(2) + ack(2) + ack_count(1) + pad(1)
|
|
34
|
-
|
|
35
|
-
// Offsets within PeerCtx struct
|
|
36
|
-
const PEER_CONNECTED_OFFSET = 0;
|
|
37
|
-
const PEER_PACKET_COUNT_OFFSET = 1;
|
|
38
|
-
const PEER_SEQ_OFFSET = 2;
|
|
39
|
-
const PEER_ACK_OFFSET = 4;
|
|
40
|
-
const PEER_ACK_COUNT_OFFSET = 6;
|
|
41
|
-
|
|
42
52
|
const STATUS_MAP: Record<number, NetStatus> = {
|
|
43
53
|
0: "offline",
|
|
44
54
|
1: "local",
|
|
@@ -48,14 +58,8 @@ const STATUS_MAP: Record<number, NetStatus> = {
|
|
|
48
58
|
};
|
|
49
59
|
|
|
50
60
|
/**
|
|
51
|
-
* NetCtx struct layout
|
|
52
|
-
*
|
|
53
|
-
* - local_peer_id: u8 (offset 1)
|
|
54
|
-
* - in_session: u8 (offset 2)
|
|
55
|
-
* - status: u8 (offset 3)
|
|
56
|
-
* - match_frame: u32 (offset 4)
|
|
57
|
-
* - session_start_frame: u32 (offset 8)
|
|
58
|
-
* - room_code: [8]u8 (offset 12)
|
|
61
|
+
* NetCtx struct layout is defined in context.zig.
|
|
62
|
+
* Field offsets are generated in codegen/offsets.ts.
|
|
59
63
|
*
|
|
60
64
|
* All getters read directly from the engine's memory via DataView.
|
|
61
65
|
* State is managed by the Zig engine, not TypeScript.
|
|
@@ -64,7 +68,7 @@ export class NetContext {
|
|
|
64
68
|
dataView?: DataView;
|
|
65
69
|
|
|
66
70
|
// Pre-allocated peer objects to avoid GC pressure
|
|
67
|
-
#peers: PeerInfo[] = Array.from({ length:
|
|
71
|
+
#peers: PeerInfo[] = Array.from({ length: MAX_PLAYERS }, () => ({
|
|
68
72
|
isLocal: false,
|
|
69
73
|
seq: -1,
|
|
70
74
|
ack: -1,
|
|
@@ -87,7 +91,7 @@ export class NetContext {
|
|
|
87
91
|
if (!this.#hasValidBuffer()) {
|
|
88
92
|
throw new Error("NetContext dataView is not valid");
|
|
89
93
|
}
|
|
90
|
-
return this.dataView!.getUint8(
|
|
94
|
+
return this.dataView!.getUint8(NET_CTX_PEER_COUNT_OFFSET);
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/** Local peer ID in the session */
|
|
@@ -95,7 +99,7 @@ export class NetContext {
|
|
|
95
99
|
if (!this.#hasValidBuffer()) {
|
|
96
100
|
throw new Error("NetContext dataView is not valid");
|
|
97
101
|
}
|
|
98
|
-
return this.dataView!.getUint8(
|
|
102
|
+
return this.dataView!.getUint8(NET_CTX_LOCAL_PEER_ID_OFFSET);
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
/** Whether we're in an active multiplayer session */
|
|
@@ -103,7 +107,7 @@ export class NetContext {
|
|
|
103
107
|
if (!this.#hasValidBuffer()) {
|
|
104
108
|
throw new Error("NetContext dataView is not valid");
|
|
105
109
|
}
|
|
106
|
-
return this.dataView!.getUint8(
|
|
110
|
+
return this.dataView!.getUint8(NET_CTX_IN_SESSION_OFFSET) !== 0;
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
/** Current network status */
|
|
@@ -111,7 +115,7 @@ export class NetContext {
|
|
|
111
115
|
if (!this.#hasValidBuffer()) {
|
|
112
116
|
throw new Error("NetContext dataView is not valid");
|
|
113
117
|
}
|
|
114
|
-
const statusByte = this.dataView!.getUint8(
|
|
118
|
+
const statusByte = this.dataView!.getUint8(NET_CTX_STATUS_OFFSET);
|
|
115
119
|
return STATUS_MAP[statusByte] ?? "local";
|
|
116
120
|
}
|
|
117
121
|
|
|
@@ -120,7 +124,7 @@ export class NetContext {
|
|
|
120
124
|
if (!this.#hasValidBuffer()) {
|
|
121
125
|
throw new Error("NetContext dataView is not valid");
|
|
122
126
|
}
|
|
123
|
-
return this.dataView!.getUint32(
|
|
127
|
+
return this.dataView!.getUint32(NET_CTX_MATCH_FRAME_OFFSET, true);
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
/**
|
|
@@ -131,7 +135,7 @@ export class NetContext {
|
|
|
131
135
|
if (!this.#hasValidBuffer()) {
|
|
132
136
|
throw new Error("NetContext dataView is not valid");
|
|
133
137
|
}
|
|
134
|
-
return this.dataView!.getUint32(
|
|
138
|
+
return this.dataView!.getUint32(NET_CTX_SESSION_START_FRAME_OFFSET, true);
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
/** Current room code (empty string if not in a room) */
|
|
@@ -139,10 +143,10 @@ export class NetContext {
|
|
|
139
143
|
if (!this.#hasValidBuffer()) {
|
|
140
144
|
throw new Error("NetContext dataView is not valid");
|
|
141
145
|
}
|
|
142
|
-
// Read 8 bytes starting at offset
|
|
146
|
+
// Read 8 bytes starting at room_code offset, convert to string until null terminator
|
|
143
147
|
const bytes: number[] = [];
|
|
144
148
|
for (let i = 0; i < 8; i++) {
|
|
145
|
-
const byte = this.dataView!.getUint8(
|
|
149
|
+
const byte = this.dataView!.getUint8(NET_CTX_ROOM_CODE_OFFSET + i);
|
|
146
150
|
if (byte === 0) break;
|
|
147
151
|
bytes.push(byte);
|
|
148
152
|
}
|
|
@@ -154,10 +158,10 @@ export class NetContext {
|
|
|
154
158
|
if (!this.#hasValidBuffer()) {
|
|
155
159
|
return undefined;
|
|
156
160
|
}
|
|
157
|
-
// Read 8 bytes starting at offset
|
|
161
|
+
// Read 8 bytes starting at wants_room_code offset, convert to string until null terminator
|
|
158
162
|
const bytes: number[] = [];
|
|
159
163
|
for (let i = 0; i < 8; i++) {
|
|
160
|
-
const byte = this.dataView!.getUint8(
|
|
164
|
+
const byte = this.dataView!.getUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i);
|
|
161
165
|
if (byte === 0) break;
|
|
162
166
|
bytes.push(byte);
|
|
163
167
|
}
|
|
@@ -170,12 +174,12 @@ export class NetContext {
|
|
|
170
174
|
}
|
|
171
175
|
// Clear first
|
|
172
176
|
for (let i = 0; i < 8; i++) {
|
|
173
|
-
this.dataView!.setUint8(
|
|
177
|
+
this.dataView!.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i, 0);
|
|
174
178
|
}
|
|
175
179
|
if (code) {
|
|
176
180
|
// Write up to 7 chars (leave room for null terminator)
|
|
177
181
|
for (let i = 0; i < Math.min(code.length, 7); i++) {
|
|
178
|
-
this.dataView!.setUint8(
|
|
182
|
+
this.dataView!.setUint8(NET_CTX_WANTS_ROOM_CODE_OFFSET + i, code.charCodeAt(i));
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
}
|
|
@@ -185,14 +189,14 @@ export class NetContext {
|
|
|
185
189
|
if (!this.#hasValidBuffer()) {
|
|
186
190
|
return false;
|
|
187
191
|
}
|
|
188
|
-
return this.dataView!.getUint8(
|
|
192
|
+
return this.dataView!.getUint8(NET_CTX_WANTS_DISCONNECT_OFFSET) !== 0;
|
|
189
193
|
}
|
|
190
194
|
|
|
191
195
|
set wantsDisconnect(value: boolean) {
|
|
192
196
|
if (!this.#hasValidBuffer()) {
|
|
193
197
|
throw new Error("NetContext dataView is not valid");
|
|
194
198
|
}
|
|
195
|
-
this.dataView!.setUint8(
|
|
199
|
+
this.dataView!.setUint8(NET_CTX_WANTS_DISCONNECT_OFFSET, value ? 1 : 0);
|
|
196
200
|
}
|
|
197
201
|
|
|
198
202
|
/**
|
|
@@ -216,14 +220,15 @@ export class NetContext {
|
|
|
216
220
|
const localPeerId = this.localPeerId;
|
|
217
221
|
const matchFrame = this.matchFrame;
|
|
218
222
|
|
|
219
|
-
// Calculate local peer ack = min(seq) across connected remotes with
|
|
223
|
+
// Calculate local peer ack = min(seq) across connected remotes with data
|
|
220
224
|
let minRemoteSeq = -1;
|
|
221
|
-
for (let i = 0; i <
|
|
225
|
+
for (let i = 0; i < MAX_PLAYERS; i++) {
|
|
222
226
|
if (i === localPeerId) continue;
|
|
223
|
-
const peerOffset =
|
|
224
|
-
if (dv.getUint8(peerOffset +
|
|
225
|
-
|
|
226
|
-
const seq = dv.
|
|
227
|
+
const peerOffset = NET_CTX_PEERS_OFFSET + i * PEER_CTX_SIZE;
|
|
228
|
+
if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET) !== 1) continue;
|
|
229
|
+
// seq is now i16 with -1 meaning "no data yet"
|
|
230
|
+
const seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET, true);
|
|
231
|
+
if (seq < 0) continue; // No data from this peer yet
|
|
227
232
|
if (minRemoteSeq === -1 || seq < minRemoteSeq) {
|
|
228
233
|
minRemoteSeq = seq;
|
|
229
234
|
}
|
|
@@ -231,9 +236,9 @@ export class NetContext {
|
|
|
231
236
|
|
|
232
237
|
// Update pre-allocated peer objects and build result array
|
|
233
238
|
this.#peersResult.length = 0;
|
|
234
|
-
for (let i = 0; i <
|
|
235
|
-
const peerOffset =
|
|
236
|
-
if (dv.getUint8(peerOffset +
|
|
239
|
+
for (let i = 0; i < MAX_PLAYERS; i++) {
|
|
240
|
+
const peerOffset = NET_CTX_PEERS_OFFSET + i * PEER_CTX_SIZE;
|
|
241
|
+
if (dv.getUint8(peerOffset + PEER_CTX_CONNECTED_OFFSET) !== 1) continue;
|
|
237
242
|
|
|
238
243
|
const peer = this.#peers[i];
|
|
239
244
|
if (!peer) {
|
|
@@ -246,13 +251,37 @@ export class NetContext {
|
|
|
246
251
|
peer.seq = matchFrame;
|
|
247
252
|
peer.ack = minRemoteSeq;
|
|
248
253
|
} else {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
peer.
|
|
252
|
-
peer.ack = ackCount === 0 ? -1 : dv.getUint16(peerOffset + PEER_ACK_OFFSET, true);
|
|
254
|
+
// seq and ack are now i16 with -1 meaning "no data yet"
|
|
255
|
+
peer.seq = dv.getInt16(peerOffset + PEER_CTX_SEQ_OFFSET, true);
|
|
256
|
+
peer.ack = dv.getInt16(peerOffset + PEER_CTX_ACK_OFFSET, true);
|
|
253
257
|
}
|
|
254
258
|
this.#peersResult.push(peer);
|
|
255
259
|
}
|
|
256
260
|
return this.#peersResult;
|
|
257
261
|
}
|
|
262
|
+
|
|
263
|
+
/** Last rollback depth (how many frames were rolled back) */
|
|
264
|
+
get lastRollbackDepth(): number {
|
|
265
|
+
if (!this.#hasValidBuffer()) {
|
|
266
|
+
throw new Error("NetContext dataView is not valid");
|
|
267
|
+
}
|
|
268
|
+
return this.dataView!.getUint32(NET_CTX_LAST_ROLLBACK_DEPTH_OFFSET, true);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Total number of rollbacks during this session */
|
|
272
|
+
get totalRollbacks(): number {
|
|
273
|
+
if (!this.#hasValidBuffer()) {
|
|
274
|
+
throw new Error("NetContext dataView is not valid");
|
|
275
|
+
}
|
|
276
|
+
return this.dataView!.getUint32(NET_CTX_TOTAL_ROLLBACKS_OFFSET, true);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Total frames resimulated during this session */
|
|
280
|
+
get framesResimulated(): number {
|
|
281
|
+
if (!this.#hasValidBuffer()) {
|
|
282
|
+
throw new Error("NetContext dataView is not valid");
|
|
283
|
+
}
|
|
284
|
+
// Read u64 as BigInt then convert to number (safe for reasonable frame counts)
|
|
285
|
+
return Number(this.dataView!.getBigUint64(NET_CTX_FRAMES_RESIMULATED_OFFSET, true));
|
|
286
|
+
}
|
|
258
287
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TIME_CTX_FRAME_OFFSET,
|
|
3
|
+
TIME_CTX_DT_MS_OFFSET,
|
|
4
|
+
TIME_CTX_TOTAL_MS_OFFSET,
|
|
5
|
+
} from "../codegen/offsets";
|
|
6
|
+
|
|
1
7
|
export class TimeContext {
|
|
2
8
|
dataView?: DataView;
|
|
3
9
|
|
|
@@ -10,7 +16,7 @@ export class TimeContext {
|
|
|
10
16
|
if (!this.dataView) {
|
|
11
17
|
throw new Error("TimeContext DataView is not initialized");
|
|
12
18
|
}
|
|
13
|
-
return this.dataView.getUint32(
|
|
19
|
+
return this.dataView.getUint32(TIME_CTX_FRAME_OFFSET, true);
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/** The number of seconds since the last frame */
|
|
@@ -18,7 +24,7 @@ export class TimeContext {
|
|
|
18
24
|
if (!this.dataView) {
|
|
19
25
|
throw new Error("TimeContext DataView is not initialized");
|
|
20
26
|
}
|
|
21
|
-
return this.dataView.getUint32(
|
|
27
|
+
return this.dataView.getUint32(TIME_CTX_DT_MS_OFFSET, true) / 1000;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
/** The total number of seconds since the engine started */
|
|
@@ -26,6 +32,6 @@ export class TimeContext {
|
|
|
26
32
|
if (!this.dataView) {
|
|
27
33
|
throw new Error("TimeContext DataView is not initialized");
|
|
28
34
|
}
|
|
29
|
-
return this.dataView.getUint32(
|
|
35
|
+
return this.dataView.getUint32(TIME_CTX_TOTAL_MS_OFFSET, true) / 1000;
|
|
30
36
|
}
|
|
31
37
|
}
|
package/js/defaultUrl.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEFAULT_WASM_URL: URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.
|
|
1
|
+
export const DEFAULT_WASM_URL: URL = new URL("https://unpkg.com/@bloopjs/engine@0.0.89/wasm/bloop.wasm");
|
package/js/inputs.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import * as Enums from "./codegen/enums";
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
// Re-export layout constants from generated offsets
|
|
4
|
+
export {
|
|
5
|
+
MAX_PLAYERS,
|
|
6
|
+
KEY_CTX_SIZE,
|
|
7
|
+
MOUSE_CTX_SIZE,
|
|
8
|
+
PLAYER_INPUTS_SIZE,
|
|
9
|
+
INPUT_CTX_SIZE,
|
|
10
|
+
PLAYER_INPUTS_KEY_CTX_OFFSET,
|
|
11
|
+
PLAYER_INPUTS_MOUSE_CTX_OFFSET,
|
|
12
|
+
MOUSE_CTX_BUTTON_STATES_OFFSET,
|
|
13
|
+
} from "./codegen/offsets";
|
|
6
14
|
|
|
7
|
-
//
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export const PLAYER_INPUTS_SIZE = 280;
|
|
15
|
-
|
|
16
|
-
// InputCtx layout: players[12] = 12 * 280 = 3360 bytes
|
|
17
|
-
export const INPUT_CTX_SIZE = MAX_PLAYERS * PLAYER_INPUTS_SIZE;
|
|
15
|
+
// Backwards compatibility aliases
|
|
16
|
+
export {
|
|
17
|
+
PLAYER_INPUTS_KEY_CTX_OFFSET as KEYBOARD_OFFSET,
|
|
18
|
+
KEY_CTX_SIZE as KEYBOARD_SIZE,
|
|
19
|
+
PLAYER_INPUTS_MOUSE_CTX_OFFSET as MOUSE_OFFSET,
|
|
20
|
+
MOUSE_CTX_BUTTON_STATES_OFFSET as MOUSE_BUTTONS_OFFSET,
|
|
21
|
+
} from "./codegen/offsets";
|
|
18
22
|
|
|
19
23
|
export const EVENT_PAYLOAD_SIZE = 8;
|
|
20
24
|
export const EVENT_PAYLOAD_ALIGN = 4;
|
package/package.json
CHANGED
package/wasm/bloop.wasm
CHANGED
|
Binary file
|