Skip to content

Commit cd100d7

Browse files
committed
int
1 parent 655d3a5 commit cd100d7

File tree

11 files changed

+374
-106
lines changed

11 files changed

+374
-106
lines changed

packages/core/src/entity/entity.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ export function createEntity(world: World, ...traits: ConfigurableTrait[]): Enti
1515
const ctx = world[$internal];
1616
const entity = allocateEntity(ctx.entityIndex);
1717

18-
for (const query of ctx.notQueries) {
19-
const match = query.check(world, entity);
20-
if (match) query.add(entity);
21-
// Reset all tracking bitmasks for the query.
22-
query.resetTrackingBitmasks(getEntityId(entity));
23-
}
18+
for (let i = 0, len = ctx.notQueries.length; i < len; i++) {
19+
const query = ctx.notQueries[i];
20+
const match = query.check(world, entity);
21+
if (match) query.add(entity);
22+
query.resetTrackingBitmasks(getEntityId(entity));
23+
}
2424

2525
ctx.entityTraits.set(entity, new Set());
2626
addTrait(world, entity, ...traits);

packages/core/src/query/query.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ export function runQuery<T extends QueryParameter[]>(
3636
): QueryResult<T> {
3737
commitQueryRemovals(world);
3838

39-
// With hybrid bitmask strategy, query.entities is already incrementally maintained
40-
// with both trait and relation filters applied. Just return the pre-filtered entities.
41-
const entities = query.entities.dense.slice() as Entity[];
39+
// query.entities.dense already returns a fresh copy, no need for extra .slice()
40+
const entities = query.entities.dense as Entity[];
4241

4342
// Clear so it can accumulate again.
4443
if (query.isTracking) {
@@ -85,13 +84,14 @@ export function commitQueryRemovals(world: World) {
8584
const ctx = world[$internal];
8685
if (!ctx.dirtyQueries.size) return;
8786

88-
for (const query of ctx.dirtyQueries) {
89-
for (let i = query.toRemove.dense.length - 1; i >= 0; i--) {
90-
const eid = query.toRemove.dense[i];
91-
query.toRemove.remove(eid);
92-
query.entities.remove(eid);
93-
}
94-
}
87+
for (const query of ctx.dirtyQueries) {
88+
const toRemoveDense = query.toRemove.dense;
89+
for (let i = toRemoveDense.length - 1; i >= 0; i--) {
90+
const eid = toRemoveDense[i];
91+
query.toRemove.remove(eid);
92+
query.entities.remove(eid);
93+
}
94+
}
9595

9696
ctx.dirtyQueries.clear();
9797
}
@@ -315,19 +315,19 @@ export function createQueryInstance<T extends QueryParameter[]>(
315315
// Add to world
316316
ctx.queriesHashMap.set(query.hash, query);
317317

318-
// Register query with trait instances
319-
if (query.isTracking) {
320-
query.traitInstances.all.forEach((instance) => {
321-
instance.trackingQueries.add(query);
322-
});
323-
} else {
324-
query.traitInstances.all.forEach((instance) => {
325-
instance.queries.add(query);
326-
});
327-
}
318+
// Register query with trait instances
319+
if (query.isTracking) {
320+
for (let i = 0; i < query.traitInstances.all.length; i++) {
321+
query.traitInstances.all[i].trackingQueries.push(query);
322+
}
323+
} else {
324+
for (let i = 0; i < query.traitInstances.all.length; i++) {
325+
query.traitInstances.all[i].queries.push(query);
326+
}
327+
}
328328

329329
// Add to notQueries if has forbidden traits
330-
if (query.traitInstances.forbidden.length > 0) ctx.notQueries.add(query);
330+
if (query.traitInstances.forbidden.length > 0) ctx.notQueries.push(query);
331331

332332
// Index queries with relation filters
333333
const hasRelationFilters = query.relationFilters && query.relationFilters.length > 0;
@@ -337,7 +337,7 @@ export function createQueryInstance<T extends QueryParameter[]>(
337337
const relationTrait = pair[$internal].relation[$internal].trait;
338338
const relationTraitInstance = getTraitInstance(ctx.traitInstances, relationTrait);
339339
if (relationTraitInstance) {
340-
relationTraitInstance.relationQueries.add(query);
340+
relationTraitInstance.relationQueries.push(query);
341341
}
342342
}
343343
}

packages/core/src/query/utils/tracking-cursor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function getTrackingCursor() {
1717

1818
export function setTrackingMasks(world: World, id: number) {
1919
const ctx = world[$internal];
20-
const snapshot = structuredClone(ctx.entityMasks);
20+
const snapshot = ctx.entityMasks.map((mask) => Array.from(mask)) as number[][];
2121
ctx.trackingSnapshots.set(id, snapshot);
2222

2323
// For dirty and changed masks, make clone of entity masks and set all bits to 0.

packages/core/src/relation/relation.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -319,15 +319,15 @@ function updateQueriesForRelationChange(
319319

320320
// Update queries indexed by this relation (much faster than iterating all queries)
321321
// All queries in relationQueries already filter by this relation
322-
for (const query of traitData.relationQueries) {
323-
// Re-check entity against query
324-
const match = checkQueryWithRelations(world, query, entity);
325-
if (match) {
326-
query.add(entity);
327-
} else {
328-
query.remove(world, entity);
329-
}
330-
}
322+
for (let i = 0, len = traitData.relationQueries.length; i < len; i++) {
323+
const query = traitData.relationQueries[i];
324+
const match = checkQueryWithRelations(world, query, entity);
325+
if (match) {
326+
query.add(entity);
327+
} else {
328+
query.remove(world, entity);
329+
}
330+
}
331331
}
332332

333333
/** Swap-and-pop data arrays for non-exclusive relations */

packages/core/src/trait/trait.ts

Lines changed: 65 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import {
3333
validateSchema,
3434
} from '../storage';
3535
import type { World } from '../world';
36+
import { BitSet } from '../utils/bit-set';
37+
import { ensureEntityMaskSize } from '../world/utils/ensure-entity-mask-size';
3638
import { incrementWorldBitflag } from '../world/utils/increment-world-bit-flag';
3739
import { getTraitInstance, hasTraitInstance, setTraitInstance } from './trait-instance';
3840
import type {
@@ -100,11 +102,12 @@ export function registerTrait(world: World, trait: Trait) {
100102
bitflag: ctx.bitflag,
101103
trait,
102104
store: traitCtx.createStore(),
103-
queries: new Set(),
104-
trackingQueries: new Set(),
105-
notQueries: new Set(),
106-
relationQueries: new Set(),
105+
queries: [],
106+
trackingQueries: [],
107+
notQueries: [],
108+
relationQueries: [],
107109
schema: trait.schema,
110+
entityBitSet: new BitSet(),
108111
changeSubscriptions: new Set(),
109112
addSubscriptions: new Set(),
110113
removeSubscriptions: new Set(),
@@ -453,37 +456,43 @@ export function getTrait(world: World, entity: Entity, trait: Trait | RelationPa
453456

454457
// Add bitflag to entity bitmask
455458
const eid = getEntityId(entity);
459+
ensureEntityMaskSize(ctx.entityMasks, generationId, eid);
456460
ctx.entityMasks[generationId][eid] |= bitflag;
461+
instance.entityBitSet.add(eid);
457462

458463
// Set the entity as dirty
459464
for (const dirtyMask of ctx.dirtyMasks.values()) {
460465
if (!dirtyMask[generationId]) dirtyMask[generationId] = [];
461466
dirtyMask[generationId][eid] |= bitflag;
462467
}
463468

464-
// Update non-tracking queries (no event data needed)
465-
for (const query of queries) {
466-
query.toRemove.remove(entity);
467-
// Use checkQueryWithRelations if query has relation filters, otherwise use checkQuery
468-
const match =
469-
query.relationFilters && query.relationFilters.length > 0
470-
? checkQueryWithRelations(world, query, entity)
471-
: query.check(world, entity);
472-
if (match) query.add(entity);
473-
else query.remove(world, entity);
474-
}
475-
476-
// Update tracking queries (with event data)
477-
for (const query of trackingQueries) {
478-
query.toRemove.remove(entity);
479-
// Use checkQueryTrackingWithRelations if query has relation filters, otherwise use checkQueryTracking
480-
const match =
481-
query.relationFilters && query.relationFilters.length > 0
482-
? checkQueryTrackingWithRelations(world, query, entity, 'add', generationId, bitflag)
483-
: query.checkTracking(world, entity, 'add', generationId, bitflag);
484-
if (match) query.add(entity);
485-
else query.remove(world, entity);
486-
}
469+
// Update non-tracking queries (no event data needed)
470+
// PERF: Use indexed loop instead of for...of to avoid iterator overhead
471+
for (let qi = 0, qLen = queries.length; qi < qLen; qi++) {
472+
const query = queries[qi];
473+
query.toRemove.remove(entity);
474+
// Use checkQueryWithRelations if query has relation filters, otherwise use checkQuery
475+
const match =
476+
query.relationFilters && query.relationFilters.length > 0
477+
? checkQueryWithRelations(world, query, entity)
478+
: query.check(world, entity);
479+
if (match) query.add(entity);
480+
else query.remove(world, entity);
481+
}
482+
483+
// Update tracking queries (with event data)
484+
// PERF: Use indexed loop instead of for...of to avoid iterator overhead
485+
for (let qi = 0, qLen = trackingQueries.length; qi < qLen; qi++) {
486+
const query = trackingQueries[qi];
487+
query.toRemove.remove(entity);
488+
// Use checkQueryTrackingWithRelations if query has relation filters, otherwise use checkQueryTracking
489+
const match =
490+
query.relationFilters && query.relationFilters.length > 0
491+
? checkQueryTrackingWithRelations(world, query, entity, 'add', generationId, bitflag)
492+
: query.checkTracking(world, entity, 'add', generationId, bitflag);
493+
if (match) query.add(entity);
494+
else query.remove(world, entity);
495+
}
487496

488497
// Add trait to entity internally
489498
ctx.entityTraits.get(entity)!.add(trait);
@@ -510,40 +519,41 @@ function removeTraitFromEntity(world: World, entity: Entity, trait: Trait): void
510519
// Remove bitflag from entity bitmask
511520
const eid = getEntityId(entity);
512521
ctx.entityMasks[generationId][eid] &= ~bitflag;
522+
instance.entityBitSet.remove(eid);
513523

514524
// Set the entity as dirty
515525
for (const dirtyMask of ctx.dirtyMasks.values()) {
516526
dirtyMask[generationId][eid] |= bitflag;
517527
}
518528

519-
// Update non-tracking queries
520-
for (const query of queries) {
521-
// Use checkQueryWithRelations if query has relation filters, otherwise use checkQuery
522-
const match =
523-
query.relationFilters && query.relationFilters.length > 0
524-
? checkQueryWithRelations(world, query, entity)
525-
: query.check(world, entity);
526-
if (match) query.add(entity);
527-
else query.remove(world, entity);
528-
}
529-
530-
// Update tracking queries (with event data)
531-
for (const query of trackingQueries) {
532-
// Use checkQueryTrackingWithRelations if query has relation filters, otherwise use checkQueryTracking
533-
const match =
534-
query.relationFilters && query.relationFilters.length > 0
535-
? checkQueryTrackingWithRelations(
536-
world,
537-
query,
538-
entity,
539-
'remove',
540-
generationId,
541-
bitflag
542-
)
543-
: query.checkTracking(world, entity, 'remove', generationId, bitflag);
544-
if (match) query.add(entity);
545-
else query.remove(world, entity);
546-
}
529+
// Update non-tracking queries
530+
for (let qi = 0, qLen = queries.length; qi < qLen; qi++) {
531+
const query = queries[qi];
532+
const match =
533+
query.relationFilters && query.relationFilters.length > 0
534+
? checkQueryWithRelations(world, query, entity)
535+
: query.check(world, entity);
536+
if (match) query.add(entity);
537+
else query.remove(world, entity);
538+
}
539+
540+
// Update tracking queries (with event data)
541+
for (let qi = 0, qLen = trackingQueries.length; qi < qLen; qi++) {
542+
const query = trackingQueries[qi];
543+
const match =
544+
query.relationFilters && query.relationFilters.length > 0
545+
? checkQueryTrackingWithRelations(
546+
world,
547+
query,
548+
entity,
549+
'remove',
550+
generationId,
551+
bitflag
552+
)
553+
: query.checkTracking(world, entity, 'remove', generationId, bitflag);
554+
if (match) query.add(entity);
555+
else query.remove(world, entity);
556+
}
547557

548558
// Remove trait from entity internally
549559
ctx.entityTraits.get(entity)!.delete(trait);

packages/core/src/trait/types.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Entity } from '../entity/types';
33
import type { QueryInstance } from '../query/types';
44
import type { Relation, RelationPair } from '../relation/types';
55
import type { AoSFactory, Schema, Store, StoreType } from '../storage';
6+
import type { BitSet } from '../utils/bit-set';
67

78
// Backwards-compatible alias (the trait "type" is the storage layout).
89
export type TraitType = StoreType;
@@ -87,13 +88,12 @@ export interface TraitInstance<T extends Trait = Trait, S extends Schema = Extra
8788
trait: Trait;
8889
store: Store<S>;
8990
/** Non-tracking queries that include this trait */
90-
queries: Set<QueryInstance>;
91-
/** Tracking queries (Added/Removed/Changed) that include this trait */
92-
trackingQueries: Set<QueryInstance>;
93-
notQueries: Set<QueryInstance>;
94-
/** Queries that filter by this relation (only for relation traits) */
95-
relationQueries: Set<QueryInstance>;
91+
queries: QueryInstance[];
92+
trackingQueries: QueryInstance[];
93+
notQueries: QueryInstance[];
94+
relationQueries: QueryInstance[];
9695
schema: S;
96+
entityBitSet: BitSet;
9797
changeSubscriptions: Set<(entity: Entity, target?: Entity) => void>;
9898
addSubscriptions: Set<(entity: Entity, target?: Entity) => void>;
9999
removeSubscriptions: Set<(entity: Entity, target?: Entity) => void>;

0 commit comments

Comments
 (0)