archetype-ecs 1.2.0 → 1.4.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.
@@ -1,11 +1,24 @@
1
+ export interface ProfilerEntry {
2
+ avg: number;
3
+ }
4
+
5
+ export interface Profiler {
6
+ readonly enabled: boolean;
7
+ setEnabled(value: boolean): void;
8
+ begin(): number;
9
+ end(name: string, t0: number): void;
10
+ record(name: string, ms: number): void;
11
+ getData(): Map<string, ProfilerEntry>;
12
+ }
13
+
1
14
  const EMA_ALPHA = 0.1;
2
- const data = new Map();
15
+ const data = new Map<string, ProfilerEntry>();
3
16
  let enabled = false;
4
17
 
5
- export const profiler = {
18
+ export const profiler: Profiler = {
6
19
  get enabled() { return enabled; },
7
20
 
8
- setEnabled(value) {
21
+ setEnabled(value: boolean) {
9
22
  enabled = value;
10
23
  if (!value) data.clear();
11
24
  },
@@ -14,7 +27,7 @@ export const profiler = {
14
27
  return enabled ? performance.now() : 0;
15
28
  },
16
29
 
17
- end(name, t0) {
30
+ end(name: string, t0: number) {
18
31
  if (!enabled) return;
19
32
  const ms = performance.now() - t0;
20
33
  const entry = data.get(name);
@@ -25,7 +38,7 @@ export const profiler = {
25
38
  }
26
39
  },
27
40
 
28
- record(name, ms) {
41
+ record(name: string, ms: number) {
29
42
  if (!enabled) return;
30
43
  const entry = data.get(name);
31
44
  if (entry) {
package/src/System.ts ADDED
@@ -0,0 +1,201 @@
1
+ import type { ComponentDef } from './ComponentRegistry.js';
2
+ import type { EntityId, EntityManager, ArchetypeView } from './EntityManager.js';
3
+
4
+ // ── Decorators (TC39 Stage 3) ────────────────────────────
5
+
6
+ export function OnAdded(...types: ComponentDef[]) {
7
+ return function (_method: Function, context: ClassMethodDecoratorContext) {
8
+ context.addInitializer(function () {
9
+ const self = this as unknown as System;
10
+ self._registerHook('add', types, (self as any)[context.name].bind(self));
11
+ });
12
+ };
13
+ }
14
+
15
+ export function OnRemoved(...types: ComponentDef[]) {
16
+ return function (_method: Function, context: ClassMethodDecoratorContext) {
17
+ context.addInitializer(function () {
18
+ const self = this as unknown as System;
19
+ self._registerHook('remove', types, (self as any)[context.name].bind(self));
20
+ });
21
+ };
22
+ }
23
+
24
+ // ── Base class ───────────────────────────────────────────
25
+
26
+ interface Hook {
27
+ buffer: Set<EntityId>;
28
+ handler: (id: EntityId) => void;
29
+ }
30
+
31
+ export class System {
32
+ em: EntityManager;
33
+ _unsubs: (() => void)[] = [];
34
+ _hooks: Hook[] = [];
35
+
36
+ constructor(em: EntityManager) {
37
+ this.em = em;
38
+ }
39
+
40
+ _registerHook(kind: 'add' | 'remove', types: ComponentDef[], handler: (id: EntityId) => void): void {
41
+ const buffer = new Set<EntityId>();
42
+
43
+ if (kind === 'add') {
44
+ for (const comp of types) {
45
+ const unsub = this.em.onAdd(comp, (id: EntityId) => {
46
+ for (let i = 0; i < types.length; i++) {
47
+ if (!this.em.hasComponent(id, types[i])) return;
48
+ }
49
+ buffer.add(id);
50
+ });
51
+ this._unsubs.push(unsub);
52
+ }
53
+ } else {
54
+ for (const comp of types) {
55
+ const unsub = this.em.onRemove(comp, (id: EntityId) => buffer.add(id));
56
+ this._unsubs.push(unsub);
57
+ }
58
+ }
59
+
60
+ this._hooks.push({ buffer, handler });
61
+ }
62
+
63
+ forEach(types: ComponentDef[], callback: (view: ArchetypeView) => void, exclude?: ComponentDef[]): void {
64
+ this.em.forEach(types, callback, exclude);
65
+ }
66
+
67
+ tick?(): void;
68
+
69
+ run(): void {
70
+ for (const hook of this._hooks) {
71
+ for (const id of hook.buffer) hook.handler(id);
72
+ hook.buffer.clear();
73
+ }
74
+ if (this.tick) this.tick();
75
+ }
76
+
77
+ dispose(): void {
78
+ for (const unsub of this._unsubs) unsub();
79
+ this._unsubs.length = 0;
80
+ this._hooks.length = 0;
81
+ }
82
+ }
83
+
84
+ // ── Functional API ───────────────────────────────────────
85
+
86
+ type HookCallback = (entityId: EntityId) => void;
87
+
88
+ export interface SystemContext {
89
+ onAdded(...args: [...ComponentDef[], HookCallback]): void;
90
+ onRemoved(...args: [...ComponentDef[], HookCallback]): void;
91
+ forEach(types: ComponentDef[], callback: (view: ArchetypeView) => void, exclude?: ComponentDef[]): void;
92
+ }
93
+
94
+ export type FunctionalSystemConstructor = (sys: SystemContext) => (() => void) | void;
95
+
96
+ interface FunctionalHook {
97
+ unsubs: (() => void)[];
98
+ buffer: Set<EntityId>;
99
+ callback: HookCallback;
100
+ }
101
+
102
+ export interface FunctionalSystem {
103
+ (): void;
104
+ dispose(): void;
105
+ }
106
+
107
+ export function createSystem(em: EntityManager, constructor: FunctionalSystemConstructor): FunctionalSystem {
108
+ const hooks: FunctionalHook[] = [];
109
+
110
+ const sys: SystemContext = {
111
+ onAdded(...args: any[]) {
112
+ const callback = args[args.length - 1] as HookCallback;
113
+ const types = args.slice(0, -1) as ComponentDef[];
114
+ const buffer = new Set<EntityId>();
115
+ const unsubs: (() => void)[] = [];
116
+
117
+ for (const comp of types) {
118
+ const unsub = em.onAdd(comp, (id: EntityId) => {
119
+ for (let i = 0; i < types.length; i++) {
120
+ if (!em.hasComponent(id, types[i])) return;
121
+ }
122
+ buffer.add(id);
123
+ });
124
+ unsubs.push(unsub);
125
+ }
126
+
127
+ hooks.push({ unsubs, buffer, callback });
128
+ },
129
+
130
+ onRemoved(...args: any[]) {
131
+ const callback = args[args.length - 1] as HookCallback;
132
+ const types = args.slice(0, -1) as ComponentDef[];
133
+ const buffer = new Set<EntityId>();
134
+ const unsubs: (() => void)[] = [];
135
+
136
+ for (const comp of types) {
137
+ const unsub = em.onRemove(comp, (id: EntityId) => {
138
+ buffer.add(id);
139
+ });
140
+ unsubs.push(unsub);
141
+ }
142
+
143
+ hooks.push({ unsubs, buffer, callback });
144
+ },
145
+
146
+ forEach(types: ComponentDef[], callback: (view: ArchetypeView) => void, exclude?: ComponentDef[]) {
147
+ em.forEach(types, callback, exclude);
148
+ },
149
+ };
150
+
151
+ const tick = constructor(sys);
152
+
153
+ function system() {
154
+ for (const hook of hooks) {
155
+ for (const id of hook.buffer) hook.callback(id);
156
+ hook.buffer.clear();
157
+ }
158
+ if (tick) tick();
159
+ }
160
+
161
+ system.dispose = function () {
162
+ for (const hook of hooks) {
163
+ for (const unsub of hook.unsubs) unsub();
164
+ }
165
+ hooks.length = 0;
166
+ };
167
+
168
+ return system;
169
+ }
170
+
171
+ // ── Activator ────────────────────────────────────────────
172
+
173
+ interface Runnable {
174
+ run(): void;
175
+ dispose(): void;
176
+ }
177
+
178
+ export interface Pipeline {
179
+ (): void;
180
+ dispose(): void;
181
+ }
182
+
183
+ export function createSystems(em: EntityManager, entries: (FunctionalSystemConstructor | (new (em: EntityManager) => System))[]): Pipeline {
184
+ const systems: Runnable[] = entries.map(Entry => {
185
+ if ((Entry as any).prototype instanceof System) {
186
+ return new (Entry as new (em: EntityManager) => System)(em);
187
+ }
188
+ const sys = createSystem(em, Entry as FunctionalSystemConstructor);
189
+ return { run: sys, dispose: sys.dispose };
190
+ });
191
+
192
+ function pipeline() {
193
+ for (let i = 0; i < systems.length; i++) systems[i].run();
194
+ }
195
+
196
+ pipeline.dispose = function () {
197
+ for (let i = 0; i < systems.length; i++) systems[i].dispose();
198
+ };
199
+
200
+ return pipeline;
201
+ }
package/src/index.ts ADDED
@@ -0,0 +1,38 @@
1
+ export { createEntityManager } from './EntityManager.js';
2
+ export type { EntityId, FieldRef, ArchetypeView, EntityManager, SerializedData } from './EntityManager.js';
3
+ export { createSystem, createSystems, System, OnAdded, OnRemoved } from './System.js';
4
+ export type { SystemContext, FunctionalSystemConstructor, FunctionalSystem, Pipeline } from './System.js';
5
+ export { profiler } from './Profiler.js';
6
+ export type { Profiler, ProfilerEntry } from './Profiler.js';
7
+ export { TYPED, componentSchemas, parseTypeSpec } from './ComponentRegistry.js';
8
+ export type { ComponentDef, TypeSpec } from './ComponentRegistry.js';
9
+
10
+ import { parseTypeSpec, componentSchemas, type ComponentDef, type TypeSpec } from './ComponentRegistry.js';
11
+
12
+ export function component(name: string, typeOrSchema?: string | Record<string, string>, fields?: string[]): ComponentDef {
13
+ const sym = Symbol(name);
14
+ const comp: any = { _sym: sym, _name: name };
15
+
16
+ let schema: Record<string, TypeSpec> | undefined;
17
+
18
+ if (typeof typeOrSchema === 'string' && Array.isArray(fields)) {
19
+ const spec = parseTypeSpec(typeOrSchema);
20
+ schema = {};
21
+ for (const f of fields) {
22
+ schema[f] = spec;
23
+ comp[f] = { _sym: sym, _field: f };
24
+ }
25
+ } else if (typeOrSchema && typeof typeOrSchema === 'object') {
26
+ schema = {};
27
+ for (const [field, type] of Object.entries(typeOrSchema)) {
28
+ schema[field] = parseTypeSpec(type);
29
+ comp[field] = { _sym: sym, _field: field };
30
+ }
31
+ }
32
+
33
+ if (schema) {
34
+ componentSchemas.set(sym, schema);
35
+ }
36
+
37
+ return comp;
38
+ }