using System;
using System.Collections.Generic;
using System.Threading;
using UnityEditor;
namespace io.github.hatayama.uLoopMCP
{
///
/// Delay processing management class for Unity Editor
/// Driven by EditorApplication.update to manage frame-based waiting processes
///
[InitializeOnLoad]
public static class EditorDelayManager
{
private static readonly List delayTasks = new();
private static readonly object lockObject = new();
private static int currentFrameCount = 0;
///
/// Class representing a waiting task
///
private class DelayTask
{
public Action Continuation { get; }
public int RemainingFrames { get; set; }
public CancellationToken CancellationToken { get; }
public DelayTask(Action continuation, int frames, CancellationToken cancellationToken)
{
Continuation = continuation ?? throw new ArgumentNullException(nameof(continuation));
RemainingFrames = frames;
CancellationToken = cancellationToken;
}
}
///
/// Static constructor
/// Register frame processing to EditorApplication.update
///
static EditorDelayManager()
{
EditorApplication.update += UpdateDelayTasks;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
///
/// Register a new waiting task
///
/// Process to execute after waiting completion
/// Number of frames to wait
/// Cancellation token
public static void RegisterDelay(Action continuation, int frames, CancellationToken cancellationToken)
{
if (continuation == null)
{
return;
}
if (frames <= 0)
{
// Execute immediately if 0 frames or less
continuation.Invoke();
return;
}
lock (lockObject)
{
delayTasks.Add(new DelayTask(continuation, frames, cancellationToken));
}
}
///
/// Update processing for waiting tasks called every frame
///
private static void UpdateDelayTasks()
{
// Update frame counter
currentFrameCount++;
if (delayTasks.Count == 0) return;
lock (lockObject)
{
for (int i = delayTasks.Count - 1; i >= 0; i--)
{
DelayTask task = delayTasks[i];
// Remove cancelled tasks by throwing exceptions
if (task.CancellationToken.IsCancellationRequested)
{
delayTasks.RemoveAt(i);
task.Continuation.Invoke();
continue;
}
// Decrease frame count
task.RemainingFrames--;
// Execute and remove completed waiting tasks
if (task.RemainingFrames <= 0)
{
delayTasks.RemoveAt(i);
task.Continuation.Invoke();
}
}
}
}
///
/// Get current number of waiting tasks (for debugging)
///
public static int PendingTaskCount
{
get
{
lock (lockObject)
{
return delayTasks.Count;
}
}
}
///
/// Get current frame count (for testing)
///
public static int CurrentFrameCount
{
get
{
lock (lockObject)
{
return currentFrameCount;
}
}
}
///
/// Event handler for PlayMode state changes
///
private static void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.ExitingEditMode || state == PlayModeStateChange.ExitingPlayMode)
{
ResetFrameCount();
}
}
///
/// Reset frame counter (for testing and internal use)
///
public static void ResetFrameCount()
{
lock (lockObject)
{
currentFrameCount = 0;
}
}
///
/// Clear all waiting tasks (for testing)
///
public static void ClearAllTasks()
{
lock (lockObject)
{
delayTasks.Clear();
}
}
}
}