Skip to content

Commit 7b7ceb0

Browse files
committed
refactor(world): optimize entity removal process
- Simplified the entity removal logic by swapping the last entity with the one being removed for O(1) performance. - Updated component data handling to ensure correct removal and prevent undefined values. - Removed unnecessary method for calculating final component types, streamlining the code.
1 parent 586f512 commit 7b7ceb0

4 files changed

Lines changed: 25 additions & 28 deletions

File tree

src/archetype.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,23 +106,31 @@ export class Archetype {
106106
return undefined;
107107
}
108108

109-
// Remove from entity list
110-
this.entities.splice(index, 1);
111-
this.entityToIndex.delete(entityId);
112-
113-
// Extract component data
109+
// Extract component data before removal
114110
const removedData = new Map<EntityId<any>, any>();
115111
for (const componentType of this.componentTypes) {
116112
const dataArray = this.getComponentData(componentType);
117-
removedData.set(componentType, dataArray[index]);
118-
dataArray.splice(index, 1);
113+
removedData.set(componentType, dataArray[index] === MISSING_COMPONENT ? undefined : dataArray[index]);
119114
}
120115

121-
// Update indices for remaining entities
122-
for (let i = index; i < this.entities.length; i++) {
123-
this.entityToIndex.set(this.entities[i]!, i);
116+
const lastIndex = this.entities.length - 1;
117+
if (index !== lastIndex) {
118+
// Swap with last entity for O(1) removal
119+
const lastEntity = this.entities[lastIndex]!;
120+
this.entities[index] = lastEntity;
121+
this.entityToIndex.set(lastEntity, index);
122+
123+
// Swap component data
124+
for (const componentType of this.componentTypes) {
125+
const dataArray = this.getComponentData(componentType);
126+
[dataArray[index], dataArray[lastIndex]] = [dataArray[lastIndex], dataArray[index]];
127+
}
124128
}
125129

130+
// Remove the last element
131+
this.entities.pop();
132+
this.entityToIndex.delete(entityId);
133+
126134
return removedData;
127135
}
128136

src/changeset.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ describe("ComponentChangeset", () => {
124124
existing.set(VelocityId, { x: 1, y: 2 });
125125
existing.set(HealthId, 100);
126126

127-
const finalTypes = changeset.getFinalComponentTypes(existing);
127+
const finalTypes = changeset.applyTo(existing);
128128

129-
expect(finalTypes).toEqual([PositionId, HealthId]); // Sorted by ID
129+
expect([...finalTypes.keys()]).toEqual([HealthId, PositionId]);
130130
});
131131
});
132132

src/changeset.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,4 @@ export class ComponentChangeset {
7272

7373
return finalComponents;
7474
}
75-
76-
/**
77-
* Get the final component types after applying changes
78-
*/
79-
getFinalComponentTypes(existingComponents: Map<EntityId<any>, any>): EntityId<any>[] {
80-
const finalComponents = this.applyTo(existingComponents);
81-
return Array.from(finalComponents.keys()).sort((a, b) => a - b);
82-
}
8375
}

src/world.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -604,18 +604,15 @@ export class World<UpdateParams extends any[] = []> {
604604
// Apply changes to current components to get final state
605605
const finalComponents = changeset.applyTo(currentComponents);
606606

607-
// Calculate final component types
608-
const finalComponentTypes = changeset.getFinalComponentTypes(currentComponents);
609-
610607
// Check if we need to move to a different archetype
611-
const currentComponentTypes = currentArchetype.componentTypes.sort((a, b) => a - b);
608+
const currentComponentTypes = currentArchetype.componentTypes;
612609
const needsArchetypeChange =
613-
finalComponentTypes.length !== currentComponentTypes.length ||
614-
!finalComponentTypes.every((type, index) => type === currentComponentTypes[index]);
610+
finalComponents.size !== currentComponentTypes.length ||
611+
!currentComponentTypes.every((type) => finalComponents.has(type));
615612

616613
if (needsArchetypeChange) {
617614
// Move to new archetype with final component state
618-
const newArchetype = this.ensureArchetype(finalComponentTypes);
615+
const newArchetype = this.ensureArchetype(finalComponents.keys().toArray());
619616

620617
// Remove from current archetype
621618
currentArchetype.removeEntity(entityId);
@@ -668,7 +665,7 @@ export class World<UpdateParams extends any[] = []> {
668665
* @returns The archetype for the given component types
669666
*/
670667
private ensureArchetype(componentTypes: EntityId<any>[]): Archetype {
671-
const sortedTypes = [...componentTypes].sort((a, b) => a - b);
668+
const sortedTypes = componentTypes.toSorted((a, b) => a - b);
672669
const hashKey = this.createArchetypeSignature(sortedTypes);
673670

674671
return getOrCreateWithSideEffect(this.archetypeBySignature, hashKey, () => {

0 commit comments

Comments
 (0)