archetype-ecs-lib 0.4.2 → 0.5.0
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/README.md +8 -173
- package/lib/ecs/Archetype.d.ts +1 -1
- package/lib/ecs/Commands.d.ts +7 -2
- package/lib/ecs/Commands.js +110 -0
- package/lib/ecs/EntityManager.d.ts +1 -1
- package/lib/ecs/Events.d.ts +23 -0
- package/lib/ecs/Events.js +76 -0
- package/lib/ecs/Schedule.d.ts +2 -2
- package/lib/ecs/Schedule.js +5 -1
- package/lib/ecs/Signature.d.ts +1 -1
- package/lib/ecs/TypeRegistry.d.ts +1 -1
- package/lib/ecs/Types.d.ts +131 -3
- package/lib/ecs/World.d.ts +28 -3
- package/lib/ecs/World.js +212 -21
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-

|
|
1
|
+

|
|
2
2
|
|
|
3
3
|
# Archetype ECS Lib
|
|
4
4
|
|
|
@@ -7,66 +7,23 @@ A tiny **archetype-based ECS** (Entity Component System) for TypeScript.
|
|
|
7
7
|
- **Archetypes (tables)** store entities in a **SoA** layout (one column per component type).
|
|
8
8
|
- **Queries** iterate matching archetypes efficiently.
|
|
9
9
|
- **Commands** let you **defer structural changes** (spawn/despawn/add/remove) safely.
|
|
10
|
+
- **Resources (singletons)** store **global world state** (Input, Time, Config, Asset caches…) keyed by type, without using entities.
|
|
11
|
+
- **Events** transient messages (Hit happened, Click happened, Play sound).
|
|
10
12
|
- A minimal **Schedule** runs systems by phases and flushes commands between phases.
|
|
11
13
|
|
|
12
14
|
Exports are defined in `index.ts`:
|
|
13
15
|
- `Types`, `TypeRegistry`, `Commands`, `World`, `Schedule`
|
|
14
16
|
|
|
17
|
+
> :exclamation: The full documentation is at [https://piratejl.github.io/archetype-ecs-lib/](https://piratejl.github.io/archetype-ecs-lib/)
|
|
18
|
+
|
|
15
19
|
---
|
|
16
20
|
|
|
17
21
|
## Install
|
|
18
22
|
|
|
19
23
|
```bash
|
|
20
24
|
npm i archetype-ecs-lib
|
|
21
|
-
````
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Core concepts
|
|
26
|
-
|
|
27
|
-
### Entity
|
|
28
|
-
|
|
29
|
-
An entity is a lightweight handle:
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
type Entity = { id: number; gen: number };
|
|
33
25
|
```
|
|
34
26
|
|
|
35
|
-
The `gen` (generation) prevents using stale entity handles after despawn/reuse.
|
|
36
|
-
|
|
37
|
-
### Component
|
|
38
|
-
|
|
39
|
-
A component is any class used as a type key:
|
|
40
|
-
|
|
41
|
-
```ts
|
|
42
|
-
class Position { constructor(public x = 0, public y = 0) {} }
|
|
43
|
-
class Velocity { constructor(public x = 0, public y = 0) {} }
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Internally, constructors are mapped to a stable numeric `TypeId` via `typeId()`.
|
|
47
|
-
|
|
48
|
-
### World
|
|
49
|
-
|
|
50
|
-
`World` owns entities, archetypes, commands, and systems.
|
|
51
|
-
|
|
52
|
-
Structural operations:
|
|
53
|
-
|
|
54
|
-
* `spawn()`, `despawn(e)`
|
|
55
|
-
* `add(e, Ctor, value)`, `remove(e, Ctor)`
|
|
56
|
-
* `has(e, Ctor)`, `get(e, Ctor)`, `set(e, Ctor, value)`
|
|
57
|
-
* `query(...ctors)` to iterate entities with required components
|
|
58
|
-
* `cmd()` to enqueue deferred commands
|
|
59
|
-
* `flush()` applies queued commands
|
|
60
|
-
* `update(dt)` runs registered systems and flushes at the end
|
|
61
|
-
|
|
62
|
-
### Deferred structural changes (important)
|
|
63
|
-
|
|
64
|
-
While iterating a query (or while systems are running), doing structural changes directly can throw:
|
|
65
|
-
|
|
66
|
-
> “Cannot do structural change (…) while iterating. Use world.cmd() and flush …”
|
|
67
|
-
|
|
68
|
-
Use `world.cmd()` inside systems / loops, and let `world.flush()` apply changes safely.
|
|
69
|
-
|
|
70
27
|
---
|
|
71
28
|
|
|
72
29
|
## Quick start
|
|
@@ -85,7 +42,7 @@ world.add(e, Position, new Position(0, 0));
|
|
|
85
42
|
world.add(e, Velocity, new Velocity(1, 0));
|
|
86
43
|
|
|
87
44
|
// A simple system
|
|
88
|
-
world.addSystem((w
|
|
45
|
+
world.addSystem((w) => {
|
|
89
46
|
for (const { e, c1: pos, c2: vel } of w.query(Position, Velocity)) {
|
|
90
47
|
pos.x += vel.x * dt;
|
|
91
48
|
pos.y += vel.y * dt;
|
|
@@ -98,107 +55,7 @@ world.addSystem((w: any, dt: number) => {
|
|
|
98
55
|
world.update(1 / 60);
|
|
99
56
|
```
|
|
100
57
|
|
|
101
|
-
> Note: `SystemFn` is typed as `(world:
|
|
102
|
-
> In practice, you’ll typically use the concrete `World` API in systems (cast `world` or type your function accordingly).
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## Query API
|
|
107
|
-
|
|
108
|
-
```ts
|
|
109
|
-
for (const row of world.query(Position, Velocity)) {
|
|
110
|
-
// row.e -> Entity
|
|
111
|
-
// row.c1 -> Position
|
|
112
|
-
// row.c2 -> Velocity
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
`query(...ctors)` yields objects shaped like:
|
|
117
|
-
|
|
118
|
-
* `e`: the entity
|
|
119
|
-
* `c1`, `c2`, `c3`, …: component values **in the same order** as the ctor arguments
|
|
120
|
-
|
|
121
|
-
So if you call `query(A, B, C)` you’ll get `{ e, c1: A, c2: B, c3: C }`.
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## Commands API (deferred ops)
|
|
126
|
-
|
|
127
|
-
```ts
|
|
128
|
-
const cmd = world.cmd();
|
|
129
|
-
|
|
130
|
-
cmd.spawn((e) => {
|
|
131
|
-
cmd.add(e, Position, new Position(0, 0));
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
cmd.add(entity, Velocity, new Velocity(1, 0));
|
|
135
|
-
cmd.remove(entity, Velocity);
|
|
136
|
-
cmd.despawn(entity);
|
|
137
|
-
|
|
138
|
-
// Apply them (World.update() also flushes automatically at end)
|
|
139
|
-
world.flush();
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Supported commands (`Commands.ts`):
|
|
143
|
-
|
|
144
|
-
* `spawn(init?)`
|
|
145
|
-
* `despawn(e)`
|
|
146
|
-
* `add(e, ctor, value)`
|
|
147
|
-
* `remove(e, ctor)`
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
## Schedule (phases)
|
|
152
|
-
|
|
153
|
-
`Schedule` is a small phase runner:
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
import { World, Schedule } from "archetype-ecs-lib";
|
|
157
|
-
|
|
158
|
-
const world = new World();
|
|
159
|
-
const sched = new Schedule();
|
|
160
|
-
|
|
161
|
-
sched
|
|
162
|
-
.add("input", (w: any) => { /* read input, enqueue commands */ })
|
|
163
|
-
.add("sim", (w: any, dt) => { /* update movement */ })
|
|
164
|
-
.add("render",(w: any) => { /* build render data */ });
|
|
165
|
-
|
|
166
|
-
const phases = ["input", "sim", "render"];
|
|
167
|
-
|
|
168
|
-
// Runs each phase in order and calls world.flush() after each phase.
|
|
169
|
-
sched.run(world, 1/60, phases);
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
This is handy when you want deterministic ordering and command application points.
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## World API summary
|
|
177
|
-
|
|
178
|
-
### Entity lifecycle
|
|
179
|
-
|
|
180
|
-
* `spawn(): Entity`
|
|
181
|
-
* `despawn(e: Entity): void`
|
|
182
|
-
* `isAlive(e: Entity): boolean`
|
|
183
|
-
|
|
184
|
-
### Components
|
|
185
|
-
|
|
186
|
-
* `has(e, Ctor): boolean`
|
|
187
|
-
* `get(e, Ctor): T | undefined`
|
|
188
|
-
* `set(e, Ctor, value): void` *(requires the component to exist; otherwise throws)*
|
|
189
|
-
* `add(e, Ctor, value): void` *(structural: may move entity between archetypes)*
|
|
190
|
-
* `remove(e, Ctor): void` *(structural: may move entity between archetypes)*
|
|
191
|
-
|
|
192
|
-
### Systems / frame
|
|
193
|
-
|
|
194
|
-
* `addSystem(fn): this`
|
|
195
|
-
* `update(dt): void` *(runs systems in order, then flushes)*
|
|
196
|
-
* `cmd(): Commands`
|
|
197
|
-
* `flush(): void`
|
|
198
|
-
|
|
199
|
-
### Queries
|
|
200
|
-
|
|
201
|
-
* `query(...ctors): Iterable<{ e: Entity; c1?: any; c2?: any; ... }>`
|
|
58
|
+
> Note: `SystemFn` is typed as `(world: WorldApi, dt) => void`..
|
|
202
59
|
|
|
203
60
|
---
|
|
204
61
|
|
|
@@ -212,26 +69,4 @@ This is handy when you want deterministic ordering and command application point
|
|
|
212
69
|
|
|
213
70
|
## License
|
|
214
71
|
|
|
215
|
-
|
|
216
|
-
MIT License
|
|
217
|
-
|
|
218
|
-
Copyright (c) 2025 Jean-Laurent Duzant
|
|
219
|
-
|
|
220
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
221
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
222
|
-
in the Software without restriction, including without limitation the rights
|
|
223
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
224
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
225
|
-
furnished to do so, subject to the following conditions:
|
|
226
|
-
|
|
227
|
-
The above copyright notice and this permission notice shall be included in all
|
|
228
|
-
copies or substantial portions of the Software.
|
|
229
|
-
|
|
230
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
231
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
232
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
233
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
234
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
235
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
236
|
-
SOFTWARE.
|
|
237
|
-
```
|
|
72
|
+
This code is distributed under the terms and conditions of the [MIT license](https://github.com/PirateJL/archetype-ecs-lib/blob/master/LICENSE).
|
package/lib/ecs/Archetype.d.ts
CHANGED
package/lib/ecs/Commands.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComponentCtor, Entity } from "./Types";
|
|
1
|
+
import type { CommandsApi, ComponentCtor, ComponentCtorBundleItem, Entity } from "./Types";
|
|
2
2
|
export type Command = {
|
|
3
3
|
k: "spawn";
|
|
4
4
|
init?: (e: Entity) => void;
|
|
@@ -15,11 +15,16 @@ export type Command = {
|
|
|
15
15
|
e: Entity;
|
|
16
16
|
ctor: ComponentCtor<any>;
|
|
17
17
|
};
|
|
18
|
-
export declare class Commands {
|
|
18
|
+
export declare class Commands implements CommandsApi {
|
|
19
19
|
private q;
|
|
20
20
|
spawn(init?: (e: Entity) => void): void;
|
|
21
|
+
spawnBundle(...items: ComponentCtorBundleItem[]): void;
|
|
21
22
|
despawn(e: Entity): void;
|
|
23
|
+
despawnBundle(entities: Entity[]): void;
|
|
22
24
|
add<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
|
|
25
|
+
addBundle(e: Entity, ...items: ComponentCtorBundleItem[]): void;
|
|
23
26
|
remove<T>(e: Entity, ctor: ComponentCtor<T>): void;
|
|
27
|
+
removeBundle(e: Entity, ...ctors: ComponentCtor<any>[]): void;
|
|
28
|
+
hasPending(): boolean;
|
|
24
29
|
drain(): Command[];
|
|
25
30
|
}
|
package/lib/ecs/Commands.js
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __values = (this && this.__values) || function(o) {
|
|
3
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
4
|
+
if (m) return m.call(o);
|
|
5
|
+
if (o && typeof o.length === "number") return {
|
|
6
|
+
next: function () {
|
|
7
|
+
if (o && i >= o.length) o = void 0;
|
|
8
|
+
return { value: o && o[i++], done: !o };
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
12
|
+
};
|
|
13
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
14
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
15
|
+
if (!m) return o;
|
|
16
|
+
var i = m.call(o), r, ar = [], e;
|
|
17
|
+
try {
|
|
18
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
19
|
+
}
|
|
20
|
+
catch (error) { e = { error: error }; }
|
|
21
|
+
finally {
|
|
22
|
+
try {
|
|
23
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
24
|
+
}
|
|
25
|
+
finally { if (e) throw e.error; }
|
|
26
|
+
}
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
2
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
30
|
exports.Commands = void 0;
|
|
4
31
|
var Commands = /** @class */ (function () {
|
|
@@ -8,15 +35,98 @@ var Commands = /** @class */ (function () {
|
|
|
8
35
|
Commands.prototype.spawn = function (init) {
|
|
9
36
|
this.q.push({ k: "spawn", init: init });
|
|
10
37
|
};
|
|
38
|
+
Commands.prototype.spawnBundle = function () {
|
|
39
|
+
var _this = this;
|
|
40
|
+
var items = [];
|
|
41
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
42
|
+
items[_i] = arguments[_i];
|
|
43
|
+
}
|
|
44
|
+
this.spawn(function (e) {
|
|
45
|
+
var e_1, _a;
|
|
46
|
+
try {
|
|
47
|
+
// Applied during the same flush thanks to World.flush draining until empty.
|
|
48
|
+
for (var items_1 = __values(items), items_1_1 = items_1.next(); !items_1_1.done; items_1_1 = items_1.next()) {
|
|
49
|
+
var _b = __read(items_1_1.value, 2), ctor = _b[0], value = _b[1];
|
|
50
|
+
_this.add(e, ctor, value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
54
|
+
finally {
|
|
55
|
+
try {
|
|
56
|
+
if (items_1_1 && !items_1_1.done && (_a = items_1.return)) _a.call(items_1);
|
|
57
|
+
}
|
|
58
|
+
finally { if (e_1) throw e_1.error; }
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
11
62
|
Commands.prototype.despawn = function (e) {
|
|
12
63
|
this.q.push({ k: "despawn", e: e });
|
|
13
64
|
};
|
|
65
|
+
Commands.prototype.despawnBundle = function (entities) {
|
|
66
|
+
var e_2, _a;
|
|
67
|
+
try {
|
|
68
|
+
for (var entities_1 = __values(entities), entities_1_1 = entities_1.next(); !entities_1_1.done; entities_1_1 = entities_1.next()) {
|
|
69
|
+
var e = entities_1_1.value;
|
|
70
|
+
this.despawn(e);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
74
|
+
finally {
|
|
75
|
+
try {
|
|
76
|
+
if (entities_1_1 && !entities_1_1.done && (_a = entities_1.return)) _a.call(entities_1);
|
|
77
|
+
}
|
|
78
|
+
finally { if (e_2) throw e_2.error; }
|
|
79
|
+
}
|
|
80
|
+
};
|
|
14
81
|
Commands.prototype.add = function (e, ctor, value) {
|
|
15
82
|
this.q.push({ k: "add", e: e, ctor: ctor, value: value });
|
|
16
83
|
};
|
|
84
|
+
Commands.prototype.addBundle = function (e) {
|
|
85
|
+
var e_3, _a;
|
|
86
|
+
var items = [];
|
|
87
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
88
|
+
items[_i - 1] = arguments[_i];
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
for (var items_2 = __values(items), items_2_1 = items_2.next(); !items_2_1.done; items_2_1 = items_2.next()) {
|
|
92
|
+
var _b = __read(items_2_1.value, 2), ctor = _b[0], value = _b[1];
|
|
93
|
+
this.add(e, ctor, value);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
97
|
+
finally {
|
|
98
|
+
try {
|
|
99
|
+
if (items_2_1 && !items_2_1.done && (_a = items_2.return)) _a.call(items_2);
|
|
100
|
+
}
|
|
101
|
+
finally { if (e_3) throw e_3.error; }
|
|
102
|
+
}
|
|
103
|
+
};
|
|
17
104
|
Commands.prototype.remove = function (e, ctor) {
|
|
18
105
|
this.q.push({ k: "remove", e: e, ctor: ctor });
|
|
19
106
|
};
|
|
107
|
+
Commands.prototype.removeBundle = function (e) {
|
|
108
|
+
var e_4, _a;
|
|
109
|
+
var ctors = [];
|
|
110
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
111
|
+
ctors[_i - 1] = arguments[_i];
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
for (var ctors_1 = __values(ctors), ctors_1_1 = ctors_1.next(); !ctors_1_1.done; ctors_1_1 = ctors_1.next()) {
|
|
115
|
+
var ctor = ctors_1_1.value;
|
|
116
|
+
this.remove(e, ctor);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
120
|
+
finally {
|
|
121
|
+
try {
|
|
122
|
+
if (ctors_1_1 && !ctors_1_1.done && (_a = ctors_1.return)) _a.call(ctors_1);
|
|
123
|
+
}
|
|
124
|
+
finally { if (e_4) throw e_4.error; }
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
Commands.prototype.hasPending = function () {
|
|
128
|
+
return this.q.length > 0;
|
|
129
|
+
};
|
|
20
130
|
Commands.prototype.drain = function () {
|
|
21
131
|
var out = this.q;
|
|
22
132
|
this.q = [];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare class EventChannel<T> {
|
|
2
|
+
private _read;
|
|
3
|
+
private _write;
|
|
4
|
+
/** Emit an event into the current phase write buffer. */
|
|
5
|
+
emit(ev: T): void;
|
|
6
|
+
/**
|
|
7
|
+
* Drain readable events (emitted in the previous phase), then clears the read buffer.
|
|
8
|
+
* Zero allocations; fast for hot paths.
|
|
9
|
+
*/
|
|
10
|
+
drain(fn: (ev: T) => void): void;
|
|
11
|
+
/**
|
|
12
|
+
* Read-only view of readable events (previous phase).
|
|
13
|
+
* Valid until the next schedule boundary (swapEvents).
|
|
14
|
+
* Do not store this reference long-term.
|
|
15
|
+
*/
|
|
16
|
+
values(): readonly T[];
|
|
17
|
+
count(): number;
|
|
18
|
+
clear(): void;
|
|
19
|
+
/** Clears both buffers (rarely needed, but useful for resets). */
|
|
20
|
+
clearAll(): void;
|
|
21
|
+
/** @internal Called by World at phase boundaries. */
|
|
22
|
+
swapBuffers(): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __values = (this && this.__values) || function(o) {
|
|
3
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
4
|
+
if (m) return m.call(o);
|
|
5
|
+
if (o && typeof o.length === "number") return {
|
|
6
|
+
next: function () {
|
|
7
|
+
if (o && i >= o.length) o = void 0;
|
|
8
|
+
return { value: o && o[i++], done: !o };
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.EventChannel = void 0;
|
|
15
|
+
var EventChannel = /** @class */ (function () {
|
|
16
|
+
function EventChannel() {
|
|
17
|
+
this._read = [];
|
|
18
|
+
this._write = [];
|
|
19
|
+
}
|
|
20
|
+
/** Emit an event into the current phase write buffer. */
|
|
21
|
+
EventChannel.prototype.emit = function (ev) {
|
|
22
|
+
this._write.push(ev);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Drain readable events (emitted in the previous phase), then clears the read buffer.
|
|
26
|
+
* Zero allocations; fast for hot paths.
|
|
27
|
+
*/
|
|
28
|
+
EventChannel.prototype.drain = function (fn) {
|
|
29
|
+
var e_1, _a;
|
|
30
|
+
var read = this._read;
|
|
31
|
+
try {
|
|
32
|
+
for (var read_1 = __values(read), read_1_1 = read_1.next(); !read_1_1.done; read_1_1 = read_1.next()) {
|
|
33
|
+
var r = read_1_1.value;
|
|
34
|
+
fn(r);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
38
|
+
finally {
|
|
39
|
+
try {
|
|
40
|
+
if (read_1_1 && !read_1_1.done && (_a = read_1.return)) _a.call(read_1);
|
|
41
|
+
}
|
|
42
|
+
finally { if (e_1) throw e_1.error; }
|
|
43
|
+
}
|
|
44
|
+
this.clear();
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Read-only view of readable events (previous phase).
|
|
48
|
+
* Valid until the next schedule boundary (swapEvents).
|
|
49
|
+
* Do not store this reference long-term.
|
|
50
|
+
*/
|
|
51
|
+
EventChannel.prototype.values = function () {
|
|
52
|
+
return this._read;
|
|
53
|
+
};
|
|
54
|
+
EventChannel.prototype.count = function () {
|
|
55
|
+
return this._read.length;
|
|
56
|
+
};
|
|
57
|
+
EventChannel.prototype.clear = function () {
|
|
58
|
+
this._read.length = 0;
|
|
59
|
+
};
|
|
60
|
+
/** Clears both buffers (rarely needed, but useful for resets). */
|
|
61
|
+
EventChannel.prototype.clearAll = function () {
|
|
62
|
+
this._read.length = 0;
|
|
63
|
+
this._write.length = 0;
|
|
64
|
+
};
|
|
65
|
+
/** @internal Called by World at phase boundaries. */
|
|
66
|
+
EventChannel.prototype.swapBuffers = function () {
|
|
67
|
+
if (this._write.length === 0 && this._read.length === 0)
|
|
68
|
+
return;
|
|
69
|
+
var tmp = this._read;
|
|
70
|
+
this._read = this._write;
|
|
71
|
+
this._write = tmp;
|
|
72
|
+
this._write.length = 0;
|
|
73
|
+
};
|
|
74
|
+
return EventChannel;
|
|
75
|
+
}());
|
|
76
|
+
exports.EventChannel = EventChannel;
|
package/lib/ecs/Schedule.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SystemFn,
|
|
1
|
+
import type { SystemFn, WorldApi } from "./Types";
|
|
2
2
|
/**
|
|
3
3
|
* Minimal scheduler that supports phases, without borrow-checking.
|
|
4
4
|
* (Add conflict detection later if you want parallelism.)
|
|
@@ -6,5 +6,5 @@ import type { SystemFn, WorldI } from "./Types";
|
|
|
6
6
|
export declare class Schedule {
|
|
7
7
|
private readonly phases;
|
|
8
8
|
add(phase: string, fn: SystemFn): this;
|
|
9
|
-
run(world:
|
|
9
|
+
run(world: WorldApi, dt: number, phaseOrder: string[]): void;
|
|
10
10
|
}
|
package/lib/ecs/Schedule.js
CHANGED
|
@@ -58,7 +58,11 @@ var Schedule = /** @class */ (function () {
|
|
|
58
58
|
finally { if (e_2) throw e_2.error; }
|
|
59
59
|
}
|
|
60
60
|
// apply deferred commands between phases
|
|
61
|
-
world.
|
|
61
|
+
if (world.cmd().hasPending()) {
|
|
62
|
+
world.flush();
|
|
63
|
+
}
|
|
64
|
+
// deliver events emitted in this phase to the next phase
|
|
65
|
+
world.swapEvents();
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
package/lib/ecs/Signature.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Signature, TypeId } from "./Types";
|
|
1
|
+
import type { Signature, TypeId } from "./Types";
|
|
2
2
|
export declare function signatureKey(sig: Signature): string;
|
|
3
3
|
export declare function mergeSignature(sig: Signature, add: TypeId): TypeId[];
|
|
4
4
|
export declare function subtractSignature(sig: Signature, remove: TypeId): TypeId[];
|
package/lib/ecs/Types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventChannel } from "./Events";
|
|
1
2
|
export type EntityId = number;
|
|
2
3
|
export type Entity = Readonly<{
|
|
3
4
|
id: EntityId;
|
|
@@ -12,13 +13,140 @@ export type EntityMeta = {
|
|
|
12
13
|
export type Column<T = unknown> = T[];
|
|
13
14
|
export type Signature = ReadonlyArray<TypeId>;
|
|
14
15
|
export type ComponentCtor<T> = new (...args: any[]) => T;
|
|
16
|
+
export type ComponentCtorBundleItem<T = any> = readonly [ComponentCtor<T>, T];
|
|
15
17
|
/**
|
|
16
18
|
* Internal numeric id for a component "type".
|
|
17
19
|
* (We keep it numeric so signatures can be sorted quickly.)
|
|
18
20
|
*/
|
|
19
21
|
export type TypeId = number;
|
|
20
|
-
export type SystemFn = (world:
|
|
21
|
-
/**
|
|
22
|
-
|
|
22
|
+
export type SystemFn = (world: WorldApi, dt: number) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Commands API exposed to systems.
|
|
25
|
+
* > Hides internal stuff like `drain()`
|
|
26
|
+
*/
|
|
27
|
+
export interface CommandsApi {
|
|
28
|
+
spawn(init?: (e: Entity) => void): void;
|
|
29
|
+
spawnBundle(...items: ComponentCtorBundleItem[]): void;
|
|
30
|
+
despawn(e: Entity): void;
|
|
31
|
+
despawnBundle(entities: Entity[]): void;
|
|
32
|
+
add<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
|
|
33
|
+
addBundle(e: Entity, ...items: ComponentCtorBundleItem[]): void;
|
|
34
|
+
remove<T>(e: Entity, ctor: ComponentCtor<T>): void;
|
|
35
|
+
removeBundle(e: Entity, ...ctors: ComponentCtor<any>[]): void;
|
|
36
|
+
hasPending(): boolean;
|
|
37
|
+
}
|
|
38
|
+
export type QueryRow1<A> = {
|
|
39
|
+
e: Entity;
|
|
40
|
+
c1: A;
|
|
41
|
+
};
|
|
42
|
+
export type QueryRow2<A, B> = {
|
|
43
|
+
e: Entity;
|
|
44
|
+
c1: A;
|
|
45
|
+
c2: B;
|
|
46
|
+
};
|
|
47
|
+
export type QueryRow3<A, B, C> = {
|
|
48
|
+
e: Entity;
|
|
49
|
+
c1: A;
|
|
50
|
+
c2: B;
|
|
51
|
+
c3: C;
|
|
52
|
+
};
|
|
53
|
+
export type QueryRow4<A, B, C, D> = {
|
|
54
|
+
e: Entity;
|
|
55
|
+
c1: A;
|
|
56
|
+
c2: B;
|
|
57
|
+
c3: C;
|
|
58
|
+
c4: D;
|
|
59
|
+
};
|
|
60
|
+
export type QueryRow5<A, B, C, D, E> = {
|
|
61
|
+
e: Entity;
|
|
62
|
+
c1: A;
|
|
63
|
+
c2: B;
|
|
64
|
+
c3: C;
|
|
65
|
+
c4: D;
|
|
66
|
+
c5: E;
|
|
67
|
+
};
|
|
68
|
+
export type QueryRow6<A, B, C, D, E, F> = {
|
|
69
|
+
e: Entity;
|
|
70
|
+
c1: A;
|
|
71
|
+
c2: B;
|
|
72
|
+
c3: C;
|
|
73
|
+
c4: D;
|
|
74
|
+
c5: E;
|
|
75
|
+
c6: F;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Public World API visible from system functions.
|
|
79
|
+
* Structural typing keeps typings fast and avoids generic plumbing across the whole library.
|
|
80
|
+
*/
|
|
81
|
+
export interface WorldApi {
|
|
82
|
+
cmd(): CommandsApi;
|
|
23
83
|
flush(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Insert or replace a resource (singleton) stored on the World, keyed by ctor.
|
|
86
|
+
* This is NOT a structural change (safe during iteration).
|
|
87
|
+
*/
|
|
88
|
+
setResource<T>(key: ComponentCtor<T>, value: T): void;
|
|
89
|
+
/**
|
|
90
|
+
* Returns the resource value if present, otherwise `undefined`.
|
|
91
|
+
* Use this for optional resources (debug tools, plugins, editor-only state, etc.).
|
|
92
|
+
*
|
|
93
|
+
* Note: if you explicitly stored `undefined` as a resource value, this also returns `undefined`.
|
|
94
|
+
* Use `hasResource()` to distinguish "missing" vs "present but undefined".
|
|
95
|
+
*/
|
|
96
|
+
getResource<T>(key: ComponentCtor<T>): T | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Returns the resource value if present, otherwise throws.
|
|
99
|
+
* Use this for required resources (Time, Input, AssetCache, Config...) to keep systems clean.
|
|
100
|
+
*
|
|
101
|
+
* The check is based on `hasResource(key)` so "missing" is unambiguous even if the stored value is `undefined`.
|
|
102
|
+
*/
|
|
103
|
+
requireResource<T>(key: ComponentCtor<T>): T;
|
|
104
|
+
hasResource<T>(key: ComponentCtor<T>): boolean;
|
|
105
|
+
removeResource<T>(key: ComponentCtor<T>): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Insert once, returning the existing value if already present.
|
|
108
|
+
* Great for bootstrapping defaults without double-initializing.
|
|
109
|
+
*/
|
|
110
|
+
initResource<T>(key: ComponentCtor<T>, factory: () => T): T;
|
|
111
|
+
/** Emit an event into the current phase (write buffer). */
|
|
112
|
+
emit<T>(key: ComponentCtor<T>, ev: T): void;
|
|
113
|
+
/**
|
|
114
|
+
* Get the channel for an event type (created on first use).
|
|
115
|
+
* - readable events are from the previous phase
|
|
116
|
+
* - emitted events go to the current phase
|
|
117
|
+
*/
|
|
118
|
+
events<T>(key: ComponentCtor<T>): EventChannel<T>;
|
|
119
|
+
/**
|
|
120
|
+
* Drain readable events for this type (previous phase), calling fn for each.
|
|
121
|
+
* If the channel doesn't exist yet, this is a no-op.
|
|
122
|
+
*/
|
|
123
|
+
drainEvents<T>(key: ComponentCtor<T>, fn: (ev: T) => void): void;
|
|
124
|
+
/**
|
|
125
|
+
* Clears readable events for a type (or all types if omitted).
|
|
126
|
+
* Does not affect entity structure.
|
|
127
|
+
*/
|
|
128
|
+
clearEvents<T>(key?: ComponentCtor<T>): void;
|
|
129
|
+
/**
|
|
130
|
+
* @internal Called by Schedule at phase boundaries to deliver events to the next phase.
|
|
131
|
+
*/
|
|
132
|
+
swapEvents(): void;
|
|
133
|
+
spawn(): Entity;
|
|
134
|
+
spawnMany(...items: ComponentCtorBundleItem[]): void;
|
|
135
|
+
despawn(e: Entity): void;
|
|
136
|
+
despawnMany(entities: Entity[]): void;
|
|
137
|
+
isAlive(e: Entity): boolean;
|
|
138
|
+
add<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
|
|
139
|
+
addMany(e: Entity, ...items: ComponentCtorBundleItem[]): void;
|
|
140
|
+
remove<T>(e: Entity, ctor: ComponentCtor<T>): void;
|
|
141
|
+
removeMany(e: Entity, ...ctors: ComponentCtor<any>[]): void;
|
|
142
|
+
has<T>(e: Entity, ctor: ComponentCtor<T>): boolean;
|
|
143
|
+
get<T>(e: Entity, ctor: ComponentCtor<T>): T | undefined;
|
|
144
|
+
set<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
|
|
145
|
+
query<A>(c1: ComponentCtor<A>): Iterable<QueryRow1<A>>;
|
|
146
|
+
query<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>): Iterable<QueryRow2<A, B>>;
|
|
147
|
+
query<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>): Iterable<QueryRow3<A, B, C>>;
|
|
148
|
+
query<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>): Iterable<QueryRow4<A, B, C, D>>;
|
|
149
|
+
query<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>): Iterable<QueryRow5<A, B, C, D, E>>;
|
|
150
|
+
query<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>): Iterable<QueryRow6<A, B, C, D, E, F>>;
|
|
151
|
+
query(...ctors: ComponentCtor<any>[]): Iterable<any>;
|
|
24
152
|
}
|
package/lib/ecs/World.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { Commands } from "./Commands";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { EventChannel } from "./Events";
|
|
3
|
+
import type { ComponentCtor, ComponentCtorBundleItem, Entity, QueryRow1, QueryRow2, QueryRow3, QueryRow4, QueryRow5, QueryRow6, SystemFn, WorldApi } from "./Types";
|
|
4
|
+
export declare class World implements WorldApi {
|
|
4
5
|
private readonly entities;
|
|
5
6
|
private readonly archetypes;
|
|
6
7
|
private readonly archByKey;
|
|
7
8
|
private readonly systems;
|
|
8
9
|
private readonly commands;
|
|
9
10
|
private _iterateDepth;
|
|
11
|
+
private readonly resources;
|
|
12
|
+
private readonly eventChannels;
|
|
10
13
|
constructor();
|
|
11
14
|
/** Queue structural changes to apply safely after systems run. */
|
|
12
15
|
cmd(): Commands;
|
|
@@ -18,19 +21,40 @@ export declare class World implements WorldI {
|
|
|
18
21
|
*/
|
|
19
22
|
update(dt: number): void;
|
|
20
23
|
flush(): void;
|
|
24
|
+
setResource<T>(key: ComponentCtor<T>, value: T): void;
|
|
25
|
+
getResource<T>(key: ComponentCtor<T>): T | undefined;
|
|
26
|
+
requireResource<T>(key: ComponentCtor<T>): T;
|
|
27
|
+
hasResource<T>(key: ComponentCtor<T>): boolean;
|
|
28
|
+
removeResource<T>(key: ComponentCtor<T>): boolean;
|
|
29
|
+
initResource<T>(key: ComponentCtor<T>, factory: () => T): T;
|
|
30
|
+
emit<T>(key: ComponentCtor<T>, ev: T): void;
|
|
31
|
+
events<T>(key: ComponentCtor<T>): EventChannel<T>;
|
|
32
|
+
drainEvents<T>(key: ComponentCtor<T>, fn: (ev: T) => void): void;
|
|
33
|
+
clearEvents<T>(key?: ComponentCtor<T>): void;
|
|
34
|
+
/** @internal Called by Schedule at phase boundaries */
|
|
35
|
+
swapEvents(): void;
|
|
21
36
|
spawn(): Entity;
|
|
37
|
+
spawnMany(...items: ComponentCtorBundleItem[]): Entity;
|
|
22
38
|
isAlive(e: Entity): boolean;
|
|
23
39
|
despawn(e: Entity): void;
|
|
40
|
+
despawnMany(entities: Entity[]): void;
|
|
24
41
|
has<T>(e: Entity, ctor: ComponentCtor<T>): boolean;
|
|
25
42
|
get<T>(e: Entity, ctor: ComponentCtor<T>): T | undefined;
|
|
26
43
|
set<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
|
|
27
44
|
add<T>(e: Entity, ctor: ComponentCtor<T>, value: T): void;
|
|
45
|
+
addMany(e: Entity, ...items: ComponentCtorBundleItem[]): void;
|
|
28
46
|
remove<T>(e: Entity, ctor: ComponentCtor<T>): void;
|
|
47
|
+
removeMany(e: Entity, ...ctors: ComponentCtor<any>[]): void;
|
|
29
48
|
/**
|
|
30
49
|
* Query all entities having all required component types.
|
|
31
50
|
* Iterates archetypes (tables) and yields SoA columns for cache-friendly loops.
|
|
32
51
|
*/
|
|
33
|
-
query(
|
|
52
|
+
query<A>(c1: ComponentCtor<A>): Iterable<QueryRow1<A>>;
|
|
53
|
+
query<A, B>(c1: ComponentCtor<A>, c2: ComponentCtor<B>): Iterable<QueryRow2<A, B>>;
|
|
54
|
+
query<A, B, C>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>): Iterable<QueryRow3<A, B, C>>;
|
|
55
|
+
query<A, B, C, D>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>): Iterable<QueryRow4<A, B, C, D>>;
|
|
56
|
+
query<A, B, C, D, E>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>): Iterable<QueryRow5<A, B, C, D, E>>;
|
|
57
|
+
query<A, B, C, D, E, F>(c1: ComponentCtor<A>, c2: ComponentCtor<B>, c3: ComponentCtor<C>, c4: ComponentCtor<D>, c5: ComponentCtor<E>, c6: ComponentCtor<F>): Iterable<QueryRow6<A, B, C, D, E, F>>;
|
|
34
58
|
private _ensureNotIterating;
|
|
35
59
|
private _getOrCreateArchetype;
|
|
36
60
|
private _removeFromArchetype;
|
|
@@ -46,4 +70,5 @@ export declare class World implements WorldI {
|
|
|
46
70
|
* Throws an error if the entity is not alive
|
|
47
71
|
*/
|
|
48
72
|
private _assertAlive;
|
|
73
|
+
private _events;
|
|
49
74
|
}
|
package/lib/ecs/World.js
CHANGED
|
@@ -37,11 +37,28 @@ var __values = (this && this.__values) || function(o) {
|
|
|
37
37
|
};
|
|
38
38
|
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
39
39
|
};
|
|
40
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
41
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
42
|
+
if (!m) return o;
|
|
43
|
+
var i = m.call(o), r, ar = [], e;
|
|
44
|
+
try {
|
|
45
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
46
|
+
}
|
|
47
|
+
catch (error) { e = { error: error }; }
|
|
48
|
+
finally {
|
|
49
|
+
try {
|
|
50
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
51
|
+
}
|
|
52
|
+
finally { if (e) throw e.error; }
|
|
53
|
+
}
|
|
54
|
+
return ar;
|
|
55
|
+
};
|
|
40
56
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
57
|
exports.World = void 0;
|
|
42
58
|
var Archetype_1 = require("./Archetype");
|
|
43
59
|
var Commands_1 = require("./Commands");
|
|
44
60
|
var EntityManager_1 = require("./EntityManager");
|
|
61
|
+
var Events_1 = require("./Events");
|
|
45
62
|
var Signature_1 = require("./Signature");
|
|
46
63
|
var TypeRegistry_1 = require("./TypeRegistry");
|
|
47
64
|
var World = /** @class */ (function () {
|
|
@@ -52,6 +69,8 @@ var World = /** @class */ (function () {
|
|
|
52
69
|
this.systems = [];
|
|
53
70
|
this.commands = new Commands_1.Commands();
|
|
54
71
|
this._iterateDepth = 0;
|
|
72
|
+
this.resources = new Map();
|
|
73
|
+
this.eventChannels = new Map();
|
|
55
74
|
// Archetype 0: empty signature
|
|
56
75
|
var archetype0 = new Archetype_1.Archetype(0, []);
|
|
57
76
|
this.archetypes[0] = archetype0;
|
|
@@ -96,21 +115,113 @@ var World = /** @class */ (function () {
|
|
|
96
115
|
World.prototype.flush = function () {
|
|
97
116
|
var e_2, _a;
|
|
98
117
|
this._ensureNotIterating("flush");
|
|
99
|
-
|
|
118
|
+
// Apply commands until queue is empty. This allows spawn(init) to enqueue add/remove
|
|
119
|
+
// operations that will be applied during the same flush.
|
|
120
|
+
while (true) {
|
|
121
|
+
var ops = this.commands.drain();
|
|
122
|
+
if (ops.length === 0)
|
|
123
|
+
break;
|
|
124
|
+
try {
|
|
125
|
+
for (var ops_1 = (e_2 = void 0, __values(ops)), ops_1_1 = ops_1.next(); !ops_1_1.done; ops_1_1 = ops_1.next()) {
|
|
126
|
+
var op = ops_1_1.value;
|
|
127
|
+
this._apply(op);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
131
|
+
finally {
|
|
132
|
+
try {
|
|
133
|
+
if (ops_1_1 && !ops_1_1.done && (_a = ops_1.return)) _a.call(ops_1);
|
|
134
|
+
}
|
|
135
|
+
finally { if (e_2) throw e_2.error; }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
//#region ---------- Resources (singletons) ----------
|
|
140
|
+
World.prototype.setResource = function (key, value) {
|
|
141
|
+
this.resources.set(key, value);
|
|
142
|
+
};
|
|
143
|
+
World.prototype.getResource = function (key) {
|
|
144
|
+
if (!this.resources.has(key))
|
|
145
|
+
return undefined;
|
|
146
|
+
return this.resources.get(key);
|
|
147
|
+
};
|
|
148
|
+
World.prototype.requireResource = function (key) {
|
|
149
|
+
if (!this.resources.has(key)) {
|
|
150
|
+
var name = this._formatCtor(key);
|
|
151
|
+
throw new Error("Missing resource ".concat(name, ". ") +
|
|
152
|
+
"Insert it via world.setResource(".concat(name, ", value) or world.initResource(").concat(name, ", () => value)."));
|
|
153
|
+
}
|
|
154
|
+
return this.resources.get(key);
|
|
155
|
+
};
|
|
156
|
+
World.prototype.hasResource = function (key) {
|
|
157
|
+
return this.resources.has(key);
|
|
158
|
+
};
|
|
159
|
+
World.prototype.removeResource = function (key) {
|
|
160
|
+
return this.resources.delete(key);
|
|
161
|
+
};
|
|
162
|
+
World.prototype.initResource = function (key, factory) {
|
|
163
|
+
if (this.resources.has(key))
|
|
164
|
+
return this.resources.get(key);
|
|
165
|
+
var value = factory();
|
|
166
|
+
this.resources.set(key, value);
|
|
167
|
+
return value;
|
|
168
|
+
};
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region ---------- Events (phase-scoped) ----------
|
|
171
|
+
World.prototype.emit = function (key, ev) {
|
|
172
|
+
this._events(key).emit(ev);
|
|
173
|
+
};
|
|
174
|
+
World.prototype.events = function (key) {
|
|
175
|
+
return this._events(key);
|
|
176
|
+
};
|
|
177
|
+
World.prototype.drainEvents = function (key, fn) {
|
|
178
|
+
var ch = this.eventChannels.get(key);
|
|
179
|
+
if (!ch)
|
|
180
|
+
return;
|
|
181
|
+
ch.drain(fn);
|
|
182
|
+
};
|
|
183
|
+
World.prototype.clearEvents = function (key) {
|
|
184
|
+
var e_3, _a;
|
|
185
|
+
if (key) {
|
|
186
|
+
var ch = this.eventChannels.get(key);
|
|
187
|
+
if (!ch)
|
|
188
|
+
return;
|
|
189
|
+
ch.clear();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
// clear all readable buffers
|
|
194
|
+
for (var _b = __values(this.eventChannels.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
195
|
+
var ch = _c.value;
|
|
196
|
+
ch.clear();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
200
|
+
finally {
|
|
201
|
+
try {
|
|
202
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
203
|
+
}
|
|
204
|
+
finally { if (e_3) throw e_3.error; }
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
/** @internal Called by Schedule at phase boundaries */
|
|
208
|
+
World.prototype.swapEvents = function () {
|
|
209
|
+
var e_4, _a;
|
|
100
210
|
try {
|
|
101
|
-
for (var
|
|
102
|
-
var
|
|
103
|
-
|
|
211
|
+
for (var _b = __values(this.eventChannels.values()), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
212
|
+
var ch = _c.value;
|
|
213
|
+
ch.swapBuffers();
|
|
104
214
|
}
|
|
105
215
|
}
|
|
106
|
-
catch (
|
|
216
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
107
217
|
finally {
|
|
108
218
|
try {
|
|
109
|
-
if (
|
|
219
|
+
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
110
220
|
}
|
|
111
|
-
finally { if (
|
|
221
|
+
finally { if (e_4) throw e_4.error; }
|
|
112
222
|
}
|
|
113
223
|
};
|
|
224
|
+
//#endregion
|
|
114
225
|
//#region ---------- Entity lifecycle ----------
|
|
115
226
|
World.prototype.spawn = function () {
|
|
116
227
|
var entity = this.entities.create();
|
|
@@ -122,6 +233,28 @@ var World = /** @class */ (function () {
|
|
|
122
233
|
entityMeta.row = row;
|
|
123
234
|
return entity;
|
|
124
235
|
};
|
|
236
|
+
World.prototype.spawnMany = function () {
|
|
237
|
+
var e_5, _a;
|
|
238
|
+
var items = [];
|
|
239
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
240
|
+
items[_i] = arguments[_i];
|
|
241
|
+
}
|
|
242
|
+
var e = this.spawn();
|
|
243
|
+
try {
|
|
244
|
+
for (var items_1 = __values(items), items_1_1 = items_1.next(); !items_1_1.done; items_1_1 = items_1.next()) {
|
|
245
|
+
var _b = __read(items_1_1.value, 2), ctor = _b[0], value = _b[1];
|
|
246
|
+
this.add(e, ctor, value);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
250
|
+
finally {
|
|
251
|
+
try {
|
|
252
|
+
if (items_1_1 && !items_1_1.done && (_a = items_1.return)) _a.call(items_1);
|
|
253
|
+
}
|
|
254
|
+
finally { if (e_5) throw e_5.error; }
|
|
255
|
+
}
|
|
256
|
+
return e;
|
|
257
|
+
};
|
|
125
258
|
World.prototype.isAlive = function (e) {
|
|
126
259
|
return this.entities.isAlive(e);
|
|
127
260
|
};
|
|
@@ -131,6 +264,22 @@ var World = /** @class */ (function () {
|
|
|
131
264
|
this._removeFromArchetype(e);
|
|
132
265
|
this.entities.kill(e);
|
|
133
266
|
};
|
|
267
|
+
World.prototype.despawnMany = function (entities) {
|
|
268
|
+
var e_6, _a;
|
|
269
|
+
try {
|
|
270
|
+
for (var entities_1 = __values(entities), entities_1_1 = entities_1.next(); !entities_1_1.done; entities_1_1 = entities_1.next()) {
|
|
271
|
+
var e = entities_1_1.value;
|
|
272
|
+
this.despawn(e);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
276
|
+
finally {
|
|
277
|
+
try {
|
|
278
|
+
if (entities_1_1 && !entities_1_1.done && (_a = entities_1.return)) _a.call(entities_1);
|
|
279
|
+
}
|
|
280
|
+
finally { if (e_6) throw e_6.error; }
|
|
281
|
+
}
|
|
282
|
+
};
|
|
134
283
|
//#endregion
|
|
135
284
|
//#region ---------- Components ----------
|
|
136
285
|
World.prototype.has = function (e, ctor) {
|
|
@@ -177,6 +326,26 @@ var World = /** @class */ (function () {
|
|
|
177
326
|
return src.column(t)[srcMeta.row];
|
|
178
327
|
});
|
|
179
328
|
};
|
|
329
|
+
World.prototype.addMany = function (e) {
|
|
330
|
+
var e_7, _a;
|
|
331
|
+
var items = [];
|
|
332
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
333
|
+
items[_i - 1] = arguments[_i];
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
for (var items_2 = __values(items), items_2_1 = items_2.next(); !items_2_1.done; items_2_1 = items_2.next()) {
|
|
337
|
+
var _b = __read(items_2_1.value, 2), ctor = _b[0], value = _b[1];
|
|
338
|
+
this.add(e, ctor, value);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
342
|
+
finally {
|
|
343
|
+
try {
|
|
344
|
+
if (items_2_1 && !items_2_1.done && (_a = items_2.return)) _a.call(items_2);
|
|
345
|
+
}
|
|
346
|
+
finally { if (e_7) throw e_7.error; }
|
|
347
|
+
}
|
|
348
|
+
};
|
|
180
349
|
World.prototype.remove = function (e, ctor) {
|
|
181
350
|
this._assertAlive(e, "remove(".concat(this._formatCtor(ctor), ")"));
|
|
182
351
|
this._ensureNotIterating("remove");
|
|
@@ -192,12 +361,26 @@ var World = /** @class */ (function () {
|
|
|
192
361
|
return src.column(t)[srcMeta.row];
|
|
193
362
|
});
|
|
194
363
|
};
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
364
|
+
World.prototype.removeMany = function (e) {
|
|
365
|
+
var e_8, _a;
|
|
366
|
+
var ctors = [];
|
|
367
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
368
|
+
ctors[_i - 1] = arguments[_i];
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
for (var ctors_1 = __values(ctors), ctors_1_1 = ctors_1.next(); !ctors_1_1.done; ctors_1_1 = ctors_1.next()) {
|
|
372
|
+
var ctor = ctors_1_1.value;
|
|
373
|
+
this.remove(e, ctor);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (e_8_1) { e_8 = { error: e_8_1 }; }
|
|
377
|
+
finally {
|
|
378
|
+
try {
|
|
379
|
+
if (ctors_1_1 && !ctors_1_1.done && (_a = ctors_1.return)) _a.call(ctors_1);
|
|
380
|
+
}
|
|
381
|
+
finally { if (e_8) throw e_8.error; }
|
|
382
|
+
}
|
|
383
|
+
};
|
|
201
384
|
World.prototype.query = function () {
|
|
202
385
|
var ctors = [];
|
|
203
386
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
@@ -218,8 +401,8 @@ var World = /** @class */ (function () {
|
|
|
218
401
|
}
|
|
219
402
|
needSorted.length = w;
|
|
220
403
|
function gen(world) {
|
|
221
|
-
var _a, _b, a, cols, i, row, e, out, i,
|
|
222
|
-
var
|
|
404
|
+
var _a, _b, a, cols, i, row, e, out, i, e_9_1;
|
|
405
|
+
var e_9, _c;
|
|
223
406
|
return __generator(this, function (_d) {
|
|
224
407
|
switch (_d.label) {
|
|
225
408
|
case 0:
|
|
@@ -262,14 +445,14 @@ var World = /** @class */ (function () {
|
|
|
262
445
|
return [3 /*break*/, 3];
|
|
263
446
|
case 8: return [3 /*break*/, 11];
|
|
264
447
|
case 9:
|
|
265
|
-
|
|
266
|
-
|
|
448
|
+
e_9_1 = _d.sent();
|
|
449
|
+
e_9 = { error: e_9_1 };
|
|
267
450
|
return [3 /*break*/, 11];
|
|
268
451
|
case 10:
|
|
269
452
|
try {
|
|
270
453
|
if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
|
|
271
454
|
}
|
|
272
|
-
finally { if (
|
|
455
|
+
finally { if (e_9) throw e_9.error; }
|
|
273
456
|
return [7 /*endfinally*/];
|
|
274
457
|
case 11: return [3 /*break*/, 13];
|
|
275
458
|
case 12:
|
|
@@ -314,7 +497,7 @@ var World = /** @class */ (function () {
|
|
|
314
497
|
* Then swap-remove from src.
|
|
315
498
|
*/
|
|
316
499
|
World.prototype._moveEntity = function (e, src, srcRow, dst, pick) {
|
|
317
|
-
var
|
|
500
|
+
var e_10, _a;
|
|
318
501
|
// add row in dst
|
|
319
502
|
var dstRow = dst.addRow(e);
|
|
320
503
|
try {
|
|
@@ -323,12 +506,12 @@ var World = /** @class */ (function () {
|
|
|
323
506
|
dst.column(t).push(pick(t));
|
|
324
507
|
}
|
|
325
508
|
}
|
|
326
|
-
catch (
|
|
509
|
+
catch (e_10_1) { e_10 = { error: e_10_1 }; }
|
|
327
510
|
finally {
|
|
328
511
|
try {
|
|
329
512
|
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
330
513
|
}
|
|
331
|
-
finally { if (
|
|
514
|
+
finally { if (e_10) throw e_10.error; }
|
|
332
515
|
}
|
|
333
516
|
// update meta to dst before removing from src (in case src==dst should never happen here)
|
|
334
517
|
var m = this.entities.meta[e.id];
|
|
@@ -376,6 +559,14 @@ var World = /** @class */ (function () {
|
|
|
376
559
|
}
|
|
377
560
|
return meta;
|
|
378
561
|
};
|
|
562
|
+
World.prototype._events = function (key) {
|
|
563
|
+
var ch = this.eventChannels.get(key);
|
|
564
|
+
if (!ch) {
|
|
565
|
+
ch = new Events_1.EventChannel();
|
|
566
|
+
this.eventChannels.set(key, ch);
|
|
567
|
+
}
|
|
568
|
+
return ch;
|
|
569
|
+
};
|
|
379
570
|
return World;
|
|
380
571
|
}());
|
|
381
572
|
exports.World = World;
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archetype-ecs-lib",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "An Archetype Entity Component System (AECS)",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"type": "git",
|
|
14
14
|
"url": "git+https://github.com/PirateJL/archetype-ecs-lib.git"
|
|
15
15
|
},
|
|
16
|
+
"homepage": "https://piratejl.github.io/archetype-ecs-lib",
|
|
16
17
|
"keywords": [
|
|
17
18
|
"game",
|
|
18
19
|
"gamedev",
|