@bloopjs/bloop 0.0.21 → 0.0.23
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 +12 -13
- package/dist/bloop.d.ts.map +1 -1
- package/dist/mod.d.ts +6 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +140 -129
- package/dist/mod.js.map +7 -6
- package/dist/mount.d.ts +17 -0
- package/dist/mount.d.ts.map +1 -0
- package/dist/runtime.d.ts +11 -21
- package/dist/runtime.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/bloop.ts +145 -129
- package/src/mod.ts +8 -3
- package/src/mount.ts +86 -0
- package/src/runtime.ts +41 -94
package/src/bloop.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
MouseMoveEvent,
|
|
22
22
|
MouseWheelEvent,
|
|
23
23
|
} from "./events";
|
|
24
|
-
import type { DeserializeFn, SerializeFn } from "./runtime";
|
|
24
|
+
import type { DeserializeFn, EngineHooks, SerializeFn } from "./runtime";
|
|
25
25
|
import type { System } from "./system";
|
|
26
26
|
|
|
27
27
|
export type BloopOpts<B extends Bag> = {
|
|
@@ -63,6 +63,9 @@ export class Bloop<GS extends BloopSchema> {
|
|
|
63
63
|
return new Bloop<MakeGS<B>>(opts, "dontCallMeDirectly");
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* DO NOT USE `new Bloop` - use `Bloop.create()` instead for proper type hints.
|
|
68
|
+
*/
|
|
66
69
|
constructor(opts: BloopOpts<GS["B"]> = {}, dontCallMeDirectly: string) {
|
|
67
70
|
if (dontCallMeDirectly !== "dontCallMeDirectly") {
|
|
68
71
|
throw new Error(
|
|
@@ -78,51 +81,22 @@ export class Bloop<GS extends BloopSchema> {
|
|
|
78
81
|
};
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Read the game singleton bag
|
|
86
|
+
*/
|
|
81
87
|
get bag(): GS["B"] {
|
|
82
88
|
return this.#context.bag;
|
|
83
89
|
}
|
|
84
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Read the game context object
|
|
93
|
+
*/
|
|
85
94
|
get context(): Readonly<Context<GS>> {
|
|
86
95
|
return this.#context;
|
|
87
96
|
}
|
|
88
97
|
|
|
89
|
-
/**
|
|
90
|
-
* Take a snapshot of the game state outside the engine
|
|
91
|
-
* @returns linear memory representation of the game state that the engine is unaware of
|
|
92
|
-
*/
|
|
93
|
-
serialize = (): ReturnType<SerializeFn> => {
|
|
94
|
-
const str = JSON.stringify(this.#context.bag);
|
|
95
|
-
const encoder = new TextEncoder();
|
|
96
|
-
const textBytes = encoder.encode(str);
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
size: textBytes.byteLength,
|
|
100
|
-
write: (buffer: ArrayBufferLike, ptr: EnginePointer) => {
|
|
101
|
-
const memoryView = new Uint8Array(buffer, ptr, textBytes.byteLength);
|
|
102
|
-
memoryView.set(textBytes);
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Restore a snapshot of the game state outside the engine
|
|
109
|
-
* @returns linear memory representation of the game state that the engine is unaware of
|
|
110
|
-
*/
|
|
111
|
-
deserialize: DeserializeFn = (buffer, ptr, len) => {
|
|
112
|
-
const bagBytes = new Uint8Array(buffer, ptr, len);
|
|
113
|
-
const decoder = new TextDecoder();
|
|
114
|
-
const str = decoder.decode(bagBytes);
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
this.#context.bag = JSON.parse(str);
|
|
118
|
-
} catch (e) {
|
|
119
|
-
console.error("failed to deserialize bag", { json: str, error: e });
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
|
|
123
98
|
/**
|
|
124
99
|
* Register a system with the game loop.
|
|
125
|
-
*
|
|
126
100
|
*/
|
|
127
101
|
system(label: string, system: System<GS>): number {
|
|
128
102
|
system.label ??= label;
|
|
@@ -130,102 +104,144 @@ export class Bloop<GS extends BloopSchema> {
|
|
|
130
104
|
return this.#systems.length;
|
|
131
105
|
}
|
|
132
106
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.#
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Low level hooks to engine functionality. Editing these is for advanced use cases, defaults should usually work.
|
|
109
|
+
*/
|
|
110
|
+
hooks: EngineHooks = {
|
|
111
|
+
/**
|
|
112
|
+
* Take a snapshot of the game state outside the engine
|
|
113
|
+
* @returns linear memory representation of the game state that the engine is unaware of
|
|
114
|
+
*/
|
|
115
|
+
serialize: () => {
|
|
116
|
+
const str = JSON.stringify(this.#context.bag);
|
|
117
|
+
const encoder = new TextEncoder();
|
|
118
|
+
const textBytes = encoder.encode(str);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
size: textBytes.byteLength,
|
|
122
|
+
write: (buffer: ArrayBufferLike, ptr: EnginePointer) => {
|
|
123
|
+
const memoryView = new Uint8Array(buffer, ptr, textBytes.byteLength);
|
|
124
|
+
memoryView.set(textBytes);
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Restore a snapshot of the game state outside the engine
|
|
131
|
+
* @returns linear memory representation of the game state that the engine is unaware of
|
|
132
|
+
*/
|
|
133
|
+
deserialize: (buffer, ptr, len) => {
|
|
134
|
+
const bagBytes = new Uint8Array(buffer, ptr, len);
|
|
135
|
+
const decoder = new TextDecoder();
|
|
136
|
+
const str = decoder.decode(bagBytes);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
this.#context.bag = JSON.parse(str);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error("failed to deserialize bag", { json: str, error: e });
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
setBuffer: (buffer: ArrayBuffer) => {
|
|
146
|
+
this.#engineBuffer = buffer;
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
systemsCallback: (system_handle: number, ptr: EnginePointer) => {
|
|
150
|
+
// todo - move this to engine
|
|
151
|
+
const dv = new DataView(this.#engineBuffer, ptr);
|
|
152
|
+
const timeCtxPtr = dv.getUint32(TIME_CTX_OFFSET, true);
|
|
153
|
+
const inputCtxPtr = dv.getUint32(INPUT_CTX_OFFSET, true);
|
|
154
|
+
const eventsPtr = dv.getUint32(EVENTS_OFFSET, true);
|
|
155
|
+
|
|
156
|
+
this.#context.rawPointer = ptr;
|
|
157
|
+
this.#context.inputs.dataView = new DataView(
|
|
158
|
+
this.#engineBuffer,
|
|
159
|
+
inputCtxPtr,
|
|
160
|
+
);
|
|
161
|
+
this.#context.time.dataView = new DataView(
|
|
162
|
+
this.#engineBuffer,
|
|
163
|
+
timeCtxPtr,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const eventsDataView = new DataView(this.#engineBuffer, eventsPtr);
|
|
167
|
+
|
|
168
|
+
for (const system of this.#systems) {
|
|
169
|
+
system.update?.(this.#context);
|
|
170
|
+
|
|
171
|
+
const eventCount = eventsDataView.getUint32(0, true);
|
|
172
|
+
|
|
173
|
+
let offset = Uint32Array.BYTES_PER_ELEMENT;
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < eventCount; i++) {
|
|
176
|
+
const eventType = eventsDataView.getUint8(offset);
|
|
177
|
+
const payloadSize = EVENT_PAYLOAD_SIZE;
|
|
178
|
+
const payloadByte = eventsDataView.getUint8(
|
|
179
|
+
offset + EVENT_PAYLOAD_ALIGN,
|
|
180
|
+
);
|
|
181
|
+
const payloadVec2 = {
|
|
182
|
+
x: eventsDataView.getFloat32(offset + EVENT_PAYLOAD_ALIGN, true),
|
|
183
|
+
y: eventsDataView.getFloat32(
|
|
184
|
+
offset + EVENT_PAYLOAD_ALIGN + Float32Array.BYTES_PER_ELEMENT,
|
|
185
|
+
true,
|
|
186
|
+
),
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
switch (eventType) {
|
|
190
|
+
case Enums.EventType.KeyDown: {
|
|
191
|
+
system.keydown?.(
|
|
192
|
+
attachEvent<KeyEvent, GS>(this.#context, {
|
|
193
|
+
key: keyCodeToKey(payloadByte),
|
|
194
|
+
}),
|
|
195
|
+
);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case Enums.EventType.KeyUp:
|
|
199
|
+
system.keyup?.(
|
|
200
|
+
attachEvent<KeyEvent, GS>(this.#context, {
|
|
201
|
+
key: keyCodeToKey(payloadByte),
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
break;
|
|
205
|
+
case Enums.EventType.MouseDown:
|
|
206
|
+
system.mousedown?.(
|
|
207
|
+
attachEvent<MouseButtonEvent, GS>(this.#context, {
|
|
208
|
+
button: mouseButtonCodeToMouseButton(payloadByte),
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
211
|
+
break;
|
|
212
|
+
case Enums.EventType.MouseUp:
|
|
213
|
+
system.mouseup?.(
|
|
214
|
+
attachEvent<MouseButtonEvent, GS>(this.#context, {
|
|
215
|
+
button: mouseButtonCodeToMouseButton(payloadByte),
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
break;
|
|
219
|
+
case Enums.EventType.MouseMove:
|
|
220
|
+
system.mousemove?.(
|
|
221
|
+
attachEvent<MouseMoveEvent, GS>(this.#context, {
|
|
222
|
+
x: payloadVec2.x,
|
|
223
|
+
y: payloadVec2.y,
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
226
|
+
break;
|
|
227
|
+
case Enums.EventType.MouseWheel:
|
|
228
|
+
system.mousewheel?.(
|
|
229
|
+
attachEvent<MouseWheelEvent, GS>(this.#context, {
|
|
230
|
+
x: payloadVec2.x,
|
|
231
|
+
y: payloadVec2.y,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
throw new Error(`Unknown event type: ${eventType}`);
|
|
178
237
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
attachEvent<KeyEvent, GS>(this.#context, {
|
|
182
|
-
key: keyCodeToKey(payloadByte),
|
|
183
|
-
}),
|
|
184
|
-
);
|
|
185
|
-
break;
|
|
186
|
-
case Enums.EventType.MouseDown:
|
|
187
|
-
system.mousedown?.(
|
|
188
|
-
attachEvent<MouseButtonEvent, GS>(this.#context, {
|
|
189
|
-
button: mouseButtonCodeToMouseButton(payloadByte),
|
|
190
|
-
}),
|
|
191
|
-
);
|
|
192
|
-
break;
|
|
193
|
-
case Enums.EventType.MouseUp:
|
|
194
|
-
system.mouseup?.(
|
|
195
|
-
attachEvent<MouseButtonEvent, GS>(this.#context, {
|
|
196
|
-
button: mouseButtonCodeToMouseButton(payloadByte),
|
|
197
|
-
}),
|
|
198
|
-
);
|
|
199
|
-
break;
|
|
200
|
-
case Enums.EventType.MouseMove:
|
|
201
|
-
system.mousemove?.(
|
|
202
|
-
attachEvent<MouseMoveEvent, GS>(this.#context, {
|
|
203
|
-
x: payloadVec2.x,
|
|
204
|
-
y: payloadVec2.y,
|
|
205
|
-
}),
|
|
206
|
-
);
|
|
207
|
-
break;
|
|
208
|
-
case Enums.EventType.MouseWheel:
|
|
209
|
-
system.mousewheel?.(
|
|
210
|
-
attachEvent<MouseWheelEvent, GS>(this.#context, {
|
|
211
|
-
x: payloadVec2.x,
|
|
212
|
-
y: payloadVec2.y,
|
|
213
|
-
}),
|
|
214
|
-
);
|
|
215
|
-
break;
|
|
216
|
-
default:
|
|
217
|
-
throw new Error(`Unknown event type: ${eventType}`);
|
|
238
|
+
// the event type u8 + padding + payload
|
|
239
|
+
offset += EVENT_PAYLOAD_ALIGN + EVENT_PAYLOAD_SIZE;
|
|
218
240
|
}
|
|
219
|
-
|
|
220
|
-
offset += EVENT_PAYLOAD_ALIGN + EVENT_PAYLOAD_SIZE;
|
|
241
|
+
(this.#context as any).event = undefined;
|
|
221
242
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
setBuffer(buffer: ArrayBuffer) {
|
|
227
|
-
this.#engineBuffer = buffer;
|
|
228
|
-
}
|
|
243
|
+
},
|
|
244
|
+
};
|
|
229
245
|
}
|
|
230
246
|
|
|
231
247
|
function attachEvent<IE extends InputEvent, GS extends BloopSchema>(
|
package/src/mod.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
export * from "./runtime"
|
|
2
|
-
export * from "./
|
|
1
|
+
export type * as typesRuntime from "./runtime"
|
|
2
|
+
export type * as typesMount from "./mount"
|
|
3
|
+
export type * as typesBloop from "./bloop"
|
|
4
|
+
export type * as typesContext from "./context"
|
|
3
5
|
|
|
4
|
-
export * as Util from "./util";
|
|
6
|
+
export * as Util from "./util";
|
|
7
|
+
|
|
8
|
+
export { mount } from "./mount";
|
|
9
|
+
export { Bloop } from "./bloop"
|
package/src/mount.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_WASM_URL,
|
|
3
|
+
type EnginePointer,
|
|
4
|
+
type WasmEngine,
|
|
5
|
+
} from "@bloopjs/engine";
|
|
6
|
+
import { type EngineHooks, Runtime } from "./runtime";
|
|
7
|
+
import { assert } from "./util";
|
|
8
|
+
|
|
9
|
+
export async function mount(opts: MountOpts): Promise<MountResult> {
|
|
10
|
+
// https://github.com/oven-sh/bun/issues/12434
|
|
11
|
+
const bytes = await fetch(opts.wasmUrl ?? DEFAULT_WASM_URL)
|
|
12
|
+
.then((res) => res.arrayBuffer())
|
|
13
|
+
.catch((e) => {
|
|
14
|
+
console.error(
|
|
15
|
+
`Failed to fetch wasm at ${opts.wasmUrl ?? DEFAULT_WASM_URL}`,
|
|
16
|
+
e,
|
|
17
|
+
);
|
|
18
|
+
throw e;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// 1mb to 64mb
|
|
22
|
+
// use bun check:wasm to find initial memory page size
|
|
23
|
+
const memory = new WebAssembly.Memory({ initial: 17, maximum: 1000 });
|
|
24
|
+
const wasmInstantiatedSource = await WebAssembly.instantiate(bytes, {
|
|
25
|
+
env: {
|
|
26
|
+
memory,
|
|
27
|
+
__cb: function (system_handle: number, ptr: number) {
|
|
28
|
+
opts.hooks.setBuffer(memory.buffer);
|
|
29
|
+
opts.hooks.systemsCallback(system_handle, ptr);
|
|
30
|
+
},
|
|
31
|
+
console_log: function (ptr: EnginePointer, len: number) {
|
|
32
|
+
const bytes = new Uint8Array(memory.buffer, ptr, len);
|
|
33
|
+
const string = new TextDecoder("utf-8").decode(bytes);
|
|
34
|
+
console.log(string);
|
|
35
|
+
},
|
|
36
|
+
user_data_len: function () {
|
|
37
|
+
const serializer = opts.hooks.serialize();
|
|
38
|
+
return serializer ? serializer.size : 0;
|
|
39
|
+
},
|
|
40
|
+
user_data_serialize: function (ptr: EnginePointer, len: number) {
|
|
41
|
+
const serializer = opts.hooks.serialize();
|
|
42
|
+
assert(
|
|
43
|
+
len === serializer.size,
|
|
44
|
+
`user_data_serialize length mismatch, expected=${serializer.size} got=${len}`,
|
|
45
|
+
);
|
|
46
|
+
serializer.write(memory.buffer, ptr);
|
|
47
|
+
},
|
|
48
|
+
user_data_deserialize: function (ptr: EnginePointer, len: number) {
|
|
49
|
+
opts.hooks.deserialize(memory.buffer, ptr, len);
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const wasm = wasmInstantiatedSource.instance.exports as WasmEngine;
|
|
54
|
+
|
|
55
|
+
wasm.initialize();
|
|
56
|
+
|
|
57
|
+
const runtime = new Runtime(wasm, memory, {
|
|
58
|
+
serialize: opts.hooks.serialize,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (opts.startRecording ?? true) {
|
|
62
|
+
runtime.record();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
runtime,
|
|
67
|
+
wasm,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type MountOpts = {
|
|
72
|
+
hooks: EngineHooks;
|
|
73
|
+
|
|
74
|
+
wasmUrl?: URL;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether to start recording immediately upon mount
|
|
78
|
+
* Defaults to true
|
|
79
|
+
*/
|
|
80
|
+
startRecording?: boolean;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type MountResult = {
|
|
84
|
+
runtime: Runtime;
|
|
85
|
+
wasm: WasmEngine;
|
|
86
|
+
};
|
package/src/runtime.ts
CHANGED
|
@@ -6,44 +6,55 @@ import {
|
|
|
6
6
|
type MouseButton,
|
|
7
7
|
mouseButtonToMouseButtonCode,
|
|
8
8
|
SNAPSHOT_HEADER_ENGINE_LEN_OFFSET,
|
|
9
|
-
SNAPSHOT_HEADER_LEN,
|
|
10
9
|
SNAPSHOT_HEADER_USER_LEN_OFFSET,
|
|
11
10
|
TimeContext,
|
|
12
11
|
type WasmEngine,
|
|
13
12
|
} from "@bloopjs/engine";
|
|
14
13
|
import { assert } from "./util";
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
const originalConsole = (globalThis as any).console;
|
|
16
|
+
|
|
17
|
+
const noop = () => {};
|
|
18
|
+
|
|
19
|
+
const stubConsole = Object.fromEntries(
|
|
20
|
+
Object.keys(originalConsole).map((key) => [key, noop]),
|
|
21
|
+
) as unknown as Console;
|
|
22
|
+
|
|
23
|
+
function muteConsole() {
|
|
24
|
+
(globalThis.console as unknown as Console) = stubConsole;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function unmuteConsole() {
|
|
28
|
+
(globalThis.console as unknown as Console) = originalConsole;
|
|
29
|
+
}
|
|
22
30
|
|
|
31
|
+
|
|
32
|
+
export type EngineHooks = {
|
|
23
33
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* Note that if the engine wasm memory grows, all dataviews into the memory must be updated
|
|
34
|
+
* Hook to serialize some data when snapshotting
|
|
27
35
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
serialize: SerializeFn;
|
|
30
37
|
/**
|
|
31
|
-
*
|
|
32
|
-
* Defaults to true
|
|
38
|
+
* Hook to deserialize some data when restoring
|
|
33
39
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
deserialize: DeserializeFn;
|
|
36
41
|
/**
|
|
37
|
-
*
|
|
42
|
+
* A callback function to run logic for a given frame
|
|
38
43
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
systemsCallback: SystemsCallback;
|
|
41
45
|
/**
|
|
42
|
-
*
|
|
46
|
+
* Sets buffer to the latest engine memory buffer
|
|
47
|
+
*
|
|
48
|
+
* Note that if the engine wasm memory grows, all dataviews into the memory must be updated
|
|
43
49
|
*/
|
|
44
|
-
|
|
50
|
+
setBuffer: (buffer: ArrayBuffer) => void;
|
|
45
51
|
};
|
|
46
52
|
|
|
53
|
+
export type SystemsCallback = (
|
|
54
|
+
system_handle: number,
|
|
55
|
+
ptr: EnginePointer,
|
|
56
|
+
) => void;
|
|
57
|
+
|
|
47
58
|
export type SerializeFn = () => {
|
|
48
59
|
size: number;
|
|
49
60
|
write(buffer: ArrayBufferLike, ptr: EnginePointer): void;
|
|
@@ -87,6 +98,7 @@ export class Runtime {
|
|
|
87
98
|
if (this.time.frame === 0) {
|
|
88
99
|
return;
|
|
89
100
|
}
|
|
101
|
+
|
|
90
102
|
this.seek(this.time.frame - 1);
|
|
91
103
|
}
|
|
92
104
|
|
|
@@ -95,7 +107,15 @@ export class Runtime {
|
|
|
95
107
|
this.hasHistory,
|
|
96
108
|
"Not recording or playing back, can't seek to frame",
|
|
97
109
|
);
|
|
110
|
+
|
|
111
|
+
const shouldMute = frame < this.time.frame;
|
|
112
|
+
if (shouldMute) {
|
|
113
|
+
muteConsole();
|
|
114
|
+
}
|
|
98
115
|
this.wasm.seek(frame);
|
|
116
|
+
if (shouldMute) {
|
|
117
|
+
unmuteConsole();
|
|
118
|
+
}
|
|
99
119
|
}
|
|
100
120
|
|
|
101
121
|
record() {
|
|
@@ -198,76 +218,3 @@ export class Runtime {
|
|
|
198
218
|
},
|
|
199
219
|
};
|
|
200
220
|
}
|
|
201
|
-
|
|
202
|
-
export type MountResult = {
|
|
203
|
-
runtime: Runtime;
|
|
204
|
-
wasm: WasmEngine;
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
export async function mount(opts: MountOpts): Promise<MountResult> {
|
|
208
|
-
if (
|
|
209
|
-
(opts.serialize && !opts.deserialize) ||
|
|
210
|
-
(!opts.serialize && opts.deserialize)
|
|
211
|
-
) {
|
|
212
|
-
throw new Error("Snapshot and restore hooks must be provided together");
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// https://github.com/oven-sh/bun/issues/12434
|
|
216
|
-
const bytes = await Bun.file(opts.wasmUrl ?? DEFAULT_WASM_URL).arrayBuffer();
|
|
217
|
-
|
|
218
|
-
// 1mb to 64mb
|
|
219
|
-
// use bun check:wasm to find initial memory page size
|
|
220
|
-
const memory = new WebAssembly.Memory({ initial: 17, maximum: 1000 });
|
|
221
|
-
const wasmInstantiatedSource = await WebAssembly.instantiate(bytes, {
|
|
222
|
-
env: {
|
|
223
|
-
memory,
|
|
224
|
-
__cb: function (system_handle: number, ptr: number) {
|
|
225
|
-
opts.setBuffer(memory.buffer);
|
|
226
|
-
opts.systemsCallback(system_handle, ptr);
|
|
227
|
-
},
|
|
228
|
-
console_log: function (ptr: EnginePointer, len: number) {
|
|
229
|
-
const bytes = new Uint8Array(memory.buffer, ptr, len);
|
|
230
|
-
const string = new TextDecoder("utf-8").decode(bytes);
|
|
231
|
-
console.log(string);
|
|
232
|
-
},
|
|
233
|
-
user_data_len: function () {
|
|
234
|
-
const serializer = opts.serialize ? opts.serialize() : null;
|
|
235
|
-
return serializer ? serializer.size : 0;
|
|
236
|
-
},
|
|
237
|
-
user_data_serialize: function (ptr: EnginePointer, len: number) {
|
|
238
|
-
if (!opts.serialize) {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
const serializer = opts.serialize();
|
|
242
|
-
if (len !== serializer.size) {
|
|
243
|
-
throw new Error(
|
|
244
|
-
`user_data_write length mismatch, expected=${serializer.size} got=${len}`,
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
serializer.write(memory.buffer, ptr);
|
|
248
|
-
},
|
|
249
|
-
user_data_deserialize: function (ptr: EnginePointer, len: number) {
|
|
250
|
-
if (!opts.deserialize) {
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
opts.deserialize(memory.buffer, ptr, len);
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
});
|
|
257
|
-
const wasm = wasmInstantiatedSource.instance.exports as WasmEngine;
|
|
258
|
-
|
|
259
|
-
wasm.initialize();
|
|
260
|
-
|
|
261
|
-
const runtime = new Runtime(wasm, memory, {
|
|
262
|
-
serialize: opts.serialize,
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
if (opts.startRecording ?? true) {
|
|
266
|
-
runtime.record();
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return {
|
|
270
|
-
runtime,
|
|
271
|
-
wasm,
|
|
272
|
-
};
|
|
273
|
-
}
|