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

Open
#9,749 1 comment 0 reactions 0 assignees View on GitHub
2-medium asset bundles enhancement ui
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.