@codehz/ecs 0.7.2 → 0.7.3
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/examples/advanced-scheduling.ts +96 -0
- package/examples/collision-detection.ts +229 -0
- package/examples/inventory-system-relations.ts +108 -0
- package/examples/parent-child-hierarchy.ts +206 -0
- package/examples/serialization.ts +337 -0
- package/examples/simple.ts +96 -0
- package/examples/spatial-grid.ts +276 -0
- package/examples/state-machine.ts +273 -0
- package/examples/tag-filtering.ts +266 -0
- package/package.json +58 -12
- package/src/__tests__/commands/buffer-limits.test.ts +72 -0
- package/src/__tests__/commands/buffer.test.ts +195 -0
- package/src/__tests__/component/singleton.test.ts +148 -0
- package/src/__tests__/core/archetype.test.ts +247 -0
- package/src/__tests__/core/bitset.test.ts +171 -0
- package/src/__tests__/core/changeset.test.ts +254 -0
- package/src/__tests__/core/multi-map.test.ts +74 -0
- package/src/__tests__/entity/component-registry.test.ts +66 -0
- package/src/__tests__/entity/entity.test.ts +520 -0
- package/src/__tests__/entity/id-manager.test.ts +157 -0
- package/src/__tests__/entity/id-system.test.ts +260 -0
- package/src/__tests__/perf/comprehensive.perf.test.ts +300 -0
- package/src/__tests__/perf/sync-hotpath.perf.test.ts +79 -0
- package/src/__tests__/query/basic.test.ts +341 -0
- package/src/__tests__/query/caching.test.ts +112 -0
- package/src/__tests__/query/filter.test.ts +111 -0
- package/src/__tests__/query/optional.test.ts +231 -0
- package/src/__tests__/query/perf.test.ts +99 -0
- package/src/__tests__/relations/dont-fragment/basic.test.ts +496 -0
- package/src/__tests__/relations/dont-fragment/query-notification.test.ts +125 -0
- package/src/__tests__/relations/wildcard.test.ts +179 -0
- package/src/__tests__/serialization/bounds.test.ts +237 -0
- package/src/__tests__/testing/assertions.test.ts +224 -0
- package/src/__tests__/testing/entity-builder.test.ts +84 -0
- package/src/__tests__/testing/snapshot.test.ts +150 -0
- package/src/__tests__/testing/world-fixture.test.ts +73 -0
- package/src/__tests__/world/component-hooks.test.ts +185 -0
- package/src/__tests__/world/component-management.test.ts +447 -0
- package/src/__tests__/world/entity-management.test.ts +86 -0
- package/src/__tests__/world/get-optional.test.ts +96 -0
- package/src/__tests__/world/multi-component-hooks.test.ts +502 -0
- package/src/__tests__/world/perf.test.ts +93 -0
- package/src/__tests__/world/query.test.ts +223 -0
- package/src/__tests__/world/serialize.test.ts +83 -0
- package/src/__tests__/world/wildcard-relation-hooks.test.ts +332 -0
- package/src/archetype/archetype.ts +472 -0
- package/src/archetype/helpers.ts +186 -0
- package/src/archetype/store.ts +33 -0
- package/src/commands/buffer.ts +110 -0
- package/src/commands/changeset.ts +104 -0
- package/src/component/entity-store.ts +223 -0
- package/src/component/registry.ts +657 -0
- package/src/component/type-utils.ts +9 -0
- package/src/entity/index.ts +63 -0
- package/src/entity/manager.ts +115 -0
- package/src/entity/relation.ts +319 -0
- package/src/entity/types.ts +135 -0
- package/src/index.ts +41 -0
- package/src/query/filter.ts +75 -0
- package/src/query/query.ts +313 -0
- package/src/query/registry.ts +101 -0
- package/src/storage/serialization.ts +130 -0
- package/src/testing/index.ts +634 -0
- package/src/types/index.ts +99 -0
- package/src/utils/bit-set.ts +133 -0
- package/src/utils/multi-map.ts +96 -0
- package/src/utils/utils.ts +19 -0
- package/src/world/builder.ts +100 -0
- package/src/world/commands.ts +378 -0
- package/src/world/hooks.ts +358 -0
- package/src/world/references.ts +38 -0
- package/src/world/serialization.ts +122 -0
- package/src/world/world.ts +1201 -0
- /package/{builder.d.mts → dist/builder.d.mts} +0 -0
- /package/{index.d.mts → dist/index.d.mts} +0 -0
- /package/{index.mjs → dist/index.mjs} +0 -0
- /package/{testing.d.mts → dist/testing.d.mts} +0 -0
- /package/{testing.mjs → dist/testing.mjs} +0 -0
- /package/{testing.mjs.map → dist/testing.mjs.map} +0 -0
- /package/{world.mjs → dist/world.mjs} +0 -0
- /package/{world.mjs.map → dist/world.mjs.map} +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import type { Archetype } from "../archetype/archetype";
|
|
2
|
+
import type { DontFragmentStore } from "../archetype/store";
|
|
3
|
+
import type { Command } from "../commands/buffer";
|
|
4
|
+
import type { ComponentChangeset } from "../commands/changeset";
|
|
5
|
+
import { normalizeComponentTypes } from "../component/type-utils";
|
|
6
|
+
import {
|
|
7
|
+
getComponentIdFromRelationId,
|
|
8
|
+
getComponentMerge,
|
|
9
|
+
isDontFragmentComponent,
|
|
10
|
+
isDontFragmentRelation,
|
|
11
|
+
isDontFragmentWildcard,
|
|
12
|
+
isWildcardRelationId,
|
|
13
|
+
relation,
|
|
14
|
+
type ComponentId,
|
|
15
|
+
type EntityId,
|
|
16
|
+
} from "../entity";
|
|
17
|
+
|
|
18
|
+
export interface CommandProcessorContext {
|
|
19
|
+
dontFragmentStore: DontFragmentStore;
|
|
20
|
+
ensureArchetype: (componentTypes: Iterable<EntityId<any>>) => Archetype;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function processCommands(
|
|
24
|
+
entityId: EntityId,
|
|
25
|
+
currentArchetype: Archetype,
|
|
26
|
+
commands: Command[],
|
|
27
|
+
changeset: ComponentChangeset,
|
|
28
|
+
handleExclusiveRelation: (entityId: EntityId, archetype: Archetype, componentId: ComponentId<any>) => void,
|
|
29
|
+
): void {
|
|
30
|
+
for (const command of commands) {
|
|
31
|
+
if (command.type === "set") {
|
|
32
|
+
// TypeScript knows command.componentType and command.component exist
|
|
33
|
+
processSetCommand(
|
|
34
|
+
entityId,
|
|
35
|
+
currentArchetype,
|
|
36
|
+
command.componentType,
|
|
37
|
+
command.component,
|
|
38
|
+
changeset,
|
|
39
|
+
handleExclusiveRelation,
|
|
40
|
+
);
|
|
41
|
+
} else if (command.type === "delete") {
|
|
42
|
+
// TypeScript knows command.componentType exists
|
|
43
|
+
processDeleteCommand(entityId, currentArchetype, command.componentType, changeset);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function processSetCommand(
|
|
49
|
+
entityId: EntityId,
|
|
50
|
+
currentArchetype: Archetype,
|
|
51
|
+
componentType: EntityId<any>,
|
|
52
|
+
component: any,
|
|
53
|
+
changeset: ComponentChangeset,
|
|
54
|
+
handleExclusiveRelation: (entityId: EntityId, archetype: Archetype, componentId: ComponentId<any>) => void,
|
|
55
|
+
): void {
|
|
56
|
+
// Extract componentId if it's a relation (fast path)
|
|
57
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
58
|
+
if (componentId !== undefined) {
|
|
59
|
+
// Handle exclusive relations by removing existing relations with the same base component
|
|
60
|
+
handleExclusiveRelation(entityId, currentArchetype, componentId);
|
|
61
|
+
|
|
62
|
+
// For dontFragment relations, ensure wildcard marker is in archetype signature
|
|
63
|
+
if (isDontFragmentComponent(componentId)) {
|
|
64
|
+
const wildcardMarker = relation(componentId, "*");
|
|
65
|
+
// Add wildcard marker to changeset if not already in archetype
|
|
66
|
+
if (!currentArchetype.componentTypeSet.has(wildcardMarker)) {
|
|
67
|
+
changeset.set(wildcardMarker, undefined);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const merge = getComponentMerge(componentType);
|
|
73
|
+
if (merge !== undefined && changeset.adds.has(componentType)) {
|
|
74
|
+
const prev = changeset.adds.get(componentType);
|
|
75
|
+
changeset.set(componentType, merge(prev, component));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
changeset.set(componentType, component);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function processDeleteCommand(
|
|
83
|
+
entityId: EntityId,
|
|
84
|
+
currentArchetype: Archetype,
|
|
85
|
+
componentType: EntityId<any>,
|
|
86
|
+
changeset: ComponentChangeset,
|
|
87
|
+
): void {
|
|
88
|
+
const componentId = getComponentIdFromRelationId(componentType);
|
|
89
|
+
|
|
90
|
+
if (isWildcardRelationId(componentType) && componentId !== undefined) {
|
|
91
|
+
removeWildcardRelations(entityId, currentArchetype, componentId, changeset);
|
|
92
|
+
} else {
|
|
93
|
+
changeset.delete(componentType);
|
|
94
|
+
maybeRemoveWildcardMarker(entityId, currentArchetype, componentType, componentId, changeset);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function removeMatchingRelations(
|
|
99
|
+
entityId: EntityId,
|
|
100
|
+
archetype: Archetype,
|
|
101
|
+
baseComponentId: ComponentId<any>,
|
|
102
|
+
changeset: ComponentChangeset,
|
|
103
|
+
): void {
|
|
104
|
+
// Check archetype components
|
|
105
|
+
for (const componentType of archetype.componentTypes) {
|
|
106
|
+
// Skip wildcard markers - they should only be removed by maybeRemoveWildcardMarker
|
|
107
|
+
if (isWildcardRelationId(componentType)) continue;
|
|
108
|
+
|
|
109
|
+
if (getComponentIdFromRelationId(componentType) === baseComponentId) {
|
|
110
|
+
changeset.delete(componentType);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check dontFragment relations stored on entity
|
|
115
|
+
const dontFragmentData = archetype.getEntityDontFragmentRelations(entityId);
|
|
116
|
+
if (dontFragmentData) {
|
|
117
|
+
for (const componentType of dontFragmentData.keys()) {
|
|
118
|
+
if (getComponentIdFromRelationId(componentType) === baseComponentId) {
|
|
119
|
+
changeset.delete(componentType);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function removeWildcardRelations(
|
|
126
|
+
entityId: EntityId,
|
|
127
|
+
currentArchetype: Archetype,
|
|
128
|
+
baseComponentId: ComponentId<any>,
|
|
129
|
+
changeset: ComponentChangeset,
|
|
130
|
+
): void {
|
|
131
|
+
removeMatchingRelations(entityId, currentArchetype, baseComponentId, changeset);
|
|
132
|
+
|
|
133
|
+
// If removing dontFragment relations, also remove the wildcard marker
|
|
134
|
+
if (isDontFragmentComponent(baseComponentId)) {
|
|
135
|
+
changeset.delete(relation(baseComponentId, "*"));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function maybeRemoveWildcardMarker(
|
|
140
|
+
entityId: EntityId,
|
|
141
|
+
archetype: Archetype,
|
|
142
|
+
removedComponentType: EntityId<any>,
|
|
143
|
+
componentId: ComponentId<any> | undefined,
|
|
144
|
+
changeset: ComponentChangeset,
|
|
145
|
+
): void {
|
|
146
|
+
if (componentId === undefined || !isDontFragmentComponent(componentId)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const wildcardMarker = relation(componentId, "*");
|
|
151
|
+
|
|
152
|
+
// Check if there are any other relations with the same component ID
|
|
153
|
+
for (const otherComponentType of archetype.componentTypes) {
|
|
154
|
+
if (otherComponentType === removedComponentType) continue;
|
|
155
|
+
if (otherComponentType === wildcardMarker) continue;
|
|
156
|
+
if (changeset.removes.has(otherComponentType)) continue;
|
|
157
|
+
|
|
158
|
+
if (getComponentIdFromRelationId(otherComponentType) === componentId) {
|
|
159
|
+
return; // Found another relation, keep the marker
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const dontFragmentData = archetype.getEntityDontFragmentRelations(entityId);
|
|
164
|
+
if (dontFragmentData) {
|
|
165
|
+
for (const otherComponentType of dontFragmentData.keys()) {
|
|
166
|
+
if (otherComponentType === removedComponentType) continue;
|
|
167
|
+
if (changeset.removes.has(otherComponentType)) continue;
|
|
168
|
+
|
|
169
|
+
if (getComponentIdFromRelationId(otherComponentType) === componentId) {
|
|
170
|
+
return; // Found another relation, keep the marker
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
changeset.delete(wildcardMarker);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function hasEntityComponent(archetype: Archetype, entityId: EntityId, componentType: EntityId<any>): boolean {
|
|
179
|
+
if (archetype.componentTypeSet.has(componentType)) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return archetype.getEntityDontFragmentRelations(entityId)?.has(componentType) ?? false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function pruneMissingRemovals(changeset: ComponentChangeset, archetype: Archetype, entityId: EntityId): void {
|
|
187
|
+
// Collect to-prune entries first to avoid mutating the set during iteration
|
|
188
|
+
let toPrune: EntityId<any>[] | undefined;
|
|
189
|
+
for (const componentType of changeset.removes) {
|
|
190
|
+
if (!hasEntityComponent(archetype, entityId, componentType)) {
|
|
191
|
+
if (toPrune === undefined) toPrune = [];
|
|
192
|
+
toPrune.push(componentType);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (toPrune !== undefined) {
|
|
196
|
+
for (const componentType of toPrune) {
|
|
197
|
+
changeset.removes.delete(componentType);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function hasArchetypeStructuralChange(changeset: ComponentChangeset, currentArchetype: Archetype): boolean {
|
|
203
|
+
for (const componentType of changeset.removes) {
|
|
204
|
+
if (!isDontFragmentRelation(componentType) && currentArchetype.componentTypeSet.has(componentType)) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const componentType of changeset.adds.keys()) {
|
|
210
|
+
if (!isDontFragmentRelation(componentType) && !currentArchetype.componentTypeSet.has(componentType)) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildFinalRegularComponentTypes(currentArchetype: Archetype, changeset: ComponentChangeset): EntityId<any>[] {
|
|
219
|
+
const finalRegularTypes = new Set(currentArchetype.componentTypes);
|
|
220
|
+
|
|
221
|
+
for (const componentType of changeset.removes) {
|
|
222
|
+
if (!isDontFragmentRelation(componentType)) {
|
|
223
|
+
finalRegularTypes.delete(componentType);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (const [componentType] of changeset.adds) {
|
|
228
|
+
if (!isDontFragmentRelation(componentType)) {
|
|
229
|
+
finalRegularTypes.add(componentType);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return Array.from(finalRegularTypes);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function applyChangeset(
|
|
237
|
+
ctx: CommandProcessorContext,
|
|
238
|
+
entityId: EntityId,
|
|
239
|
+
currentArchetype: Archetype,
|
|
240
|
+
changeset: ComponentChangeset,
|
|
241
|
+
entityToArchetype: Map<EntityId, Archetype>,
|
|
242
|
+
removedComponents: Map<EntityId<any>, any> | null,
|
|
243
|
+
): Archetype {
|
|
244
|
+
pruneMissingRemovals(changeset, currentArchetype, entityId);
|
|
245
|
+
const archetypeChanged = hasArchetypeStructuralChange(changeset, currentArchetype);
|
|
246
|
+
|
|
247
|
+
if (archetypeChanged) {
|
|
248
|
+
const finalRegularTypes = buildFinalRegularComponentTypes(currentArchetype, changeset);
|
|
249
|
+
const newArchetype = ctx.ensureArchetype(finalRegularTypes);
|
|
250
|
+
const currentComponents = currentArchetype.removeEntity(entityId)!;
|
|
251
|
+
|
|
252
|
+
if (removedComponents !== null) {
|
|
253
|
+
for (const componentType of changeset.removes) {
|
|
254
|
+
removedComponents.set(componentType, currentComponents.get(componentType));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
|
|
259
|
+
entityToArchetype.set(entityId, newArchetype);
|
|
260
|
+
return newArchetype;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// No archetype move needed: only component payload updates and/or dontFragment relation updates.
|
|
264
|
+
if (removedComponents !== null) {
|
|
265
|
+
applyDontFragmentChanges(ctx.dontFragmentStore, entityId, changeset, removedComponents);
|
|
266
|
+
} else {
|
|
267
|
+
applyDontFragmentChangesNoHooks(ctx.dontFragmentStore, entityId, changeset);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Direct update for regular components in archetype
|
|
271
|
+
for (const [componentType, component] of changeset.adds) {
|
|
272
|
+
if (isDontFragmentRelation(componentType)) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
currentArchetype.set(entityId, componentType, component);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return currentArchetype;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* No-hooks variant of applyDontFragmentChanges that skips tracking removed component data.
|
|
283
|
+
*/
|
|
284
|
+
function applyDontFragmentChanges(
|
|
285
|
+
dontFragmentRelations: DontFragmentStore,
|
|
286
|
+
entityId: EntityId,
|
|
287
|
+
changeset: ComponentChangeset,
|
|
288
|
+
removedComponents: Map<EntityId<any>, any>,
|
|
289
|
+
): void {
|
|
290
|
+
// Get or create the entity's dontFragment relations map
|
|
291
|
+
let entityRelations = dontFragmentRelations.get(entityId);
|
|
292
|
+
|
|
293
|
+
for (const componentType of changeset.removes) {
|
|
294
|
+
if (isDontFragmentRelation(componentType)) {
|
|
295
|
+
if (entityRelations) {
|
|
296
|
+
const removedValue = entityRelations.get(componentType);
|
|
297
|
+
if (removedValue !== undefined || entityRelations.has(componentType)) {
|
|
298
|
+
removedComponents.set(componentType, removedValue);
|
|
299
|
+
entityRelations.delete(componentType);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
for (const [componentType, component] of changeset.adds) {
|
|
306
|
+
if (isDontFragmentRelation(componentType)) {
|
|
307
|
+
if (!entityRelations) {
|
|
308
|
+
entityRelations = new Map();
|
|
309
|
+
dontFragmentRelations.set(entityId, entityRelations);
|
|
310
|
+
}
|
|
311
|
+
entityRelations.set(componentType, component);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Clean up empty map
|
|
316
|
+
if (entityRelations && entityRelations.size === 0) {
|
|
317
|
+
dontFragmentRelations.delete(entityId);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function applyDontFragmentChangesNoHooks(
|
|
322
|
+
dontFragmentRelations: DontFragmentStore,
|
|
323
|
+
entityId: EntityId,
|
|
324
|
+
changeset: ComponentChangeset,
|
|
325
|
+
): void {
|
|
326
|
+
let entityRelations = dontFragmentRelations.get(entityId);
|
|
327
|
+
|
|
328
|
+
for (const componentType of changeset.removes) {
|
|
329
|
+
if (isDontFragmentRelation(componentType)) {
|
|
330
|
+
if (entityRelations) {
|
|
331
|
+
entityRelations.delete(componentType);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
for (const [componentType, component] of changeset.adds) {
|
|
337
|
+
if (isDontFragmentRelation(componentType)) {
|
|
338
|
+
if (!entityRelations) {
|
|
339
|
+
entityRelations = new Map();
|
|
340
|
+
dontFragmentRelations.set(entityId, entityRelations);
|
|
341
|
+
}
|
|
342
|
+
entityRelations.set(componentType, component);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Clean up empty map
|
|
347
|
+
if (entityRelations && entityRelations.size === 0) {
|
|
348
|
+
dontFragmentRelations.delete(entityId);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function filterRegularComponentTypes(componentTypes: Iterable<EntityId<any>>): EntityId<any>[] {
|
|
353
|
+
const regularTypes: EntityId<any>[] = [];
|
|
354
|
+
|
|
355
|
+
for (const componentType of componentTypes) {
|
|
356
|
+
// Keep wildcard markers for dontFragment components (they mark the archetype)
|
|
357
|
+
if (isDontFragmentWildcard(componentType)) {
|
|
358
|
+
regularTypes.push(componentType);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Skip specific dontFragment relations from archetype signature
|
|
363
|
+
if (isDontFragmentRelation(componentType)) {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
regularTypes.push(componentType);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return regularTypes;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function areComponentTypesEqual(types1: EntityId<any>[], types2: EntityId<any>[]): boolean {
|
|
374
|
+
if (types1.length !== types2.length) return false;
|
|
375
|
+
const sorted1 = normalizeComponentTypes(types1);
|
|
376
|
+
const sorted2 = normalizeComponentTypes(types2);
|
|
377
|
+
return sorted1.every((v, i) => v === sorted2[i]);
|
|
378
|
+
}
|