Skip to content

Commit b055846

Browse files
authored
fix: entity ids collision (#1332)
* fix: entity ids collision * update snapshots * fix: implementation * add: tests * remove: unnecessary comments
1 parent f823a18 commit b055846

File tree

16 files changed

+103
-40
lines changed

16 files changed

+103
-40
lines changed

packages/@dcl/ecs/src/engine/entity.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { createVersionGSet } from '../systems/crdt/gset'
22

3+
/** @internal Optionally injected at build time (ex: esbuild define) */
4+
declare const DCL_MAX_COMPOSITE_ENTITY: number
5+
36
/**
47
* @public It only defines the type explicitly, no effects.
58
*/
@@ -103,8 +106,10 @@ export type IEntityContainer = {
103106
*/
104107
export function createEntityContainer(opts?: { reservedStaticEntities: number }): IEntityContainer {
105108
const reservedStaticEntities = opts?.reservedStaticEntities ?? RESERVED_STATIC_ENTITIES
106-
// Local entities counter
107-
let entityCounter = reservedStaticEntities
109+
// If a build tool has set DCL_MAX_COMPOSITE_ENTITY (via esbuild define),
110+
// start the counter past composite entities to prevent ID collisions.
111+
const maxCompositeEntity = typeof DCL_MAX_COMPOSITE_ENTITY !== 'undefined' ? DCL_MAX_COMPOSITE_ENTITY : 0
112+
let entityCounter = Math.max(reservedStaticEntities, maxCompositeEntity > 0 ? maxCompositeEntity + 1 : 0)
108113

109114
const usedEntities: Set<Entity> = new Set()
110115
let toRemoveEntities: Entity[] = []

packages/@dcl/sdk-commands/src/logic/bundle.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ type SingleProjectOptions = CompileOptions & {
170170
export async function bundleSingleProject(components: BundleComponents, options: SingleProjectOptions) {
171171
printProgressStep(components.logger, `Bundling file ${colors.bold(options.entrypoint)}`, 1, MAX_STEP)
172172
const editorScene = await isEditorScene(components, options.workingDirectory)
173+
174+
// Pre-compute composite data so we can inject maxCompositeEntity via esbuild define.
175+
// This must happen before the esbuild context is created because the define values
176+
// are baked into the engine at compile time (the entity counter initializer reads it)
177+
let maxCompositeEntity = 0
178+
if (!options.ignoreComposite) {
179+
const composites = await getAllComposites(components, options.workingDirectory)
180+
maxCompositeEntity = composites.maxCompositeEntity
181+
}
182+
173183
const sdkPackagePath = (() => {
174184
try {
175185
// First try to resolve from project's node_modules
@@ -236,7 +246,13 @@ export async function bundleSingleProject(components: BundleComponents, options:
236246
// pick up @dcl/asset-packs installed next to the scene (e.g. at a monorepo root) rather
237247
// than the one the user intentionally installed inside the scene.
238248
'@dcl/asset-packs': (() => {
239-
const sceneOwnAssetPacks = path.join(options.workingDirectory, 'node_modules', '@dcl', 'asset-packs', 'package.json')
249+
const sceneOwnAssetPacks = path.join(
250+
options.workingDirectory,
251+
'node_modules',
252+
'@dcl',
253+
'asset-packs',
254+
'package.json'
255+
)
240256
if (fs.existsSync(sceneOwnAssetPacks)) {
241257
return path.dirname(sceneOwnAssetPacks)
242258
}
@@ -259,7 +275,8 @@ export async function bundleSingleProject(components: BundleComponents, options:
259275
window: 'undefined',
260276
DEBUG: options.production ? 'false' : 'true',
261277
'globalThis.DEBUG': options.production ? 'false' : 'true',
262-
'process.env.NODE_ENV': JSON.stringify(options.production ? 'production' : 'development')
278+
'process.env.NODE_ENV': JSON.stringify(options.production ? 'production' : 'development'),
279+
DCL_MAX_COMPOSITE_ENTITY: String(maxCompositeEntity)
263280
},
264281
tsconfig: options.tsconfig,
265282
supported: {

packages/@dcl/sdk-commands/src/logic/composite.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ export type Script = ScriptItem & {
2222
export async function getAllComposites(
2323
components: CompositeComponents,
2424
workingDirectory: string
25-
): Promise<{ watchFiles: string[]; compositeLines: string[]; scripts: Map<string, Script[]>; withErrors: boolean }> {
25+
): Promise<{
26+
watchFiles: string[]
27+
compositeLines: string[]
28+
scripts: Map<string, Script[]>
29+
withErrors: boolean
30+
maxCompositeEntity: number
31+
}> {
2632
let withErrors = false
2733
const composites: Record<string, Composite.Definition> = {}
2834
const watchFiles = globSync('**/*.composite', { cwd: workingDirectory })
@@ -84,10 +90,23 @@ export async function getAllComposites(
8490
}
8591
}
8692

93+
// Compute composite's highest entity number
94+
let maxCompositeEntity = 0
95+
for (const composite of Object.values(composites)) {
96+
for (const component of composite.components) {
97+
for (const entityId of component.data.keys()) {
98+
const entityNumber = (entityId & 0xffff) >>> 0
99+
if (entityNumber > maxCompositeEntity) {
100+
maxCompositeEntity = entityNumber
101+
}
102+
}
103+
}
104+
}
105+
87106
// generate CRDT binary
88107
const crdtFilePath = path.join(workingDirectory, 'main.crdt')
89108
const crdtData = dumpEngineToCrdtCommands(engine as any)
90109
await components.fs.writeFile(crdtFilePath, crdtData)
91110

92-
return { compositeLines, watchFiles, scripts, withErrors }
111+
return { compositeLines, watchFiles, scripts, withErrors, maxCompositeEntity }
93112
}

test/ecs/entity.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,26 @@ describe('Entity container', () => {
100100

101101
expect(entityContainer.updateUsedEntity(entity)).toBe(false)
102102
})
103+
104+
describe('DCL_MAX_COMPOSITE_ENTITY (build-time define)', () => {
105+
afterEach(() => {
106+
delete (globalThis as any).DCL_MAX_COMPOSITE_ENTITY
107+
})
108+
109+
it('starts entity counter past the max composite entity', () => {
110+
;(globalThis as any).DCL_MAX_COMPOSITE_ENTITY = 600
111+
const entityContainer = createEntityContainer()
112+
const entity = entityContainer.generateEntity()
113+
// counter should start at 601 (one past the max composite entity)
114+
expect((entity & 0xffff) >>> 0).toBe(601)
115+
})
116+
117+
it('is a no-op when the value is below reservedStaticEntities', () => {
118+
;(globalThis as any).DCL_MAX_COMPOSITE_ENTITY = 100
119+
const entityContainer = createEntityContainer()
120+
const entity = entityContainer.generateEntity()
121+
// reservedStaticEntities (512) wins over maxCompositeEntity (100)
122+
expect((entity & 0xffff) >>> 0).toBe(RESERVED_STATIC_ENTITIES)
123+
})
124+
})
103125
})

test/snapshots/development-bundles/static-scene.test.ts.crdt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
SCENE_COMPILED_JS_SIZE_PROD=509.2k bytes
1+
SCENE_COMPILED_JS_SIZE_PROD=509.3k bytes
22
THE BUNDLE HAS SOURCEMAPS
33
(start empty vm 0.21.0-3680274614.commit-1808aa1)
44
OPCODES ~= 0k
@@ -11,7 +11,7 @@ EVAL test/snapshots/development-bundles/static-scene.test.js
1111
REQUIRE: ~system/EngineApi
1212
REQUIRE: ~system/EngineApi
1313
OPCODES ~= 62k
14-
MALLOC_COUNT = 14987
14+
MALLOC_COUNT = 14990
1515
ALIVE_OBJS_DELTA ~= 2.98k
1616
CALL onStart()
1717
main.crdt: PUT_COMPONENT e=0x200 c=1 t=0 data={"position":{"x":5.880000114440918,"y":2.7916901111602783,"z":7.380000114440918},"rotation":{"x":0,"y":0,"z":0,"w":1},"scale":{"x":1,"y":1,"z":1},"parent":0}
@@ -56,4 +56,4 @@ CALL onUpdate(0.1)
5656
OPCODES ~= 3k
5757
MALLOC_COUNT = -5
5858
ALIVE_OBJS_DELTA ~= 0.00k
59-
MEMORY_USAGE_COUNT ~= 1318.92k bytes
59+
MEMORY_USAGE_COUNT ~= 1319.18k bytes

test/snapshots/development-bundles/testing-fw.test.ts.crdt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
SCENE_COMPILED_JS_SIZE_PROD=509.7k bytes
1+
SCENE_COMPILED_JS_SIZE_PROD=509.8k bytes
22
THE BUNDLE HAS SOURCEMAPS
33
(start empty vm 0.21.0-3680274614.commit-1808aa1)
44
OPCODES ~= 0k
@@ -11,7 +11,7 @@ EVAL test/snapshots/development-bundles/testing-fw.test.js
1111
REQUIRE: ~system/EngineApi
1212
REQUIRE: ~system/EngineApi
1313
OPCODES ~= 71k
14-
MALLOC_COUNT = 15517
14+
MALLOC_COUNT = 15520
1515
ALIVE_OBJS_DELTA ~= 3.13k
1616
CALL onStart()
1717
LOG: ["Adding one to position.y=0"]
@@ -64,4 +64,4 @@ CALL onUpdate(0.1)
6464
OPCODES ~= 5k
6565
MALLOC_COUNT = -40
6666
ALIVE_OBJS_DELTA ~= -0.01k
67-
MEMORY_USAGE_COUNT ~= 1327.82k bytes
67+
MEMORY_USAGE_COUNT ~= 1328.08k bytes

test/snapshots/development-bundles/two-way-crdt.test.ts.crdt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
SCENE_COMPILED_JS_SIZE_PROD=509.7k bytes
1+
SCENE_COMPILED_JS_SIZE_PROD=509.8k bytes
22
THE BUNDLE HAS SOURCEMAPS
33
(start empty vm 0.21.0-3680274614.commit-1808aa1)
44
OPCODES ~= 0k
@@ -11,7 +11,7 @@ EVAL test/snapshots/development-bundles/two-way-crdt.test.js
1111
REQUIRE: ~system/EngineApi
1212
REQUIRE: ~system/EngineApi
1313
OPCODES ~= 71k
14-
MALLOC_COUNT = 15517
14+
MALLOC_COUNT = 15520
1515
ALIVE_OBJS_DELTA ~= 3.13k
1616
CALL onStart()
1717
LOG: ["Adding one to position.y=0"]
@@ -64,4 +64,4 @@ CALL onUpdate(0.1)
6464
OPCODES ~= 5k
6565
MALLOC_COUNT = -40
6666
ALIVE_OBJS_DELTA ~= -0.01k
67-
MEMORY_USAGE_COUNT ~= 1327.82k bytes
67+
MEMORY_USAGE_COUNT ~= 1328.08k bytes

test/snapshots/production-bundles/append-value-crdt.ts.crdt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
SCENE_COMPILED_JS_SIZE_PROD=223.8k bytes
1+
SCENE_COMPILED_JS_SIZE_PROD=223.9k bytes
22
(start empty vm 0.21.0-3680274614.commit-1808aa1)
33
OPCODES ~= 0k
44
MALLOC_COUNT = 1005
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/append-value-crdt.js
99
REQUIRE: ~system/EngineApi
1010
REQUIRE: ~system/EngineApi
1111
OPCODES ~= 74k
12-
MALLOC_COUNT = 13944
12+
MALLOC_COUNT = 13946
1313
ALIVE_OBJS_DELTA ~= 3.13k
1414
CALL onStart()
1515
Renderer: APPEND_VALUE e=0x200 c=1063 t=0 data={"button":0,"hit":{"position":{"x":1,"y":2,"z":3},"globalOrigin":{"x":1,"y":2,"z":3},"direction":{"x":1,"y":2,"z":3},"normalHit":{"x":1,"y":2,"z":3},"length":10,"meshName":"mesh","entityId":512},"state":1,"timestamp":1,"analog":5,"tickNumber":0}
@@ -55,4 +55,4 @@ CALL onUpdate(0.1)
5555
OPCODES ~= 14k
5656
MALLOC_COUNT = 31
5757
ALIVE_OBJS_DELTA ~= 0.01k
58-
MEMORY_USAGE_COUNT ~= 993.03k bytes
58+
MEMORY_USAGE_COUNT ~= 993.17k bytes

test/snapshots/production-bundles/billboard.ts.crdt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/billboard.js
99
REQUIRE: ~system/EngineApi
1010
REQUIRE: ~system/EngineApi
1111
OPCODES ~= 75k
12-
MALLOC_COUNT = 16454
12+
MALLOC_COUNT = 16456
1313
ALIVE_OBJS_DELTA ~= 3.60k
1414
CALL onStart()
1515
OPCODES ~= 0k
@@ -77,4 +77,4 @@ CALL onUpdate(0.1)
7777
OPCODES ~= 10k
7878
MALLOC_COUNT = 0
7979
ALIVE_OBJS_DELTA ~= 0.00k
80-
MEMORY_USAGE_COUNT ~= 1171.08k bytes
80+
MEMORY_USAGE_COUNT ~= 1171.22k bytes

test/snapshots/production-bundles/cube-deleted.ts.crdt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ EVAL test/snapshots/production-bundles/cube-deleted.js
99
REQUIRE: ~system/EngineApi
1010
REQUIRE: ~system/EngineApi
1111
OPCODES ~= 64k
12-
MALLOC_COUNT = 13091
12+
MALLOC_COUNT = 13093
1313
ALIVE_OBJS_DELTA ~= 2.90k
1414
CALL onStart()
1515
OPCODES ~= 0k
@@ -42,4 +42,4 @@ CALL onUpdate(0.1)
4242
OPCODES ~= 5k
4343
MALLOC_COUNT = 1
4444
ALIVE_OBJS_DELTA ~= 0.00k
45-
MEMORY_USAGE_COUNT ~= 945.77k bytes
45+
MEMORY_USAGE_COUNT ~= 945.91k bytes

0 commit comments

Comments
 (0)