@bloopjs/bloop 0.0.8 → 0.0.10
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 +39 -5
- package/src/data/bag.ts +15 -0
- package/src/data/schema.ts +11 -0
- package/src/mod.ts +140 -74
- package/src/system.ts +5 -9
- package/test/inputs.test.ts +82 -9
- package/src/schema.ts +0 -1
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.10",
|
|
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.10"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"typescript": "^5"
|
package/src/context.ts
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { InputSnapshot } from "@bloopjs/engine";
|
|
2
|
+
import type { BloopSchema } from "./data/schema";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
//
|
|
4
|
+
export type Context<
|
|
5
|
+
GS extends BloopSchema = BloopSchema,
|
|
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
|
|
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
|
+
};
|
package/src/data/bag.ts
ADDED
package/src/mod.ts
CHANGED
|
@@ -1,86 +1,152 @@
|
|
|
1
|
+
import type { BloopSchema } 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
|
|
5
|
-
|
|
6
|
-
|
|
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> = BloopSchema<B>;
|
|
29
|
+
|
|
30
|
+
export class Bloop<GS extends BloopSchema> {
|
|
31
|
+
#systems: System<GS>[] = [];
|
|
32
|
+
#bag: GS["B"];
|
|
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
|
+
}
|
|
8
54
|
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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 {
|
|
8
|
+
import type { BloopSchema } from "./data/schema";
|
|
9
9
|
|
|
10
|
-
export type System<GS extends
|
|
11
|
-
|
|
10
|
+
export type System<GS extends BloopSchema = BloopSchema> = {
|
|
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;
|
package/test/inputs.test.ts
CHANGED
|
@@ -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,85 @@ describe("loop", () => {
|
|
|
83
78
|
expect(events.mousemove).toEqual({ x: 3, y: 4 });
|
|
84
79
|
});
|
|
85
80
|
|
|
86
|
-
it
|
|
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
|
+
});
|
|
145
|
+
|
|
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
|
+
});
|
|
87
159
|
|
|
88
|
-
it.skip(
|
|
160
|
+
it.skip("handles multiple frames between accumulated inputs", async () => {});
|
|
161
|
+
it.skip("handles down and up between frames", async () => {});
|
|
89
162
|
});
|
package/src/schema.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type GameSchema = unknown;
|