@bloopjs/bloop 0.0.10 → 0.0.12
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 +2 -2
- package/src/context.ts +46 -0
- package/src/mod.ts +56 -7
- package/test/tape.test.ts +80 -0
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bloopjs/bloop",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@types/bun": "latest"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@bloopjs/engine": "0.0.
|
|
26
|
+
"@bloopjs/engine": "0.0.12"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"typescript": "^5"
|
package/src/context.ts
CHANGED
|
@@ -39,3 +39,49 @@ export type TimingSnapshot = {
|
|
|
39
39
|
/** The total number of milliseconds since the engine started */
|
|
40
40
|
highResTime: bigint;
|
|
41
41
|
};
|
|
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/mod.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { BloopSchema } from "./data/schema";
|
|
|
2
2
|
import type { Bag } from "./data/bag";
|
|
3
3
|
import type { System } from "./system";
|
|
4
4
|
import { EngineInputs, type PlatformEvent } from "@bloopjs/engine";
|
|
5
|
-
import type
|
|
5
|
+
import { decodeTimingSnapshot, encodeTimingSnapshot, TIMING_SNAPSHOT_SIZE, type Context } from "./context";
|
|
6
6
|
|
|
7
7
|
export type BloopOpts<B extends Bag> = {
|
|
8
8
|
/** defaults to "Game" */
|
|
@@ -29,7 +29,6 @@ type MakeGS<B extends Bag> = BloopSchema<B>;
|
|
|
29
29
|
|
|
30
30
|
export class Bloop<GS extends BloopSchema> {
|
|
31
31
|
#systems: System<GS>[] = [];
|
|
32
|
-
#bag: GS["B"];
|
|
33
32
|
#context: Context<GS>;
|
|
34
33
|
|
|
35
34
|
/**
|
|
@@ -52,9 +51,9 @@ export class Bloop<GS extends BloopSchema> {
|
|
|
52
51
|
);
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
this.#bag = opts.bag ?? {};
|
|
56
54
|
this.#context = {
|
|
57
|
-
bag:
|
|
55
|
+
bag: opts.bag ?? {},
|
|
56
|
+
// todo: this should be a pointer to memory managed by the engine
|
|
58
57
|
inputs: new EngineInputs.InputSnapshot(new DataView(new ArrayBuffer(100))),
|
|
59
58
|
time: {
|
|
60
59
|
dt: 0,
|
|
@@ -67,21 +66,66 @@ export class Bloop<GS extends BloopSchema> {
|
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
get bag() {
|
|
70
|
-
return this.#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
|
+
|
|
71
109
|
}
|
|
72
110
|
|
|
73
111
|
/**
|
|
74
112
|
* Register a system with the game loop.
|
|
75
113
|
*
|
|
76
114
|
*/
|
|
77
|
-
system(label: string, system: System): number {
|
|
115
|
+
system(label: string, system: System<GS>): number {
|
|
78
116
|
system.label ??= label;
|
|
79
117
|
this.#systems.push(system);
|
|
80
118
|
return this.#systems.length;
|
|
81
119
|
}
|
|
82
120
|
|
|
83
|
-
|
|
121
|
+
// todo - get data from ptr
|
|
122
|
+
systemsCallback(ptr: number, events: PlatformEvent[], dt: number) {
|
|
84
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
|
+
|
|
85
129
|
for (const system of this.#systems) {
|
|
86
130
|
system.update?.(this.#context);
|
|
87
131
|
|
|
@@ -146,6 +190,11 @@ export class Bloop<GS extends BloopSchema> {
|
|
|
146
190
|
}
|
|
147
191
|
}
|
|
148
192
|
}
|
|
193
|
+
|
|
194
|
+
// do this in the engine snapshot
|
|
195
|
+
this.#context.time.frame++;
|
|
196
|
+
this.#context.time.highResFrame += 1n;
|
|
197
|
+
|
|
149
198
|
this.#context.inputs.flush();
|
|
150
199
|
}
|
|
151
200
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { it, expect, describe } from "bun:test";
|
|
2
|
+
import { mount } from "@bloopjs/engine";
|
|
3
|
+
import { Bloop } from "../src/mod";
|
|
4
|
+
|
|
5
|
+
describe("tapes", () => {
|
|
6
|
+
describe("snapshots", () => {
|
|
7
|
+
it("can snapshot the bag", async () => {
|
|
8
|
+
const bloop = Bloop.create({
|
|
9
|
+
bag: {
|
|
10
|
+
cool: 42,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
bloop.system("inc", {
|
|
15
|
+
update({ bag }) {
|
|
16
|
+
bag.cool++;
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const { runtime } = await mount(bloop);
|
|
21
|
+
runtime.step();
|
|
22
|
+
|
|
23
|
+
expect(bloop.bag.cool).toEqual(43);
|
|
24
|
+
const snapshot = bloop.snapshot();
|
|
25
|
+
|
|
26
|
+
runtime.step();
|
|
27
|
+
expect(bloop.bag.cool).toEqual(44);
|
|
28
|
+
|
|
29
|
+
bloop.restore(snapshot);
|
|
30
|
+
expect(bloop.bag.cool).toEqual(43);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("snapshots time", async () => {
|
|
34
|
+
const bloop = Bloop.create({
|
|
35
|
+
bag: {
|
|
36
|
+
nope: 10,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const timeCheck = {
|
|
41
|
+
dt: 0,
|
|
42
|
+
time: 0,
|
|
43
|
+
frame: 0,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
bloop.system("timeCheck", {
|
|
47
|
+
update({ time }) {
|
|
48
|
+
timeCheck.dt = time.dt;
|
|
49
|
+
timeCheck.time = time.time;
|
|
50
|
+
timeCheck.frame = time.frame;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const { runtime } = await mount(bloop);
|
|
55
|
+
|
|
56
|
+
runtime.step();
|
|
57
|
+
|
|
58
|
+
expect(timeCheck.dt).toBeCloseTo(0.016);
|
|
59
|
+
expect(timeCheck.time).toBeCloseTo(0.016);
|
|
60
|
+
expect(timeCheck.frame).toEqual(0);
|
|
61
|
+
expect(bloop.context.time.frame).toEqual(1);
|
|
62
|
+
|
|
63
|
+
const snapshot = bloop.snapshot();
|
|
64
|
+
|
|
65
|
+
runtime.step();
|
|
66
|
+
expect(timeCheck.dt).toBeCloseTo(0.016);
|
|
67
|
+
expect(timeCheck.time).toBeCloseTo(0.016 * 2);
|
|
68
|
+
expect(timeCheck.frame).toEqual(1);
|
|
69
|
+
expect(bloop.context.time.frame).toEqual(2);
|
|
70
|
+
|
|
71
|
+
bloop.restore(snapshot);
|
|
72
|
+
expect(bloop.context.time.frame).toEqual(1);
|
|
73
|
+
runtime.step();
|
|
74
|
+
|
|
75
|
+
expect(timeCheck.dt).toBeCloseTo(0.016);
|
|
76
|
+
expect(timeCheck.time).toBeCloseTo(0.016 * 2);
|
|
77
|
+
expect(bloop.context.time.frame).toEqual(2);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|