diff --git a/Assets/Tests/Editor/OnionAssemblyDependencyTests.cs b/Assets/Tests/Editor/OnionAssemblyDependencyTests.cs
index bee6840bba..330b861b86 100644
--- a/Assets/Tests/Editor/OnionAssemblyDependencyTests.cs
+++ b/Assets/Tests/Editor/OnionAssemblyDependencyTests.cs
@@ -887,7 +887,8 @@ public void SetupWizardStartup_WhenLoaded_SchedulesVersionCheckInsteadOfReadingS
// A cold-start session that hits Unity's native "Scripts have compiler errors" dialog
// never flushes EditorApplication.delayCall again for the rest of that process's
// lifetime, so this startup check rides on a self-unsubscribing EditorApplication.update
- // tick instead of delayCall.
+ // tick instead of delayCall, and the flow then shows the window directly from its
+ // main-thread continuation instead of through delayCall.
string setupWizardSource = ReadProductionSource(
"Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs");
string setupWizardStartupFlowSource = ReadProductionSource(
@@ -898,7 +899,7 @@ public void SetupWizardStartup_WhenLoaded_SchedulesVersionCheckInsteadOfReadingS
Does.Contain("EditorApplication.update += RunStartupCheckOnFirstUpdateTick;"));
Assert.That(
setupWizardStartupFlowSource,
- Does.Contain("EditorApplication.delayCall += () => _showWindowOnVersionChange();"));
+ Does.Not.Contain("EditorApplication.delayCall +="));
Assert.That(setupWizardSource, Does.Not.Contain("\n startupFlow.TryShowOnVersionChange();"));
}
diff --git a/Assets/Tests/Editor/SetupWizardStartupFlowVersionChangeTests.cs b/Assets/Tests/Editor/SetupWizardStartupFlowVersionChangeTests.cs
index e47fd69640..44282b600a 100644
--- a/Assets/Tests/Editor/SetupWizardStartupFlowVersionChangeTests.cs
+++ b/Assets/Tests/Editor/SetupWizardStartupFlowVersionChangeTests.cs
@@ -15,8 +15,8 @@ namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
///
/// Verifies the setup wizard's version-change evaluation for already-seen package versions:
- /// when it records the last-seen state, when it refreshes the CLI, and when it does nothing. Also covers the
- /// migration auto-scan poll actions and the fallback full scan with recording ports.
+ /// when it shows the window, when it records the last-seen state, when it refreshes the CLI, and when it does
+ /// nothing. Also covers the migration auto-scan poll actions and the fallback full scan with recording ports.
///
public sealed class SetupWizardStartupFlowVersionChangeTests
{
@@ -203,6 +203,51 @@ public void TryShowOnVersionChange_WhenThePackageChangedAndTheSkillsAreCurrent_S
Assert.That(_showWindowCount, Is.EqualTo(0));
}
+ ///
+ /// Verifies a new package version with a dispatcher older than the minimum shows the window synchronously
+ /// through the flow, without depending on delayCall.
+ ///
+ [Test]
+ public void TryShowOnVersionChange_WhenThePackageChangedAndTheCliNeedsUpdate_ShowsTheWindow()
+ {
+ const string PreviousVersion = "3.0.0-previous.1";
+ Assume.That(UnityCliLoopConstants.PackageInfo.version, Is.Not.EqualTo(PreviousVersion));
+ _editorSettingsPort.Settings = new UnityCliLoopEditorSettingsData
+ {
+ lastSeenSetupWizardVersion = PreviousVersion,
+ lastSeenSetupWizardMinimumDispatcherVersion = MinimumDispatcherVersion
+ };
+ _cliDetector.CliVersion = "1.0.0";
+ _cliDetector.IsDispatcher = true;
+
+ _flow.TryShowOnVersionChange();
+
+ Assert.That(_showWindowCount, Is.EqualTo(1));
+ }
+
+ ///
+ /// Verifies a new package version with outdated installed skills shows the window synchronously through the
+ /// flow, without depending on delayCall.
+ ///
+ [Test]
+ public void TryShowOnVersionChange_WhenThePackageChangedAndTheSkillsAreOutdated_ShowsTheWindow()
+ {
+ const string PreviousVersion = "3.0.0-previous.1";
+ Assume.That(UnityCliLoopConstants.PackageInfo.version, Is.Not.EqualTo(PreviousVersion));
+ _editorSettingsPort.Settings = new UnityCliLoopEditorSettingsData
+ {
+ lastSeenSetupWizardVersion = PreviousVersion,
+ lastSeenSetupWizardMinimumDispatcherVersion = MinimumDispatcherVersion
+ };
+ _cliDetector.CliVersion = "3.1.0";
+ _cliDetector.IsDispatcher = true;
+ _skillSetupPort.ReturnOutdatedTarget = true;
+
+ _flow.TryShowOnVersionChange();
+
+ Assert.That(_showWindowCount, Is.EqualTo(1));
+ }
+
///
/// Verifies a compile-error detection that finds legacy files stores them as seeds, flags the auto-scan for
/// this session, and opens the migration window once.
@@ -542,6 +587,7 @@ private sealed class RecordingSkillSetupPort : ISkillSetupPort
{
internal List DetectProjectRoots { get; } = new List();
internal List DetectGroupFlags { get; } = new List();
+ internal bool ReturnOutdatedTarget { get; set; }
public void RemoveSkillFiles(string toolName) => throw new NotSupportedException();
public bool IsSkillInstalled(string toolName) => throw new NotSupportedException();
@@ -561,7 +607,7 @@ public List DetectSkillTargetsForLayoutAtProjectRoot(
hasSkillsDirectory: true,
hasExistingSkills: true,
hasDifferentLayoutSkills: false,
- SkillInstallState.Installed)
+ ReturnOutdatedTarget ? SkillInstallState.Outdated : SkillInstallState.Installed)
};
}
diff --git a/Assets/Tests/Editor/UnityCliLoopEditorSettingsRecoverySchedulerTests.cs b/Assets/Tests/Editor/UnityCliLoopEditorSettingsRecoverySchedulerTests.cs
new file mode 100644
index 0000000000..7d8c24a377
--- /dev/null
+++ b/Assets/Tests/Editor/UnityCliLoopEditorSettingsRecoverySchedulerTests.cs
@@ -0,0 +1,93 @@
+using System;
+
+using NUnit.Framework;
+
+using io.github.hatayama.UnityCliLoop.Domain;
+using io.github.hatayama.UnityCliLoop.Infrastructure;
+using io.github.hatayama.UnityCliLoop.ToolContracts;
+
+namespace io.github.hatayama.UnityCliLoop.Tests.Editor
+{
+ ///
+ /// Verifies when the settings file recovery scheduled at Editor startup runs.
+ ///
+ public sealed class UnityCliLoopEditorSettingsRecoverySchedulerTests
+ {
+ ///
+ /// Verifies the startup recovery does not run inline but waits on the main-thread dispatcher, and runs
+ /// once when the dispatcher drains its queue.
+ ///
+ [Test]
+ public void ScheduleForEditorStartup_QueuesTheRecoveryOnTheDispatcher_AndRunsItOnce()
+ {
+ CountingEditorSettingsPort editorSettingsPort = new CountingEditorSettingsPort();
+ QueueingDispatcher dispatcher = new QueueingDispatcher();
+ MainThreadSwitcher.RegisterService(dispatcher);
+
+ try
+ {
+ UnityCliLoopEditorSettingsRecoveryScheduler.ScheduleForEditorStartup(editorSettingsPort);
+
+ Assert.That(editorSettingsPort.RecoverCount, Is.EqualTo(0));
+
+ dispatcher.RunQueued();
+
+ Assert.That(editorSettingsPort.RecoverCount, Is.EqualTo(1));
+ }
+ finally
+ {
+ EditorMainThreadDispatcherRestorer.Restore();
+ }
+ }
+
+ private sealed class CountingEditorSettingsPort : IUnityCliLoopEditorSettingsPort
+ {
+ internal int RecoverCount { get; private set; }
+
+ public void RecoverSettingsFileIfNeeded()
+ {
+ RecoverCount++;
+ }
+
+ public UnityCliLoopEditorSettingsData GetSettings()
+ {
+ throw new NotSupportedException();
+ }
+
+ public void SaveSettings(UnityCliLoopEditorSettingsData settings)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void UpdateSettings(Func transform)
+ {
+ throw new NotSupportedException();
+ }
+
+ public string GetLastSeenSetupWizardVersion()
+ {
+ throw new NotSupportedException();
+ }
+
+ public bool GetSuppressSetupWizardAutoShow()
+ {
+ throw new NotSupportedException();
+ }
+
+ public void SetSuppressSetupWizardAutoShow(bool suppressAutoShow)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void SetShowToolSettings(bool showToolSettings)
+ {
+ throw new NotSupportedException();
+ }
+
+ public void SetInstallSkillsFlat(bool installSkillsFlat)
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+}
diff --git a/Assets/Tests/Editor/UnityCliLoopEditorSettingsRecoverySchedulerTests.cs.meta b/Assets/Tests/Editor/UnityCliLoopEditorSettingsRecoverySchedulerTests.cs.meta
new file mode 100644
index 0000000000..75d32b8be2
--- /dev/null
+++ b/Assets/Tests/Editor/UnityCliLoopEditorSettingsRecoverySchedulerTests.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b5abdccaf3906414baefef7f18f8060b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Tests/Editor/UnityCliLoopServerControllerStartupLockTests.cs b/Assets/Tests/Editor/UnityCliLoopServerControllerStartupLockTests.cs
index f1ffc60f94..d89dccf953 100644
--- a/Assets/Tests/Editor/UnityCliLoopServerControllerStartupLockTests.cs
+++ b/Assets/Tests/Editor/UnityCliLoopServerControllerStartupLockTests.cs
@@ -157,7 +157,7 @@ public async Task ScheduleTrackedRecovery_WhenRecoveryAlreadyRunning_ReturnsCurr
[Test]
public async Task ScheduleStartupRecovery_WhenTrackedRecoveryIsRunning_ReturnsCurrentTaskWithoutSchedulingStartupRecovery()
{
- // Tests that startup recovery joins an active tracked recovery without registering a delay call.
+ // Tests that startup recovery joins an active tracked recovery without scheduling the startup recovery action.
int scheduledActionCount = 0;
TaskCompletionSource trackedRecoveryCompletionSource = new();
UnityCliLoopServerRecoveryTrackingService service = CreateRecoveryTrackingService();
@@ -319,7 +319,7 @@ public async Task StartRecoveryIfNeededAsync_WhenEditorIsBusy_ShouldDelayReadine
{
// Tests that recovery does not spend readiness timeout while Unity is still compiling or updating.
bool editorIsBusy = true;
- int delayCallCount = 0;
+ int readinessRetryWaitCount = 0;
TestServerInstanceFactory serverInstanceFactory = new();
UnityCliLoopServerLifecycleRegistryService lifecycleRegistry =
new UnityCliLoopServerLifecycleRegistryService();
@@ -333,7 +333,7 @@ public async Task StartRecoveryIfNeededAsync_WhenEditorIsBusy_ShouldDelayReadine
isReadinessProbeBlocked: () => editorIsBusy,
waitBeforeReadinessRetryAsync: (delayMilliseconds, ct) =>
{
- delayCallCount++;
+ readinessRetryWaitCount++;
Assert.That(readinessProbe.CallCount, Is.EqualTo(0));
editorIsBusy = false;
return Task.CompletedTask;
@@ -341,7 +341,7 @@ public async Task StartRecoveryIfNeededAsync_WhenEditorIsBusy_ShouldDelayReadine
await service.StartRecoveryIfNeededAsync(isAfterCompile: false, CancellationToken.None);
- Assert.That(delayCallCount, Is.EqualTo(1));
+ Assert.That(readinessRetryWaitCount, Is.EqualTo(1));
Assert.That(readinessProbe.CallCount, Is.EqualTo(1));
Assert.That(serverStartedCount, Is.EqualTo(1));
}
@@ -373,7 +373,7 @@ public void StartRecoveryIfNeededAsync_WhenPartiallyCreatedServerDisposeFails_Sh
public void StartRecoveryIfNeededAsync_WhenEditorNeverBecomesIdle_ShouldFailWithoutReadinessProbe()
{
// Tests that recovery does not hang forever when Unity never leaves compile or update state.
- int delayCallCount = 0;
+ int readinessRetryWaitCount = 0;
TestServerInstanceFactory serverInstanceFactory = new();
UnityCliLoopServerLifecycleRegistryService lifecycleRegistry =
new UnityCliLoopServerLifecycleRegistryService();
@@ -387,7 +387,7 @@ public void StartRecoveryIfNeededAsync_WhenEditorNeverBecomesIdle_ShouldFailWith
isReadinessProbeBlocked: () => true,
waitBeforeReadinessRetryAsync: (delayMilliseconds, ct) =>
{
- delayCallCount++;
+ readinessRetryWaitCount++;
return Task.CompletedTask;
},
readinessIdleTimeoutMilliseconds: 1);
@@ -399,7 +399,7 @@ public void StartRecoveryIfNeededAsync_WhenEditorNeverBecomesIdle_ShouldFailWith
CancellationToken.None));
Assert.That(exception.Message, Does.Contain("Unity editor idle"));
- Assert.That(delayCallCount, Is.EqualTo(1));
+ Assert.That(readinessRetryWaitCount, Is.EqualTo(1));
Assert.That(readinessProbe.CallCount, Is.EqualTo(0));
Assert.That(serverStartedCount, Is.EqualTo(0));
}
diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileLifecycleRecoveryCoordinator.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileLifecycleRecoveryCoordinator.cs
index ec5c0638ab..9eb2c98585 100644
--- a/Packages/src/Editor/FirstPartyTools/Compile/CompileLifecycleRecoveryCoordinator.cs
+++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileLifecycleRecoveryCoordinator.cs
@@ -165,7 +165,9 @@ private void HandleCompileLifecycleWatchdogFault(
Debug.LogException(exception);
}
- EditorApplication.delayCall += () => AbortCompileAfterWatchdogFault(compileTask);
+ // The fault continuation runs on the thread pool; the dispatcher hands the abort to the main
+ // thread and wakes the Editor with SignalTick, even in sessions where delayCall stops flushing.
+ MainThreadSwitcher.AddContinuation(() => AbortCompileAfterWatchdogFault(compileTask));
}
///
diff --git a/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerController.cs b/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerController.cs
index e4fe040528..12a32dfb84 100644
--- a/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerController.cs
+++ b/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerController.cs
@@ -5,7 +5,6 @@
using io.github.hatayama.UnityCliLoop.Application;
using io.github.hatayama.UnityCliLoop.Domain;
-using io.github.hatayama.UnityCliLoop.InternalAPIBridge;
using io.github.hatayama.UnityCliLoop.ToolContracts;
namespace io.github.hatayama.UnityCliLoop.Infrastructure
@@ -146,9 +145,10 @@ public void InitializeForEditorStartup()
_serverLifecycleRegistry.ServerLoopExited += OnServerLoopUnexpectedlyExited;
// Recovery binds the project IPC endpoint and may touch config files, so keep it off the
- // synchronous Editor startup path while preserving automatic startup.
+ // synchronous Editor startup path while preserving automatic startup. The dispatcher drains
+ // on update and tick, so this runs even in a session where delayCall stops flushing.
_recoveryTrackingService.ScheduleStartupRecovery(
- action => EditorApplication.delayCall += () => action(),
+ MainThreadSwitcher.AddContinuation,
RestoreServerStateIfNeeded);
}
@@ -344,12 +344,14 @@ private void OnEditorQuitting()
///
/// OnServerLoopExited fires from the thread pool, but Unity APIs (EditorSettings,
/// VibeLogger with SerializedObject, etc.) are main-thread-only.
- /// EditorApplication.delayCall marshals the recovery to the next editor tick.
+ /// The main-thread dispatcher marshals the recovery to the next editor tick (update or tick),
+ /// which keeps working in sessions where delayCall stops flushing. Enqueueing also signals a
+ /// tick, so an unfocused idle Editor still runs the recovery.
///
private void OnServerLoopUnexpectedlyExited()
{
// OnServerLoopExited fires from thread pool — marshal to main thread for Unity API safety
- EditorApplication.delayCall += () =>
+ MainThreadSwitcher.AddContinuation(() =>
{
// The server just crashed — startup protection blocks recovery if the crash happens
// within the 5-second protection window after a successful start
@@ -364,12 +366,7 @@ private void OnServerLoopUnexpectedlyExited()
// Resources already cleaned up by CleanupAfterUnexpectedLoopExit — just clear the reference
_bridgeServer = null;
_recoveryTrackingService.ScheduleTrackedRecovery(() => StartRecoveryIfNeededAsync(false, CancellationToken.None));
- };
-
- // delayCall only runs on the next editor tick, and a backgrounded idle editor may never
- // tick again on its own — the recovery would then wait forever. Signal one tick so the
- // scheduled recovery actually executes even while the editor is unfocused.
- EditorApplicationTickBridge.SignalTick();
+ });
}
///
diff --git a/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerRecoveryTrackingService.cs b/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerRecoveryTrackingService.cs
index 90828214f8..3efccdb075 100644
--- a/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerRecoveryTrackingService.cs
+++ b/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerRecoveryTrackingService.cs
@@ -35,10 +35,10 @@ internal UnityCliLoopServerRecoveryTrackingService(
internal Task RecoveryTask => _currentRecoveryTask;
internal Task ScheduleStartupRecovery(
- Action scheduleDelayCall,
+ Action scheduleOnEditorTick,
Func restoreServerState)
{
- Debug.Assert(scheduleDelayCall != null, "scheduleDelayCall must not be null");
+ Debug.Assert(scheduleOnEditorTick != null, "scheduleOnEditorTick must not be null");
Debug.Assert(restoreServerState != null, "restoreServerState must not be null");
TaskCompletionSource scheduledRecoveryCompletionSource = null;
@@ -52,7 +52,7 @@ internal Task ScheduleStartupRecovery(
return scheduledRecoveryTask;
}
- scheduleDelayCall(() =>
+ scheduleOnEditorTick(() =>
{
Task restoreTask;
try
diff --git a/Packages/src/Editor/Infrastructure/Settings/UnityCliLoopEditorSettingsRecoveryScheduler.cs b/Packages/src/Editor/Infrastructure/Settings/UnityCliLoopEditorSettingsRecoveryScheduler.cs
index 4376fd4dca..302bee2a2e 100644
--- a/Packages/src/Editor/Infrastructure/Settings/UnityCliLoopEditorSettingsRecoveryScheduler.cs
+++ b/Packages/src/Editor/Infrastructure/Settings/UnityCliLoopEditorSettingsRecoveryScheduler.cs
@@ -1,12 +1,15 @@
using UnityEditor;
using io.github.hatayama.UnityCliLoop.Domain;
+using io.github.hatayama.UnityCliLoop.ToolContracts;
namespace io.github.hatayama.UnityCliLoop.Infrastructure
{
// Infrastructure scheduler for delayed settings file recovery during Editor startup.
///
/// Schedules Unity CLI Loop Editor Settings Recovery work at the point the owning workflow expects.
+ /// The recovery runs on the next Editor tick through the main-thread dispatcher, which keeps working in
+ /// sessions where delayCall stops flushing.
///
internal static class UnityCliLoopEditorSettingsRecoveryScheduler
{
@@ -19,7 +22,7 @@ internal static void ScheduleForEditorStartup(IUnityCliLoopEditorSettingsPort ed
return;
}
- EditorApplication.delayCall += editorSettingsPort.RecoverSettingsFileIfNeeded;
+ MainThreadSwitcher.AddContinuation(editorSettingsPort.RecoverSettingsFileIfNeeded);
}
}
}
diff --git a/Packages/src/Editor/Infrastructure/UnityCliLoopBridgeServer.cs b/Packages/src/Editor/Infrastructure/UnityCliLoopBridgeServer.cs
index c533f34ee5..e5d9857804 100644
--- a/Packages/src/Editor/Infrastructure/UnityCliLoopBridgeServer.cs
+++ b/Packages/src/Editor/Infrastructure/UnityCliLoopBridgeServer.cs
@@ -228,7 +228,7 @@ private static Task[] BuildShutdownWaitTasks(Task serverTask, Task[] clientTasks
/// StopServer() guards on _isRunning==true, but by the time this runs _isRunning may already
/// be false or the normal shutdown path may race with the finally block.
/// A separate cleanup path that skips the _isRunning guard is needed.
- /// Lifecycle events are deferred to OnServerLoopExited → EditorApplication.delayCall
+ /// Lifecycle events are deferred to OnServerLoopExited → the main-thread dispatcher
/// because this runs on the thread pool where Unity APIs are unsafe.
///
private void CleanupAfterUnexpectedLoopExit()
diff --git a/Packages/src/Editor/Presentation/Setup/SetupWizardStartupFlow.cs b/Packages/src/Editor/Presentation/Setup/SetupWizardStartupFlow.cs
index 1e9995a0f5..0f427897cb 100644
--- a/Packages/src/Editor/Presentation/Setup/SetupWizardStartupFlow.cs
+++ b/Packages/src/Editor/Presentation/Setup/SetupWizardStartupFlow.cs
@@ -315,7 +315,9 @@ private async Task EvaluateVersionChange(CancellationToken ct)
return;
}
- EditorApplication.delayCall += () => _showWindowOnVersionChange();
+ // Every await above resumes on Unity's synchronization context, so this already runs on the main
+ // thread; delayCall would only add a hop that some sessions never flush.
+ _showWindowOnVersionChange();
}
private async Task NeedsCliUpdateForSetupWizardAsync(CancellationToken ct)
diff --git a/Packages/src/Editor/Presentation/UnityCliLoopSettingsWindow.cs b/Packages/src/Editor/Presentation/UnityCliLoopSettingsWindow.cs
index 3da43fbfc3..2ee04ff0f1 100644
--- a/Packages/src/Editor/Presentation/UnityCliLoopSettingsWindow.cs
+++ b/Packages/src/Editor/Presentation/UnityCliLoopSettingsWindow.cs
@@ -357,7 +357,7 @@ private void HandleToolToggled(string toolName, bool enabled)
_view?.UpdateSingleToolToggle(toolName, enabled);
// Skill synchronization can touch many files, so defer it to keep UI input responsive.
- EditorApplication.delayCall += () => _skillsPresenter.ApplyToolToggleSideEffects(toolName, enabled).Forget();
+ MainThreadSwitcher.AddContinuation(() => _skillsPresenter.ApplyToolToggleSideEffects(toolName, enabled).Forget());
}
private void UpdateShowConfiguration(bool show)
diff --git a/Packages/src/Editor/ToolContracts/AssemblyInfo.cs b/Packages/src/Editor/ToolContracts/AssemblyInfo.cs
index bc84b2d45c..8284c7e479 100644
--- a/Packages/src/Editor/ToolContracts/AssemblyInfo.cs
+++ b/Packages/src/Editor/ToolContracts/AssemblyInfo.cs
@@ -2,6 +2,9 @@
[assembly: InternalsVisibleTo("UnityCLILoop.CompositionRoot.Editor")]
[assembly: InternalsVisibleTo("UnityCLILoop.Application")]
+[assembly: InternalsVisibleTo("UnityCLILoop.Infrastructure")]
+[assembly: InternalsVisibleTo("UnityCLILoop.Presentation")]
+[assembly: InternalsVisibleTo("UnityCLILoop.FirstPartyTools.Compile.Editor")]
[assembly: InternalsVisibleTo("UnityCLILoop.FirstPartyTools.Screenshot.Editor")]
[assembly: InternalsVisibleTo("UnityCLILoop.FirstPartyTools.PausePoint.Editor")]
[assembly: InternalsVisibleTo("UnityCLILoop.Tests.Editor")]