@bloopjs/bloop 0.0.13 → 0.0.15

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.13",
3
+ "version": "0.0.15",
4
4
  "license": "MIT",
5
5
  "exports": "./src/mod.ts"
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@bloopjs/bloop",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
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.13"
30
+ "@bloopjs/engine": "0.0.15"
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 { EngineTiming, InputSnapshot } from "@bloopjs/engine";
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,8 +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: EngineTiming.TimingSnapshot;
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;
25
25
  };
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
- import type { BloopSchema } from "./data/schema";
2
- import type { Bag } from "./data/bag";
3
- import type { System } from "./system";
4
- import { EngineTiming, EngineInputs, type PlatformEvent } from "@bloopjs/engine";
5
- import { type Context } from "./context";
6
- import { TIMING_SNAPSHOT_SIZE } from "../../engine/js/timing";
7
-
8
- export type BloopOpts<B extends Bag> = {
9
- /** defaults to "Game" */
10
- name?: string;
11
- /**
12
- * component definitions, defaults to empty object
13
- *
14
- * use `defineComponents` to generate component definitions from a simple schema
15
- */
16
- // components?: SchemaWithLayout<CS>;
17
- /**
18
- * input map, defaults to empty object
19
- */
20
- // inputMap?: IM;
21
- /**
22
- * bag definition, defaults to empty object
23
- *
24
- */
25
- bag?: B
26
- // schema: GS;
27
- };
28
-
29
- type MakeGS<B extends Bag> = BloopSchema<B>;
30
-
31
- export class Bloop<GS extends BloopSchema> {
32
- #systems: System<GS>[] = [];
33
- #context: Context<GS>;
34
-
35
- /**
36
- * Bloop.create() is the way to create a new bloop instance.
37
- */
38
- static create<
39
- // S extends Schema,
40
- // RS extends ResourceSchema,
41
- B extends Bag,
42
- // const IM extends InputMap,
43
- // >(opts: GameOpts<S, RS, B, IM> = {}) {
44
- >(opts: BloopOpts<B> = {}): Bloop<MakeGS<B>> {
45
- return new Bloop<MakeGS<B>>(opts, "dontCallMeDirectly");
46
- }
47
-
48
- constructor(opts: BloopOpts<GS["B"]> = {}, dontCallMeDirectly: string) {
49
- if (dontCallMeDirectly !== "dontCallMeDirectly") {
50
- throw new Error(
51
- "Bloop constructor is private. Use Bloop.create() to create a new game instance."
52
- );
53
- }
54
-
55
- this.#context = {
56
- bag: opts.bag ?? {},
57
- // todo: this should be a pointer to memory managed by the engine
58
- inputs: new EngineInputs.InputSnapshot(new DataView(new ArrayBuffer(100))),
59
- time: {
60
- dt: 0,
61
- time: 0,
62
- frame: 0,
63
- highResFrame: 0n,
64
- highResTime: 0n,
65
- },
66
- };
67
- }
68
-
69
- get bag(): GS["B"] {
70
- return this.#context.bag;
71
- }
72
-
73
- get context() : Readonly<Context<GS>> {
74
- return this.#context;
75
- }
76
-
77
- /**
78
- * Take a snapshot of the game state
79
- * @returns linear memory representation of the game state
80
- */
81
- snapshot(): Uint8Array {
82
- const str = JSON.stringify(this.#context.bag);
83
- const encoder = new TextEncoder();
84
- const textBytes = encoder.encode(str);
85
-
86
- const size = EngineTiming.TIMING_SNAPSHOT_SIZE + 4 + textBytes.length;
87
-
88
- const buffer = new Uint8Array(size);
89
- const view = new DataView(buffer.buffer);
90
- let offset = EngineTiming.encodeTimingSnapshot(this.#context.time, buffer.subarray(0, EngineTiming.TIMING_SNAPSHOT_SIZE));
91
- view.setUint32(offset, textBytes.length, true);
92
- offset += 4;
93
-
94
- buffer.set(textBytes, offset);
95
- offset += textBytes.length;
96
- return buffer;
97
- }
98
-
99
- restore(snapshot: Uint8Array) {
100
- const size = EngineTiming.decodeTimingSnapshot(new Uint8Array(snapshot.buffer, 0, EngineTiming.TIMING_SNAPSHOT_SIZE), this.#context.time);
101
- const view = new DataView(snapshot.buffer);
102
- let offset = size;
103
- const length = view.getUint32(offset, true);
104
- offset += 4;
105
- const bagBytes = snapshot.slice(offset, offset + length);
106
- const decoder = new TextDecoder();
107
- const str = decoder.decode(bagBytes);
108
- this.#context.bag = JSON.parse(str);
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";