fix(nui): force a fresh reload of an inherited skin before copying from it - #5386
fix(nui): force a fresh reload of an inherited skin before copying from it#5386soloturn wants to merge 2 commits into
Conversation
Related to #1621. UISkinFormat.DefaultInfo.apply() silently proceeded when Assets.get(inherit, UISkinAsset.class) came back empty - the skin loaded "successfully" with every property the base skin would have supplied (textures included) quietly missing, and nothing logged anywhere to say why. That's what made #1621 so hard to track down in the first place: from the reporter's side this looked like skins randomly forgetting how to inherit, not a specific, locatable failure. Now warns with the unresolved URN, matching the pattern this same file already uses for an analogous case a few lines below (failing to resolve a UIWidget class during style application). This does not fix the underlying race #1621 is actually hitting - ModuleAwareAssetTypeManagerImpl#reloadAssets() (gestalt-asset-core) reloads already-loaded assets one at a time in whatever order getLoadedAssetUrns() returns, with no dependency ordering. If a derived skin's reload happens before its base skin's reload in the same pass, it inherits from the base skin's still-stale pre-reload data for that pass - explaining exactly the reported symptom (moved textures missing after the loading screen resets the module environment, until reloadSkin is called by hand). Fixing that properly means teaching reloadAssets() (or UISkinFormat specifically) to order by declared dependency, which is a bigger, foundational change to a library shared by every asset type, not something to land without live UI verification. Filed as a follow-up rather than attempted here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…om it Fixes #1621. UISkinBuilder.build() copies the base skin's *current* properties into a derived skin at build time - a one-time snapshot, not a live reference to the base. UISkinFormat's DefaultInfo.apply() resolved "inherit" via Assets.get(), which returns whatever's already cached - during a module environment switch that can be the base skin's stale pre-switch data, if ModuleAwareAssetTypeManagerImpl#reloadAssets() (gestalt-asset-core) hasn't reached the base yet. reloadAssets() reloads every already-loaded asset once, in whatever order getLoadedAssetUrns() returns, with no dependency ordering between them - if a derived skin's own reload lands before its base's in that same pass, the snapshot it takes is stale. That's what the original report actually hit: a texture moved to a different module, referenced only through an inherited skin property, missing until the affected skins were reloaded again by hand. Fixed at the actual point of the stale read: force the base skin to reload from its data producer right before copying from it, resolved the same way (full or partial urn) the subsequent Assets.get() resolves it, so the asset forced fresh here is exactly the one that lookup finds. Guarantees a current copy regardless of where either skin falls in a reload pass, without touching the shared reload-ordering mechanism or UISkin's inheritance/merge architecture (touches every widget in the game, not something to restructure blind). Also logs when "inherit" can't be resolved at all (a different, genuine failure - the module providing it not being available), which previously failed completely silently: the skin "loaded successfully" missing every property the base would have supplied, with nothing logged anywhere to say why. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughSummary by CodeRabbit
Walkthrough
ChangesUI skin inheritance
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to This localized skin-loading fix is merge-ready after normal checks and review; no actionable merge-blocking risk remains. Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Noting two more complete fixes for the same underlying class of bug, deliberately not attempted here (see #5386 for why - foundational-library changes I'm not making blind without live visual QA):
The fix actually shipped in #5386 addresses the specific reported symptom (a stale cross-reference at one known choke point) without either of the above. If either is wanted, it's its own separate piece of work with live verification, not bundled here. |
Fixes #1621 - skin inheritance breaking specifically across a module environment switch (the loading screen).
Root cause
UISkinBuilder.build()copies the base skin's current properties into a derived skin at build time - a one-time snapshot, not a live reference.UISkinFormat'sinheritresolution usedAssets.get(), which returns whatever's already cached - during an environment switch that can be the base skin's stale pre-switch data, becauseModuleAwareAssetTypeManagerImpl#reloadAssets()(gestalt-asset-core) reloads every already-loaded asset once, in whatever ordergetLoadedAssetUrns()returns, with no dependency ordering between them. If a derived skin's own reload lands before its base's in that same pass, the snapshot it takes is stale. That's what the original report actually hit: a texture moved to a different module, referenced only through an inherited skin property, missing until the affected skins were reloaded again by hand (reloadSkin- since removed from the engine entirely, for what it's worth).Fix
At the actual point of the stale read: force the base skin to reload from its data producer right before copying from it, resolved the same way (full or partial urn) the subsequent
Assets.get()resolves it, so the asset forced fresh here is exactly the one that lookup finds. This guarantees a current copy regardless of where either skin falls in a reload pass.Deliberately not touching the shared reload-ordering mechanism (
gestalt-asset-core, used by every asset type) orUISkin's inheritance/merge architecture (touches every widget in the game) - both would be the more "complete" fix but are foundational changes I'm not making blind without extensive live visual QA across the whole UI. This fixes it at the actual site of the defect for this specific case.Also logs when
inheritcan't be resolved at all (a genuinely different failure - the module providing it not being available) - previously failed completely silently, which is what made this issue so hard to track down in the first place.Test plan
UISkinFormat→UISkinBuilder.build()→UISkin→AssetType.loadAsset()/reload()to confirm the fix addresses the actual defect (Asset.reload()mutates the cached instance in place, so forcing it before the read guarantees the subsequent snapshot is current).