@bloopjs/bloop 0.0.13 → 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 -6
- 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 +16 -15
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
KeyboardContext,
|
|
4
|
+
type KeyState,
|
|
5
|
+
MOUSE_OFFSET,
|
|
6
|
+
MouseContext,
|
|
7
|
+
} from "@bloopjs/engine";
|
|
8
|
+
import { mount } from "../src/runtime";
|
|
9
|
+
import { assert } from "../src/util";
|
|
10
|
+
|
|
11
|
+
it("hello wasm", async () => {
|
|
12
|
+
let count = 0;
|
|
13
|
+
const { wasm } = await mount({
|
|
14
|
+
systemsCallback() {
|
|
15
|
+
count++;
|
|
16
|
+
},
|
|
17
|
+
setBuffer() {},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
wasm.step(16);
|
|
21
|
+
expect(count).toBe(1);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("time", () => {
|
|
25
|
+
it("injects frame and dt", async () => {
|
|
26
|
+
const { runtime } = await mount({
|
|
27
|
+
systemsCallback() {},
|
|
28
|
+
setBuffer() {},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
runtime.step(16);
|
|
32
|
+
expect(runtime.time.frame).toEqual(1);
|
|
33
|
+
expect(runtime.time.dt).toEqual(0.016);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("exposes time context pointer in system callback", async () => {
|
|
37
|
+
let called = false;
|
|
38
|
+
const { runtime } = await mount({
|
|
39
|
+
systemsCallback(_handle, ptr) {
|
|
40
|
+
called = true;
|
|
41
|
+
const dataView = new DataView(runtime.buffer, ptr);
|
|
42
|
+
const timeCtxPtr = dataView.getUint32(0, true);
|
|
43
|
+
const timeDataView = new DataView(runtime.buffer, timeCtxPtr);
|
|
44
|
+
const frame = timeDataView.getUint32(0, true);
|
|
45
|
+
const dt = timeDataView.getUint32(4, true);
|
|
46
|
+
expect(frame).toEqual(0);
|
|
47
|
+
expect(dt).toEqual(16);
|
|
48
|
+
},
|
|
49
|
+
setBuffer() {},
|
|
50
|
+
});
|
|
51
|
+
runtime.step(16);
|
|
52
|
+
|
|
53
|
+
expect(called).toEqual(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("snapshots", () => {
|
|
58
|
+
it("can capture time to a snapshot", async () => {
|
|
59
|
+
const { runtime } = await mount({
|
|
60
|
+
systemsCallback() {},
|
|
61
|
+
setBuffer() {},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
runtime.step(16);
|
|
65
|
+
runtime.step(16);
|
|
66
|
+
expect(runtime.time.frame).toEqual(2);
|
|
67
|
+
expect(runtime.time.dt).toEqual(0.016);
|
|
68
|
+
|
|
69
|
+
const snapshot = runtime.snapshot();
|
|
70
|
+
|
|
71
|
+
runtime.step(16);
|
|
72
|
+
expect(runtime.time.frame).toEqual(3);
|
|
73
|
+
|
|
74
|
+
runtime.restore(snapshot);
|
|
75
|
+
expect(runtime.time.frame).toEqual(2);
|
|
76
|
+
|
|
77
|
+
runtime.step(16);
|
|
78
|
+
expect(runtime.time.frame).toEqual(3);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("inputs", () => {
|
|
83
|
+
it("updates input context in response to keyboard events", async () => {
|
|
84
|
+
let called = false;
|
|
85
|
+
const states: KeyState[] = [];
|
|
86
|
+
|
|
87
|
+
const { runtime } = await mount({
|
|
88
|
+
systemsCallback(_handle, ptr) {
|
|
89
|
+
const dataView = new DataView(runtime.buffer, ptr);
|
|
90
|
+
const inputCtxPtr = dataView.getUint32(4, true);
|
|
91
|
+
const inputDataView = new DataView(runtime.buffer, inputCtxPtr);
|
|
92
|
+
|
|
93
|
+
const keyboardContext = new KeyboardContext(inputDataView);
|
|
94
|
+
states.push(keyboardContext.digit8);
|
|
95
|
+
|
|
96
|
+
called = true;
|
|
97
|
+
},
|
|
98
|
+
setBuffer() {},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
runtime.emit.keydown("Digit8");
|
|
102
|
+
runtime.step();
|
|
103
|
+
runtime.step();
|
|
104
|
+
runtime.emit.keyup("Digit8");
|
|
105
|
+
runtime.step();
|
|
106
|
+
expect(called).toEqual(true);
|
|
107
|
+
|
|
108
|
+
expect(states[0]).toEqual({
|
|
109
|
+
down: true,
|
|
110
|
+
held: true,
|
|
111
|
+
up: false,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(states[1]).toEqual({
|
|
115
|
+
down: false,
|
|
116
|
+
held: true,
|
|
117
|
+
up: false,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(states[2]).toEqual({
|
|
121
|
+
down: false,
|
|
122
|
+
held: false,
|
|
123
|
+
up: true,
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("updates input context in response to mouse events", async () => {
|
|
128
|
+
let called = false;
|
|
129
|
+
type MouseState = {
|
|
130
|
+
x: number;
|
|
131
|
+
y: number;
|
|
132
|
+
wheelX: number;
|
|
133
|
+
wheelY: number;
|
|
134
|
+
left: KeyState;
|
|
135
|
+
};
|
|
136
|
+
const states: MouseState[] = [];
|
|
137
|
+
|
|
138
|
+
const { runtime } = await mount({
|
|
139
|
+
systemsCallback(_handle, ptr) {
|
|
140
|
+
const dataView = new DataView(runtime.buffer, ptr);
|
|
141
|
+
const inputCtxPtr = dataView.getUint32(4, true);
|
|
142
|
+
const inputDataView = new DataView(runtime.buffer, inputCtxPtr);
|
|
143
|
+
|
|
144
|
+
const dv = new DataView(
|
|
145
|
+
inputDataView.buffer,
|
|
146
|
+
inputDataView.byteOffset + MOUSE_OFFSET,
|
|
147
|
+
);
|
|
148
|
+
const mouseContext = new MouseContext(dv);
|
|
149
|
+
|
|
150
|
+
states.push({
|
|
151
|
+
x: mouseContext.x,
|
|
152
|
+
y: mouseContext.y,
|
|
153
|
+
left: mouseContext.left,
|
|
154
|
+
wheelX: mouseContext.wheelX,
|
|
155
|
+
wheelY: mouseContext.wheelY,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
called = true;
|
|
159
|
+
},
|
|
160
|
+
setBuffer() {},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
runtime.emit.mousedown("Left");
|
|
164
|
+
runtime.step();
|
|
165
|
+
|
|
166
|
+
runtime.emit.mousemove(123, 456);
|
|
167
|
+
runtime.emit.mousewheel(789, 101);
|
|
168
|
+
runtime.step();
|
|
169
|
+
|
|
170
|
+
runtime.emit.mouseup("Left");
|
|
171
|
+
runtime.step();
|
|
172
|
+
|
|
173
|
+
expect(called).toEqual(true);
|
|
174
|
+
|
|
175
|
+
expect(states[0]).toMatchObject({
|
|
176
|
+
left: {
|
|
177
|
+
down: true,
|
|
178
|
+
held: true,
|
|
179
|
+
up: false,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(states[1]).toMatchObject({
|
|
184
|
+
left: {
|
|
185
|
+
down: false,
|
|
186
|
+
held: true,
|
|
187
|
+
up: false,
|
|
188
|
+
},
|
|
189
|
+
x: 123,
|
|
190
|
+
y: 456,
|
|
191
|
+
wheelX: 789,
|
|
192
|
+
wheelY: 101,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(states[2]).toMatchObject({
|
|
196
|
+
left: {
|
|
197
|
+
down: false,
|
|
198
|
+
held: false,
|
|
199
|
+
up: true,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("updates platform events with input events", async () => {
|
|
205
|
+
let called = false;
|
|
206
|
+
const { runtime } = await mount({
|
|
207
|
+
systemsCallback(_handle, ptr) {
|
|
208
|
+
const dataView = new DataView(runtime.buffer, ptr);
|
|
209
|
+
const eventsPtr = dataView.getUint32(8, true);
|
|
210
|
+
const eventsDataView = new DataView(runtime.buffer, eventsPtr);
|
|
211
|
+
const eventCount = eventsDataView.getUint32(0, true);
|
|
212
|
+
expect(eventCount).toEqual(1);
|
|
213
|
+
// kind = 1 byte + 3 bytes padding
|
|
214
|
+
// payload = 8 bytes
|
|
215
|
+
const typeOffset = 4;
|
|
216
|
+
const payloadOffset = 7;
|
|
217
|
+
const eventType = eventsDataView.getUint8(typeOffset + 0);
|
|
218
|
+
const eventPayload = eventsDataView.getUint8(payloadOffset + 1);
|
|
219
|
+
expect(eventCount).toEqual(1);
|
|
220
|
+
expect(eventType).toEqual(1);
|
|
221
|
+
expect(eventPayload).toEqual(3); // KeyCode for BracketLeft
|
|
222
|
+
called = true;
|
|
223
|
+
},
|
|
224
|
+
setBuffer() {},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
runtime.emit.keydown("BracketLeft");
|
|
228
|
+
runtime.step();
|
|
229
|
+
expect(called).toEqual(true);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("tapes", () => {
|
|
234
|
+
describe("engine snapshot", () => {
|
|
235
|
+
it("saves and restores time context", async () => {
|
|
236
|
+
const { runtime } = await mount({
|
|
237
|
+
systemsCallback() {},
|
|
238
|
+
setBuffer() {},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const snapshot = runtime.snapshot();
|
|
242
|
+
expect(runtime.time.frame).toEqual(0);
|
|
243
|
+
runtime.step();
|
|
244
|
+
expect(runtime.time.frame).toEqual(1);
|
|
245
|
+
runtime.step();
|
|
246
|
+
expect(runtime.time.frame).toEqual(2);
|
|
247
|
+
runtime.restore(snapshot);
|
|
248
|
+
|
|
249
|
+
expect(runtime.time.frame).toEqual(0);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe("caller payload", () => {
|
|
254
|
+
it("can capture and restore arbitrary payloads", async () => {
|
|
255
|
+
let called = false;
|
|
256
|
+
const { runtime } = await mount({
|
|
257
|
+
startRecording: false,
|
|
258
|
+
systemsCallback() {},
|
|
259
|
+
setBuffer() {},
|
|
260
|
+
serialize() {
|
|
261
|
+
return {
|
|
262
|
+
write(buffer, ptr) {
|
|
263
|
+
const data = new Uint8Array(buffer, ptr, 1);
|
|
264
|
+
data[0] = 66;
|
|
265
|
+
},
|
|
266
|
+
size: 1,
|
|
267
|
+
};
|
|
268
|
+
},
|
|
269
|
+
deserialize(buffer, ptr, length) {
|
|
270
|
+
called = true;
|
|
271
|
+
const data = new Uint8Array(buffer, ptr, length);
|
|
272
|
+
expect(data.byteLength).toBe(1);
|
|
273
|
+
expect(length).toEqual(1);
|
|
274
|
+
expect(data[0]).toBe(66);
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const snapshot = runtime.snapshot();
|
|
279
|
+
runtime.restore(snapshot);
|
|
280
|
+
expect(called).toEqual(true);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
function toHexString(bytes: DataView | Uint8Array, length?: number): string {
|
|
286
|
+
const dv =
|
|
287
|
+
bytes instanceof DataView
|
|
288
|
+
? bytes
|
|
289
|
+
: new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
290
|
+
length ??= dv.byteLength;
|
|
291
|
+
|
|
292
|
+
let hexString = "";
|
|
293
|
+
for (let i = 0; i < length; i++) {
|
|
294
|
+
const byte = dv.getUint8(i);
|
|
295
|
+
hexString += `${byte.toString(16).padStart(2, "0")} `;
|
|
296
|
+
}
|
|
297
|
+
return hexString.trim();
|
|
298
|
+
}
|
package/test/tape.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { Bloop } from "../src/bloop";
|
|
3
|
+
import { mount } from "../src/runtime";
|
|
4
4
|
|
|
5
5
|
describe("tapes", () => {
|
|
6
6
|
describe("snapshots", () => {
|
|
@@ -21,12 +21,12 @@ describe("tapes", () => {
|
|
|
21
21
|
runtime.step();
|
|
22
22
|
|
|
23
23
|
expect(bloop.bag.cool).toEqual(43);
|
|
24
|
-
const snapshot =
|
|
24
|
+
const snapshot = runtime.snapshot();
|
|
25
25
|
|
|
26
26
|
runtime.step();
|
|
27
27
|
expect(bloop.bag.cool).toEqual(44);
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
runtime.restore(snapshot);
|
|
30
30
|
expect(bloop.bag.cool).toEqual(43);
|
|
31
31
|
});
|
|
32
32
|
|
|
@@ -37,9 +37,9 @@ describe("tapes", () => {
|
|
|
37
37
|
},
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
await mount(bloop);
|
|
41
|
-
const snapshot =
|
|
42
|
-
|
|
40
|
+
const { runtime } = await mount(bloop);
|
|
41
|
+
const snapshot = runtime.snapshot();
|
|
42
|
+
runtime.restore(snapshot);
|
|
43
43
|
expect(bloop.bag.score).toEqual(10);
|
|
44
44
|
});
|
|
45
45
|
|
|
@@ -69,7 +69,8 @@ describe("tapes", () => {
|
|
|
69
69
|
expect(timeCheck.frame).toEqual(0);
|
|
70
70
|
expect(bloop.context.time.frame).toEqual(1);
|
|
71
71
|
|
|
72
|
-
const snapshot = bloop.snapshot();
|
|
72
|
+
// const snapshot = bloop.snapshot();
|
|
73
|
+
const snapshot = runtime.snapshot();
|
|
73
74
|
|
|
74
75
|
runtime.step();
|
|
75
76
|
expect(timeCheck.dt).toBeCloseTo(0.016);
|
|
@@ -77,7 +78,7 @@ describe("tapes", () => {
|
|
|
77
78
|
expect(timeCheck.frame).toEqual(1);
|
|
78
79
|
expect(bloop.context.time.frame).toEqual(2);
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
runtime.restore(snapshot);
|
|
81
82
|
expect(bloop.context.time.frame).toEqual(1);
|
|
82
83
|
runtime.step();
|
|
83
84
|
|
|
@@ -88,7 +89,7 @@ describe("tapes", () => {
|
|
|
88
89
|
});
|
|
89
90
|
|
|
90
91
|
describe("playback", () => {
|
|
91
|
-
it("can
|
|
92
|
+
it("can step back", async () => {
|
|
92
93
|
const bloop = Bloop.create({
|
|
93
94
|
bag: {
|
|
94
95
|
clicks: 0,
|
|
@@ -103,14 +104,14 @@ describe("tapes", () => {
|
|
|
103
104
|
},
|
|
104
105
|
});
|
|
105
106
|
|
|
106
|
-
const { runtime
|
|
107
|
+
const { runtime } = await mount(bloop);
|
|
107
108
|
|
|
108
|
-
runtime.
|
|
109
|
-
emitter.mousedown("Left");
|
|
109
|
+
runtime.emit.mousedown("Left");
|
|
110
110
|
runtime.step();
|
|
111
111
|
expect(bloop.context.time.frame).toEqual(1);
|
|
112
112
|
expect(bloop.bag.clicks).toEqual(1);
|
|
113
|
-
|
|
113
|
+
|
|
114
|
+
runtime.emit.mouseup("Left");
|
|
114
115
|
runtime.step();
|
|
115
116
|
expect(bloop.context.time.frame).toEqual(2);
|
|
116
117
|
expect(bloop.bag.clicks).toEqual(1);
|