@bloopjs/bloop 0.0.11 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloop/bloop",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "license": "MIT",
5
5
  "exports": "./src/mod.ts"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloopjs/bloop",
3
- "version": "0.0.11",
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.11"
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 { Context } from "./context";
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" */
@@ -53,6 +53,7 @@ export class Bloop<GS extends BloopSchema> {
53
53
 
54
54
  this.#context = {
55
55
  bag: opts.bag ?? {},
56
+ // todo: this should be a pointer to memory managed by the engine
56
57
  inputs: new EngineInputs.InputSnapshot(new DataView(new ArrayBuffer(100))),
57
58
  time: {
58
59
  dt: 0,
@@ -68,6 +69,10 @@ export class Bloop<GS extends BloopSchema> {
68
69
  return this.#context.bag;
69
70
  }
70
71
 
72
+ get context() : Readonly<Context<GS>> {
73
+ return this.#context;
74
+ }
75
+
71
76
  /**
72
77
  * Take a snapshot of the game state
73
78
  * @returns linear memory representation of the game state
@@ -77,25 +82,30 @@ export class Bloop<GS extends BloopSchema> {
77
82
  const encoder = new TextEncoder();
78
83
  const textBytes = encoder.encode(str);
79
84
 
80
- const buffer = new Uint8Array(1024*1024);
85
+ const size = TIMING_SNAPSHOT_SIZE + 4 + textBytes.length;
86
+
87
+ const buffer = new Uint8Array(size);
81
88
  const view = new DataView(buffer.buffer);
82
- let offset = 0;
83
- view.setUint32(0, textBytes.length, true);
89
+ let offset = encodeTimingSnapshot(this.#context.time, buffer.subarray(0, TIMING_SNAPSHOT_SIZE));
90
+ view.setUint32(offset, textBytes.length, true);
84
91
  offset += 4;
92
+
85
93
  buffer.set(textBytes, offset);
86
94
  offset += textBytes.length;
87
95
  return buffer;
88
96
  }
89
97
 
90
98
  restore(snapshot: Uint8Array) {
99
+ const size = decodeTimingSnapshot(new Uint8Array(snapshot.buffer, 0, 32), this.#context.time);
91
100
  const view = new DataView(snapshot.buffer);
92
- let offset = 0;
101
+ let offset = size;
93
102
  const length = view.getUint32(0, true);
94
103
  offset += 4;
95
104
  const bagBytes = snapshot.slice(offset, offset + length);
96
105
  const decoder = new TextDecoder();
97
106
  const str = decoder.decode(bagBytes);
98
107
  this.#context.bag = JSON.parse(str);
108
+
99
109
  }
100
110
 
101
111
  /**
@@ -108,8 +118,14 @@ export class Bloop<GS extends BloopSchema> {
108
118
  return this.#systems.length;
109
119
  }
110
120
 
111
- systemsCallback(events: PlatformEvent[]) {
121
+ // todo - get data from ptr
122
+ systemsCallback(ptr: number, events: PlatformEvent[], dt: number) {
112
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
+
113
129
  for (const system of this.#systems) {
114
130
  system.update?.(this.#context);
115
131
 
@@ -174,6 +190,11 @@ export class Bloop<GS extends BloopSchema> {
174
190
  }
175
191
  }
176
192
  }
193
+
194
+ // do this in the engine snapshot
195
+ this.#context.time.frame++;
196
+ this.#context.time.highResFrame += 1n;
197
+
177
198
  this.#context.inputs.flush();
178
199
  }
179
200
  }
package/test/tape.test.ts CHANGED
@@ -29,5 +29,52 @@ describe("tapes", () => {
29
29
  bloop.restore(snapshot);
30
30
  expect(bloop.bag.cool).toEqual(43);
31
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
+ });
32
79
  });
33
80
  });