@draug/engine 1.0.0 → 1.0.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/ecs/query.ts DELETED
@@ -1,169 +0,0 @@
1
- import type { ComponentType } from "./components";
2
- import type { EntityID } from "./entity";
3
- import type { World } from "./world";
4
- import { Bitmap } from "bitmap-index";
5
-
6
- export type QueryParameters = {
7
- include?: ComponentType[];
8
- exclude?: ComponentType[];
9
- anyOf?: ComponentType[];
10
- excludeEntitiesIds?: number[];
11
- filter?: (id: number) => boolean;
12
- };
13
-
14
- type CachedQuery = {
15
- params: QueryParameters;
16
- bitmap: Bitmap;
17
- dirty: boolean;
18
- deps: Set<ComponentType>;
19
- };
20
-
21
- export class QueryManager {
22
- private cache = new Map<string, CachedQuery>();
23
-
24
- constructor(
25
- private readonly world: World,
26
- ) { }
27
-
28
- public get(params: QueryParameters): EntityID[] {
29
- const key = this.getKey(params);
30
- let entry = this.cache.get(key);
31
-
32
- if (!entry) {
33
- entry = {
34
- params,
35
- bitmap: this.compute(params),
36
- dirty: false,
37
- deps: this.collectDeps(params),
38
- };
39
- this.cache.set(key, entry);
40
- }
41
-
42
- if (entry.dirty) {
43
- entry.bitmap = this.compute(entry.params);
44
- entry.dirty = false;
45
- }
46
-
47
- // 1. Динамически применяем исключение конкретных ID
48
- let targetBitmap = entry.bitmap;
49
- if (params.excludeEntitiesIds?.length) {
50
- targetBitmap = entry.bitmap.clone();
51
- const excludeBm = new Bitmap();
52
- for (const id of params.excludeEntitiesIds) {
53
- excludeBm.set(id);
54
- }
55
- targetBitmap.andNot(excludeBm);
56
- }
57
-
58
- // 2. Динамически применяем фильтр
59
- if (params.filter) {
60
- const result: number[] = [];
61
- targetBitmap.range(id => {
62
- if (params.filter!(id)) result.push(id);
63
- });
64
- return result;
65
- }
66
-
67
- return this.extractIds(targetBitmap);
68
- }
69
-
70
- public invalidate(component: ComponentType): void {
71
- for (const entry of this.cache.values()) {
72
- if (entry.deps.has(component)) {
73
- entry.dirty = true;
74
- }
75
- }
76
- }
77
-
78
- private getKey(q: QueryParameters): string {
79
- return [
80
- this.ids(q.include),
81
- this.ids(q.exclude),
82
- this.ids(q.anyOf),
83
- ].join("|");
84
- }
85
-
86
- private ids(arr?: ComponentType[]): string {
87
- if (!arr || arr.length === 0) return "";
88
- return arr
89
- .map(c => this.world.components.getComponentId(c))
90
- .sort((a, b) => a - b)
91
- .join(",");
92
- }
93
-
94
- private collectDeps(q: QueryParameters): Set<ComponentType> {
95
- const set = new Set<ComponentType>();
96
- q.include?.forEach(c => set.add(c));
97
- q.exclude?.forEach(c => set.add(c));
98
- q.anyOf?.forEach(c => set.add(c));
99
- return set;
100
- }
101
-
102
- private compute(params: QueryParameters): Bitmap {
103
- let result = this.combineBitmaps(params.include, 'and');
104
-
105
- const any = this.combineBitmaps(params.anyOf, 'or');
106
- if (any) {
107
- result = result ? result.and(any) : any;
108
- }
109
-
110
- if (!result) {
111
- return new Bitmap();
112
- }
113
-
114
- this.applyExclusions(result, params.exclude);
115
- return result;
116
- }
117
-
118
- private combineBitmaps(
119
- components: ComponentType[] | undefined,
120
- op: 'and' | 'or'
121
- ): Bitmap | null {
122
- if (!components?.length) return null;
123
-
124
- let result: Bitmap | null = null;
125
- let hasAtLeastOneValid = false;
126
-
127
- for (const c of components) {
128
- const bm = this.world.components.getStorage(c)?.bitmap();
129
-
130
- if (!bm) {
131
- if (op === 'and') {
132
- return new Bitmap();
133
- }
134
- continue;
135
- }
136
-
137
- hasAtLeastOneValid = true;
138
- if (!result) {
139
- result = bm.clone();
140
- } else {
141
- op === 'and' ? result.and(bm) : result.or(bm);
142
- }
143
- }
144
-
145
- if (op === 'or' && !hasAtLeastOneValid) {
146
- return new Bitmap();
147
- }
148
-
149
- return result;
150
- }
151
-
152
- private applyExclusions(
153
- target: Bitmap,
154
- excludeComponents?: ComponentType[],
155
- ): void {
156
- if (excludeComponents?.length) {
157
- for (const c of excludeComponents) {
158
- const bm = this.world.components.getStorage(c)?.bitmap();
159
- if (bm) target.andNot(bm);
160
- }
161
- }
162
- }
163
-
164
- private extractIds(bitmap: Bitmap): number[] {
165
- const result: number[] = [];
166
- bitmap.range(id => { result.push(id) });
167
- return result;
168
- }
169
- }
@@ -1 +0,0 @@
1
- export { ResourcesManager } from './resources'
@@ -1,28 +0,0 @@
1
- import type { ClassType } from "@draug/types/class";
2
-
3
- export class ResourcesManager {
4
- private readonly items_ = new Map<ClassType<any>, unknown>();
5
- public insert<T extends object>(type: ClassType<T>, value: T): T {
6
- this.items_.set(type, value);
7
- return value;
8
- };
9
- public get<T extends object>(type: ClassType<T>): T {
10
- const value = this.items_.get(type);
11
- if (!value)
12
- throw new Error(`Resource of class ${type.name} does not exist!`);
13
-
14
- return value as T;
15
- }
16
- public getOrInsert<T extends object>(type: ClassType<T>, factory: () => T): T {
17
- let value: T | null = (this.items_.get(type) ?? null) as T | null;
18
- if (value === null) {
19
- value = factory();
20
- this.items_.set(type, value);
21
- }
22
-
23
- return value;
24
- }
25
- public remove<T>(type: ClassType<T>): void {
26
- this.items_.delete(type);
27
- }
28
- };
package/ecs/system.ts DELETED
@@ -1,214 +0,0 @@
1
- import { DAGNode, topologicalSort } from '@draug/core/graph/dag';
2
- import type { ClassType, ComponentType } from '@draug/types/class'
3
- import type { World } from "./world";
4
- import type { QueryParameters } from './query';
5
-
6
-
7
- export class SystemError extends Error {
8
- constructor(target: Function) {
9
- super(`[System Error] (System "${target.name}".`)
10
- }
11
- }
12
- export class ErrNotASystem extends Error {
13
- constructor(target: Function) {
14
- super(`Provided class "${target.name}" is not a System! Extend your class from SystemBase.`)
15
- }
16
- }
17
- export class ErrMissingSystemMetadata extends SystemError {
18
- constructor(target: SystemCtor) {
19
- super(target);
20
- this.message = `${this.message}: Missing system metadata! Define system class with @System decorator.`
21
- }
22
- };
23
-
24
- export type SystemMetadata = {
25
- /**
26
- * ECS query definition used to select entities for this system execution.
27
- *
28
- * Determines the iteration set passed to {@link SystemBase.compute}.
29
- * The ECS runtime resolves entities based on this query each update.
30
- */
31
- query: Readonly<QueryParameters>;
32
- /**
33
- * Explicit list of component types required by the system but not necessarily
34
- * part of the iteration query.
35
- *
36
- * Used for:
37
- * - ensuring component storages are initialized in the world
38
- * - safe access via `world.components.getStorage`
39
- * - dependencies that should not affect entity selection
40
- *
41
- * This does NOT influence entity iteration; only runtime validation/setup.
42
- */
43
- requiredComponents: Set<ComponentType>;
44
- /**
45
- * Systems that must run before this one. Pass constructor arguments
46
- * `super(OtherSystemCtor, ...)` to add edges; each listed system is scheduled earlier than
47
- * this instance. Used when {@link SystemsManager.build} computes a topological execution order.
48
- */
49
- computeAfter?: Set<SystemCtor>;
50
- };
51
- export type SystemDecoratorProps = {
52
- query: SystemMetadata['query'];
53
- requiredComponents?: ComponentType[];
54
- computeAfter?: SystemCtor[];
55
- };
56
- const SystemMetadataSymbol = Symbol("system");
57
- type FunctionWithMetadata = Function & { [SystemMetadataSymbol]?: SystemMetadata };
58
-
59
- export function System(props: SystemDecoratorProps): ClassDecorator {
60
- return (target: Function) => {
61
- const systemTarget = target as FunctionWithMetadata;
62
-
63
- if ('__proto__' in systemTarget && systemTarget.__proto__ !== SystemBase) {
64
- throw new ErrNotASystem(target);
65
- }
66
-
67
- const query = { ...props.query };
68
- const requiredComponents = new Set(props.requiredComponents);
69
- const computeAfter = new Set(props.computeAfter);
70
- const metadata: SystemMetadata = { query, requiredComponents, computeAfter };
71
-
72
- // Теперь TS позволяет записать значение
73
- systemTarget[SystemMetadataSymbol] = metadata;
74
- };
75
- }
76
-
77
- export function getSystemMetadata(system: SystemCtor): SystemMetadata {
78
- if (hasMetadata(system)) {
79
- return system[SystemMetadataSymbol] as SystemMetadata;
80
- }
81
- throw new ErrMissingSystemMetadata(system);
82
- }
83
-
84
-
85
- export function hasMetadata(ctor: Function): ctor is Required<FunctionWithMetadata> {
86
- return SystemMetadataSymbol in ctor;
87
- }
88
-
89
- export function isSystem(ctor: Function): boolean {
90
- return hasMetadata(ctor);
91
- }
92
-
93
-
94
- export type SystemCtor<T extends SystemBase = SystemBase> = ClassType<T>;
95
-
96
- /**
97
- * Arguments passed to {@link SystemBase.compute} on each {@link SystemsManager.update} call.
98
- */
99
- export type SystemComputeContext = {
100
- /** Entity IDs from the query for this {@link SystemBase.compute} invocation. */
101
- entities: number[];
102
- /** ECS world instance. */
103
- world: World;
104
- /** Delta time (seconds or your engine's convention) since the previous update. */
105
- dt: number;
106
- };
107
-
108
- /**
109
- * Base class for ECS systems executed by {@link SystemsManager}.
110
- *
111
- * Subclasses declare which components they iterate over (`queryComponents`), which component
112
- * types must exist in the world for registration (`requiredComponents`), and optional ordering
113
- * relative to other systems (`computeAfter` / `super(OtherSystem)`).
114
- */
115
- export abstract class SystemBase {
116
- /**
117
- * Logic for one systems pass: run for all entities in {@link SystemComputeContext.entities}.
118
- */
119
- public abstract compute(ctx: SystemComputeContext): void;
120
-
121
- public onInit?(world: World): void;
122
- };
123
-
124
-
125
- export class SystemsManager {
126
- private systems_ = new Map<SystemCtor, SystemBase>();
127
- private executionOrder_: SystemBase[] = [];
128
- private requiredComponents_: Set<ComponentType> = new Set();
129
-
130
- private dirty_ = true;
131
-
132
- constructor(
133
- private readonly world: World,
134
- ) { }
135
-
136
- public getRequiredComponents(): ComponentType[] {
137
- return Array.from(this.requiredComponents_);
138
- }
139
-
140
- public register<T extends SystemBase>(sys: T): void {
141
- const ctor = sys.constructor as SystemCtor<T>;
142
- if (this.systems_.has(ctor)) throw new Error("Duplicate system");
143
- const { query, requiredComponents } = getSystemMetadata(ctor);
144
-
145
- this.systems_.set(ctor, sys);
146
-
147
- const q = query;
148
-
149
- for (const c of q.include ?? [])
150
- this.requiredComponents_.add(c);
151
-
152
- for (const c of q.exclude ?? [])
153
- this.requiredComponents_.add(c);
154
-
155
- for (const c of q.anyOf ?? [])
156
- this.requiredComponents_.add(c);
157
- for (const c of requiredComponents)
158
- this.requiredComponents_.add(c);
159
- }
160
-
161
- public build(): void {
162
- this.buildSystemsArray();
163
- for (const sys of this.systems_.values())
164
- sys.onInit?.(this.world);
165
- }
166
-
167
- private rebuild(): void {
168
- this.build();
169
- };
170
-
171
- public get<T extends SystemBase>(ctor: SystemCtor<T>): T {
172
- const s = this.systems_.get(ctor);
173
-
174
- if (!s)
175
- throw new Error("System not registered");
176
- return s as T;
177
- }
178
-
179
- public update(dt: number): void {
180
- if (this.dirty_)
181
- this.rebuild();
182
-
183
- this.world.events.swapAll();
184
-
185
- for (const s of this.executionOrder_) {
186
- const { query } = getSystemMetadata(s.constructor as SystemCtor)
187
- const entities = this.world.query(query);
188
- s.compute({ entities, world: this.world, dt });
189
- }
190
- }
191
-
192
- private buildSystemsArray(): void {
193
- const map = new Map<SystemCtor, DAGNode<SystemBase>>();
194
-
195
- for (const [ctor, system] of this.systems_.entries()) {
196
- map.set(ctor, new DAGNode(system));
197
- }
198
-
199
- for (const ctor of this.systems_.keys()) {
200
- const currentNode = map.get(ctor)!;
201
- const { computeAfter } = getSystemMetadata(ctor);
202
- for (const depCtor of computeAfter ?? []) {
203
- const depNode = map.get(depCtor);
204
- if (!depNode) {
205
- throw new Error(`Dependency ${depCtor.name} not registered`);
206
- }
207
-
208
- depNode.vertices.push(currentNode);
209
- }
210
- }
211
-
212
- this.executionOrder_ = topologicalSort(map.values()).map(x => x.data);
213
- }
214
- }
package/ecs/world.ts DELETED
@@ -1,99 +0,0 @@
1
- /*
2
- ECS World implementation
3
- ⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
4
- ⢻⣿⡗⢶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣄
5
- ⠀⢻⣇⠀⠈⠙⠳⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⠶⠛⠋⣹⣿⡿
6
- ⠀⠀⠹⣆⠀⠀⠀⠀⠙⢷⣄⣀⣀⣀⣤⣤⣤⣄⣀⣴⠞⠋⠉⠀⠀⠀⢀⣿⡟⠁
7
- ⠀⠀⠀⠙⢷⡀⠀⠀⠀⠀⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠋⠀⠀
8
- ⠀⠀⠀⠀⠈⠻⡶⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣠⡾⠋⠀⠀⠀⠀
9
- ⠀⠀⠀⠀⠀⣼⠃⠀⢠⠒⣆⠀⠀⠀⠀⠀⠀⢠⢲⣄⠀⠀⠀⢻⣆⠀⠀⠀⠀⠀
10
- ⠀⠀⠀⠀⢰⡏⠀⠀⠈⠛⠋⠀⢀⣀⡀⠀⠀⠘⠛⠃⠀⠀⠀⠈⣿⡀⠀⠀⠀⠀
11
- ⠀⠀⠀⠀⣾⡟⠛⢳⠀⠀⠀⠀⠀⣉⣀⠀⠀⠀⠀⣰⢛⠙⣶⠀⢹⣇⠀⠀⠀⠀
12
- ⠀⠀⠀⠀⢿⡗⠛⠋⠀⠀⠀⠀⣾⠋⠀⢱⠀⠀⠀⠘⠲⠗⠋⠀⠈⣿⠀⠀⠀⠀
13
- ⠀⠀⠀⠀⠘⢷⡀⠀⠀⠀⠀⠀⠈⠓⠒⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡇⠀⠀⠀
14
- ⠀⠀⠀⠀⠀⠈⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣧⠀⠀⠀
15
- ⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁⠀⠀⠀
16
- */
17
-
18
- import { type ClassType, type ComponentType } from "@draug/types/class";
19
- import { EntitiesManager, type EntityID, EntityRef } from "./entity";
20
- import { SystemsManager } from "./system";
21
- import { ECS_DEFAULTS } from "./constant";
22
- import { EventBus } from "./events-buffer";
23
- import { ComponentsManager } from "./components";
24
- import { ResourcesManager } from "./resources/resources";
25
- import { Commands } from "./command";
26
- import { QueryManager, type QueryParameters } from "./query";
27
- import { PluginsManager } from "../plugin";
28
-
29
- export class World {
30
- public readonly entities = new EntitiesManager();
31
- public readonly components = new ComponentsManager();
32
- public readonly systems = new SystemsManager(this);
33
- public readonly events = new EventBus();
34
- public readonly resources = new ResourcesManager();
35
- public readonly commands = new Commands(this);
36
- public readonly queries = new QueryManager(this);
37
- public readonly plugins = new PluginsManager();
38
-
39
- private entityRefs_ = new Map<number, EntityRef>();
40
-
41
- constructor(maxEntityCount: number = ECS_DEFAULTS.MAX_ENTITY_COUNT) {
42
- this.components = new ComponentsManager(maxEntityCount);
43
- }
44
-
45
- public getEntityRef(id: number): EntityRef {
46
- let ref = this.entityRefs_.get(id);
47
- if (!ref) {
48
- ref = new EntityRef(this, id);
49
- this.entityRefs_.set(id, ref);
50
- }
51
- return ref;
52
- }
53
- public query(params: QueryParameters): number[] {
54
- return this.queries.get(params);
55
- }
56
-
57
- public removeComponent<T extends object>(ref: EntityRef, component: ComponentType<T>): void;
58
- public removeComponent<T extends object>(entity: EntityID, component: ComponentType<T>): void;
59
- public removeComponent<T extends object>(
60
- entity: EntityID | EntityRef,
61
- component: ComponentType<T>
62
- ): void {
63
- const id = typeof entity === 'number' ? entity : entity.id;
64
-
65
- const storage = this.components.getStorage(component);
66
- storage.remove(id);
67
-
68
- this.queries.invalidate(component);
69
- }
70
-
71
- public addComponent<T extends object>(id: EntityID, component: ClassType<T>, initFn?: (obj: T) => void): T;
72
- public addComponent<T extends object>(id: EntityRef, component: ClassType<T>, initFn?: (obj: T) => void): T
73
- public addComponent<T extends object>(entity: EntityID | EntityRef, component: ClassType<T>, initFn?: (obj: T) => void): T {
74
- const storage = this.components.getStorage(component);
75
- let id: number;
76
- if (typeof entity === 'number') {
77
- id = entity;
78
- } else {
79
- id = entity.id;
80
- }
81
- const c = storage.add(id, (o) => {
82
- if (initFn) {
83
- initFn(o);
84
- }
85
- return o;
86
- });
87
- this.queries.invalidate(component);
88
- return c;
89
- };
90
-
91
- public update(dt: number): void {
92
- this.systems.update(dt);
93
- this.commands.flush(this);
94
- }
95
-
96
- public build(): void {
97
- this.plugins.build();
98
- }
99
- };
package/index.ts DELETED
@@ -1,74 +0,0 @@
1
- export {
2
- System,
3
- SystemBase,
4
- SystemError,
5
- ErrMissingSystemMetadata,
6
- ErrNotASystem,
7
- isSystem,
8
- getSystemMetadata,
9
- SystemsManager,
10
- type SystemComputeContext
11
- } from './ecs/system';
12
-
13
- export {
14
- World
15
- } from './ecs/world';
16
-
17
- export {
18
- Component,
19
- ComponentAlreadyRegisteredError,
20
- ComponentStorage,
21
- ComponentsManager,
22
- type IStorage,
23
- SingletonStorage,
24
- } from './ecs/components';
25
-
26
- export { ResourcesManager } from './ecs/resources';
27
-
28
- export {
29
- entry,
30
- Commands,
31
- type CreateEntityComponentEntry,
32
- type ComponentInitFn,
33
- type WorldCommand,
34
- } from './ecs/command';
35
-
36
- export {
37
- EntityMaskNotFoundError,
38
- EntityRef,
39
- EntitiesManager,
40
- UnregisteredComponentStorageError,
41
- type EntityID,
42
- } from './ecs/entity';
43
-
44
- export {
45
- Plugin,
46
- isPlugin,
47
- getPluginMetadata,
48
- PluginBase,
49
- PluginsManager,
50
- PluginError,
51
- ErrMissingPluginMetadata,
52
- ErrNotAPlugin,
53
- ErrPluginNotInit,
54
- ErrUnknownPlugin,
55
- type PluginID,
56
- type PluginMetadata,
57
- type PluginDependencies,
58
- } from './plugin';
59
-
60
- export {
61
- EventBuffer,
62
- EventBus,
63
- createEventKey,
64
- } from './ecs/events-buffer';
65
-
66
- export {
67
- Clock,
68
- type TimeSource,
69
- } from './runtime/clock';
70
-
71
- export { GameLoop, type StepFunction } from './runtime/game-loop';
72
- export {
73
- Runtime
74
- } from './runtime/runtime';
package/plugin/index.ts DELETED
@@ -1,15 +0,0 @@
1
- export {
2
- Plugin,
3
- isPlugin,
4
- getPluginMetadata,
5
- PluginBase,
6
- PluginsManager,
7
- PluginError,
8
- ErrMissingPluginMetadata,
9
- ErrNotAPlugin,
10
- ErrPluginNotInit,
11
- ErrUnknownPlugin,
12
- type PluginID,
13
- type PluginMetadata,
14
- type PluginDependencies,
15
- } from './plugin'