diff --git a/Assets/Tests/Editor/HotReload/HotReloadCompiledCallersTestRuns.cs b/Assets/Tests/Editor/HotReload/HotReloadCompiledCallersTestRuns.cs
new file mode 100644
index 000000000..40b592607
--- /dev/null
+++ b/Assets/Tests/Editor/HotReload/HotReloadCompiledCallersTestRuns.cs
@@ -0,0 +1,31 @@
+using System.Threading.Tasks;
+
+using NUnit.Framework;
+
+using io.github.hatayama.UnityCliLoop.FirstPartyTools;
+
+namespace io.github.hatayama.UnityCliLoop.Tests.Editor.HotReload
+{
+ ///
+ /// Compiled-caller runs for tests that build an apply context by hand, outside any hot reload run.
+ ///
+ internal static class HotReloadCompiledCallersTestRuns
+ {
+ ///
+ /// Opens a run on compiled callers of its own: a fresh cache and a backfill that reads
+ /// nothing. Its hold is taken on that fresh cache, never on the shared one, so it keeps no
+ /// shared entry alive and needs no Dispose; the caller scans it does still read the shared
+ /// cache without a hold, as gate tests did before scans moved onto the run. The backfill is
+ /// idle, so the run begins synchronously and is read without blocking.
+ ///
+ internal static HotReloadCompiledCallersRun OpenIsolated()
+ {
+ HotReloadCompiledCallers compiledCallers = new HotReloadCompiledCallers(
+ new HotReloadCompiledCallSiteCache(HotReloadCompiledCallSiteCache.DefaultBudgetBytes),
+ new HotReloadCallSiteBackfill((dllPath, ct) => Task.CompletedTask));
+ Task begin = compiledCallers.BeginRunAsync();
+ Assert.That(begin.IsCompleted, Is.True, "Precondition: a run on an idle backfill begins synchronously.");
+ return begin.Result;
+ }
+ }
+}
diff --git a/Assets/Tests/Editor/HotReload/HotReloadCompiledCallersTestRuns.cs.meta b/Assets/Tests/Editor/HotReload/HotReloadCompiledCallersTestRuns.cs.meta
new file mode 100644
index 000000000..c59a245e6
--- /dev/null
+++ b/Assets/Tests/Editor/HotReload/HotReloadCompiledCallersTestRuns.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 43582fde9e890428e9bef0a545db5708
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Tests/Editor/HotReload/HotReloadCompiledCallersTests.cs b/Assets/Tests/Editor/HotReload/HotReloadCompiledCallersTests.cs
index 46140d5e7..5c04a7bfa 100644
--- a/Assets/Tests/Editor/HotReload/HotReloadCompiledCallersTests.cs
+++ b/Assets/Tests/Editor/HotReload/HotReloadCompiledCallersTests.cs
@@ -1,9 +1,14 @@
+using System;
using System.Collections.Generic;
+using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
+using UnityEngine;
+using UnityEngine.TestTools;
+
using io.github.hatayama.UnityCliLoop.FirstPartyTools;
using io.github.hatayama.UnityCliLoop.ToolContracts;
@@ -104,5 +109,35 @@ public async Task Dispose_WithoutEnd_ReleasesTheHoldAndStartsNoBackgroundRead()
Assert.That(_loader.Ran, Is.Empty, "no background read starts");
Assert.That(_backfill.State, Is.EqualTo(HotReloadCallSiteBackfillState.Idle));
}
+
+ ///
+ /// What: a caller scan asked of a run that already ended is reported, since its reads are
+ /// no longer held against eviction.
+ ///
+ [Test]
+ public async Task FindCallSites_AfterTheRunEnded_Asserts()
+ {
+ HotReloadCompiledCallersRun run = await _compiledCallers.BeginRunAsync();
+ run.End("corr");
+ LogAssert.Expect(LogType.Assert, "FindCallSites is called only while the run is open.");
+
+ run.FindCallSites(Path.GetFullPath(Path.Combine(Application.dataPath, "..")), Array.Empty());
+
+ run.Dispose();
+ }
+
+ ///
+ /// What: a caller scan asked of a run that was disposed without End (a run that threw) is
+ /// reported too, since its hold is released as well.
+ ///
+ [Test]
+ public async Task FindCallSites_AfterTheRunWasDisposedWithoutEnd_Asserts()
+ {
+ HotReloadCompiledCallersRun run = await _compiledCallers.BeginRunAsync();
+ run.Dispose();
+ LogAssert.Expect(LogType.Assert, "FindCallSites is called only while the run is open.");
+
+ run.FindCallSites(Path.GetFullPath(Path.Combine(Application.dataPath, "..")), Array.Empty());
+ }
}
}
diff --git a/Assets/Tests/Editor/HotReload/HotReloadGroupProcessorLeaveOutTests.cs b/Assets/Tests/Editor/HotReload/HotReloadGroupProcessorLeaveOutTests.cs
index 07e0f1dac..df72056bc 100644
--- a/Assets/Tests/Editor/HotReload/HotReloadGroupProcessorLeaveOutTests.cs
+++ b/Assets/Tests/Editor/HotReload/HotReloadGroupProcessorLeaveOutTests.cs
@@ -289,11 +289,16 @@ private async Task> RunGroupAsync(
collaborators, context, compileResult, entriesToPatch),
collaborators.EntryApplier.ApplyPreparedEntries)))
{
- return await HotReloadCompositionRoot.Services.GroupProcessor.ProcessGroupAsync(
- files,
- "leave-out-test",
- new HotReloadRunTiming(),
- CancellationToken.None);
+ using (HotReloadCompiledCallersRun compiledCallersRun =
+ await HotReloadCompositionRoot.Services.CompiledCallers.BeginRunAsync())
+ {
+ return await HotReloadCompositionRoot.Services.GroupProcessor.ProcessGroupAsync(
+ files,
+ compiledCallersRun,
+ "leave-out-test",
+ new HotReloadRunTiming(),
+ CancellationToken.None);
+ }
}
}
diff --git a/Assets/Tests/Editor/HotReload/HotReloadGroupProcessorTests.cs b/Assets/Tests/Editor/HotReload/HotReloadGroupProcessorTests.cs
index 2455fe8df..befbc164a 100644
--- a/Assets/Tests/Editor/HotReload/HotReloadGroupProcessorTests.cs
+++ b/Assets/Tests/Editor/HotReload/HotReloadGroupProcessorTests.cs
@@ -639,11 +639,16 @@ public async Task ProcessGroupAsync_WhenTheWorkerFails_BuildsUnappliedResultsWit
collaborators, context, compileResult, entriesToPatch),
collaborators.EntryApplier.ApplyPreparedEntries)))
{
- results = await HotReloadCompositionRoot.Services.GroupProcessor.ProcessGroupAsync(
- new[] { file },
- "worker-failure-test",
- new HotReloadRunTiming(),
- CancellationToken.None);
+ using (HotReloadCompiledCallersRun compiledCallersRun =
+ await HotReloadCompositionRoot.Services.CompiledCallers.BeginRunAsync())
+ {
+ results = await HotReloadCompositionRoot.Services.GroupProcessor.ProcessGroupAsync(
+ new[] { file },
+ compiledCallersRun,
+ "worker-failure-test",
+ new HotReloadRunTiming(),
+ CancellationToken.None);
+ }
}
Assert.That(results, Has.Count.EqualTo(1));
@@ -749,13 +754,18 @@ private static async Task RunGroupWithPhaseStubsAsync(
{
try
{
- total.Start();
- await HotReloadCompositionRoot.Services.GroupProcessor.ProcessGroupAsync(
- new[] { stubs.GroupFile },
- "phase-timing-test",
- timing,
- CancellationToken.None);
- total.Stop();
+ using (HotReloadCompiledCallersRun compiledCallersRun =
+ await HotReloadCompositionRoot.Services.CompiledCallers.BeginRunAsync())
+ {
+ total.Start();
+ await HotReloadCompositionRoot.Services.GroupProcessor.ProcessGroupAsync(
+ new[] { stubs.GroupFile },
+ compiledCallersRun,
+ "phase-timing-test",
+ timing,
+ CancellationToken.None);
+ total.Stop();
+ }
}
catch (OperationCanceledException exception)
{
@@ -1821,7 +1831,8 @@ private static HotReloadApplyContext CreateContext(
workerOutput,
new[] { callerFile, targetFile },
null,
- new HotReloadCompileFailureNoteSources(workerOutput.skipped, Array.Empty()));
+ new HotReloadCompileFailureNoteSources(workerOutput.skipped, Array.Empty()),
+ HotReloadCompiledCallersTestRuns.OpenIsolated());
}
private static HotReloadApplyContext CreateEmptyEntriesContext(
@@ -1848,7 +1859,8 @@ private static HotReloadApplyContext CreateEmptyEntriesContext(
emptyWorkerOutput,
context.Files,
null,
- new HotReloadCompileFailureNoteSources(emptyWorkerOutput.skipped, Array.Empty()));
+ new HotReloadCompileFailureNoteSources(emptyWorkerOutput.skipped, Array.Empty()),
+ context.CompiledCallersRun);
}
private static Assembly FindCompilationAssembly()
diff --git a/Assets/Tests/Editor/HotReload/HotReloadIntroducedTypeE2ETestBase.cs b/Assets/Tests/Editor/HotReload/HotReloadIntroducedTypeE2ETestBase.cs
index 1f4d37377..e96a7887d 100644
--- a/Assets/Tests/Editor/HotReload/HotReloadIntroducedTypeE2ETestBase.cs
+++ b/Assets/Tests/Editor/HotReload/HotReloadIntroducedTypeE2ETestBase.cs
@@ -102,8 +102,7 @@ private protected static HotReloadGroupStageCollaborators WithInternalAccessGran
collaborators.EditorStateSnapshotCapture,
collaborators.CommitPolicy,
collaborators.PlayMode,
- internalAccessGrant,
- collaborators.CompiledCallers);
+ internalAccessGrant);
}
private protected static Task RunReloadAsync(
diff --git a/Assets/Tests/Editor/HotReload/HotReloadOrchestratorTests.cs b/Assets/Tests/Editor/HotReload/HotReloadOrchestratorTests.cs
index 3c6c1987f..f9c8cf418 100644
--- a/Assets/Tests/Editor/HotReload/HotReloadOrchestratorTests.cs
+++ b/Assets/Tests/Editor/HotReload/HotReloadOrchestratorTests.cs
@@ -3710,7 +3710,8 @@ private static HotReloadApplyContext CreateApplyContext(
workerOutput,
new[] { file },
null,
- new HotReloadCompileFailureNoteSources(workerOutput.skipped, Array.Empty()));
+ new HotReloadCompileFailureNoteSources(workerOutput.skipped, Array.Empty()),
+ HotReloadCompiledCallersTestRuns.OpenIsolated());
}
private static HotReloadOrchestratorResult ToOrchestratorResult(
diff --git a/Packages/src/Editor/FirstPartyTools/HotReload/CompiledCallers/HotReloadCompiledCallers.cs b/Packages/src/Editor/FirstPartyTools/HotReload/CompiledCallers/HotReloadCompiledCallers.cs
index e4729bea5..7785aacae 100644
--- a/Packages/src/Editor/FirstPartyTools/HotReload/CompiledCallers/HotReloadCompiledCallers.cs
+++ b/Packages/src/Editor/FirstPartyTools/HotReload/CompiledCallers/HotReloadCompiledCallers.cs
@@ -41,17 +41,6 @@ public static HotReloadCompiledCallers CreateProduction()
return new HotReloadCompiledCallers(cache, backfill);
}
- ///
- /// Finds the compiled call and ldftn sites that reference any of ,
- /// reading every compiled assembly it needs. Call it only while a run begun with
- /// is open: outside one, a read on another thread may evict and
- /// dispose a cached assembly this is still reading.
- ///
- public List FindCallSites(string projectRoot, HotReloadCompiledMethodIdentity[] targets)
- {
- return HotReloadCallSiteScanner.FindCallSites(projectRoot, targets).Hits;
- }
-
/// The compiled dlls of the assemblies that reference .
public IReadOnlyList CollectReferencingDllPaths(string projectRoot, string assemblyName)
{
diff --git a/Packages/src/Editor/FirstPartyTools/HotReload/CompiledCallers/HotReloadCompiledCallersRun.cs b/Packages/src/Editor/FirstPartyTools/HotReload/CompiledCallers/HotReloadCompiledCallersRun.cs
index dc425c659..7a306c6cc 100644
--- a/Packages/src/Editor/FirstPartyTools/HotReload/CompiledCallers/HotReloadCompiledCallersRun.cs
+++ b/Packages/src/Editor/FirstPartyTools/HotReload/CompiledCallers/HotReloadCompiledCallersRun.cs
@@ -24,6 +24,7 @@ public sealed class HotReloadCompiledCallersRun : IDisposable
private IReadOnlyList _requests;
private bool _notesBuilt;
private bool _ended;
+ private bool _disposed;
internal HotReloadCompiledCallersRun(
IDisposable hold,
@@ -38,6 +39,18 @@ internal HotReloadCompiledCallersRun(
_backfill = backfill;
}
+ ///
+ /// Finds the compiled call and ldftn sites that reference any of ,
+ /// reading every compiled assembly it needs. Call before the run ends: the assemblies it
+ /// reads stay cached only while the run is open, so another thread cannot evict and
+ /// dispose one this is still reading.
+ ///
+ public List FindCallSites(string projectRoot, HotReloadCompiledMethodIdentity[] targets)
+ {
+ Debug.Assert(!_ended && !_disposed, "FindCallSites is called only while the run is open.");
+ return HotReloadCallSiteScanner.FindCallSites(projectRoot, targets).Hits;
+ }
+
///
/// Builds the one-shot lifecycle note of each request: one entry per request, in the same
/// order, null where no note can be proven. Reads at most the configured number of compiled
@@ -105,6 +118,7 @@ public void End(string correlationId)
///
public void Dispose()
{
+ _disposed = true;
_hold.Dispose();
}
diff --git a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadApplyContext.cs b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadApplyContext.cs
index 9913911cc..80ceb1b94 100644
--- a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadApplyContext.cs
+++ b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadApplyContext.cs
@@ -28,7 +28,8 @@ internal HotReloadApplyContext(
TransformWorkerOutputDto workerOutput,
IReadOnlyList files,
HotReloadPreparedIntroducedTypes preparedIntroducedTypes,
- HotReloadCompileFailureNoteSources compileFailureNoteSources)
+ HotReloadCompileFailureNoteSources compileFailureNoteSources,
+ HotReloadCompiledCallersRun compiledCallersRun)
{
Debug.Assert(!string.IsNullOrEmpty(projectRoot), "projectRoot must not be empty.");
Debug.Assert(!string.IsNullOrEmpty(assemblyName), "assemblyName must not be empty.");
@@ -39,6 +40,7 @@ internal HotReloadApplyContext(
Debug.Assert(workerOutput != null, "workerOutput must not be null.");
Debug.Assert(files != null && files.Count > 0, "A group must hold a file.");
Debug.Assert(compileFailureNoteSources != null, "compileFailureNoteSources must not be null.");
+ Debug.Assert(compiledCallersRun != null, "compiledCallersRun must not be null.");
ProjectRoot = projectRoot;
AssemblyName = assemblyName;
@@ -51,6 +53,7 @@ internal HotReloadApplyContext(
Files = files;
PreparedIntroducedTypes = preparedIntroducedTypes;
CompileFailureNoteSources = compileFailureNoteSources;
+ CompiledCallersRun = compiledCallersRun;
List<(string ProjectRelativePath, string AssemblyResolvePath)> filePaths =
new List<(string ProjectRelativePath, string AssemblyResolvePath)>(files.Count);
@@ -101,6 +104,10 @@ internal HotReloadApplyContext(
// What a shim compile failure of this group can be explained with.
internal HotReloadCompileFailureNoteSources CompileFailureNoteSources { get; }
+ // The compiled-caller analysis of the run this group belongs to; caller scans go through it
+ // so they read only while the run keeps the cached assemblies alive.
+ internal HotReloadCompiledCallersRun CompiledCallersRun { get; }
+
// The edited files of this group, in the order they were sent to the worker.
internal IReadOnlyList Files { get; }
diff --git a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadCompositionRoot.cs b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadCompositionRoot.cs
index 0297501ec..10676d4b2 100644
--- a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadCompositionRoot.cs
+++ b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadCompositionRoot.cs
@@ -152,8 +152,7 @@ internal static HotReloadServices CreateServices(
editorStateSnapshotCapture,
new HotReloadGroupCommitPolicy(domain, fileEntryApplier),
playMode,
- HotReloadMonoInternalAccessGrant.ForThisProcess(),
- compiledCallers);
+ HotReloadMonoInternalAccessGrant.ForThisProcess());
// A factory, not a built value: the stages are bound to the collaborators built here,
// and a caller that built them from the installed services would bind a replacement's
// group run back to the domain that was installed when it called.
diff --git a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupProcessor.cs b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupProcessor.cs
index 9bd7eb7e6..bd52a5172 100644
--- a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupProcessor.cs
+++ b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupProcessor.cs
@@ -48,11 +48,13 @@ internal HotReloadGroupProcessor(
internal async Task> ProcessGroupAsync(
IReadOnlyList files,
+ HotReloadCompiledCallersRun compiledCallersRun,
string correlationId,
HotReloadRunTiming timing,
CancellationToken ct)
{
Debug.Assert(files != null && files.Count > 0, "A group must hold a file.");
+ Debug.Assert(compiledCallersRun != null, "compiledCallersRun must not be null.");
Debug.Assert(timing != null, "timing must not be null.");
HotReloadGroupFile firstFile = files[0];
@@ -72,7 +74,7 @@ internal async Task> ProcessGroupAsync
using (timing.MeasureDetail(HotReloadConstants.TimingDetailStepSnapshotGroupState))
{
- SnapshotGroupState(files);
+ HotReloadGroupStateSnapshot.Capture(_domain, _collaborators.PlayMode, files);
}
// Why here and not at the leave-out decision: the ledgers need the main thread, which
@@ -159,7 +161,7 @@ internal async Task> ProcessGroupAsync
if (preparation.Prepared == null)
{
- return await TransformAndApplyGroupAsync(files, workerInput, null, refusedTypes, activePaths, correlationId, timing, ct)
+ return await TransformAndApplyGroupAsync(files, workerInput, null, refusedTypes, activePaths, compiledCallersRun, correlationId, timing, ct)
.ConfigureAwait(false);
}
@@ -169,6 +171,7 @@ internal async Task> ProcessGroupAsync
preparation.Prepared,
refusedTypes,
activePaths,
+ compiledCallersRun,
correlationId,
timing,
ct).ConfigureAwait(false);
@@ -194,6 +197,7 @@ private async Task> TransformAndApplyP
HotReloadPreparedIntroducedTypes prepared,
IReadOnlyList refusedTypes,
HashSet activePaths,
+ HotReloadCompiledCallersRun compiledCallersRun,
string correlationId,
HotReloadRunTiming timing,
CancellationToken ct)
@@ -227,6 +231,7 @@ private async Task> TransformAndApplyP
prepared,
refusedTypes,
activePaths,
+ compiledCallersRun,
correlationId,
timing,
ct).ConfigureAwait(false);
@@ -255,6 +260,7 @@ private async Task> TransformAndApplyG
HotReloadPreparedIntroducedTypes prepared,
IReadOnlyList refusedTypes,
HashSet activePaths,
+ HotReloadCompiledCallersRun compiledCallersRun,
string correlationId,
HotReloadRunTiming timing,
CancellationToken ct)
@@ -281,7 +287,7 @@ private async Task> TransformAndApplyG
if (leftOutPaths.Count == 0)
{
- return await ApplyTransformedGroupAsync(files, workerInput, workerOutput, prepared, refusedTypes, correlationId, timing, ct)
+ return await ApplyTransformedGroupAsync(files, workerInput, workerOutput, prepared, refusedTypes, compiledCallersRun, correlationId, timing, ct)
.ConfigureAwait(false);
}
@@ -306,6 +312,7 @@ private async Task> TransformAndApplyG
retryResult.Output,
prepared,
refusedTypes,
+ compiledCallersRun,
correlationId,
timing,
ct).ConfigureAwait(false)
@@ -366,6 +373,7 @@ private async Task> ApplyTransformedGr
TransformWorkerOutputDto workerOutput,
HotReloadPreparedIntroducedTypes prepared,
IReadOnlyList refusedTypes,
+ HotReloadCompiledCallersRun compiledCallersRun,
string correlationId,
HotReloadRunTiming timing,
CancellationToken ct)
@@ -424,7 +432,8 @@ private async Task> ApplyTransformedGr
workerOutput,
files,
prepared,
- new HotReloadCompileFailureNoteSources(workerOutput.skipped, refusedTypes));
+ new HotReloadCompileFailureNoteSources(workerOutput.skipped, refusedTypes),
+ compiledCallersRun);
}
Stopwatch gateWatch = Stopwatch.StartNew();
@@ -610,37 +619,5 @@ internal async Task RevalidateBeforeRevertAsync(
revertUnchangedPatches();
return true;
}
-
- private void SnapshotGroupState(IReadOnlyList files)
- {
- foreach (HotReloadGroupFile file in files)
- {
- // Why snapshot at the group's apply entry: runs process groups sequentially, and
- // RevertUnchangedPatches / BeginFileGeneration mutate ledgers after the worker.
- // The worker itself does not.
- file.SnapshotLabels = HotReloadAppliedSourceLifecycle.CollectActiveLabelsForFile(
- _domain,
- file.ProjectRelativePath);
- file.SnapshotAddedLabels = new HashSet(
- _domain.ListActiveAddedMethodKeys(file.ProjectRelativePath),
- StringComparer.Ordinal);
- file.SnapshotForwardedUnityMessageLabels = _collaborators.PlayMode.IsPlaying
- ? HotReloadDeactivatedUnityMessageNote.CollectForwardedLabels(
- _domain.DescribeAddedMembersOfFile(file.ProjectRelativePath))
- : new HashSet(StringComparer.Ordinal);
- // Why projectRelativePath (not workerSourcePath): contentPathOverride E2E copies
- // live under Library/UloopHotReload/TestSources/ and are absent from the PDB
- // document list. Assembly resolution already computed the on-disk path.
- file.SnapshotSource = HotReloadSourceBaseline.LoadVerifiedSnapshotSource(
- file.ProjectRelativePath,
- file.TargetDllPath);
- file.SnapshotMissReason = file.SnapshotSource == null
- ? HotReloadSourceBaseline.DescribeSnapshotMiss(
- file.ProjectRelativePath,
- file.TargetDllPath,
- file.NewSourceMembershipEvidence)
- : HotReloadSnapshotMissReason.None;
- }
- }
}
}
diff --git a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupStageCollaborators.cs b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupStageCollaborators.cs
index a9c5e9aae..f4390284b 100644
--- a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupStageCollaborators.cs
+++ b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupStageCollaborators.cs
@@ -24,8 +24,7 @@ internal HotReloadGroupStageCollaborators(
IHotReloadEditorStateSnapshotCapture editorStateSnapshotCapture,
HotReloadGroupCommitPolicy commitPolicy,
IHotReloadPlayModeQuery playMode,
- IHotReloadInternalAccessGrant internalAccessGrant,
- HotReloadCompiledCallers compiledCallers)
+ IHotReloadInternalAccessGrant internalAccessGrant)
{
Debug.Assert(domain != null, "domain must not be null.");
Debug.Assert(patcher != null, "patcher must not be null.");
@@ -38,7 +37,6 @@ internal HotReloadGroupStageCollaborators(
Debug.Assert(commitPolicy != null, "commitPolicy must not be null.");
Debug.Assert(playMode != null, "playMode must not be null.");
Debug.Assert(internalAccessGrant != null, "internalAccessGrant must not be null.");
- Debug.Assert(compiledCallers != null, "compiledCallers must not be null.");
Domain = domain;
Patcher = patcher;
FileEntryApplier = fileEntryApplier;
@@ -49,7 +47,6 @@ internal HotReloadGroupStageCollaborators(
CommitPolicy = commitPolicy;
PlayMode = playMode;
InternalAccessGrant = internalAccessGrant;
- CompiledCallers = compiledCallers;
}
internal HotReloadDomain Domain { get; }
@@ -73,7 +70,5 @@ internal HotReloadGroupStageCollaborators(
// Why one grant per services graph: its availability is probed once when it is built, and
// every artifact of the domain these collaborators serve is granted through that answer.
internal IHotReloadInternalAccessGrant InternalAccessGrant { get; }
-
- internal HotReloadCompiledCallers CompiledCallers { get; }
}
}
diff --git a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupStateSnapshot.cs b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupStateSnapshot.cs
new file mode 100644
index 000000000..5f66c5aa7
--- /dev/null
+++ b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupStateSnapshot.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+
+namespace io.github.hatayama.UnityCliLoop.FirstPartyTools
+{
+ ///
+ /// Takes the per-file snapshot a group compares its apply against.
+ ///
+ internal static class HotReloadGroupStateSnapshot
+ {
+ ///
+ /// Records on each file of the group what the domain holds for it before the group
+ /// applies: active labels, added methods, forwarded Unity messages, and the verified
+ /// source baseline (or why there is none). Call on the Unity main thread.
+ ///
+ internal static void Capture(
+ HotReloadDomain domain,
+ IHotReloadPlayModeQuery playMode,
+ IReadOnlyList files)
+ {
+ foreach (HotReloadGroupFile file in files)
+ {
+ // Why snapshot at the group's apply entry: runs process groups sequentially, and
+ // RevertUnchangedPatches / BeginFileGeneration mutate ledgers after the worker.
+ // The worker itself does not.
+ file.SnapshotLabels = HotReloadAppliedSourceLifecycle.CollectActiveLabelsForFile(
+ domain,
+ file.ProjectRelativePath);
+ file.SnapshotAddedLabels = new HashSet(
+ domain.ListActiveAddedMethodKeys(file.ProjectRelativePath),
+ StringComparer.Ordinal);
+ file.SnapshotForwardedUnityMessageLabels = playMode.IsPlaying
+ ? HotReloadDeactivatedUnityMessageNote.CollectForwardedLabels(
+ domain.DescribeAddedMembersOfFile(file.ProjectRelativePath))
+ : new HashSet(StringComparer.Ordinal);
+ // Why projectRelativePath (not workerSourcePath): contentPathOverride E2E copies
+ // live under Library/UloopHotReload/TestSources/ and are absent from the PDB
+ // document list. Assembly resolution already computed the on-disk path.
+ file.SnapshotSource = HotReloadSourceBaseline.LoadVerifiedSnapshotSource(
+ file.ProjectRelativePath,
+ file.TargetDllPath);
+ file.SnapshotMissReason = file.SnapshotSource == null
+ ? HotReloadSourceBaseline.DescribeSnapshotMiss(
+ file.ProjectRelativePath,
+ file.TargetDllPath,
+ file.NewSourceMembershipEvidence)
+ : HotReloadSnapshotMissReason.None;
+ }
+ }
+ }
+}
diff --git a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupStateSnapshot.cs.meta b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupStateSnapshot.cs.meta
new file mode 100644
index 000000000..8b345cf26
--- /dev/null
+++ b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadGroupStateSnapshot.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b7d746a1cec3742b4a908fa41e333abc
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadOrchestrator.cs b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadOrchestrator.cs
index 97b64015d..4072ad520 100644
--- a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadOrchestrator.cs
+++ b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadOrchestrator.cs
@@ -206,7 +206,8 @@ await ProcessPlannedGroupAsync(
contentPathOverrideByFile,
isLastChangedGroup,
extraResults,
- run)
+ run,
+ compiledCallersRun)
.ConfigureAwait(false);
}
@@ -322,7 +323,8 @@ private async Task ProcessPlannedGroupAsync(
IReadOnlyDictionary contentPathOverrideByFile,
bool isLastGroupOfAssembly,
List<(string Path, HotReloadFileProcessResult Result)> extraResults,
- HotReloadRunAccumulator run)
+ HotReloadRunAccumulator run,
+ HotReloadCompiledCallersRun compiledCallersRun)
{
Debug.Assert(inputIndexes != null && inputIndexes.Count > 0, "A group must hold a file.");
List filesOfGroup = new List(inputIndexes.Count);
@@ -351,7 +353,7 @@ private async Task ProcessPlannedGroupAsync(
// ProcessGroupAsync switches back via MainThreadSwitcher (EditorApplication.update
// queue) before any main-thread-only editor API or Harmony patch.
IReadOnlyList groupResults =
- await _groupProcessor.ProcessGroupAsync(filesOfGroup, correlationId, timing, ct)
+ await _groupProcessor.ProcessGroupAsync(filesOfGroup, compiledCallersRun, correlationId, timing, ct)
.ConfigureAwait(false);
Debug.Assert(
groupResults.Count == filesOfGroup.Count,
diff --git a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadSignatureChangeGate.cs b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadSignatureChangeGate.cs
index b3e614afa..8905fac86 100644
--- a/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadSignatureChangeGate.cs
+++ b/Packages/src/Editor/FirstPartyTools/HotReload/HotReloadSignatureChangeGate.cs
@@ -42,7 +42,7 @@ internal static async Task TryApplySignatureChangeGat
context.WorkerOutput.unchangedMethods ?? Array.Empty(),
context.WorkerOutput.skipped ?? Array.Empty(),
removedSignatures);
- List hits = collaborators.CompiledCallers.FindCallSites(context.ProjectRoot, targets);
+ List hits = context.CompiledCallersRun.FindCallSites(context.ProjectRoot, targets);
Dictionary> uncoveredCallersByTarget =
CollectInitialUncoveredCallers(context.AssemblyName, entries, hits, deletedCallerExemptions);