Skip to content

perf: Hot reload's source snapshot capture no longer asks the Package Manager for each assembly and source - #3282

Merged
hatayama merged 4 commits into
perf/hot-reload-capture-off-main-threadfrom
perf/hot-reload-capture-inputs
Oct 11, 2026
Merged

hatayama merged 4 commits into
perf/hot-reload-capture-off-main-threadfrom
perf/hot-reload-capture-inputs

Conversation

@hatayama

@hatayama hatayama commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Hot reload's source snapshot capture after a domain reload now reads everything it needs from Unity once, up front, and then asks Unity nothing while it copies sources. It no longer asks the Package Manager once per assembly (whether its sources are read-only) or once per suspect file (where the file lives on disk).
  • This is the first step toward running the capture off the main thread. The capture still runs synchronously on the main thread in this PR; nothing about when it runs changes.

User Impact

  • Before: for every compiled assembly the capture asked the Package Manager whether the assembly belonged to a read-only package, and for every source written since the compile started it asked again for that file's physical path. Both calls are main-thread-only, so the capture could never leave the main thread.
  • After: the package list is read once with the other inputs, and both answers come from it. The capture copies the same files and marks the same files as edited after the compile as before.

Changes

  • HotReloadSnapshotCaptureInputs.ReadOnMainThread() reads the project root, the compile start, the registered package roots, the path comparison of the platform, and the compilation assemblies. Each assembly is classified as read-only or editable from the package roots, by its first source, the same rule as before. The assembly list still comes from GetAssemblies in this PR; a follow-up replaces it with a listing of the compiled dlls and their PDBs and removes it from the inputs.
  • HotReloadSourceSnapshotter.CaptureAfterDomainReload(inputs) and CaptureAssemblies(inputs, documentIndex) take the inputs. The capture reads the read-only flag instead of asking the Package Manager. ShouldSkipImmutablePackageSources stays for the default file selection of a run and for the existing cross-check test.
  • HotReloadSourceBaseline.CompareWithCompiledDocument is split. The new CompareWithCompiledDocumentAtLookupPath takes a path already named the way the PDB records it and does not touch the Package Manager; the existing method builds that path with the Package Manager and delegates. Its other callers (a run's baseline, the pause-point port) are unchanged.
  • HotReloadSnapshotLookupPath.ToPdbLookupPath builds that path from the package roots, through the existing ScriptPathNormalizer.ToPhysicalProjectRelative. HotReloadSnapshotSourceCheck takes the package roots and the path comparison and uses it.
  • The folder-boundary prefix match of the PDB source map moved to HotReloadPackageFolderMatch, and the new read-only classification uses the same match.
  • docs/vibe-logs.md: packageLookupMs is now close to 0, and totalMs covers listing the assemblies plus the per-assembly loop. Reading the compile start and the package roots is not in totalMs; captureMs in the captured entry still covers it.

Verification

Local, Unity 2022.3 Editor via the dev uloop binary.

  • uloop compile: ErrorCount 0, WarningCount 0.
  • scripts/check-file-length.sh and scripts/check-code-complexity.sh: no findings.
  • Cross-check tests on this project's real assemblies, against the Package Manager's answers:
    • HotReloadSnapshotCaptureInputsTests.ToCaptureAssembly_FromPackageRoots_MatchesThePackageManagerAnswerForEveryAssembly: 112 assemblies with sources (48 read-only package assemblies), 0 mismatches against ShouldSkipImmutablePackageSources.
    • HotReloadSnapshotLookupPathTests.PdbLookupPath_FromPackageRoots_MatchesThePackageManagerAnswerForEverySource: 5595 sources (4601 of them behind a package folder), 0 mismatches against ScriptPackageRoots.ToPhysicalPath.
  • Test classes, run together with one regex filter, 326 passed, 0 failed: HotReloadSourceSnapshotTests, HotReloadSourceSnapshotterTests, HotReloadSourceSnapshotCaptureTests, HotReloadSnapshotAssemblyEnumerationTests, HotReloadIncrementalSnapshotCaptureTests, HotReloadSnapshotEditedDuringCompileTests, HotReloadCompiledSourceCrossCheckTests, HotReloadCompiledSourceMapTests, PausePointScriptPathFormTests, HotReloadOrchestratorTests, HotReloadDefaultFilesTests, and the new HotReloadSnapshotCaptureInputsTests and HotReloadSnapshotLookupPathTests.
  • Mutation check, applied on top of the committed tests and then reverted:
    • The read-only classification always answers "editable": the unit test for a read-only package and the assembly cross-check (48 mismatches) failed.
    • The lookup path returns its input unchanged: the two unit cases for packages and the source cross-check (4601 mismatches) failed.
    • The capture ignores the read-only flag: the new CaptureAssemblies_ImmutablePackageAssembly_CountsItAndWritesNothing failed. No capture test covered that skip before, because a temporary project root registers no package.
    • The capture's PDB check passes the asset path instead of the lookup path, or ignores the package roots: the new HotReloadSnapshotEditedDuringCompileTests.CaptureAtomically_APackageSourceWrittenSinceTheStartThatMatchesThePdb_RecordsAnUnmarkedStamp failed each time, and only it. Every earlier test that built the check from a real dll and PDB used Assets sources, which the lookup returns unchanged. The PDB records an embedded package's sources relative to the compiling project (./Packages/<folder>/...), so the test moves the package root under its temporary project root, as the real one lies under the real project.
  • HotReloadSnapshotEditedDuringCompileTests: 5 passed.

…e Package Manager inside it

The capture now takes its project root, compile start, package roots and
assembly list as values read on the main thread, and classifies each assembly
and names each suspect source from those package roots instead of asking the
Package Manager per assembly and per file. This is the first step toward
running the capture off the main thread; it still runs synchronously here.

- HotReloadSnapshotCaptureInputs.ReadOnMainThread reads the values; the
  assembly list still comes from GetAssemblies until the PDB listing lands.
- The PDB comparison is split so the capture can call the stage that takes
  an already-built lookup path; the existing entry keeps asking the Package
  Manager for the other callers.
- The folder-boundary match is shared with the PDB source map.
- Two tests cross-check the new answers against the Package Manager on every
  assembly and source of this project.
…able

No capture test covered the immutable skip, since a temporary root registers no package; the classification now arrives as a value, so a test can hand it in directly.
…the Package Manager per assembly

The capture now classifies assemblies from package roots read once with its inputs, so packageLookupMs is close to 0, and totalMs no longer covers reading the compile start and the package roots, which captureMs still does.
@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: hatayama/unity-cli-loop/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d75874a5-159d-4ce5-9b09-a87e9fa5d194


📥 Commits

Reviewing files that changed from the base of the PR and between 974a2b5 and 0a6b701.



⛔ Files ignored due to path filters (6)
  • Assets/Tests/Editor/HotReload/HotReloadSnapshotCaptureInputsTests.cs.meta is excluded by none and included by none
  • Assets/Tests/Editor/HotReload/HotReloadSnapshotLookupPathTests.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadCaptureAssembly.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadPackageFolderMatch.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadSnapshotCaptureInputs.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadSnapshotLookupPath.cs.meta is excluded by none and included by none


📒 Files selected for processing (20)
  • Assets/Tests/Editor/HotReload/HotReloadIncrementalSnapshotCaptureTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadSnapshotAssemblyEnumerationTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadSnapshotCaptureInputsTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadSnapshotEditedDuringCompileTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadSnapshotLookupPathTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadSourceSnapshotTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadSourceSnapshotterTests.cs
  • Assets/Tests/Editor/HotReload/PausePointScriptPathFormTests.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadCompositionRoot.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadCaptureAssembly.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadCompiledSourceMap.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadPackageFolderMatch.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadSnapshotCaptureInputs.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadSnapshotLookupPath.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadSnapshotSourceCheck.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadSourceBaseline.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadSourceSnapshotCopier.Incremental.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadSourceSnapshotCopier.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadSourceSnapshotter.cs
  • docs/vibe-logs.md


Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.




📝 Walkthrough
📝 Walkthrough

Walkthrough

Hot-reload snapshot capture now receives pre-collected project, assembly, compile-start, and package-root inputs. Package roots support immutable-package classification and PDB lookup-path resolution. Editor tests cover these inputs and the updated capture flow.

Changes

Hot-reload snapshot pipeline

Layer / File(s) Summary
Collect inputs and resolve package paths
Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadCaptureAssembly.cs, .../HotReloadPackageFolderMatch.cs, .../HotReloadSnapshotCaptureInputs.cs, .../HotReloadSnapshotLookupPath.cs, .../HotReloadCompiledSourceMap.cs
The new input records hold compilation data and package roots. Shared helpers normalize package paths, classify immutable packages, and resolve PDB lookup paths.
Integrate inputs into snapshot capture
Packages/src/Editor/FirstPartyTools/HotReload/HotReloadCompositionRoot.cs, Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadSourceSnapshotter.cs, .../HotReloadSnapshotSourceCheck.cs, .../HotReloadSourceBaseline.cs, .../HotReloadSourceSnapshotCopier*.cs, docs/vibe-logs.md
The composition root reads capture inputs before calling the snapshotter. Capture and source verification use the supplied assembly data, package roots, and path comparison. The copier accepts source files as IReadOnlyList<string>, and the timing documentation reflects the updated measurements.
Validate capture, classification, and path lookup
Assets/Tests/Editor/HotReload/*
Editor tests check package classification and lookup paths, capture inputs, immutable assembly handling, and snapshot behavior with the updated APIs.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant CompositionRoot
  participant SnapshotCaptureInputs
  participant SourceSnapshotter
  participant SnapshotSourceCheck
  participant SourceBaseline
  CompositionRoot->>SnapshotCaptureInputs: ReadOnMainThread
  SnapshotCaptureInputs-->>CompositionRoot: project, assemblies, package roots, compile start
  CompositionRoot->>SourceSnapshotter: CaptureAfterDomainReload(inputs)
  SourceSnapshotter->>SnapshotSourceCheck: Check source with package roots and path comparison
  SnapshotSourceCheck->>SourceBaseline: Compare source at PDB lookup path
Loading

Possibly related PRs

  • hatayama/unity-cli-loop#2117: Introduced PDB-validated source snapshot capture and immutable-package filtering used by this snapshot pipeline.


Merge Risk: ⚪ Minimal · up to 0a6b7

No identified issue currently blocks merging the hot-reload snapshot change after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 0a6b7

The change preserves editor-only execution and existing source-verification controls. No expanded access or weakened protection was identified in the inspected paths, but complete security coverage has not been established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The inspected exposure remains within the Unity Editor's project and registered package context, including packages physically outside the project. Production inputs still originate from Unity metadata; the handoff does not establish a new remote or lower-privilege source for filesystem paths.

Trust Boundaries and Controls

  • observed — A capture stamp is not sufficient authority to use snapshot contents. Readers load source bytes and require a matching compiled-document checksum, rejecting missing or unverifiable source. This control predates the PR and remains in place after the input handoff.

Resilience and Maintainability Implications

  • observed — Existing recovery paths discard leftover temporary state before republishing. Inspected tests assert recovery after failed incremental publication and exceptions following adoption of a previous snapshot, supporting preservation of failure containment rather than an unverified new rollback mechanism.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 77.46% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 71 functions across 19 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly and concisely describes the main performance change: removing per-assembly and per-source Package Manager queries during hot-reload snapshot capture.
Description check Passed The description is directly related to the changeset. It explains the performance change, implementation details, user impact, verification results, and scope limitations.

Full details: Docstring Coverage

Explanation

Docstring coverage is 77.46% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 71 functions across 19 files. (1 skipped: 1 unsupported.)


  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR



🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR



  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

… behind its package path

Every test that built the check from a real dll and PDB used Assets sources, which the lookup returns unchanged, so passing the asset path or ignoring the package roots went unnoticed. The new test checks an embedded package's source, whose PDB records it under the package's folder relative to the compiling project.
@hatayama
hatayama merged commit f79a2a0 into perf/hot-reload-capture-off-main-thread Oct 11, 2026
5 checks passed
@hatayama
hatayama deleted the perf/hot-reload-capture-inputs branch October 11, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant