@bloopjs/bloop 0.0.8 → 0.0.9

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.8",
3
+ "version": "0.0.9",
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.8",
3
+ "version": "0.0.9",
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.8"
26
+ "@bloopjs/engine": "0.0.9"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "typescript": "^5"
package/src/context.ts CHANGED
@@ -1,7 +1,41 @@
1
- import type { GameSchema } from "./schema";
1
+ import type { InputSnapshot } from "@bloopjs/engine";
2
+ import type { GameSchema } from "./data/schema";
2
3
 
3
- // export type Context<GS extends GameSchema> = {
4
- // GS: GS;
5
- // };
4
+ export type Context<
5
+ GS extends GameSchema = GameSchema,
6
+ // Q extends Query<GS["CS"]> = Query<GS["CS"]>,
7
+ // QS extends readonly Query<GS["CS"]>[] = readonly Query<GS["CS"]>[],
8
+ > = {
9
+ /** The wrapper to the engine instance */
10
+ // engine: Bridge<GS["CS"]>;
11
+ /** The engine pointer to the injected system arguments (for advanced use cases) */
12
+ // rawPointer: EnginePointer;
13
+ /** Result of any resources requested */
14
+ // resources: ResourcesResult<GS["RS"], R>;
15
+ /** Result of the main query if there was one */
16
+ // query: ResultsIterator<GS["CS"], Q>;
17
+ /** Results of multiple queries if there were any */
18
+ // queries: QueriesResults<GS["CS"], QS>;
19
+ /** The bag of values for the system */
20
+ bag: GS["B"];
21
+ /** The input snapshot */
22
+ inputs: InputSnapshot;
23
+ /** The timing information for the current frame */
24
+ time: TimingSnapshot;
25
+ };
6
26
 
