272 lines
9.5 KiB
C#
272 lines
9.5 KiB
C#
using System.Diagnostics;
|
|
using System.Threading;
|
|
|
|
using UnityEditor;
|
|
|
|
using Codice.LogWrapper;
|
|
using PlasticGui;
|
|
using PlasticPipe.Client;
|
|
using Unity.PlasticSCM.Editor.UI;
|
|
|
|
namespace Unity.PlasticSCM.Editor
|
|
{
|
|
internal static class WaitForPendingOperations
|
|
{
|
|
internal static void ClearProgressBarAndEnterPlayModeIfNeeded()
|
|
{
|
|
// If play mode is pending (we were waiting for operations when domain reload hit),
|
|
// clear the progress bar and resume entering play mode.
|
|
if (!SessionState.GetBool(PLAY_MODE_PENDING_KEY, false))
|
|
return;
|
|
|
|
mLog.Debug("Clear progress bar and enter play mode after domain reload");
|
|
|
|
// Wait for editor to be ready to clear the progress bar and enter play mode
|
|
Execute.WhenEditorIsReady(ClearProgressBarAndEnterPlayMode);
|
|
}
|
|
|
|
internal static void BeforeAssemblyReload()
|
|
{
|
|
mLog.Debug("BeforeAssemblyReload started");
|
|
|
|
CancelPlayModeWaitIfNeeded();
|
|
|
|
// If we already timed out waiting for play mode, skip the wait loop entirely,
|
|
// to avoid waiting and showing a progress bar again.
|
|
if (mPlayModeWaitTimeoutReached)
|
|
{
|
|
mPlayModeWaitTimeoutReached = false;
|
|
return;
|
|
}
|
|
|
|
mLastLoggedSecond = -1;
|
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
|
bool progressBarShown = false;
|
|
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
// Tick manually the EditorDispatcher to allow UVCS operations
|
|
// to report completion in the main thread through the afterOperationDelegate.
|
|
EditorDispatcher.Update();
|
|
|
|
if (!HasRunningOperations())
|
|
{
|
|
mLog.DebugFormat(
|
|
"All operations finished after {0}ms",
|
|
stopwatch.ElapsedMilliseconds);
|
|
break;
|
|
}
|
|
|
|
bool hasLongRunningOperations = HasLongRunningOperations();
|
|
|
|
// Always wait for UVCS operations without timeout.
|
|
// Only apply timeout to ThreadWaiters and InUseConnections.
|
|
bool timeoutReached = stopwatch.ElapsedMilliseconds >= MAX_WAIT_TIMEOUT_MS;
|
|
if (timeoutReached && !hasLongRunningOperations)
|
|
{
|
|
LogTimeoutReached(stopwatch.ElapsedMilliseconds, "Forcing shutdown.");
|
|
break;
|
|
}
|
|
|
|
// Show progress bar after the delay to avoid flicker for quick operations
|
|
if (!progressBarShown && stopwatch.ElapsedMilliseconds >= PROGRESS_BAR_DELAY_MS)
|
|
{
|
|
DisplayProgressBar();
|
|
progressBarShown = true;
|
|
}
|
|
|
|
LogWaitingForOperations(stopwatch.ElapsedMilliseconds);
|
|
|
|
Thread.Sleep(POLL_INTERVAL_MS);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (progressBarShown)
|
|
{
|
|
EditorUtility.ClearProgressBar();
|
|
}
|
|
|
|
mLog.DebugFormat(
|
|
"WaitForPendingOperations completed in {0}ms",
|
|
stopwatch.ElapsedMilliseconds);
|
|
}
|
|
}
|
|
|
|
internal static void OnPlayModeStateChanged(PlayModeStateChange change)
|
|
{
|
|
if (change != PlayModeStateChange.ExitingEditMode)
|
|
return;
|
|
|
|
bool isDomainReloadDisabled = EditorSettings.enterPlayModeOptionsEnabled &&
|
|
(EditorSettings.enterPlayModeOptions &
|
|
EnterPlayModeOptions.DisableDomainReload) != 0;
|
|
|
|
if (isDomainReloadDisabled)
|
|
return;
|
|
|
|
if (!HasRunningOperations())
|
|
return;
|
|
|
|
// If we already timed out, don't delay play mode again to avoid infinite loop.
|
|
// This happens when FinishWaitAndEnterPlayMode() calls EnterPlaymode() after timeout,
|
|
// which triggers this callback again while operations are still running.
|
|
// Note: mPlayModeWaitTimeoutReached is reset in BeforeAssemblyReload().
|
|
if (mPlayModeWaitTimeoutReached)
|
|
return;
|
|
|
|
mLog.Debug("OnPlayModeStateChanged delaying play mode");
|
|
|
|
// Cancel entering play mode to wait for operations to finish
|
|
EditorApplication.ExitPlaymode();
|
|
|
|
// Prevent reentrancy: the ProgressBar doesn't lock the UI.
|
|
if (mPlayModeWaitStopwatch != null)
|
|
return;
|
|
|
|
// Mark play mode as pending. Cleared on success, or used to resume after reload.
|
|
SessionState.SetBool(PLAY_MODE_PENDING_KEY, true);
|
|
|
|
mLastLoggedSecond = -1;
|
|
mPlayModeWaitStopwatch = Stopwatch.StartNew();
|
|
EditorApplication.update += EnterPlayModeWhenOperationsFinish;
|
|
}
|
|
|
|
static void EnterPlayModeWhenOperationsFinish()
|
|
{
|
|
if (mPlayModeWaitStopwatch == null)
|
|
return;
|
|
|
|
// Show the progress bar on every frame to ensure it stays displayed regardless of any
|
|
// interruptions (like a popup on Editor Wants To Quit, or taking a screenshot!)
|
|
DisplayProgressBar();
|
|
|
|
bool hasLongRunningOperations = HasLongRunningOperations();
|
|
|
|
// Always wait for UVCS operations without timeout.
|
|
// Only apply timeout to ThreadWaiters and InUseConnections.
|
|
mPlayModeWaitTimeoutReached = mPlayModeWaitStopwatch.ElapsedMilliseconds >= MAX_WAIT_TIMEOUT_MS;
|
|
if (mPlayModeWaitTimeoutReached && !hasLongRunningOperations)
|
|
{
|
|
LogTimeoutReached(
|
|
mPlayModeWaitStopwatch.ElapsedMilliseconds,
|
|
"Forcing play mode.");
|
|
FinishWaitAndEnterPlayMode();
|
|
return;
|
|
}
|
|
|
|
if (HasRunningOperations())
|
|
{
|
|
LogWaitingForOperations(mPlayModeWaitStopwatch.ElapsedMilliseconds);
|
|
return;
|
|
}
|
|
|
|
FinishWaitAndEnterPlayMode();
|
|
}
|
|
|
|
static void FinishWaitAndEnterPlayMode()
|
|
{
|
|
mLog.Debug("FinishWaitAndEnterPlayMode");
|
|
|
|
EditorApplication.update -= EnterPlayModeWhenOperationsFinish;
|
|
mPlayModeWaitStopwatch = null;
|
|
|
|
ClearProgressBarAndEnterPlayMode();
|
|
}
|
|
|
|
static void ClearProgressBarAndEnterPlayMode()
|
|
{
|
|
ClearProgressBar();
|
|
|
|
mLog.Debug("Enter play mode");
|
|
EditorApplication.EnterPlaymode();
|
|
|
|
SessionState.SetBool(PLAY_MODE_PENDING_KEY, false);
|
|
}
|
|
|
|
static void CancelPlayModeWaitIfNeeded()
|
|
{
|
|
if (mPlayModeWaitStopwatch == null)
|
|
return;
|
|
|
|
mLog.Debug("CancelPlayModeWait");
|
|
|
|
EditorApplication.update -= EnterPlayModeWhenOperationsFinish;
|
|
mPlayModeWaitStopwatch = null;
|
|
}
|
|
|
|
static bool HasRunningOperations()
|
|
{
|
|
return HasLowLevelRunningOperations() || HasLongRunningOperations();
|
|
}
|
|
|
|
static bool HasLowLevelRunningOperations()
|
|
{
|
|
return ThreadWaiterRegistry.HasRunningOperations() ||
|
|
ClientConnectionPool.HasInUseConnections();
|
|
}
|
|
|
|
static bool HasLongRunningOperations()
|
|
{
|
|
return UVCSPlugin.Instance.HasRunningOperation();
|
|
}
|
|
|
|
static void DisplayProgressBar()
|
|
{
|
|
EditorUtility.DisplayProgressBar(
|
|
UnityConstants.UVCS_WINDOW_TITLE,
|
|
PlasticLocalization.Name.WaitingForPendingOperationsToFinish.GetString(),
|
|
1f);
|
|
}
|
|
|
|
static void ClearProgressBar()
|
|
{
|
|
mLog.Debug("ClearProgressBar");
|
|
|
|
EditorUtility.ClearProgressBar();
|
|
}
|
|
|
|
static void LogTimeoutReached(long elapsedMs, string action)
|
|
{
|
|
mLog.WarnFormat(
|
|
"Timeout reached after {0}ms. {1} " +
|
|
"Remaining: ThreadWaiters={2}, InUseConnections={3}",
|
|
elapsedMs,
|
|
action,
|
|
ThreadWaiterRegistry.GetRunningOperationsCount(),
|
|
ClientConnectionPool.GetInUseConnectionsCount());
|
|
}
|
|
|
|
static void LogWaitingForOperations(long elapsedMs)
|
|
{
|
|
// Only log once per second to avoid excessive log spam
|
|
long currentSecond = elapsedMs / 1000;
|
|
if (currentSecond == mLastLoggedSecond)
|
|
return;
|
|
|
|
mLastLoggedSecond = currentSecond;
|
|
|
|
mLog.DebugFormat(
|
|
"Waiting: ThreadWaiters={0}, InUseConnections={1}, " +
|
|
"UVCSOperations={2}, elapsed={3}ms",
|
|
ThreadWaiterRegistry.GetRunningOperationsCount(),
|
|
ClientConnectionPool.GetInUseConnectionsCount(),
|
|
UVCSPlugin.Instance.HasRunningOperation(),
|
|
elapsedMs);
|
|
}
|
|
|
|
const int MAX_WAIT_TIMEOUT_MS = 10000;
|
|
const int PROGRESS_BAR_DELAY_MS = 1000;
|
|
const int POLL_INTERVAL_MS = 50;
|
|
const string PLAY_MODE_PENDING_KEY = "WaitForPendingOperations.PlayModePending";
|
|
|
|
static long mLastLoggedSecond = -1;
|
|
static bool mPlayModeWaitTimeoutReached;
|
|
static Stopwatch mPlayModeWaitStopwatch;
|
|
|
|
static readonly ILog mLog = PlasticApp.GetLogger("WaitForPendingOperations");
|
|
}
|
|
}
|