The Application layer orchestrates analysis, owns the ViewModel, and provides pure-function engines for filtering and diffing. It has no IMGUI code and no file I/O — all persistence delegates to the Storage folder, and build-report/dependency reading delegates to Analysis.
Editor/Application/AnalysisCoordinator.cs
The central static coordinator. One instance per Editor session (reset on domain reload).
| Method / Property | Description |
|---|---|
RunAnalysis() |
Start real analysis from Library/LastBuild.buildreport |
RunDemo() |
Load the synthetic demo snapshot |
ApplyFilter() |
Re-apply current FilterState to the active snapshot |
SelectNode(node) |
Set vm.SelectedNode and publish |
ComputeDiff(a, b) |
Run DiffEngine.Compute(a, b) and store result |
ReloadSnapshots() |
Call SessionManager.Reload(), refresh vm.Snapshots, and — if there's no active snapshot — restore the most recent one as vm.ActiveSnapshot so the Overview/Dependencies tabs don't go blank after every domain reload |
ClearLogs() |
Empty vm.Logs and publish |
CurrentViewModel |
Returns the current AnalysisViewModel reference |
OnViewModelUpdated |
event Action<AnalysisViewModel> — fired after every state mutation |
Log(level, message) exists but is private — it appends a LogEntry to vm.Logs without publishing on its own; the surrounding Transition()/SetFailed() call handles that. It's not part of the coordinator's public surface.
RunAnalysis()
ClearLogs()
Log(Info, "Analysis started.")
Transition(LoadingReport, "Loading build report…", 0.05)
BuildReportAdapter.Load()
→ error: SetFailed()
Log(Info, "Loaded N assets.")
Transition(WalkingDependencies, "Scanning dependencies…", 0.30)
AssetDatabaseWalker.BuildDependencyGraph(nodes)
→ error: SetFailed()
Log(Info, "Dependency graph built successfully.")
FinalizeSnapshot(nodes, graph, platform, productName)
Transition(SavingSnapshot, "Saving snapshot…", 0.88)
BuildSnapshot created
SessionManager.AddSnapshot(snapshot)
Log(Info, "Snapshot saved.")
ApplySmartDefaultFilterIfPristine(nodes)
→ only if FilterState is still untouched this session, and one category
crosses the dominance threshold: excludes it, Log(Info, "...")
FilterEngine.Apply(nodes, FilterState)
Log(Info, "Analysis complete — N assets, X MB compressed.")
Transition(Complete, "Analysis complete.", 1.0)
| State | IsAnalyzing |
Description |
|---|---|---|
Idle |
false | No analysis has run this session |
LoadingReport |
true | Reading BuildReport |
WalkingDependencies |
true | Running AssetDatabase traversal |
SavingSnapshot |
true | Writing to disk |
Complete |
false | Finished successfully |
Failed |
false | Stopped with an error |
Editor/Application/SessionManager.cs
Thin wrapper over SnapshotStore that maintains an in-memory list sorted newest-first.
| Method | Description |
|---|---|
Reload() |
Calls SnapshotStore.LoadAll(), which fully deserialises every .blsnapshot file referenced by the index — not just summaries |
AddSnapshot(snapshot) |
Persists via SnapshotStore.Save(); inserts at index 0; Save() enforces the retention limit internally |
Snapshots |
IReadOnlyList<BuildSnapshot> — current in-memory list |
Editor/Application/FilterEngine.cs
public static List<AssetNode> Apply(
IReadOnlyList<AssetNode> nodes,
FilterState filter)Applies the type mask, a min/max compressed-size range, and a scene-name substring filter in a single pass, then sorts the result via SortEngine. Returns a new list — never mutates input. Called by AnalysisCoordinator.ApplyFilter() after any filter state change.
public static AssetType? FindDominantCategoryToExcludeByDefault(
IReadOnlyList<AssetNode> nodes)Pure function, no dependency on FilterState or any mutable state — given a node list, returns the one AssetType that should start unchecked in the default view because it makes up 90% or more of total compressed size, provided at least two other categories still have non-zero data of their own; returns null otherwise. At most one category can ever cross 90%, since shares sum to 100%, so there's never an ambiguous choice between candidates. The caller (AnalysisCoordinator.ApplySmartDefaultFilterIfPristine) is responsible for deciding when to act on this — specifically, only against a FilterState that hasn't been touched yet this session — this function itself has no opinion on that and will happily return an answer against any node list regardless of the caller's filter state.
Editor/Application/DiffEngine.cs
public static BuildDiff Compute(BuildSnapshot snapshotA, BuildSnapshot snapshotB)Compares by GUID using two dictionary lookups (one per snapshot):
- GUIDs in B but not A →
Added - GUIDs in A but not B →
Removed - GUIDs in both where
CompressedBytesdiffers at all →Changed
There is no minimum-delta threshold — a one-byte difference is enough to land an asset in Changed. Results in each list are sorted by size (or by |SizeDelta| for Changed), largest first.