@aura3d/ecs 1.4.0 → 1.4.2
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/package.json +35 -4
- package/src/Archetype.ts +0 -23
- package/src/Bitset.ts +0 -47
- package/src/CommandBuffer.ts +0 -97
- package/src/Component.ts +0 -25
- package/src/ComponentRegistry.ts +0 -38
- package/src/ComponentStore.ts +0 -72
- package/src/ECSProfiler.ts +0 -19
- package/src/ECSSerializer.ts +0 -45
- package/src/Entity.ts +0 -19
- package/src/EntityManager.ts +0 -36
- package/src/Query.ts +0 -57
- package/src/SparseSet.ts +0 -56
- package/src/System.ts +0 -20
- package/src/SystemScheduler.ts +0 -81
- package/src/World.ts +0 -76
- package/src/components/ActiveComponent.ts +0 -56
- package/src/components/CameraComponent.ts +0 -32
- package/src/components/HierarchyComponent.ts +0 -134
- package/src/components/LightComponent.ts +0 -18
- package/src/components/MeshComponent.ts +0 -23
- package/src/components/NameComponent.ts +0 -7
- package/src/components/TagComponent.ts +0 -7
- package/src/components/TransformComponent.ts +0 -34
- package/src/components/WorldTransformComponent.ts +0 -22
- package/src/index.ts +0 -25
- package/src/systems/ActiveSystem.ts +0 -64
- package/src/systems/CameraSystem.ts +0 -50
- package/src/systems/HierarchySystem.ts +0 -182
- package/src/systems/TransformSystem.ts +0 -108
- package/src/systems/index.ts +0 -4
- package/tests/ecs.test.ts +0 -134
- package/tsconfig.json +0 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aura3d/ecs",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,7 +12,38 @@
|
|
|
12
12
|
},
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@aura3d/core": "1.4.
|
|
16
|
-
"@aura3d/math": "1.4.
|
|
17
|
-
}
|
|
15
|
+
"@aura3d/core": "1.4.2",
|
|
16
|
+
"@aura3d/math": "1.4.2"
|
|
17
|
+
},
|
|
18
|
+
"description": "Entity identity, components, sparse-set and archetype storage, queries, command buffers, systems, scheduling, serialization, and transform components.",
|
|
19
|
+
"keywords": [
|
|
20
|
+
"aura3d",
|
|
21
|
+
"3d",
|
|
22
|
+
"browser-3d",
|
|
23
|
+
"web-3d",
|
|
24
|
+
"typescript",
|
|
25
|
+
"ecs",
|
|
26
|
+
"entity-component-system",
|
|
27
|
+
"game-dev",
|
|
28
|
+
"systems",
|
|
29
|
+
"queries",
|
|
30
|
+
"component-storage"
|
|
31
|
+
],
|
|
32
|
+
"homepage": "https://aura3d.auraone.ai",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/auraoneai/aura3d.git",
|
|
36
|
+
"directory": "packages/ecs"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/auraoneai/aura3d/issues"
|
|
40
|
+
},
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"README.md",
|
|
47
|
+
"dist"
|
|
48
|
+
]
|
|
18
49
|
}
|
package/src/Archetype.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { Bitset } from "./Bitset.js";
|
|
2
|
-
import { type Entity } from "./Entity.js";
|
|
3
|
-
|
|
4
|
-
export class Archetype {
|
|
5
|
-
readonly signature: Bitset;
|
|
6
|
-
private readonly members = new Map<number, Entity>();
|
|
7
|
-
|
|
8
|
-
constructor(signature: Bitset) {
|
|
9
|
-
this.signature = signature.clone();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
add(entity: Entity): void {
|
|
13
|
-
this.members.set(entity.id, entity);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
delete(entity: Entity): boolean {
|
|
17
|
-
return this.members.delete(entity.id);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
entities(): Entity[] {
|
|
21
|
-
return [...this.members.values()].sort((a, b) => a.id - b.id);
|
|
22
|
-
}
|
|
23
|
-
}
|
package/src/Bitset.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { ValidationError } from "@aura3d/core";
|
|
2
|
-
|
|
3
|
-
export class Bitset {
|
|
4
|
-
private readonly values = new Set<number>();
|
|
5
|
-
|
|
6
|
-
static from(values: Iterable<number>): Bitset {
|
|
7
|
-
const bitset = new Bitset();
|
|
8
|
-
for (const value of values) bitset.add(value);
|
|
9
|
-
return bitset;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
add(value: number): this {
|
|
13
|
-
if (!Number.isSafeInteger(value) || value < 0) throw new ValidationError("BITSET_VALUE", "Bitset values must be non-negative safe integers.");
|
|
14
|
-
this.values.add(value);
|
|
15
|
-
return this;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
delete(value: number): boolean {
|
|
19
|
-
return this.values.delete(value);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
has(value: number): boolean {
|
|
23
|
-
return this.values.has(value);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
containsAll(other: Bitset): boolean {
|
|
27
|
-
for (const value of other.values) {
|
|
28
|
-
if (!this.values.has(value)) return false;
|
|
29
|
-
}
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
intersects(other: Bitset): boolean {
|
|
34
|
-
for (const value of other.values) {
|
|
35
|
-
if (this.values.has(value)) return true;
|
|
36
|
-
}
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
toArray(): number[] {
|
|
41
|
-
return [...this.values].sort((a, b) => a - b);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
clone(): Bitset {
|
|
45
|
-
return Bitset.from(this.values);
|
|
46
|
-
}
|
|
47
|
-
}
|
package/src/CommandBuffer.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { ValidationError } from "@aura3d/core";
|
|
2
|
-
import { type ComponentCtor } from "./Component.js";
|
|
3
|
-
import { entityKey, type Entity } from "./Entity.js";
|
|
4
|
-
import { type World } from "./World.js";
|
|
5
|
-
|
|
6
|
-
export type EntityRef = Entity | { readonly tempId: number };
|
|
7
|
-
|
|
8
|
-
type Command =
|
|
9
|
-
| { kind: "create"; tempId: number }
|
|
10
|
-
| { kind: "destroy"; entity: EntityRef }
|
|
11
|
-
| { kind: "add"; entity: EntityRef; ctor: ComponentCtor; component: object }
|
|
12
|
-
| { kind: "remove"; entity: EntityRef; ctor: ComponentCtor };
|
|
13
|
-
|
|
14
|
-
export class CommandBuffer {
|
|
15
|
-
private nextTempId = 1;
|
|
16
|
-
private readonly commands: Command[] = [];
|
|
17
|
-
|
|
18
|
-
createEntity(): EntityRef {
|
|
19
|
-
const ref = { tempId: this.nextTempId };
|
|
20
|
-
this.nextTempId += 1;
|
|
21
|
-
this.commands.push({ kind: "create", tempId: ref.tempId });
|
|
22
|
-
return ref;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
destroyEntity(entity: EntityRef): void {
|
|
26
|
-
this.commands.push({ kind: "destroy", entity });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
add<T extends object>(entity: EntityRef, ctor: ComponentCtor<T>, component: T): void {
|
|
30
|
-
this.commands.push({ kind: "add", entity, ctor, component });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
remove<T extends object>(entity: EntityRef, ctor: ComponentCtor<T>): void {
|
|
34
|
-
this.commands.push({ kind: "remove", entity, ctor });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
flush(world: World): Entity[] {
|
|
38
|
-
validateCommands(world, this.commands);
|
|
39
|
-
const tempEntities = new Map<number, Entity>();
|
|
40
|
-
const created: Entity[] = [];
|
|
41
|
-
const commands = this.commands.splice(0, this.commands.length);
|
|
42
|
-
for (const command of commands) {
|
|
43
|
-
if (command.kind === "create") {
|
|
44
|
-
const entity = world.createEntity();
|
|
45
|
-
tempEntities.set(command.tempId, entity);
|
|
46
|
-
created.push(entity);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
const entity = resolve(command.entity, tempEntities);
|
|
50
|
-
if (command.kind === "destroy") world.destroyEntity(entity);
|
|
51
|
-
if (command.kind === "add") world.add(entity, command.ctor, command.component);
|
|
52
|
-
if (command.kind === "remove") world.remove(entity, command.ctor);
|
|
53
|
-
}
|
|
54
|
-
return created;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function validateCommands(world: World, commands: readonly Command[]): void {
|
|
59
|
-
const tempAlive = new Set<number>();
|
|
60
|
-
const deadEntities = new Set<string>();
|
|
61
|
-
|
|
62
|
-
for (const command of commands) {
|
|
63
|
-
if (command.kind === "create") {
|
|
64
|
-
tempAlive.add(command.tempId);
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
validateEntityRef(world, command.entity, tempAlive, deadEntities);
|
|
69
|
-
if (command.kind === "remove") world.registry.require(command.ctor);
|
|
70
|
-
if (command.kind === "destroy") {
|
|
71
|
-
if ("tempId" in command.entity) {
|
|
72
|
-
tempAlive.delete(command.entity.tempId);
|
|
73
|
-
} else {
|
|
74
|
-
deadEntities.add(entityKey(command.entity));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function validateEntityRef(world: World, ref: EntityRef, tempAlive: Set<number>, deadEntities: Set<string>): void {
|
|
81
|
-
if ("tempId" in ref) {
|
|
82
|
-
if (!tempAlive.has(ref.tempId)) throw new ValidationError("TEMP_ENTITY", `Temporary entity is not alive: ${ref.tempId}`);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
world.assertAlive(ref);
|
|
87
|
-
if (deadEntities.has(entityKey(ref))) throw new ValidationError("ENTITY_NOT_ALIVE", `Entity was already destroyed by this command buffer: ${entityKey(ref)}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function resolve(ref: EntityRef, tempEntities: Map<number, Entity>): Entity {
|
|
91
|
-
if ("tempId" in ref) {
|
|
92
|
-
const entity = tempEntities.get(ref.tempId);
|
|
93
|
-
if (!entity) throw new ValidationError("TEMP_ENTITY", `Temporary entity was not created: ${ref.tempId}`);
|
|
94
|
-
return entity;
|
|
95
|
-
}
|
|
96
|
-
return ref;
|
|
97
|
-
}
|
package/src/Component.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { ValidationError } from "@aura3d/core";
|
|
2
|
-
|
|
3
|
-
export interface ComponentCtor<T = object> {
|
|
4
|
-
readonly prototype: T;
|
|
5
|
-
readonly name: string;
|
|
6
|
-
readonly schema?: ComponentSchema;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ComponentSchema {
|
|
10
|
-
readonly version: number;
|
|
11
|
-
serialize?(component: object): unknown;
|
|
12
|
-
deserialize?(data: unknown): object;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ComponentType<T = object> {
|
|
16
|
-
readonly id: number;
|
|
17
|
-
readonly name: string;
|
|
18
|
-
readonly ctor: ComponentCtor<T>;
|
|
19
|
-
readonly schema: ComponentSchema;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function componentName(ctor: ComponentCtor): string {
|
|
23
|
-
if (!ctor.name) throw new ValidationError("COMPONENT_NAME", "Component classes must have a stable name.");
|
|
24
|
-
return ctor.name;
|
|
25
|
-
}
|
package/src/ComponentRegistry.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { ValidationError } from "@aura3d/core";
|
|
2
|
-
import { componentName, type ComponentCtor, type ComponentSchema, type ComponentType } from "./Component.js";
|
|
3
|
-
|
|
4
|
-
export class ComponentRegistry {
|
|
5
|
-
private nextId = 0;
|
|
6
|
-
private readonly byCtor = new Map<ComponentCtor, ComponentType>();
|
|
7
|
-
private readonly byName = new Map<string, ComponentType>();
|
|
8
|
-
|
|
9
|
-
register<T extends object>(ctor: ComponentCtor<T>, schema?: ComponentSchema): ComponentType<T> {
|
|
10
|
-
const name = componentName(ctor);
|
|
11
|
-
const existingByCtor = this.byCtor.get(ctor);
|
|
12
|
-
if (existingByCtor) return existingByCtor as ComponentType<T>;
|
|
13
|
-
if (this.byName.has(name)) throw new ValidationError("DUPLICATE_COMPONENT", `Duplicate component name: ${name}`);
|
|
14
|
-
const type: ComponentType<T> = { id: this.nextId, name, ctor, schema: schema ?? ctor.schema ?? { version: 1 } };
|
|
15
|
-
this.nextId += 1;
|
|
16
|
-
this.byCtor.set(ctor, type as ComponentType);
|
|
17
|
-
this.byName.set(name, type as ComponentType);
|
|
18
|
-
return type;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
get<T extends object>(ctor: ComponentCtor<T>): ComponentType<T> | undefined {
|
|
22
|
-
return this.byCtor.get(ctor) as ComponentType<T> | undefined;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
require<T extends object>(ctor: ComponentCtor<T>): ComponentType<T> {
|
|
26
|
-
const type = this.get(ctor);
|
|
27
|
-
if (!type) throw new ValidationError("UNREGISTERED_COMPONENT", `Component is not registered: ${componentName(ctor)}`);
|
|
28
|
-
return type;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
getByName(name: string): ComponentType | undefined {
|
|
32
|
-
return this.byName.get(name);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
list(): ComponentType[] {
|
|
36
|
-
return [...this.byCtor.values()].sort((a, b) => a.id - b.id);
|
|
37
|
-
}
|
|
38
|
-
}
|
package/src/ComponentStore.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Bitset } from "./Bitset.js";
|
|
2
|
-
import { type ComponentCtor, type ComponentType } from "./Component.js";
|
|
3
|
-
import { ComponentRegistry } from "./ComponentRegistry.js";
|
|
4
|
-
import { type Entity } from "./Entity.js";
|
|
5
|
-
import { SparseSet } from "./SparseSet.js";
|
|
6
|
-
|
|
7
|
-
export class ComponentStore {
|
|
8
|
-
private readonly stores = new Map<number, SparseSet<object>>();
|
|
9
|
-
private readonly signatures = new Map<number, Bitset>();
|
|
10
|
-
|
|
11
|
-
constructor(private readonly registry: ComponentRegistry) {}
|
|
12
|
-
|
|
13
|
-
add<T extends object>(entity: Entity, ctor: ComponentCtor<T>, component: T): void {
|
|
14
|
-
const type = this.registry.require(ctor);
|
|
15
|
-
this.storeFor(type).add(entity, component);
|
|
16
|
-
this.signatureFor(entity).add(type.id);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
remove<T extends object>(entity: Entity, ctor: ComponentCtor<T>): boolean {
|
|
20
|
-
const type = this.registry.require(ctor);
|
|
21
|
-
const removed = this.storeFor(type).delete(entity);
|
|
22
|
-
if (removed) this.signatureFor(entity).delete(type.id);
|
|
23
|
-
return removed;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
get<T extends object>(entity: Entity, ctor: ComponentCtor<T>): T | undefined {
|
|
27
|
-
const type = this.registry.require(ctor);
|
|
28
|
-
return this.storeFor(type).get(entity) as T | undefined;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
has<T extends object>(entity: Entity, ctor: ComponentCtor<T>): boolean {
|
|
32
|
-
const type = this.registry.require(ctor);
|
|
33
|
-
return this.storeFor(type).has(entity);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
deleteEntity(entity: Entity): void {
|
|
37
|
-
for (const store of this.stores.values()) store.delete(entity);
|
|
38
|
-
this.signatures.delete(entity.id);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
signature(entity: Entity): Bitset {
|
|
42
|
-
return this.signatures.get(entity.id)?.clone() ?? new Bitset();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
entries<T extends object>(ctor: ComponentCtor<T>): [Entity, T][] {
|
|
46
|
-
const type = this.registry.require(ctor);
|
|
47
|
-
return this.storeFor(type).entries() as [Entity, T][];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
entitiesWith<T extends object>(ctor: ComponentCtor<T>): Entity[] {
|
|
51
|
-
const type = this.registry.require(ctor);
|
|
52
|
-
return this.storeFor(type).entities();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private storeFor(type: ComponentType): SparseSet<object> {
|
|
56
|
-
let store = this.stores.get(type.id);
|
|
57
|
-
if (!store) {
|
|
58
|
-
store = new SparseSet<object>();
|
|
59
|
-
this.stores.set(type.id, store);
|
|
60
|
-
}
|
|
61
|
-
return store;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private signatureFor(entity: Entity): Bitset {
|
|
65
|
-
let signature = this.signatures.get(entity.id);
|
|
66
|
-
if (!signature) {
|
|
67
|
-
signature = new Bitset();
|
|
68
|
-
this.signatures.set(entity.id, signature);
|
|
69
|
-
}
|
|
70
|
-
return signature;
|
|
71
|
-
}
|
|
72
|
-
}
|
package/src/ECSProfiler.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type World } from "./World.js";
|
|
2
|
-
|
|
3
|
-
export interface ECSProfileSnapshot {
|
|
4
|
-
entities: number;
|
|
5
|
-
components: { name: string; id: number; count: number }[];
|
|
6
|
-
version: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function profileWorld(world: World): ECSProfileSnapshot {
|
|
10
|
-
return {
|
|
11
|
-
entities: world.entities.size,
|
|
12
|
-
components: world.registry.list().map((type) => ({
|
|
13
|
-
name: type.name,
|
|
14
|
-
id: type.id,
|
|
15
|
-
count: world.components.entries(type.ctor).length
|
|
16
|
-
})),
|
|
17
|
-
version: world.getVersion()
|
|
18
|
-
};
|
|
19
|
-
}
|
package/src/ECSSerializer.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { ValidationError } from "@aura3d/core";
|
|
2
|
-
import { type ComponentCtor } from "./Component.js";
|
|
3
|
-
import { type Entity } from "./Entity.js";
|
|
4
|
-
import { World } from "./World.js";
|
|
5
|
-
|
|
6
|
-
export interface SerializedEntity {
|
|
7
|
-
id: number;
|
|
8
|
-
generation: number;
|
|
9
|
-
components: Record<string, unknown>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface SerializedWorld {
|
|
13
|
-
version: 1;
|
|
14
|
-
entities: SerializedEntity[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function serializeWorld(world: World): SerializedWorld {
|
|
18
|
-
const entities: SerializedEntity[] = world.entities.entities().map((entity) => {
|
|
19
|
-
const components: Record<string, unknown> = {};
|
|
20
|
-
for (const type of world.registry.list()) {
|
|
21
|
-
const value = world.components.get(entity, type.ctor as ComponentCtor);
|
|
22
|
-
if (value) components[type.name] = type.schema.serialize ? type.schema.serialize(value) : structuredClone(value);
|
|
23
|
-
}
|
|
24
|
-
return { id: entity.id, generation: entity.generation, components };
|
|
25
|
-
});
|
|
26
|
-
return { version: 1, entities };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function deserializeWorld(serialized: SerializedWorld, componentTypes: ComponentCtor[]): { world: World; remap: Map<string, Entity> } {
|
|
30
|
-
if (serialized.version !== 1) throw new ValidationError("ECS_VERSION", `Unsupported ECS version: ${serialized.version}`);
|
|
31
|
-
const world = new World();
|
|
32
|
-
for (const type of componentTypes) world.registerComponent(type);
|
|
33
|
-
const remap = new Map<string, Entity>();
|
|
34
|
-
for (const serializedEntity of serialized.entities) {
|
|
35
|
-
const entity = world.createEntity();
|
|
36
|
-
remap.set(`${serializedEntity.id}:${serializedEntity.generation}`, entity);
|
|
37
|
-
for (const [name, data] of Object.entries(serializedEntity.components)) {
|
|
38
|
-
const type = world.registry.getByName(name);
|
|
39
|
-
if (!type) throw new ValidationError("ECS_DESERIALIZE_COMPONENT", `Missing component type during ECS deserialize: ${name}`);
|
|
40
|
-
const component = type.schema.deserialize ? type.schema.deserialize(data) : data;
|
|
41
|
-
world.add(entity, type.ctor as ComponentCtor, component as object);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return { world, remap };
|
|
45
|
-
}
|
package/src/Entity.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { ValidationError } from "@aura3d/core";
|
|
2
|
-
|
|
3
|
-
export interface Entity {
|
|
4
|
-
readonly id: number;
|
|
5
|
-
readonly generation: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function entityKey(entity: Entity): string {
|
|
9
|
-
return `${entity.id}:${entity.generation}`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function sameEntity(a: Entity, b: Entity): boolean {
|
|
13
|
-
return a.id === b.id && a.generation === b.generation;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function assertEntity(entity: Entity): void {
|
|
17
|
-
if (!Number.isSafeInteger(entity.id) || entity.id < 0) throw new ValidationError("ENTITY_ID", "Entity id must be a non-negative safe integer.");
|
|
18
|
-
if (!Number.isSafeInteger(entity.generation) || entity.generation < 0) throw new ValidationError("ENTITY_GENERATION", "Entity generation must be a non-negative safe integer.");
|
|
19
|
-
}
|
package/src/EntityManager.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { ValidationError } from "@aura3d/core";
|
|
2
|
-
import { type Entity } from "./Entity.js";
|
|
3
|
-
|
|
4
|
-
export class EntityManager {
|
|
5
|
-
private readonly generations: number[] = [];
|
|
6
|
-
private readonly alive = new Set<number>();
|
|
7
|
-
private readonly free: number[] = [];
|
|
8
|
-
|
|
9
|
-
create(): Entity {
|
|
10
|
-
const id = this.free.pop() ?? this.generations.length;
|
|
11
|
-
this.generations[id] ??= 0;
|
|
12
|
-
this.alive.add(id);
|
|
13
|
-
return { id, generation: this.generations[id] };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
destroy(entity: Entity): boolean {
|
|
17
|
-
if (!this.isAlive(entity)) return false;
|
|
18
|
-
this.alive.delete(entity.id);
|
|
19
|
-
this.generations[entity.id] += 1;
|
|
20
|
-
if (!Number.isSafeInteger(this.generations[entity.id])) throw new ValidationError("ENTITY_GENERATION_OVERFLOW", "Entity generation overflow.");
|
|
21
|
-
this.free.push(entity.id);
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
isAlive(entity: Entity): boolean {
|
|
26
|
-
return this.alive.has(entity.id) && this.generations[entity.id] === entity.generation;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
get size(): number {
|
|
30
|
-
return this.alive.size;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
entities(): Entity[] {
|
|
34
|
-
return [...this.alive].sort((a, b) => a - b).map((id) => ({ id, generation: this.generations[id] }));
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/Query.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { ValidationError } from "@aura3d/core";
|
|
2
|
-
import { Bitset } from "./Bitset.js";
|
|
3
|
-
import { type ComponentCtor } from "./Component.js";
|
|
4
|
-
import { type ComponentRegistry } from "./ComponentRegistry.js";
|
|
5
|
-
import { type ComponentStore } from "./ComponentStore.js";
|
|
6
|
-
import { type Entity } from "./Entity.js";
|
|
7
|
-
import { type EntityManager } from "./EntityManager.js";
|
|
8
|
-
|
|
9
|
-
export interface QueryOptions {
|
|
10
|
-
include?: ComponentCtor[];
|
|
11
|
-
exclude?: ComponentCtor[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class Query {
|
|
15
|
-
private readonly includeMask: Bitset;
|
|
16
|
-
private readonly excludeMask: Bitset;
|
|
17
|
-
private readonly include: readonly ComponentCtor[];
|
|
18
|
-
private readonly exclude: readonly ComponentCtor[];
|
|
19
|
-
|
|
20
|
-
constructor(
|
|
21
|
-
private readonly entities: EntityManager,
|
|
22
|
-
private readonly store: ComponentStore,
|
|
23
|
-
registry: ComponentRegistry,
|
|
24
|
-
options: QueryOptions
|
|
25
|
-
) {
|
|
26
|
-
this.include = options.include ?? [];
|
|
27
|
-
this.exclude = options.exclude ?? [];
|
|
28
|
-
this.includeMask = Bitset.from(this.include.map((ctor) => registry.require(ctor).id));
|
|
29
|
-
this.excludeMask = Bitset.from(this.exclude.map((ctor) => registry.require(ctor).id));
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
matches(entity: Entity): boolean {
|
|
33
|
-
const signature = this.store.signature(entity);
|
|
34
|
-
return signature.containsAll(this.includeMask) && !signature.intersects(this.excludeMask);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
toArray(): Entity[] {
|
|
38
|
-
if (this.include.length === 1 && this.exclude.length === 0) {
|
|
39
|
-
return this.store.entitiesWith(this.include[0]!);
|
|
40
|
-
}
|
|
41
|
-
return this.entities.entities().filter((entity) => this.matches(entity));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
forEach(visitor: (entity: Entity) => void): void {
|
|
45
|
-
for (const entity of this.toArray()) visitor(entity);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
first(): Entity | undefined {
|
|
49
|
-
return this.toArray()[0];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
single(): Entity {
|
|
53
|
-
const result = this.toArray();
|
|
54
|
-
if (result.length !== 1) throw new ValidationError("QUERY_SINGLE", `Expected exactly one entity, found ${result.length}.`);
|
|
55
|
-
return result[0];
|
|
56
|
-
}
|
|
57
|
-
}
|
package/src/SparseSet.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { entityKey, type Entity } from "./Entity.js";
|
|
2
|
-
|
|
3
|
-
export class SparseSet<T> {
|
|
4
|
-
private readonly denseEntities: Entity[] = [];
|
|
5
|
-
private readonly denseValues: T[] = [];
|
|
6
|
-
private readonly sparse = new Map<string, number>();
|
|
7
|
-
|
|
8
|
-
add(entity: Entity, value: T): void {
|
|
9
|
-
const key = entityKey(entity);
|
|
10
|
-
const existing = this.sparse.get(key);
|
|
11
|
-
if (existing !== undefined) {
|
|
12
|
-
this.denseValues[existing] = value;
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
this.sparse.set(key, this.denseEntities.length);
|
|
16
|
-
this.denseEntities.push(entity);
|
|
17
|
-
this.denseValues.push(value);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
get(entity: Entity): T | undefined {
|
|
21
|
-
const index = this.sparse.get(entityKey(entity));
|
|
22
|
-
return index === undefined ? undefined : this.denseValues[index];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
has(entity: Entity): boolean {
|
|
26
|
-
return this.sparse.has(entityKey(entity));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
delete(entity: Entity): boolean {
|
|
30
|
-
const key = entityKey(entity);
|
|
31
|
-
const index = this.sparse.get(key);
|
|
32
|
-
if (index === undefined) return false;
|
|
33
|
-
const lastIndex = this.denseEntities.length - 1;
|
|
34
|
-
const lastEntity = this.denseEntities[lastIndex];
|
|
35
|
-
const lastValue = this.denseValues[lastIndex];
|
|
36
|
-
this.denseEntities[index] = lastEntity;
|
|
37
|
-
this.denseValues[index] = lastValue;
|
|
38
|
-
this.sparse.set(entityKey(lastEntity), index);
|
|
39
|
-
this.denseEntities.pop();
|
|
40
|
-
this.denseValues.pop();
|
|
41
|
-
this.sparse.delete(key);
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
entries(): [Entity, T][] {
|
|
46
|
-
return this.denseEntities.map((entity, index) => [entity, this.denseValues[index]]);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
entities(): Entity[] {
|
|
50
|
-
return this.denseEntities.map((entity) => ({ ...entity }));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
get size(): number {
|
|
54
|
-
return this.denseEntities.length;
|
|
55
|
-
}
|
|
56
|
-
}
|
package/src/System.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type World } from "./World.js";
|
|
2
|
-
|
|
3
|
-
export type SystemPhase = "fixed" | "update" | "late";
|
|
4
|
-
|
|
5
|
-
export interface SystemContext {
|
|
6
|
-
readonly deltaTime: number;
|
|
7
|
-
readonly elapsedTime: number;
|
|
8
|
-
readonly frame: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface System {
|
|
12
|
-
readonly name: string;
|
|
13
|
-
readonly phase?: SystemPhase;
|
|
14
|
-
readonly priority?: number;
|
|
15
|
-
readonly before?: readonly string[];
|
|
16
|
-
readonly after?: readonly string[];
|
|
17
|
-
onAdd?(world: World): void;
|
|
18
|
-
onRemove?(world: World): void;
|
|
19
|
-
update(world: World, context: SystemContext): void;
|
|
20
|
-
}
|