@@ -4,92 +4,79 @@ import { Color4 } from '@dcl/sdk/math'
44/**
55 * Sphere mesh-sharing benchmark — runtime spawner
66 * ------------------------------------------------------------------
7- * Validates a Unity Explorer memory optimization: SpherePrimitive shares
8- * ONE immutable Mesh asset across every sphere entity, instead of allocating
9- * a new Mesh per entity (Box/Plane/Cylinder still allocate a distinct mesh
10- * per entity).
7+ * Validates a Unity Explorer memory optimization: SpherePrimitive shares ONE
8+ * immutable Mesh asset across every sphere entity, instead of allocating a new
9+ * Mesh per entity.
1110 *
12- * The UI (see ui.tsx) drives this module in real time so the effect is
13- * observable live in the Explorer's Memory Profiler: click "Add spheres"
14- * repeatedly and mesh memory stays flat (shared mesh); click "Add boxes" and
15- * mesh memory climbs 1:1 with the box count.
11+ * The comparison is made ACROSS Explorer builds: run this same scene on an old
12+ * build (no sharing) and on a new build (shared mesh) and watch the Memory
13+ * Profiler. On the old build, mesh memory climbs 1:1 with the sphere count; on
14+ * the new build it stays flat no matter how many spheres you spawn. That's why
15+ * there's no in-scene control group — the control is the old build itself.
16+ *
17+ * The UI (see ui.tsx) drives this module in real time: click "Add spheres" to
18+ * pile on thousands of spheres, "Delete all" to reset.
1619 */
1720
18- // How many entities each button press spawns. Small enough to click through
19- // several batches while watching the Profiler, large enough that a single box
20- // batch is visible in the memory graph .
21- export const ADD_BATCH = 100
21+ // How many spheres each button press spawns. Large so the mesh-memory
22+ // divergence between old and new builds becomes obvious after just a click or
23+ // two .
24+ export const ADD_BATCH = 1000
2225
23- // Stack layout: each group fills a fixed GRID_COLUMNS x GRID_DEPTH footprint and
24- // grows UPWARD (+y), layer by layer. GRID_COLUMNS * GRID_DEPTH is sized to equal
25- // ADD_BATCH, so every button press drops one complete new layer on top of the
26- // previous one — the stack visibly rises with each click.
27- const GRID_COLUMNS = 10 // entities along x
28- const GRID_DEPTH = 10 // entities along z (COLUMNS * DEPTH === ADD_BATCH === one layer)
26+ // Stack layout: spheres fill a fixed GRID_COLUMNS x GRID_DEPTH footprint (sized
27+ // to span most of the 2-parcel scene) and grow UPWARD (+y), layer by layer, as
28+ // more are added.
29+ const GRID_COLUMNS = 48 // entities along x
30+ const GRID_DEPTH = 24 // entities along z
2931const PER_LAYER = GRID_COLUMNS * GRID_DEPTH
3032const SPACING = 0.6 // meters between entity centers
31- const ENTITY_SCALE = 0.4 // meters (diameter/side length ) — smaller than SPACING so entities never touch
33+ const ENTITY_SCALE = 0.4 // meters (diameter) — smaller than SPACING so spheres never touch
3234const BASE_HEIGHT = 0.5 // meters, y of the bottom layer
3335
34- // Local-coordinate anchors for each grid 's near corner. Scene base parcel is
36+ // Local-coordinate anchor for the stack 's near corner. Scene base parcel is
3537// "40,40" with a second parcel at "41,40" (see scene.json), giving a local
36- // coordinate space of x: 0..32, z: 0..16. Spheres fill the left half, boxes the
37- // right half.
38- const SPHERE_GRID_ORIGIN = { x : 2 , z : 2 }
39- const BOX_GRID_ORIGIN = { x : 17.4 , z : 2 }
38+ // coordinate space of x: 0..32, z: 0..16. The footprint above fits inside it.
39+ const GRID_ORIGIN = { x : 2 , z : 1 }
4040
41- const SPHERE_COLOR = Color4 . create ( 0.15 , 0.35 , 0.95 , 1 ) // blue = optimized/shared-mesh path
42- const BOX_COLOR = Color4 . create ( 0.9 , 0.2 , 0.15 , 1 ) // red = control/distinct-mesh path
41+ const SPHERE_COLOR = Color4 . create ( 0.15 , 0.35 , 0.95 , 1 ) // blue
4342
44- // Live registries of every entity we've spawned, so "Delete all" can remove
45- // them and the UI can display current counts .
43+ // Live registry of every sphere we've spawned, so "Delete all" can remove them
44+ // and the UI can display the current count .
4645const spheres : Entity [ ] = [ ]
47- const boxes : Entity [ ] = [ ]
4846
4947export function getSphereCount ( ) : number {
5048 return spheres . length
5149}
5250
53- export function getBoxCount ( ) : number {
54- return boxes . length
55- }
56-
5751export function addSpheres ( count : number = ADD_BATCH ) {
5852 for ( let i = 0 ; i < count ; i ++ ) {
59- spheres . push ( createSphere ( stackPosition ( SPHERE_GRID_ORIGIN , spheres . length ) ) )
60- }
61- }
62-
63- export function addBoxes ( count : number = ADD_BATCH ) {
64- for ( let i = 0 ; i < count ; i ++ ) {
65- boxes . push ( createBox ( stackPosition ( BOX_GRID_ORIGIN , boxes . length ) ) )
53+ spheres . push ( createSphere ( stackPosition ( spheres . length ) ) )
6654 }
6755}
6856
6957export function deleteAll ( ) {
7058 for ( const entity of spheres ) engine . removeEntity ( entity )
71- for ( const entity of boxes ) engine . removeEntity ( entity )
7259 spheres . length = 0
73- boxes . length = 0
7460}
7561
76- // Places the entity at index `i` within a stack anchored at `origin` . Each layer
77- // is a GRID_COLUMNS x GRID_DEPTH slab in the x/z plane; once a layer fills, the
78- // stack grows upward (+y) into the next layer.
79- function stackPosition ( origin : { x : number ; z : number } , i : number ) : { x : number ; y : number ; z : number } {
62+ // Places the sphere at index `i` within the stack. Each layer is a
63+ // GRID_COLUMNS x GRID_DEPTH slab in the x/z plane; once a layer fills, the stack
64+ // grows upward (+y) into the next layer.
65+ function stackPosition ( i : number ) : { x : number ; y : number ; z : number } {
8066 const layer = Math . floor ( i / PER_LAYER )
8167 const withinLayer = i % PER_LAYER
8268 const column = withinLayer % GRID_COLUMNS
8369 const row = Math . floor ( withinLayer / GRID_COLUMNS )
8470 return {
85- x : origin . x + column * SPACING ,
71+ x : GRID_ORIGIN . x + column * SPACING ,
8672 y : BASE_HEIGHT + layer * SPACING ,
87- z : origin . z + row * SPACING
73+ z : GRID_ORIGIN . z + row * SPACING
8874 }
8975}
9076
91- // Optimized path: MeshRenderer.setSphere reuses a single shared, immutable Mesh
92- // asset across every sphere entity instead of allocating its own.
77+ // MeshRenderer.setSphere reuses a single shared, immutable Mesh asset across
78+ // every sphere entity (on builds with the optimization) instead of allocating
79+ // its own.
9380function createSphere ( position : { x : number ; y : number ; z : number } ) : Entity {
9481 const entity = engine . addEntity ( )
9582 Transform . create ( entity , {
@@ -100,16 +87,3 @@ function createSphere(position: { x: number; y: number; z: number }): Entity {
10087 Material . setPbrMaterial ( entity , { albedoColor : SPHERE_COLOR , roughness : 0.6 } )
10188 return entity
10289}
103-
104- // Control/unoptimized path: MeshRenderer.setBox allocates a distinct Mesh asset
105- // per entity, so this group's Mesh count scales 1:1 with the box count.
106- function createBox ( position : { x : number ; y : number ; z : number } ) : Entity {
107- const entity = engine . addEntity ( )
108- Transform . create ( entity , {
109- position,
110- scale : { x : ENTITY_SCALE , y : ENTITY_SCALE , z : ENTITY_SCALE }
111- } )
112- MeshRenderer . setBox ( entity )
113- Material . setPbrMaterial ( entity , { albedoColor : BOX_COLOR , roughness : 0.6 } )
114- return entity
115- }
0 commit comments