Skip to content

Commit 818f397

Browse files
authoredJun 6, 2024
Merge pull request #143 from NateTheGreatt/perf/add-entity-pop
🐛 classic: Recycle entities without shift
2 parents 565944d + 0c95d78 commit 818f397

File tree

2 files changed

+33
-10
lines changed

2 files changed

+33
-10
lines changed
 

‎packages/classic/src/entity/Entity.ts

+30-10
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,23 @@ export const getGlobalSize = () => globalSize;
3838

3939
// removed eids should also be global to prevent memory leaks
4040
const removed: number[] = [];
41+
const removedOut: number[] = [];
4142
const recycled: number[] = [];
4243

44+
const dequeuFromRemoved = () => {
45+
if (removedOut.length === 0) {
46+
while (removed.length > 0) {
47+
removedOut.push(removed.pop()!);
48+
}
49+
}
50+
if (removedOut.length === 0) {
51+
throw new Error('Queue is empty');
52+
}
53+
return removedOut.pop()!;
54+
};
55+
56+
const getRemovedLength = () => removed.length + removedOut.length;
57+
4358
const defaultRemovedReuseThreshold = 0.01;
4459
let removedReuseThreshold = defaultRemovedReuseThreshold;
4560

@@ -48,6 +63,7 @@ export const resetGlobals = () => {
4863
globalEntityCursor = 0;
4964
removedReuseThreshold = defaultRemovedReuseThreshold;
5065
removed.length = 0;
66+
removedOut.length = 0;
5167
recycled.length = 0;
5268
queries.length = 0;
5369
worlds.length = 0;
@@ -104,13 +120,17 @@ export const Prefab = defineComponent();
104120
* @returns {number} eid
105121
*/
106122
export const addEntity = (world: World): number => {
107-
const eid: number = world[$manualEntityRecycling]
108-
? removed.length
109-
? removed.shift()!
110-
: globalEntityCursor++
111-
: removed.length > Math.round(globalSize * removedReuseThreshold)
112-
? removed.shift()!
113-
: globalEntityCursor++;
123+
let eid: number;
124+
125+
if (
126+
(world[$manualEntityRecycling] && getRemovedLength() > 0) ||
127+
(!world[$manualEntityRecycling] &&
128+
getRemovedLength() > Math.round(globalSize * removedReuseThreshold))
129+
) {
130+
eid = dequeuFromRemoved();
131+
} else {
132+
eid = globalEntityCursor++;
133+
}
114134

115135
if (world[$entitySparseSet].dense.length >= world[$size]) {
116136
throw new Error('bitECS - max entities reached');
@@ -177,9 +197,9 @@ export const removeEntity = (world: World, eid: number) => {
177197

178198
// Remove entity from all queries
179199
// TODO: archetype graph
180-
world[$queries].forEach((q) => {
181-
queryRemoveEntity(world, q, eid);
182-
});
200+
for (const query of world[$queries]) {
201+
queryRemoveEntity(world, query, eid);
202+
}
183203

184204
// Free the entity
185205
if (world[$manualEntityRecycling]) recycled.push(eid);

‎packages/classic/test/integration/Entity.test.ts

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ describe('Entity Integration Tests', () => {
4141
strictEqual(removed[1], 1);
4242
strictEqual(removed[2], 2);
4343
});
44+
4445
it('should recycle entity IDs after 1% have been removed by default', () => {
4546
const world = createWorld();
4647
const ents: number[] = [];
@@ -76,6 +77,7 @@ describe('Entity Integration Tests', () => {
7677
eid = addEntity(world);
7778
strictEqual(eid, 0);
7879
});
80+
7981
it('should flush entity IDs', () => {
8082
const world = createWorld();
8183
enableManualEntityRecycling(world);
@@ -121,6 +123,7 @@ describe('Entity Integration Tests', () => {
121123
eid = addEntity(world);
122124
strictEqual(eid, 5);
123125
});
126+
124127
it('should be able to configure % of removed entity IDs before recycle', () => {
125128
const world = createWorld();
126129

0 commit comments

Comments
 (0)