decentraland / decentraland/unity-explorer
local-ab follow-ups: mirror content-edit reconversions in the AB panel; fall back to production CDN when the sidecar can't start
- Dominant language
- C#
- Stars
- 23
- Forks
- 17
- Avg merge
- 2d 16h
- Merged PRs (30d)
- 101
Description
Follow-ups to #9704 (`--local-ab` explorer-owned abgen sidecar). Both were designed and prototyped during development but deliberately left out to keep the PR scoped; this issue captures the agreed designs so they don't need re-discovery.
## 1. AB Conversion panel doesn't reflect content-edit reconversions
**QA-confirmed on Windows + macOS:** with the scene warm (READY), replacing a `.glb` (tested up to ~10 MB) correctly JIT-reconverts and the new content shows in-world — but the AB panel keeps saying "SCENE ALREADY CONVERTED (warm cache)" with the stale census. Consistent, not a timing fluke.
**Root cause:** the `/progress/{entity}` mirroring loop lives only inside `AbgenSidecar.WarmUpLocalSceneAsync` and runs only for the boot warm-up's manifest request. The reconversion is triggered by a different path (LSD scene reload → manifest re-request → abgen content-digest revalidation), and nothing mirrors it into `AbgenConversionMetrics`.
**Agreed design (event-driven, not polling):**
- `LocalSceneDevelopmentController` (`SceneLifeCycle/LocalSceneDevelopment`) is the single point that learns about edits — the preview server's websocket message, already distinguishing `UpdateModel` (carries `Src`, the changed file) from `UpdateScene` (code-only). Raise a consumable signal on `AbgenConversionMetrics` (`OnContentEdit(string? changedSrc)` / `TryConsumeContentEdit`), same pattern as the existing panel-open request. `SceneLifeCycle.asmdef` already references `ECS.Unity` — no new wiring.
- The sidecar plugin starts a detached session-long watcher after warm-up (`WatchReconversionsAsync`). On signal, it **requests the manifest itself** — the request coalesces with (or front-runs) the server's rebuild and returns when the build finishes, so elapsed/READY are accurate even when the rebuild outpaces the 500 ms progress poll. Extract the warm-up's manifest+progress+census loop into a shared `MirrorManifestBuildAsync` rather than duplicating it.
- Panel outcome: flips back to converting, names the edited file (from `Src`), pulses the AB button, settles to "reconverted in Ns". Named edits always report "reconverted"; unnamed (`UpdateScene`) with no observed progress report "revalidated — already up to date".
- Known limitation: texture edits arrive as `UpdateScene` (sdk-commands `file-watch-notifier.ts` gates `UpdateModel` on `.glb/.gltf`), so they're mirrored anonymously. Optional SDK-side follow-up: widen that gate to convertible images — the explorer side is already extension-agnostic (keep passing null to `TryReloadSceneAsync` for image srcs; it uses the src for model cache eviction).
## 2. Clean production fallback when the sidecar can't be had
`ReserveBaseUrl()` seeds `DecentralandUrlsSource.optimizedAssetsBaseOverride` at construction, before the sidecar ever starts. If the sidecar then fails (download failure, launch failure, port taken by another `--local-ab` instance since #9704 binds abgen's fixed default `127.0.0.1:5147`), the override still points at the dead port for the whole session. Everything *works* via per-request failure recovery — the scene degrades to raw GLTFs — but:
- **Wearables/emotes lose their asset bundles entirely**: each request hits the dead port, gets connection-refused, and falls back to GLTF from the content server, instead of loading optimized bundles from the production CDN.
- The registry-composed endpoints on the same base — `Profiles`, `ProfilesMetadata`, `EntitiesActiveElements` (`DecentralandUrlsSource.cs` `ComposeRegistryUrl`) — eat a dead-port round trip per request too.
**Agreed design** (prototyped, reverted from #9704 as out of scope):
- `AbgenSidecarPlugin.ReadyAsync` becomes `UniTask`: true once the server is healthy (`StartAsync` succeeded — warm-up outcome doesn't matter, bundles JIT per request), false when it never came up. `Dispose` before run resolves false. `DynamicWorldContainer.AbgenSidecarReadyAsync` defaults to true when the plugin isn't mounted.
- `DecentralandUrlsSource.ClearOptimizedAssetsOverride()`: null the override and evict every `FeatureFlagsDependent`-cached entry (mirrors the existing `ResetRealmDependentUrls` idiom; that cache class covers all affected URLs, including the registry-composed ones).
- `MainSceneLoader` already awaits readiness before `LoadStartingRealmAsync` (the boot-hold), so no optimized-asset URL has resolved yet at that point — on false, clear the override and the entire session resolves to production from the first request, exactly as if `--local-ab` had not been passed.
- Out of scope even then: a sidecar that passes health and dies later (supervision exhausted) keeps the override — per-request recovery remains the safety net for that case.
Context: #9704, `docs/abgen-sidecar.md`.
Contributor guide
Research direction
Read #9704 and docs/abgen-sidecar.md, then trace LocalSceneDevelopmentController, AbgenConversionMetrics, AbgenSidecarPlugin, DynamicWorldContainer, MainSceneLoader, and DecentralandUrlsSource. Done means content-edit reconversions update the AB panel with the appropriate outcome and a sidecar startup failure clears the optimized-assets override before realm loading, while preserving the stated out-of-scope behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, unity
- Domain
- desktop, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100