2026-01-04 04:42:32 +08:00
|
|
|
// TestRunnerNoThrottle.cs
|
|
|
|
|
// Sets Unity Editor to "No Throttling" mode during test runs.
|
|
|
|
|
// This helps tests that don't trigger compilation run smoothly in the background.
|
|
|
|
|
// Note: Tests that trigger mid-run compilation may still stall due to OS-level throttling.
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using MCPForUnity.Editor.Helpers;
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEditor.TestTools.TestRunner.Api;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace MCPForUnity.Editor.Services
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Automatically sets the editor to "No Throttling" mode during test runs.
|
|
|
|
|
///
|
|
|
|
|
/// This helps prevent background stalls for normal tests. However, tests that trigger
|
|
|
|
|
/// script compilation mid-run may still stall because:
|
|
|
|
|
/// - Internal Unity coroutine waits rely on editor ticks
|
|
|
|
|
/// - OS-level throttling affects the main thread when Unity is backgrounded
|
|
|
|
|
/// - No amount of internal nudging can overcome OS thread scheduling
|
|
|
|
|
///
|
|
|
|
|
/// The MCP workflow is unaffected because socket messages provide external stimulus
|
|
|
|
|
/// that wakes Unity's main thread.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[InitializeOnLoad]
|
|
|
|
|
public static class TestRunnerNoThrottle
|
|
|
|
|
{
|
|
|
|
|
private const string ApplicationIdleTimeKey = "ApplicationIdleTime";
|
|
|
|
|
private const string InteractionModeKey = "InteractionMode";
|
|
|
|
|
|
|
|
|
|
// SessionState keys to persist across domain reload
|
|
|
|
|
private const string SessionKey_TestRunActive = "TestRunnerNoThrottle_TestRunActive";
|
|
|
|
|
private const string SessionKey_PrevIdleTime = "TestRunnerNoThrottle_PrevIdleTime";
|
|
|
|
|
private const string SessionKey_PrevInteractionMode = "TestRunnerNoThrottle_PrevInteractionMode";
|
|
|
|
|
private const string SessionKey_SettingsCaptured = "TestRunnerNoThrottle_SettingsCaptured";
|
|
|
|
|
|
|
|
|
|
// Keep reference to avoid GC and set HideFlags to avoid serialization issues
|
|
|
|
|
private static TestRunnerApi _api;
|
|
|
|
|
|
|
|
|
|
static TestRunnerNoThrottle()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_api = ScriptableObject.CreateInstance<TestRunnerApi>();
|
|
|
|
|
_api.hideFlags = HideFlags.HideAndDontSave;
|
|
|
|
|
_api.RegisterCallbacks(new TestCallbacks());
|
|
|
|
|
|
|
|
|
|
// Check if recovering from domain reload during an active test run
|
|
|
|
|
if (IsTestRunActive())
|
|
|
|
|
{
|
|
|
|
|
McpLog.Info("[TestRunnerNoThrottle] Recovered from domain reload - reapplying No Throttling.");
|
|
|
|
|
ApplyNoThrottling();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
McpLog.Warn($"[TestRunnerNoThrottle] Failed to register callbacks: {e}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region State Persistence
|
|
|
|
|
|
|
|
|
|
private static bool IsTestRunActive() => SessionState.GetBool(SessionKey_TestRunActive, false);
|
|
|
|
|
private static void SetTestRunActive(bool active) => SessionState.SetBool(SessionKey_TestRunActive, active);
|
|
|
|
|
private static bool AreSettingsCaptured() => SessionState.GetBool(SessionKey_SettingsCaptured, false);
|
|
|
|
|
private static void SetSettingsCaptured(bool captured) => SessionState.SetBool(SessionKey_SettingsCaptured, captured);
|
|
|
|
|
private static int GetPrevIdleTime() => SessionState.GetInt(SessionKey_PrevIdleTime, 4);
|
|
|
|
|
private static void SetPrevIdleTime(int value) => SessionState.SetInt(SessionKey_PrevIdleTime, value);
|
|
|
|
|
private static int GetPrevInteractionMode() => SessionState.GetInt(SessionKey_PrevInteractionMode, 0);
|
|
|
|
|
private static void SetPrevInteractionMode(int value) => SessionState.SetInt(SessionKey_PrevInteractionMode, value);
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2026-01-15 07:02:40 +08:00
|
|
|
/// <summary>
|
|
|
|
|
/// Apply no-throttling preemptively before tests start.
|
|
|
|
|
/// Call this before Execute() for PlayMode tests to ensure Unity isn't throttled
|
|
|
|
|
/// during the Play mode transition (before RunStarted fires).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static void ApplyNoThrottlingPreemptive()
|
|
|
|
|
{
|
|
|
|
|
SetTestRunActive(true);
|
|
|
|
|
ApplyNoThrottling();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-04 04:42:32 +08:00
|
|
|
private static void ApplyNoThrottling()
|
|
|
|
|
{
|
|
|
|
|
if (!AreSettingsCaptured())
|
|
|
|
|
{
|
|
|
|
|
SetPrevIdleTime(EditorPrefs.GetInt(ApplicationIdleTimeKey, 4));
|
|
|
|
|
SetPrevInteractionMode(EditorPrefs.GetInt(InteractionModeKey, 0));
|
|
|
|
|
SetSettingsCaptured(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 0ms idle + InteractionMode=1 (No Throttling)
|
|
|
|
|
EditorPrefs.SetInt(ApplicationIdleTimeKey, 0);
|
|
|
|
|
EditorPrefs.SetInt(InteractionModeKey, 1);
|
|
|
|
|
|
|
|
|
|
ForceEditorToApplyInteractionPrefs();
|
|
|
|
|
McpLog.Info("[TestRunnerNoThrottle] Applied No Throttling for test run.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void RestoreThrottling()
|
|
|
|
|
{
|
|
|
|
|
if (!AreSettingsCaptured()) return;
|
|
|
|
|
|
|
|
|
|
EditorPrefs.SetInt(ApplicationIdleTimeKey, GetPrevIdleTime());
|
|
|
|
|
EditorPrefs.SetInt(InteractionModeKey, GetPrevInteractionMode());
|
|
|
|
|
ForceEditorToApplyInteractionPrefs();
|
|
|
|
|
|
|
|
|
|
SetSettingsCaptured(false);
|
|
|
|
|
SetTestRunActive(false);
|
|
|
|
|
McpLog.Info("[TestRunnerNoThrottle] Restored Interaction Mode after test run.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void ForceEditorToApplyInteractionPrefs()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var method = typeof(EditorApplication).GetMethod(
|
|
|
|
|
"UpdateInteractionModeSettings",
|
|
|
|
|
BindingFlags.Static | BindingFlags.NonPublic
|
|
|
|
|
);
|
|
|
|
|
method?.Invoke(null, null);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Ignore reflection errors
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private sealed class TestCallbacks : ICallbacks
|
|
|
|
|
{
|
|
|
|
|
public void RunStarted(ITestAdaptor testsToRun)
|
|
|
|
|
{
|
|
|
|
|
SetTestRunActive(true);
|
|
|
|
|
ApplyNoThrottling();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void RunFinished(ITestResultAdaptor result)
|
|
|
|
|
{
|
|
|
|
|
RestoreThrottling();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void TestStarted(ITestAdaptor test) { }
|
|
|
|
|
public void TestFinished(ITestResultAdaptor result) { }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|