feat(gsplat): expose parsed manifest on GSplatOctreeResource#8699
Conversation
GSplatOctreeParser parses the LOD manifest and hands it to the resource constructor, but the resource only retains the fields GSplatOctree consumes (lodLevels, filenames, tree.bound, environment) and discards the rest. Consumers that ship custom/extension fields in the manifest (per-asset render settings, app-specific metadata, etc.) have no way to read them back without re-fetching and re-parsing the same JSON file. Retain a reference to the parsed object on resource.data so consumers can read whatever fields they wrote into the manifest. The parsed JSON object already exists during construction; this just holds a reference to it (typically a few KB per LOD asset, dominated in scale by the octree's per-node AABB storage that the resource already retains). No new allocation; no new behavior for existing consumers.
There was a problem hiding this comment.
Pull request overview
This PR updates the unified GSplat octree container resource to retain and expose the raw parsed LOD manifest JSON so downstream consumers can access custom/extension manifest fields without re-fetching and re-parsing the manifest.
Changes:
- Add a
datafield toGSplatOctreeResourceto store the parsed manifest object. - Assign the constructor’s parsed JSON parameter to
this.datafor later consumer access.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Per review feedback on playcanvas#8699: the data field is null after destroy(), so declare it as @type {object|null} initialized to null, and add this.data = null to destroy() so the parsed manifest is released along with the octree reference.
Per @mvaligursky review on playcanvas#8699: the tree hierarchy is the bulk of the parsed manifest, and GSplatOctree builds its own structure from it during construction — keeping the original data.tree reference on resource.data is dead memory. Null it after the octree is built and update the field JSDoc to point consumers at resource.octree for node-hierarchy access.
|
Also, please ignore the copilot's suggestion of data being nullable, that's pointless and makes user wonder if in some cases they get no data. I resolved thos ecomments, and hoped you'd ignore it too ;) |
Per @mvaligursky on playcanvas#8699: `resource.data` is always populated for the lifetime of the resource, so the nullable type annotation invites unnecessary defensive coding by users. Reverts commit 752a7c2; keeps the data.tree memory cleanup from 8460080.
Got it, reverted in 4155a72. Apologies — I jumped on the Copilot suggestion before you'd had a chance to weigh in. |
mvaligursky
left a comment
There was a problem hiding this comment.
Nice, thanks for this!
Summary
GSplatOctreeParserfetches and parses the LOD manifest, then hands the parsed object toGSplatOctreeResource's constructor. The resource retains the fieldsGSplatOctreeconsumes (lodLevels,filenames,tree.bound,environment) and discards the rest.That's a problem for any consumer that ships custom/extension fields in the manifest (per-asset render settings, app-specific metadata, framing defaults, etc.) — they have no way to read those fields back without re-fetching and re-parsing the same JSON file PlayCanvas already loaded.
This PR retains a reference to the parsed object on
resource.dataso consumers can read whatever fields they wrote into the manifest.Diff
class GSplatOctreeResource { /** @type {BoundingBox} */ aabb = new BoundingBox(); /** * Version counter for centers array changes. Always 0 for octree resources (static). * * @ignore */ centersVersion = 0; /** @type {GSplatOctree|null} */ octree; + /** + * Raw parsed manifest data, retained for consumers that read custom or extension + * fields the octree itself does not consume (for example application-specific + * metadata accompanying a `lod-meta.json`). + * + * @type {object} + */ + data; + /** * @param {string} assetFileUrl - The file URL of the container asset. * @param {object} data - Parsed JSON data. * @param {object} assetLoader - Asset loader instance (framework-level object). */ constructor(assetFileUrl, data, assetLoader) { this.octree = new GSplatOctree(assetFileUrl, data); this.octree.assetLoader = assetLoader; this.aabb.setMinMax(new Vec3(data.tree.bound.min), new Vec3(data.tree.bound.max)); + this.data = data; }Memory cost
The parsed JSON object already exists during construction (the constructor parameter holds it). This change just keeps a reference to the same object — no new allocation. Per-asset cost is the size of the parsed manifest, typically a few KB, dominated in scale by the octree's per-node AABB storage that the resource already retains.
If a future consumer wanted to opt out of retention they could
resource.data = nullafter reading what they need.Use case (for context)
We carry per-asset metadata in
lod-meta.jsonalongside the octree manifest — fields likeaa,shadow,lodBaseDistance, and adefaultsblock of view/render settings — that PlayCanvas itself doesn't need to know about. Without this PR we have to fetchlod-meta.jsonourselves before letting the asset load (so the data is available beforeentity.addComponent('gsplat', { ... })), causing a duplicateGETon every LOD load. With this PR we read everything frommodelAsset.resource.datainside the asset'sreadyhandler.No breaking changes
data) and one assignment.nulluntil the constructor runs (matchesoctree).Verified locally
Built and used in production via
patch-packageon PC 2.18 with the same diff (line numbers shifted from the post-#8683 refactor); upstream PR adapted to the currentmain. Both confirmed working against multi-level LOD assets.Checklist