@bloopjs/bloop 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/bloop.d.ts.map +1 -1
- package/dist/mod.js +297 -83
- package/dist/mod.js.map +6 -7
- package/dist/sim.d.ts +45 -17
- package/dist/sim.d.ts.map +1 -1
- package/dist/system.d.ts +5 -0
- package/dist/system.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/bloop.ts +64 -3
- package/src/sim.ts +158 -26
- package/src/system.ts +8 -0
- package/dist/net.d.ts +0 -60
- package/dist/net.d.ts.map +0 -1
- package/src/net.ts +0 -138
package/dist/sim.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type EnginePointer, type Key, type MouseButton, TimeContext, type WasmEngine } from "@bloopjs/engine";
|
|
2
|
-
import { Net } from "./net";
|
|
1
|
+
import { type EnginePointer, type Key, type MouseButton, NetContext, type NetEvent, type NetEventType, TimeContext, type WasmEngine } from "@bloopjs/engine";
|
|
3
2
|
export type EngineHooks = {
|
|
4
3
|
/**
|
|
5
4
|
* Hook to serialize some data when snapshotting
|
|
@@ -46,11 +45,6 @@ export declare class Sim {
|
|
|
46
45
|
#private;
|
|
47
46
|
wasm: WasmEngine;
|
|
48
47
|
id: string;
|
|
49
|
-
/**
|
|
50
|
-
* Network API for packet management in multiplayer sessions.
|
|
51
|
-
* Use this to send/receive packets and query peer network state.
|
|
52
|
-
*/
|
|
53
|
-
readonly net: Net;
|
|
54
48
|
/**
|
|
55
49
|
* Callback fired when tape buffer fills up and recording stops.
|
|
56
50
|
* The tape data is passed so you can save it before clearing.
|
|
@@ -59,6 +53,14 @@ export declare class Sim {
|
|
|
59
53
|
constructor(wasm: WasmEngine, memory: WebAssembly.Memory, opts?: {
|
|
60
54
|
serialize?: SerializeFn;
|
|
61
55
|
});
|
|
56
|
+
/**
|
|
57
|
+
* Step simulation forward by a given number of milliseconds.
|
|
58
|
+
* If ms is not provided, defaults to 16ms (60fps).
|
|
59
|
+
*
|
|
60
|
+
* Note: this will not advance time if the sim is paused.
|
|
61
|
+
*
|
|
62
|
+
* @returns Number of frames actually advanced
|
|
63
|
+
*/
|
|
62
64
|
step(ms?: number): number;
|
|
63
65
|
/**
|
|
64
66
|
* Run a single simulation frame. step wraps this in an accumulator.
|
|
@@ -73,13 +75,28 @@ export declare class Sim {
|
|
|
73
75
|
* @param source
|
|
74
76
|
*/
|
|
75
77
|
cloneSession(source: Sim): void;
|
|
78
|
+
/**
|
|
79
|
+
* Pause the simulation entirely
|
|
80
|
+
*/
|
|
76
81
|
pause(): void;
|
|
82
|
+
/**
|
|
83
|
+
* Unpause the simulation
|
|
84
|
+
*/
|
|
77
85
|
unpause(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Whether the simulation is currently paused
|
|
88
|
+
*/
|
|
78
89
|
get isPaused(): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Step back one frame
|
|
92
|
+
*/
|
|
79
93
|
stepBack(): void;
|
|
80
94
|
/**
|
|
81
|
-
* Seek to the start of a given frame
|
|
82
|
-
*
|
|
95
|
+
* Seek to the start of a given frame.
|
|
96
|
+
*
|
|
97
|
+
* Note that this will mute console output during the seek if rewinding.
|
|
98
|
+
*
|
|
99
|
+
* @param frame - frame number to replay up to
|
|
83
100
|
*/
|
|
84
101
|
seek(frame: number, inclusive?: boolean): void;
|
|
85
102
|
/**
|
|
@@ -113,6 +130,7 @@ export declare class Sim {
|
|
|
113
130
|
*/
|
|
114
131
|
unmount(): void;
|
|
115
132
|
get time(): TimeContext;
|
|
133
|
+
get net(): NetContext;
|
|
116
134
|
get buffer(): ArrayBuffer;
|
|
117
135
|
get isRecording(): boolean;
|
|
118
136
|
get isReplaying(): boolean;
|
|
@@ -124,17 +142,27 @@ export declare class Sim {
|
|
|
124
142
|
mousedown: (button: MouseButton, peerId?: number) => void;
|
|
125
143
|
mouseup: (button: MouseButton, peerId?: number) => void;
|
|
126
144
|
mousewheel: (x: number, y: number, peerId?: number) => void;
|
|
145
|
+
/**
|
|
146
|
+
* Emit a network event (join:ok, peer:join, etc.)
|
|
147
|
+
* Events are queued in the engine and processed during the next tick's systemsCallback.
|
|
148
|
+
*/
|
|
149
|
+
network: <T extends NetEventType>(type: T, data: Extract<NetEvent, {
|
|
150
|
+
type: T;
|
|
151
|
+
}>["data"]) => void;
|
|
152
|
+
/**
|
|
153
|
+
* Receive a packet from a peer
|
|
154
|
+
*/
|
|
155
|
+
packet: (data: Uint8Array) => void;
|
|
127
156
|
};
|
|
128
157
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
158
|
+
* Get an outbound packet to send to a target peer.
|
|
159
|
+
* Returns a copy of the packet data (caller owns the returned buffer).
|
|
131
160
|
*
|
|
132
|
-
*
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
* End the current session and clean up rollback state.
|
|
161
|
+
* The packet contains all unacked inputs encoded in wire format.
|
|
162
|
+
*
|
|
163
|
+
* @param targetPeer - Peer ID to send the packet to
|
|
164
|
+
* @returns Packet data to send, or null if no packet available
|
|
137
165
|
*/
|
|
138
|
-
|
|
166
|
+
getOutboundPacket(targetPeer: number): Uint8Array<ArrayBuffer> | null;
|
|
139
167
|
}
|
|
140
168
|
//# sourceMappingURL=sim.d.ts.map
|
package/dist/sim.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sim.d.ts","sourceRoot":"","sources":["../src/sim.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,GAAG,EAER,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"sim.d.ts","sourceRoot":"","sources":["../src/sim.ts"],"names":[],"mappings":"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"}
|
package/dist/system.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NetEvent } from "@bloopjs/engine";
|
|
1
2
|
import type { Context } from "./context";
|
|
2
3
|
import type { BloopSchema } from "./data/schema";
|
|
3
4
|
import type { KeyEvent, MouseButtonEvent, MouseMoveEvent, MouseWheelEvent } from "./events";
|
|
@@ -22,5 +23,9 @@ export type System<GS extends BloopSchema = BloopSchema> = {
|
|
|
22
23
|
mousewheel?: (context: Context<GS> & {
|
|
23
24
|
event: MouseWheelEvent;
|
|
24
25
|
}) => void;
|
|
26
|
+
/** Handle network events (join:ok, peer:join, etc.) */
|
|
27
|
+
netcode?: (context: Context<GS> & {
|
|
28
|
+
event: NetEvent;
|
|
29
|
+
}) => void;
|
|
25
30
|
};
|
|
26
31
|
//# sourceMappingURL=system.d.ts.map
|
package/dist/system.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../src/system.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EACV,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,eAAe,EAChB,MAAM,UAAU,CAAC;AAElB,MAAM,MAAM,MAAM,CAAC,EAAE,SAAS,WAAW,GAAG,WAAW,IAAI;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;IAExC,OAAO,CAAC,EAAE,CACR,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,QAAQ,CAAC;KACjB,KACE,IAAI,CAAC;IAEV,KAAK,CAAC,EAAE,CACN,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,QAAQ,CAAC;KACjB,KACE,IAAI,CAAC;IAEV,SAAS,CAAC,EAAE,CACV,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,gBAAgB,CAAC;KACzB,KACE,IAAI,CAAC;IAEV,OAAO,CAAC,EAAE,CACR,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,gBAAgB,CAAC;KACzB,KACE,IAAI,CAAC;IAEV,SAAS,CAAC,EAAE,CACV,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,cAAc,CAAC;KACvB,KACE,IAAI,CAAC;IAEV,UAAU,CAAC,EAAE,CACX,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,eAAe,CAAC;KACxB,KACE,IAAI,CAAC;CACX,CAAC"}
|
|
1
|
+
{"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../src/system.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EACV,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,eAAe,EAChB,MAAM,UAAU,CAAC;AAElB,MAAM,MAAM,MAAM,CAAC,EAAE,SAAS,WAAW,GAAG,WAAW,IAAI;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC;IAExC,OAAO,CAAC,EAAE,CACR,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,QAAQ,CAAC;KACjB,KACE,IAAI,CAAC;IAEV,KAAK,CAAC,EAAE,CACN,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,QAAQ,CAAC;KACjB,KACE,IAAI,CAAC;IAEV,SAAS,CAAC,EAAE,CACV,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,gBAAgB,CAAC;KACzB,KACE,IAAI,CAAC;IAEV,OAAO,CAAC,EAAE,CACR,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,gBAAgB,CAAC;KACzB,KACE,IAAI,CAAC;IAEV,SAAS,CAAC,EAAE,CACV,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,cAAc,CAAC;KACvB,KACE,IAAI,CAAC;IAEV,UAAU,CAAC,EAAE,CACX,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,eAAe,CAAC;KACxB,KACE,IAAI,CAAC;IAEV,uDAAuD;IACvD,OAAO,CAAC,EAAE,CACR,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG;QACrB,KAAK,EAAE,QAAQ,CAAC;KACjB,KACE,IAAI,CAAC;CACX,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bloopjs/bloop",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.81",
|
|
4
4
|
"author": "Neil Sarkar",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@types/bun": "latest"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@bloopjs/engine": "0.0.
|
|
38
|
+
"@bloopjs/engine": "0.0.81"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"typescript": "^5"
|
package/src/bloop.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type EnginePointer,
|
|
3
3
|
Enums,
|
|
4
|
-
EVENT_PAYLOAD_ALIGN,
|
|
5
|
-
EVENT_PAYLOAD_SIZE,
|
|
6
4
|
EVENTS_OFFSET,
|
|
7
5
|
INPUT_CTX_OFFSET,
|
|
8
6
|
InputContext,
|
|
@@ -10,6 +8,7 @@ import {
|
|
|
10
8
|
mouseButtonCodeToMouseButton,
|
|
11
9
|
NET_CTX_OFFSET,
|
|
12
10
|
NetContext,
|
|
11
|
+
type NetEvent,
|
|
13
12
|
TIME_CTX_OFFSET,
|
|
14
13
|
TimeContext,
|
|
15
14
|
} from "@bloopjs/engine";
|
|
@@ -254,8 +253,70 @@ export class Bloop<GS extends BloopSchema> {
|
|
|
254
253
|
}),
|
|
255
254
|
);
|
|
256
255
|
break;
|
|
256
|
+
case Enums.EventType.NetJoinOk: {
|
|
257
|
+
// Room code is 8 bytes starting at payload offset
|
|
258
|
+
const roomCodeBytes: number[] = [];
|
|
259
|
+
for (let j = 0; j < 8; j++) {
|
|
260
|
+
const byte = eventsDataView.getUint8(payloadOffset + j);
|
|
261
|
+
if (byte === 0) break;
|
|
262
|
+
roomCodeBytes.push(byte);
|
|
263
|
+
}
|
|
264
|
+
const roomCode = String.fromCharCode(...roomCodeBytes);
|
|
265
|
+
(this.#context as any).event = {
|
|
266
|
+
type: "join:ok",
|
|
267
|
+
data: { roomCode },
|
|
268
|
+
};
|
|
269
|
+
system.netcode?.(
|
|
270
|
+
this.#context as Context<GS> & { event: NetEvent },
|
|
271
|
+
);
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
case Enums.EventType.NetJoinFail: {
|
|
275
|
+
// Reason is stored at payload offset as u8
|
|
276
|
+
const reasonCode = eventsDataView.getUint8(payloadOffset);
|
|
277
|
+
const reasons = [
|
|
278
|
+
"unknown",
|
|
279
|
+
"timeout",
|
|
280
|
+
"room_full",
|
|
281
|
+
"room_not_found",
|
|
282
|
+
"already_in_room",
|
|
283
|
+
];
|
|
284
|
+
const reason = reasons[reasonCode] ?? "unknown";
|
|
285
|
+
(this.#context as any).event = {
|
|
286
|
+
type: "join:fail",
|
|
287
|
+
data: { reason },
|
|
288
|
+
};
|
|
289
|
+
system.netcode?.(
|
|
290
|
+
this.#context as Context<GS> & { event: NetEvent },
|
|
291
|
+
);
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case Enums.EventType.NetPeerJoin: {
|
|
295
|
+
const peerId = eventsDataView.getUint8(payloadOffset);
|
|
296
|
+
(this.#context as any).event = {
|
|
297
|
+
type: "peer:join",
|
|
298
|
+
data: { peerId },
|
|
299
|
+
};
|
|
300
|
+
system.netcode?.(
|
|
301
|
+
this.#context as Context<GS> & { event: NetEvent },
|
|
302
|
+
);
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
case Enums.EventType.NetPeerLeave: {
|
|
306
|
+
const peerId = eventsDataView.getUint8(payloadOffset);
|
|
307
|
+
(this.#context as any).event = {
|
|
308
|
+
type: "peer:leave",
|
|
309
|
+
data: { peerId },
|
|
310
|
+
};
|
|
311
|
+
system.netcode?.(
|
|
312
|
+
this.#context as Context<GS> & { event: NetEvent },
|
|
313
|
+
);
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
257
316
|
default:
|
|
258
|
-
|
|
317
|
+
// Session lifecycle and other internal events are handled by engine
|
|
318
|
+
// They don't need to be dispatched to game systems
|
|
319
|
+
break;
|
|
259
320
|
}
|
|
260
321
|
// Event is 12 bytes: kind (1) + source (1) + padding (2) + payload (8)
|
|
261
322
|
offset += 12;
|
package/src/sim.ts
CHANGED
|
@@ -4,12 +4,14 @@ import {
|
|
|
4
4
|
keyToKeyCode,
|
|
5
5
|
type MouseButton,
|
|
6
6
|
mouseButtonToMouseButtonCode,
|
|
7
|
+
NetContext,
|
|
8
|
+
type NetEvent,
|
|
9
|
+
type NetEventType,
|
|
7
10
|
SNAPSHOT_HEADER_ENGINE_LEN_OFFSET,
|
|
8
11
|
SNAPSHOT_HEADER_USER_LEN_OFFSET,
|
|
9
12
|
TimeContext,
|
|
10
13
|
type WasmEngine,
|
|
11
14
|
} from "@bloopjs/engine";
|
|
12
|
-
import { Net } from "./net";
|
|
13
15
|
import { assert } from "./util";
|
|
14
16
|
|
|
15
17
|
const originalConsole = (globalThis as any).console;
|
|
@@ -90,10 +92,10 @@ export class Sim {
|
|
|
90
92
|
#isPaused: boolean = false;
|
|
91
93
|
|
|
92
94
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
+
* Shared network context - reads from same WASM memory as game.context.net.
|
|
96
|
+
* Provides access to network state (status, peers, roomCode, wantsRoomCode, etc.)
|
|
95
97
|
*/
|
|
96
|
-
|
|
98
|
+
#net: NetContext;
|
|
97
99
|
|
|
98
100
|
/**
|
|
99
101
|
* Callback fired when tape buffer fills up and recording stops.
|
|
@@ -115,9 +117,17 @@ export class Sim {
|
|
|
115
117
|
this.id = `${Math.floor(Math.random() * 1_000_000)}`;
|
|
116
118
|
|
|
117
119
|
this.#serialize = opts?.serialize;
|
|
118
|
-
this
|
|
120
|
+
this.#net = new NetContext();
|
|
119
121
|
}
|
|
120
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Step simulation forward by a given number of milliseconds.
|
|
125
|
+
* If ms is not provided, defaults to 16ms (60fps).
|
|
126
|
+
*
|
|
127
|
+
* Note: this will not advance time if the sim is paused.
|
|
128
|
+
*
|
|
129
|
+
* @returns Number of frames actually advanced
|
|
130
|
+
*/
|
|
121
131
|
step(ms?: number): number {
|
|
122
132
|
if (this.#isPaused) {
|
|
123
133
|
return 0;
|
|
@@ -147,18 +157,30 @@ export class Sim {
|
|
|
147
157
|
this.restore(source.snapshot());
|
|
148
158
|
}
|
|
149
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Pause the simulation entirely
|
|
162
|
+
*/
|
|
150
163
|
pause() {
|
|
151
164
|
this.#isPaused = true;
|
|
152
165
|
}
|
|
153
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Unpause the simulation
|
|
169
|
+
*/
|
|
154
170
|
unpause() {
|
|
155
171
|
this.#isPaused = false;
|
|
156
172
|
}
|
|
157
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Whether the simulation is currently paused
|
|
176
|
+
*/
|
|
158
177
|
get isPaused(): boolean {
|
|
159
178
|
return this.#isPaused;
|
|
160
179
|
}
|
|
161
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Step back one frame
|
|
183
|
+
*/
|
|
162
184
|
stepBack() {
|
|
163
185
|
if (this.time.frame === 0) {
|
|
164
186
|
return;
|
|
@@ -168,8 +190,11 @@ export class Sim {
|
|
|
168
190
|
}
|
|
169
191
|
|
|
170
192
|
/**
|
|
171
|
-
* Seek to the start of a given frame
|
|
172
|
-
*
|
|
193
|
+
* Seek to the start of a given frame.
|
|
194
|
+
*
|
|
195
|
+
* Note that this will mute console output during the seek if rewinding.
|
|
196
|
+
*
|
|
197
|
+
* @param frame - frame number to replay up to
|
|
173
198
|
*/
|
|
174
199
|
seek(frame: number, inclusive?: boolean) {
|
|
175
200
|
assert(
|
|
@@ -268,7 +293,9 @@ export class Sim {
|
|
|
268
293
|
memoryView.set(tape);
|
|
269
294
|
|
|
270
295
|
// load the tape
|
|
271
|
-
this.
|
|
296
|
+
if (this.isRecording) {
|
|
297
|
+
this.wasm.stop_recording();
|
|
298
|
+
}
|
|
272
299
|
const result = this.wasm.load_tape(tapePtr, tape.byteLength);
|
|
273
300
|
assert(result === 0, `failed to load tape, error code=${result}`);
|
|
274
301
|
|
|
@@ -322,6 +349,20 @@ export class Sim {
|
|
|
322
349
|
return this.#time;
|
|
323
350
|
}
|
|
324
351
|
|
|
352
|
+
get net(): NetContext {
|
|
353
|
+
if (
|
|
354
|
+
!this.#net.dataView ||
|
|
355
|
+
this.#net.dataView.buffer !== this.#memory.buffer
|
|
356
|
+
) {
|
|
357
|
+
// update the data view to the latest memory buffer
|
|
358
|
+
this.#net.dataView = new DataView(
|
|
359
|
+
this.#memory.buffer,
|
|
360
|
+
this.wasm.get_net_ctx(),
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return this.#net;
|
|
364
|
+
}
|
|
365
|
+
|
|
325
366
|
get buffer(): ArrayBuffer {
|
|
326
367
|
return this.#memory.buffer;
|
|
327
368
|
}
|
|
@@ -357,29 +398,120 @@ export class Sim {
|
|
|
357
398
|
mousewheel: (x: number, y: number, peerId: number = 0): void => {
|
|
358
399
|
this.wasm.emit_mousewheel(x, y, peerId);
|
|
359
400
|
},
|
|
360
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Emit a network event (join:ok, peer:join, etc.)
|
|
403
|
+
* Events are queued in the engine and processed during the next tick's systemsCallback.
|
|
404
|
+
*/
|
|
405
|
+
network: <T extends NetEventType>(
|
|
406
|
+
type: T,
|
|
407
|
+
data: Extract<NetEvent, { type: T }>["data"],
|
|
408
|
+
): void => {
|
|
409
|
+
switch (type) {
|
|
410
|
+
case "join:ok": {
|
|
411
|
+
const roomCode = (data as { roomCode: string }).roomCode;
|
|
412
|
+
const encoded = new TextEncoder().encode(roomCode);
|
|
413
|
+
const ptr = this.wasm.alloc(encoded.length);
|
|
414
|
+
new Uint8Array(this.#memory.buffer, ptr, encoded.length).set(encoded);
|
|
415
|
+
this.wasm.emit_net_join_ok(ptr, encoded.length);
|
|
416
|
+
this.wasm.free(ptr, encoded.length);
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
case "join:fail":
|
|
420
|
+
this.wasm.emit_net_join_fail(0); // reason code: unknown
|
|
421
|
+
break;
|
|
422
|
+
case "peer:join": {
|
|
423
|
+
const peerId = (data as { peerId: number }).peerId;
|
|
424
|
+
this.wasm.emit_net_peer_join(peerId);
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
case "peer:leave": {
|
|
428
|
+
const peerId = (data as { peerId: number }).peerId;
|
|
429
|
+
this.wasm.emit_net_peer_leave(peerId);
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
case "peer:assign_local_id": {
|
|
433
|
+
const peerId = (data as { peerId: number }).peerId;
|
|
434
|
+
this.wasm.emit_net_peer_assign_local_id(peerId);
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
case "session:start":
|
|
438
|
+
this.wasm.emit_net_session_init();
|
|
439
|
+
break;
|
|
440
|
+
case "session:end":
|
|
441
|
+
this.wasm.emit_net_session_end();
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
},
|
|
361
445
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
446
|
+
/**
|
|
447
|
+
* Receive a packet from a peer
|
|
448
|
+
*/
|
|
449
|
+
packet: (data: Uint8Array): void => {
|
|
450
|
+
if (data.length === 0) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Allocate memory and copy packet
|
|
455
|
+
const ptr = this.wasm.alloc(data.byteLength);
|
|
456
|
+
assert(
|
|
457
|
+
ptr > 0,
|
|
458
|
+
`Failed to allocate ${data.byteLength} bytes for received packet`,
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const memoryView = new Uint8Array(
|
|
462
|
+
this.#memory.buffer,
|
|
463
|
+
ptr,
|
|
464
|
+
data.byteLength,
|
|
465
|
+
);
|
|
466
|
+
memoryView.set(data);
|
|
467
|
+
|
|
468
|
+
// Process the packet
|
|
469
|
+
const result = this.wasm.emit_receive_packet(ptr, data.byteLength);
|
|
470
|
+
|
|
471
|
+
// Free the allocated memory
|
|
472
|
+
this.wasm.free(ptr, data.byteLength);
|
|
473
|
+
|
|
474
|
+
// Check result
|
|
475
|
+
if (result !== 0) {
|
|
476
|
+
const errorMessages: Record<number, string> = {
|
|
477
|
+
1: "No active session",
|
|
478
|
+
2: "Buffer too small",
|
|
479
|
+
3: "Unsupported packet version",
|
|
480
|
+
4: "Invalid event count",
|
|
481
|
+
};
|
|
482
|
+
throw new Error(
|
|
483
|
+
errorMessages[result] ?? `Unknown packet error: ${result}`,
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
};
|
|
365
488
|
|
|
366
489
|
/**
|
|
367
|
-
*
|
|
368
|
-
*
|
|
490
|
+
* Get an outbound packet to send to a target peer.
|
|
491
|
+
* Returns a copy of the packet data (caller owns the returned buffer).
|
|
369
492
|
*
|
|
370
|
-
*
|
|
493
|
+
* The packet contains all unacked inputs encoded in wire format.
|
|
494
|
+
*
|
|
495
|
+
* @param targetPeer - Peer ID to send the packet to
|
|
496
|
+
* @returns Packet data to send, or null if no packet available
|
|
371
497
|
*/
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const result = this.wasm.session_init(peerCount, size);
|
|
376
|
-
assert(result === 0, `failed to initialize session, error code=${result}`);
|
|
377
|
-
}
|
|
498
|
+
getOutboundPacket(targetPeer: number): Uint8Array<ArrayBuffer> | null {
|
|
499
|
+
// Build the packet in engine memory
|
|
500
|
+
this.wasm.build_outbound_packet(targetPeer);
|
|
378
501
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
502
|
+
const len = this.wasm.get_outbound_packet_len();
|
|
503
|
+
if (len === 0) {
|
|
504
|
+
throw new Error(`No outbound packet available for peer ${targetPeer}`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const ptr = this.wasm.get_outbound_packet();
|
|
508
|
+
assert(ptr > 0, `Invalid outbound packet pointer: ${ptr}`);
|
|
509
|
+
|
|
510
|
+
// Copy from WASM memory
|
|
511
|
+
const memoryView = new Uint8Array(this.#memory.buffer, ptr, len);
|
|
512
|
+
const copy = new Uint8Array(len);
|
|
513
|
+
copy.set(memoryView);
|
|
514
|
+
|
|
515
|
+
return copy;
|
|
384
516
|
}
|
|
385
517
|
}
|
package/src/system.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NetEvent } from "@bloopjs/engine";
|
|
1
2
|
import type { Context } from "./context";
|
|
2
3
|
import type { BloopSchema } from "./data/schema";
|
|
3
4
|
import type {
|
|
@@ -47,4 +48,11 @@ export type System<GS extends BloopSchema = BloopSchema> = {
|
|
|
47
48
|
event: MouseWheelEvent;
|
|
48
49
|
},
|
|
49
50
|
) => void;
|
|
51
|
+
|
|
52
|
+
/** Handle network events (join:ok, peer:join, etc.) */
|
|
53
|
+
netcode?: (
|
|
54
|
+
context: Context<GS> & {
|
|
55
|
+
event: NetEvent;
|
|
56
|
+
},
|
|
57
|
+
) => void;
|
|
50
58
|
};
|
package/dist/net.d.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { WasmEngine } from "@bloopjs/engine";
|
|
2
|
-
/**
|
|
3
|
-
* Network API for packet management in multiplayer sessions.
|
|
4
|
-
*
|
|
5
|
-
* This class provides methods for:
|
|
6
|
-
* - Building outbound packets to send to peers
|
|
7
|
-
* - Processing received packets
|
|
8
|
-
* - Querying peer network state (seq/ack/unacked)
|
|
9
|
-
* - Managing peer connections
|
|
10
|
-
*
|
|
11
|
-
* Access via `sim.net` after initializing a session.
|
|
12
|
-
*/
|
|
13
|
-
export declare class Net {
|
|
14
|
-
#private;
|
|
15
|
-
constructor(wasm: WasmEngine, memory: WebAssembly.Memory);
|
|
16
|
-
/**
|
|
17
|
-
* Set the local peer ID for packet encoding.
|
|
18
|
-
* Call this after session init to identify which peer we are.
|
|
19
|
-
*/
|
|
20
|
-
setLocalPeer(peerId: number): void;
|
|
21
|
-
/**
|
|
22
|
-
* Mark a peer as connected for packet management.
|
|
23
|
-
* Call this when a peer joins the session.
|
|
24
|
-
*/
|
|
25
|
-
connectPeer(peerId: number): void;
|
|
26
|
-
/**
|
|
27
|
-
* Mark a peer as disconnected.
|
|
28
|
-
* Call this when a peer leaves the session.
|
|
29
|
-
*/
|
|
30
|
-
disconnectPeer(peerId: number): void;
|
|
31
|
-
/**
|
|
32
|
-
* Get an outbound packet to send to a target peer.
|
|
33
|
-
* Returns a copy of the packet data (caller owns the returned buffer).
|
|
34
|
-
*
|
|
35
|
-
* The packet contains all unacked inputs encoded in wire format.
|
|
36
|
-
*
|
|
37
|
-
* @param targetPeer - Peer ID to send the packet to
|
|
38
|
-
* @returns Packet data to send, or null if no packet available
|
|
39
|
-
*/
|
|
40
|
-
getOutboundPacket(targetPeer: number): Uint8Array<ArrayBuffer> | null;
|
|
41
|
-
/**
|
|
42
|
-
* Process a received packet from a peer.
|
|
43
|
-
* This updates seq/ack state and stores events for rollback.
|
|
44
|
-
*
|
|
45
|
-
* @param data - Packet data received from the network
|
|
46
|
-
* @throws Error if packet is invalid
|
|
47
|
-
*/
|
|
48
|
-
receivePacket(data: Uint8Array): void;
|
|
49
|
-
/**
|
|
50
|
-
* Get network state for a specific peer.
|
|
51
|
-
*
|
|
52
|
-
* @param peer - Peer ID to query
|
|
53
|
-
* @returns Object with seq and ack
|
|
54
|
-
*/
|
|
55
|
-
getPeerState(peer: number): {
|
|
56
|
-
seq: number;
|
|
57
|
-
ack: number;
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
//# sourceMappingURL=net.d.ts.map
|
package/dist/net.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"net.d.ts","sourceRoot":"","sources":["../src/net.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGjE;;;;;;;;;;GAUG;AACH,qBAAa,GAAG;;gBAIF,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM;IAKxD;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIlC;;;OAGG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIjC;;;OAGG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIpC;;;;;;;;OAQG;IACH,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,IAAI;IAoBrE;;;;;;OAMG;IACH,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAuCrC;;;;;OAKG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;QAC1B,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;KACb;CAMF"}
|