@bloopjs/bloop 0.0.12 → 0.0.14
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/jsr.json +1 -1
- package/package.json +6 -2
- package/src/bloop.ts +239 -0
- package/src/context.ts +6 -68
- package/src/events.ts +25 -0
- package/src/mod.ts +3 -200
- package/src/runtime.ts +273 -0
- package/src/system.ts +5 -5
- package/src/util.ts +23 -0
- package/test/inputs.test.ts +131 -66
- package/test/runtime.test.ts +298 -0
- package/test/tape.test.ts +57 -9
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bloopjs/bloop",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/bloopgames/bloop"
|
|
8
|
+
},
|
|
5
9
|
"exports": {
|
|
6
10
|
".": {
|
|
7
11
|
"import": "./dist/mod.js",
|
|
@@ -23,7 +27,7 @@
|
|
|
23
27
|
"@types/bun": "latest"
|
|
24
28
|
},
|
|
25
29
|
"dependencies": {
|
|
26
|
-
"@bloopjs/engine": "0.0.
|
|
30
|
+
"@bloopjs/engine": "0.0.14"
|
|
27
31
|
},
|
|
28
32
|
"peerDependencies": {
|
|
29
33
|
"typescript": "^5"
|
package/src/bloop.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type EnginePointer,
|
|
3
|
+
Enums,
|
|
4
|
+
EVENT_PAYLOAD_ALIGN,
|
|
5
|
+
EVENT_PAYLOAD_SIZE,
|
|
6
|
+
EVENTS_OFFSET,
|
|
7
|
+
INPUT_CTX_OFFSET,
|
|
8
|
+
InputContext,
|
|
9
|
+
keyCodeToKey,
|
|
10
|
+
mouseButtonCodeToMouseButton,
|
|
11
|
+
TIME_CTX_OFFSET,
|
|
12
|
+
TimeContext,
|
|
13
|
+
} from "@bloopjs/engine";
|
|
14
|
+
import type { Context } from "./context";
|
|
15
|
+
import type { Bag } from "./data/bag";
|
|
16
|
+
import type { BloopSchema } from "./data/schema";
|
|
17
|
+
import type {
|
|
18
|
+
InputEvent,
|
|
19
|
+
KeyEvent,
|
|
20
|
+
MouseButtonEvent,
|
|
21
|
+
MouseMoveEvent,
|
|
22
|
+
MouseWheelEvent,
|
|
23
|
+
} from "./events";
|
|
24
|
+
import type { DeserializeFn, SerializeFn } from "./runtime";
|
|
25
|
+
import type { System } from "./system";
|
|
26
|
+
|
|
27
|
+
export type BloopOpts<B extends Bag> = {
|
|
28
|
+
/** defaults to "Game" */
|
|
29
|
+
name?: string;
|
|
30
|
+
/**
|
|
31
|
+
* component definitions, defaults to empty object
|
|
32
|
+
*
|
|
33
|
+
* use `defineComponents` to generate component definitions from a simple schema
|
|
34
|
+
*/
|
|
35
|
+
// components?: SchemaWithLayout<CS>;
|
|
36
|
+
/**
|
|
37
|
+
* input map, defaults to empty object
|
|
38
|
+
*/
|
|
39
|
+
// inputMap?: IM;
|
|
40
|
+
/**
|
|
41
|
+
* bag definition, defaults to empty object
|
|
42
|
+
*
|
|
43
|
+
*/
|
|
44
|
+
bag?: B;
|
|
45
|
+
// schema: GS;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export class Bloop<GS extends BloopSchema> {
|
|
49
|
+
#systems: System<GS>[] = [];
|
|
50
|
+
#context: Context<GS>;
|
|
51
|
+
#engineBuffer: ArrayBuffer = new ArrayBuffer(0);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Bloop.create() is the way to create a new bloop instance.
|
|
55
|
+
*/
|
|
56
|
+
static create<
|
|
57
|
+
// S extends Schema,
|
|
58
|
+
// RS extends ResourceSchema,
|
|
59
|
+
B extends Bag,
|
|
60
|
+
// const IM extends InputMap,
|
|
61
|
+
// >(opts: GameOpts<S, RS, B, IM> = {}) {
|
|
62
|
+
>(opts: BloopOpts<B> = {}): Bloop<MakeGS<B>> {
|
|
63
|
+
return new Bloop<MakeGS<B>>(opts, "dontCallMeDirectly");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
constructor(opts: BloopOpts<GS["B"]> = {}, dontCallMeDirectly: string) {
|
|
67
|
+
if (dontCallMeDirectly !== "dontCallMeDirectly") {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"Bloop constructor is private. Use Bloop.create() to create a new game instance.",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.#context = {
|
|
74
|
+
bag: opts.bag ?? {},
|
|
75
|
+
time: new TimeContext(),
|
|
76
|
+
inputs: new InputContext(),
|
|
77
|
+
rawPointer: -1,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get bag(): GS["B"] {
|
|
82
|
+
return this.#context.bag;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get context(): Readonly<Context<GS>> {
|
|
86
|
+
return this.#context;
|
|
87
|
+
}
|
|
88
|
+
|
|
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
|
+
/**
|
|
124
|
+
* Register a system with the game loop.
|
|
125
|
+
*
|
|
126
|
+
*/
|
|
127
|
+
system(label: string, system: System<GS>): number {
|
|
128
|
+
system.label ??= label;
|
|
129
|
+
this.#systems.push(system);
|
|
130
|
+
return this.#systems.length;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
systemsCallback(system_handle: number, ptr: EnginePointer) {
|
|
134
|
+
// todo - move this to engine
|
|
135
|
+
const dv = new DataView(this.#engineBuffer, ptr);
|
|
136
|
+
const timeCtxPtr = dv.getUint32(TIME_CTX_OFFSET, true);
|
|
137
|
+
const inputCtxPtr = dv.getUint32(INPUT_CTX_OFFSET, true);
|
|
138
|
+
const eventsPtr = dv.getUint32(EVENTS_OFFSET, true);
|
|
139
|
+
|
|
140
|
+
this.#context.rawPointer = ptr;
|
|
141
|
+
this.#context.inputs.dataView = new DataView(
|
|
142
|
+
this.#engineBuffer,
|
|
143
|
+
inputCtxPtr,
|
|
144
|
+
);
|
|
145
|
+
this.#context.time.dataView = new DataView(this.#engineBuffer, timeCtxPtr);
|
|
146
|
+
|
|
147
|
+
const eventsDataView = new DataView(this.#engineBuffer, eventsPtr);
|
|
148
|
+
|
|
149
|
+
for (const system of this.#systems) {
|
|
150
|
+
system.update?.(this.#context);
|
|
151
|
+
|
|
152
|
+
const eventCount = eventsDataView.getUint32(0, true);
|
|
153
|
+
|
|
154
|
+
let offset = Uint32Array.BYTES_PER_ELEMENT;
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < eventCount; i++) {
|
|
157
|
+
const eventType = eventsDataView.getUint8(offset);
|
|
158
|
+
const payloadSize = EVENT_PAYLOAD_SIZE;
|
|
159
|
+
const payloadByte = eventsDataView.getUint8(
|
|
160
|
+
offset + EVENT_PAYLOAD_ALIGN,
|
|
161
|
+
);
|
|
162
|
+
const payloadVec2 = {
|
|
163
|
+
x: eventsDataView.getFloat32(offset + EVENT_PAYLOAD_ALIGN, true),
|
|
164
|
+
y: eventsDataView.getFloat32(
|
|
165
|
+
offset + EVENT_PAYLOAD_ALIGN + Float32Array.BYTES_PER_ELEMENT,
|
|
166
|
+
true,
|
|
167
|
+
),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
switch (eventType) {
|
|
171
|
+
case Enums.EventType.KeyDown: {
|
|
172
|
+
system.keydown?.(
|
|
173
|
+
attachEvent<KeyEvent, GS>(this.#context, {
|
|
174
|
+
key: keyCodeToKey(payloadByte),
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case Enums.EventType.KeyUp:
|
|
180
|
+
system.keyup?.(
|
|
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}`);
|
|
218
|
+
}
|
|
219
|
+
// the event type u8 + padding + payload
|
|
220
|
+
offset += EVENT_PAYLOAD_ALIGN + EVENT_PAYLOAD_SIZE;
|
|
221
|
+
}
|
|
222
|
+
(this.#context as any).event = undefined;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
setBuffer(buffer: ArrayBuffer) {
|
|
227
|
+
this.#engineBuffer = buffer;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function attachEvent<IE extends InputEvent, GS extends BloopSchema>(
|
|
232
|
+
context: Context<GS>,
|
|
233
|
+
event: IE,
|
|
234
|
+
): Context<GS> & { event: IE } {
|
|
235
|
+
(context as any).event = event;
|
|
236
|
+
return context as Context<GS> & { event: IE };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
type MakeGS<B extends Bag> = BloopSchema<B>;
|
package/src/context.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EnginePointer, InputContext, TimeContext } from "@bloopjs/engine";
|
|
2
2
|
import type { BloopSchema } from "./data/schema";
|
|
3
3
|
|
|
4
4
|
export type Context<
|
|
@@ -8,8 +8,6 @@ export type Context<
|
|
|
8
8
|
> = {
|
|
9
9
|
/** The wrapper to the engine instance */
|
|
10
10
|
// engine: Bridge<GS["CS"]>;
|
|
11
|
-
/** The engine pointer to the injected system arguments (for advanced use cases) */
|
|
12
|
-
// rawPointer: EnginePointer;
|
|
13
11
|
/** Result of any resources requested */
|
|
14
12
|
// resources: ResourcesResult<GS["RS"], R>;
|
|
15
13
|
/** Result of the main query if there was one */
|
|
@@ -18,70 +16,10 @@ export type Context<
|
|
|
18
16
|
// queries: QueriesResults<GS["CS"], QS>;
|
|
19
17
|
/** The bag of values for the system */
|
|
20
18
|
bag: GS["B"];
|
|
21
|
-
/** The input snapshot */
|
|
22
|
-
inputs: InputSnapshot;
|
|
23
19
|
/** The timing information for the current frame */
|
|
24
|
-
time:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
dt: number;
|
|
30
|
-
/** The total number of seconds since the engine started */
|
|
31
|
-
time: number;
|
|
32
|
-
/** The number of frames rendered since the engine started */
|
|
33
|
-
frame: number;
|
|
34
|
-
|
|
35
|
-
/** The current frame rate of the engine in frames per second */
|
|
36
|
-
// fps: number;
|
|
37
|
-
/** The number of frames rendered since the engine started */
|
|
38
|
-
highResFrame: bigint;
|
|
39
|
-
/** The total number of milliseconds since the engine started */
|
|
40
|
-
highResTime: bigint;
|
|
20
|
+
time: TimeContext;
|
|
21
|
+
/** The input snapshot */
|
|
22
|
+
inputs: InputContext;
|
|
23
|
+
/** The engine pointer to the injected system arguments (for advanced use cases) */
|
|
24
|
+
rawPointer: EnginePointer;
|
|
41
25
|
};
|
|
42
|
-
|
|
43
|
-
export const TIMING_SNAPSHOT_SIZE = 32;
|
|
44
|
-
|
|
45
|
-
// these functions should go in engine
|
|
46
|
-
export function encodeTimingSnapshot(
|
|
47
|
-
ts: TimingSnapshot,
|
|
48
|
-
target: Uint8Array,
|
|
49
|
-
): number {
|
|
50
|
-
if (target.byteLength !== TIMING_SNAPSHOT_SIZE) {
|
|
51
|
-
throw new Error("Target buffer must be exactly 32 bytes");
|
|
52
|
-
}
|
|
53
|
-
const view = new DataView(
|
|
54
|
-
target.buffer,
|
|
55
|
-
target.byteOffset,
|
|
56
|
-
target.byteLength,
|
|
57
|
-
);
|
|
58
|
-
view.setFloat32(0, ts.dt, true);
|
|
59
|
-
view.setFloat32(4, ts.time, true);
|
|
60
|
-
view.setBigUint64(8, ts.highResFrame, true);
|
|
61
|
-
view.setBigUint64(16, ts.highResTime, true);
|
|
62
|
-
view.setUint32(24, ts.frame, true);
|
|
63
|
-
return TIMING_SNAPSHOT_SIZE;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function decodeTimingSnapshot(
|
|
67
|
-
source: Uint8Array,
|
|
68
|
-
target: TimingSnapshot,
|
|
69
|
-
): number {
|
|
70
|
-
if (source.byteLength !== TIMING_SNAPSHOT_SIZE) {
|
|
71
|
-
throw new Error(
|
|
72
|
-
`Source buffer must be exactly ${TIMING_SNAPSHOT_SIZE} bytes`,
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
const view = new DataView(
|
|
76
|
-
source.buffer,
|
|
77
|
-
source.byteOffset,
|
|
78
|
-
source.byteLength,
|
|
79
|
-
);
|
|
80
|
-
target.dt = view.getFloat32(0, true);
|
|
81
|
-
target.time = view.getFloat32(4, true);
|
|
82
|
-
target.highResFrame = view.getBigUint64(8, true);
|
|
83
|
-
target.highResTime = view.getBigUint64(16, true);
|
|
84
|
-
target.frame = view.getUint32(24, true);
|
|
85
|
-
|
|
86
|
-
return TIMING_SNAPSHOT_SIZE;
|
|
87
|
-
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Key, MouseButton } from "@bloopjs/engine";
|
|
2
|
+
|
|
3
|
+
export type InputEvent =
|
|
4
|
+
| KeyEvent
|
|
5
|
+
| MouseButtonEvent
|
|
6
|
+
| MouseMoveEvent
|
|
7
|
+
| MouseWheelEvent;
|
|
8
|
+
|
|
9
|
+
export type KeyEvent = {
|
|
10
|
+
key: Key;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type MouseButtonEvent = {
|
|
14
|
+
button: MouseButton;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type MouseMoveEvent = {
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type MouseWheelEvent = {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
};
|
package/src/mod.ts
CHANGED
|
@@ -1,201 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import type { System } from "./system";
|
|
4
|
-
import { EngineInputs, type PlatformEvent } from "@bloopjs/engine";
|
|
5
|
-
import { decodeTimingSnapshot, encodeTimingSnapshot, TIMING_SNAPSHOT_SIZE, type Context } from "./context";
|
|
6
|
-
|
|
7
|
-
export type BloopOpts<B extends Bag> = {
|
|
8
|
-
/** defaults to "Game" */
|
|
9
|
-
name?: string;
|
|
10
|
-
/**
|
|
11
|
-
* component definitions, defaults to empty object
|
|
12
|
-
*
|
|
13
|
-
* use `defineComponents` to generate component definitions from a simple schema
|
|
14
|
-
*/
|
|
15
|
-
// components?: SchemaWithLayout<CS>;
|
|
16
|
-
/**
|
|
17
|
-
* input map, defaults to empty object
|
|
18
|
-
*/
|
|
19
|
-
// inputMap?: IM;
|
|
20
|
-
/**
|
|
21
|
-
* bag definition, defaults to empty object
|
|
22
|
-
*
|
|
23
|
-
*/
|
|
24
|
-
bag?: B
|
|
25
|
-
// schema: GS;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
type MakeGS<B extends Bag> = BloopSchema<B>;
|
|
29
|
-
|
|
30
|
-
export class Bloop<GS extends BloopSchema> {
|
|
31
|
-
#systems: System<GS>[] = [];
|
|
32
|
-
#context: Context<GS>;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Bloop.create() is the way to create a new bloop instance.
|
|
36
|
-
*/
|
|
37
|
-
static create<
|
|
38
|
-
// S extends Schema,
|
|
39
|
-
// RS extends ResourceSchema,
|
|
40
|
-
B extends Bag,
|
|
41
|
-
// const IM extends InputMap,
|
|
42
|
-
// >(opts: GameOpts<S, RS, B, IM> = {}) {
|
|
43
|
-
>(opts: BloopOpts<B> = {}): Bloop<MakeGS<B>> {
|
|
44
|
-
return new Bloop<MakeGS<B>>(opts, "dontCallMeDirectly");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
constructor(opts: BloopOpts<GS["B"]> = {}, dontCallMeDirectly: string) {
|
|
48
|
-
if (dontCallMeDirectly !== "dontCallMeDirectly") {
|
|
49
|
-
throw new Error(
|
|
50
|
-
"Bloop constructor is private. Use Bloop.create() to create a new game instance."
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
this.#context = {
|
|
55
|
-
bag: opts.bag ?? {},
|
|
56
|
-
// todo: this should be a pointer to memory managed by the engine
|
|
57
|
-
inputs: new EngineInputs.InputSnapshot(new DataView(new ArrayBuffer(100))),
|
|
58
|
-
time: {
|
|
59
|
-
dt: 0,
|
|
60
|
-
time: 0,
|
|
61
|
-
frame: 0,
|
|
62
|
-
highResFrame: 0n,
|
|
63
|
-
highResTime: 0n,
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
get bag() {
|
|
69
|
-
return this.#context.bag;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
get context() : Readonly<Context<GS>> {
|
|
73
|
-
return this.#context;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Take a snapshot of the game state
|
|
78
|
-
* @returns linear memory representation of the game state
|
|
79
|
-
*/
|
|
80
|
-
snapshot(): Uint8Array {
|
|
81
|
-
const str = JSON.stringify(this.#context.bag);
|
|
82
|
-
const encoder = new TextEncoder();
|
|
83
|
-
const textBytes = encoder.encode(str);
|
|
84
|
-
|
|
85
|
-
const size = TIMING_SNAPSHOT_SIZE + 4 + textBytes.length;
|
|
86
|
-
|
|
87
|
-
const buffer = new Uint8Array(size);
|
|
88
|
-
const view = new DataView(buffer.buffer);
|
|
89
|
-
let offset = encodeTimingSnapshot(this.#context.time, buffer.subarray(0, TIMING_SNAPSHOT_SIZE));
|
|
90
|
-
view.setUint32(offset, textBytes.length, true);
|
|
91
|
-
offset += 4;
|
|
92
|
-
|
|
93
|
-
buffer.set(textBytes, offset);
|
|
94
|
-
offset += textBytes.length;
|
|
95
|
-
return buffer;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
restore(snapshot: Uint8Array) {
|
|
99
|
-
const size = decodeTimingSnapshot(new Uint8Array(snapshot.buffer, 0, 32), this.#context.time);
|
|
100
|
-
const view = new DataView(snapshot.buffer);
|
|
101
|
-
let offset = size;
|
|
102
|
-
const length = view.getUint32(0, true);
|
|
103
|
-
offset += 4;
|
|
104
|
-
const bagBytes = snapshot.slice(offset, offset + length);
|
|
105
|
-
const decoder = new TextDecoder();
|
|
106
|
-
const str = decoder.decode(bagBytes);
|
|
107
|
-
this.#context.bag = JSON.parse(str);
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Register a system with the game loop.
|
|
113
|
-
*
|
|
114
|
-
*/
|
|
115
|
-
system(label: string, system: System<GS>): number {
|
|
116
|
-
system.label ??= label;
|
|
117
|
-
this.#systems.push(system);
|
|
118
|
-
return this.#systems.length;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// todo - get data from ptr
|
|
122
|
-
systemsCallback(ptr: number, events: PlatformEvent[], dt: number) {
|
|
123
|
-
this.#context.inputs.update(events);
|
|
124
|
-
const dtSeconds = dt / 1000;
|
|
125
|
-
this.#context.time.dt = dtSeconds;
|
|
126
|
-
this.#context.time.time += dtSeconds;
|
|
127
|
-
this.#context.time.highResTime += BigInt(dt);
|
|
128
|
-
|
|
129
|
-
for (const system of this.#systems) {
|
|
130
|
-
system.update?.(this.#context);
|
|
131
|
-
|
|
132
|
-
for (const event of events) {
|
|
133
|
-
switch(event.type) {
|
|
134
|
-
case "keydown":
|
|
135
|
-
system.keydown?.({
|
|
136
|
-
...this.#context,
|
|
137
|
-
event: {
|
|
138
|
-
key: event.key,
|
|
139
|
-
pressure: 1,
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
break;
|
|
143
|
-
case "keyup":
|
|
144
|
-
system.keyup?.({
|
|
145
|
-
...this.#context,
|
|
146
|
-
event: {
|
|
147
|
-
key: event.key,
|
|
148
|
-
pressure: 0,
|
|
149
|
-
}
|
|
150
|
-
})
|
|
151
|
-
break;
|
|
152
|
-
case "mousemove":
|
|
153
|
-
system.mousemove?.({
|
|
154
|
-
...this.#context,
|
|
155
|
-
event: {
|
|
156
|
-
x: event.x,
|
|
157
|
-
y: event.y,
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
break;
|
|
161
|
-
case "mousedown":
|
|
162
|
-
system.mousedown?.({
|
|
163
|
-
...this.#context,
|
|
164
|
-
event: {
|
|
165
|
-
button: event.button,
|
|
166
|
-
pressure: event.pressure,
|
|
167
|
-
}
|
|
168
|
-
})
|
|
169
|
-
break;
|
|
170
|
-
case "mouseup":
|
|
171
|
-
system.mouseup?.({
|
|
172
|
-
...this.#context,
|
|
173
|
-
event: {
|
|
174
|
-
button: event.button,
|
|
175
|
-
pressure: event.pressure,
|
|
176
|
-
}
|
|
177
|
-
})
|
|
178
|
-
break;
|
|
179
|
-
case "mousewheel":
|
|
180
|
-
system.mousewheel?.({
|
|
181
|
-
...this.#context,
|
|
182
|
-
event: {
|
|
183
|
-
x: event.x,
|
|
184
|
-
y: event.y,
|
|
185
|
-
}
|
|
186
|
-
})
|
|
187
|
-
break;
|
|
188
|
-
default:
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// do this in the engine snapshot
|
|
195
|
-
this.#context.time.frame++;
|
|
196
|
-
this.#context.time.highResFrame += 1n;
|
|
197
|
-
|
|
198
|
-
this.#context.inputs.flush();
|
|
199
|
-
}
|
|
200
|
-
}
|
|
1
|
+
export * from "./runtime"
|
|
2
|
+
export * from "./bloop"
|
|
201
3
|
|
|
4
|
+
export * as Util from "./util";
|