7
- export type Context<GS extends GameSchema> = unknown;
27
+ export type TimingSnapshot = {
28
+ /** The number of seconds (usually fractional) since the last frame */
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;
41
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * A bag of serializable values that can be recorded to a snapshot
3
+ */
4
+ export interface Bag {
5
+ [key: string]: BagValue;
6
+ }
7
+
8
+ export type BagValue =
9
+ | string
10
+ | number
11
+ | boolean
12
+ | BagValue[]
13
+ | Bag
14
+ | Uint8Array
15
+ | null;
@@ -0,0 +1,11 @@
1
+ import type { Bag } from "./bag";
2
+
3
+ export type GameSchema<
4
+ // CS extends ComponentSchema = ComponentSchema,
5
+ B extends Bag = Bag,
6
+ // IM extends InputMap = InputMap,
7
+ > = {
8
+ // CS: CS;
9
+ B: B;
10
+ // IM: IM;
11
+ };
package/src/mod.ts CHANGED
@@ -1,86 +1,152 @@
1
+ import type { GameSchema } from "./data/schema";
2
+ import type { Bag } from "./data/bag";
1
3
  import type { System } from "./system";
2
4
  import { EngineInputs, type PlatformEvent } from "@bloopjs/engine";
5
+ import type { Context } from "./context";
3
6
 
4
- type BloopLoop = {
5
- system: (label: string, system: System) => void;
6
- systemsCallback: (events: PlatformEvent[]) => void;
7
- }
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> = GameSchema<B>;
29
+
30
+ export class Bloop<GS extends GameSchema> {
31
+ #systems: System<GS>[] = [];
32
+ #bag: GS["B"];
33
+ #context: Context<GS>;
34
+
35
+ /**
36
+ * Bloop.create() is the way to create a new game 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
+ }
8
54
 
9
- export function Bloop(): BloopLoop {
10
- const systems = [] as System[];
55
+ this.#bag = opts.bag ?? {};
56
+ this.#context = {
57
+ bag: this.#bag,
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() {
70
+ return this.#bag;
71
+ }
72
+
73
+ /**
74
+ * Register a system with the game loop.
75
+ *
76
+ */
77
+ system(label: string, system: System): number {
78
+ system.label ??= label;
79
+ this.#systems.push(system);
80
+ return this.#systems.length;
81
+ }
11
82
 
12
- return {
13
- system(label: string, system: System) {
14
- systems.push(system);
15
- },
16
- systemsCallback: (events: PlatformEvent[]) => {
17
- for (const system of systems) {
18
- system.update?.();
83
+ systemsCallback(events: PlatformEvent[]) {
84
+ this.#context.inputs.update(events);
85
+ for (const system of this.#systems) {
86
+ system.update?.(this.#context);
19
87
 
20
- for (const event of events) {
21
- switch(event.type) {
22
- case "keydown":
23
- system.keydown?.({
24
- event: {
25
- key: event.key,
26
- pressure: 1,
27
- }
28
- })
29
- break;
30
- case "keyheld":
31
- system.keyheld?.({
32
- event: {
33
- key: event.key,
34
- pressure: 1,
35
- }
36
- })
37
- break;
38
- case "keyup":
39
- system.keyup?.({
40
- event: {
41
- key: event.key,
42
- pressure: 0,
43
- }
44
- })
45
- break;
46
- case "mousemove":
47
- system.mousemove?.({
48
- event: {
49
- x: event.x,
50
- y: event.y,
51
- }
52
- })
53
- break;
54
- case "mousedown":
55
- system.mousedown?.({
56
- event: {
57
- button: event.button,
58
- pressure: event.pressure,
59
- }
60
- })
61
- break;
62
- case "mouseup":
63
- system.mouseup?.({
64
- event: {
65
- button: event.button,
66
- pressure: event.pressure,
67
- }
68
- })
69
- break;
70
- case "mousewheel":
71
- system.mousewheel?.({
72
- event: {
73
- x: event.x,
74
- y: event.y,
75
- }
76
- })
77
- break;
78
- default:
79
- break;
80
- }
88
+ for (const event of events) {
89
+ switch(event.type) {
90
+ case "keydown":
91
+ system.keydown?.({
92
+ ...this.#context,
93
+ event: {
94
+ key: event.key,
95
+ pressure: 1,
96
+ }
97
+ })
98
+ break;
99
+ case "keyup":
100
+ system.keyup?.({
101
+ ...this.#context,
102
+ event: {
103
+ key: event.key,
104
+ pressure: 0,
105
+ }
106
+ })
107
+ break;
108
+ case "mousemove":
109
+ system.mousemove?.({
110
+ ...this.#context,
111
+ event: {
112
+ x: event.x,
113
+ y: event.y,
114
+ }
115
+ })
116
+ break;
117
+ case "mousedown":
118
+ system.mousedown?.({
119
+ ...this.#context,
120
+ event: {
121
+ button: event.button,
122
+ pressure: event.pressure,
123
+ }
124
+ })
125
+ break;
126
+ case "mouseup":
127
+ system.mouseup?.({
128
+ ...this.#context,
129
+ event: {
130
+ button: event.button,
131
+ pressure: event.pressure,
132
+ }
133
+ })
134
+ break;
135
+ case "mousewheel":
136
+ system.mousewheel?.({
137
+ ...this.#context,
138
+ event: {
139
+ x: event.x,
140
+ y: event.y,
141
+ }
142
+ })
143
+ break;
144
+ default:
145
+ break;
81
146
  }
82
147
  }
83
148
  }
149
+ this.#context.inputs.flush();
84
150
  }
85
151
  }
86
152
 
package/src/system.ts CHANGED
@@ -5,10 +5,12 @@ import type {
5
5
  MouseWheelEvent,
6
6
  } from "@bloopjs/engine";
7
7
  import type { Context } from "./context";
8
- import type { GameSchema } from "./schema";
8
+ import type { GameSchema } from "./data/schema";
9
9
 
10
10
  export type System<GS extends GameSchema = GameSchema> = {
11
- update?: () => void;
11
+ label?: string;
12
+
13
+ update?: (context: Context<GS>) => void;
12
14
 
13
15
  keydown?: (
14
16
  context: Context<GS> & {
@@ -22,12 +24,6 @@ export type System<GS extends GameSchema = GameSchema> = {
22
24
  },
23
25
  ) => void;
24
26
 
25
- keyheld?: (
26
- context: Context<GS> & {
27
- event: KeyEvent;
28
- },
29
- ) => void;
30
-
31
27
  mousedown?: (
32
28
  context: Context<GS> & {
33
29
  event: MouseButtonEvent;
@@ -4,7 +4,7 @@ import { Bloop } from "../src/mod";
4
4
 
5
5
  describe("loop", () => {
6
6
  it("runs a single system", async () => {
7
- const bloop = Bloop();
7
+ const bloop = Bloop.create();
8
8
  let count = 0;
9
9
 
10
10
  bloop.system("test", {
@@ -20,16 +20,14 @@ describe("loop", () => {
20
20
  });
21
21
 
22
22
  it("passes through input events", async () => {
23
- const bloop = Bloop();
23
+ const bloop = Bloop.create();
24
24
 
25
25
  const events = {
26
26
  keydown: null as Key | null,
27
27
  keyup: null as Key | null,
28
- keyheld: null as Key | null,
29
28
 
30
29
  mousemove: null as { x: number; y: number } | null,
31
30
  mousedown: null as MouseButton | null,
32
- mouseheld: null as MouseButton | null,
33
31
  mouseup: null as MouseButton | null,
34
32
  mousewheel: null as { x: number; y: number } | null,
35
33
  };
@@ -41,9 +39,6 @@ describe("loop", () => {
41
39
  keyup({ event }) {
42
40
  events.keyup = event.key;
43
41
  },
44
- keyheld({ event }) {
45
- events.keyheld = event.key;
46
- },
47
42
  mousemove({ event }) {
48
43
  events.mousemove = { x: event.x, y: event.y };
49
44
  },
@@ -83,7 +78,82 @@ describe("loop", () => {
83
78
  expect(events.mousemove).toEqual({ x: 3, y: 4 });
84
79
  });
85
80
 
86
- it.skip("keeps track of keyboard and mouse snapshots", async () => {});
81
+ it("keeps track of keyboard and mouse snapshots", async () => {
82
+ const bloop = Bloop.create({
83
+ bag: {
84
+ cool: "nice",
85
+ },
86
+ });
87
+
88
+ const events = {
89
+ keydown: null as boolean | null,
90
+ keyheld: null as boolean | null,
91
+ keyup: null as boolean | null,
92
+ mouseheld: null as boolean | null,
93
+ mousedown: null as boolean | null,
94
+ mouseup: null as boolean | null,
95
+ };
96
+
97
+ bloop.system("input snapshots", {
98
+ update({ inputs }) {
99
+ events.keydown = inputs.keys.space.down;
100
+ events.keyheld = inputs.keys.space.held;
101
+ events.keyup = inputs.keys.space.up;
102
+
103
+ events.mousedown = inputs.mouse.left.down;
104
+ events.mouseheld = inputs.mouse.left.held;
105
+ events.mouseup = inputs.mouse.left.up;
106
+ },
107
+ });
108
+
109
+ const { runtime, emitter } = await mount(bloop);
110
+
111
+ // Initial state
112
+ runtime.step();
113
+ expect(events).toEqual({
114
+ keydown: false,
115
+ keyheld: false,
116
+ keyup: false,
117
+ mousedown: false,
118
+ mouseheld: false,
119
+ mouseup: false,
120
+ });
121
+
122
+ // down and held are both true on the first frame of a key down
123
+ emitter.keydown("Space");
124
+ emitter.mousedown("Left");
125
+ runtime.step();
126
+ expect(events).toEqual({
127
+ keydown: true,
128
+ keyheld: true,
129
+ keyup: false,
130
+ mousedown: true,
131
+ mouseheld: true,
132
+ mouseup: false,
133
+ });
134
+
135
+ // held remains true, down goes false
136
+ runtime.step();
137
+ expect(events).toEqual({
138
+ keydown: false,
139
+ keyheld: true,
140
+ keyup: false,
141
+ mousedown: false,
142
+ mouseheld: true,
143
+ mouseup: false,
144
+ });
87
145
 
88
- it.skip('synthesizes "keyheld" and "mouseheld" events', async () => {});
146
+ // on key up, up is true, held and down are false
147
+ emitter.keyup("Space");
148
+ emitter.mouseup("Left");
149
+ runtime.step();
150
+ expect(events).toEqual({
151
+ keydown: false,
152
+ keyheld: false,
153
+ keyup: true,
154
+ mousedown: false,
155
+ mouseheld: false,
156
+ mouseup: true,
157
+ });
158
+ });
89
159
  });
package/src/schema.ts DELETED
@@ -1 +0,0 @@
1
- export type GameSchema = unknown;