using System;
using System.Collections.Generic;
using System.Threading;
using UnityEditor;
using UnityEngine;
namespace LocalMcp.UnityServer
{
/// <summary>
/// Editor-only main thread dispatcher (no MonoBehaviour required)
/// Executes actions on Unity's main thread using EditorApplication.update
/// </summary>
public static class UnityMainThreadDispatcher
{
private static readonly Queue<Action> _executionQueue = new Queue<Action>();
private static readonly object _queueLock = new object();
private static bool _initialized = false;
private static bool _verboseLogging = false;
/// <summary>
/// Initializes the dispatcher on Unity Editor load
/// </summary>
[InitializeOnLoadMethod]
private static void Initialize()
{
if (_initialized)
{
return;
}
// Register to EditorApplication.update for main thread execution
EditorApplication.update += ExecuteQueuedActions;
_initialized = true;
Debug.Log("[MCP] UnityMainThreadDispatcher initialized (Editor-only mode)");
}
/// <summary>
/// Executes all queued actions on the main thread
/// Called by EditorApplication.update
/// </summary>
private static void ExecuteQueuedActions()
{
lock (_queueLock)
{
int count = _executionQueue.Count;
if (count > 0 && _verboseLogging)
{
Debug.Log($"[MCP] ExecuteQueuedActions: Processing {count} queued actions on thread {Thread.CurrentThread.ManagedThreadId}");
}
while (_executionQueue.Count > 0)
{
var action = _executionQueue.Dequeue();
try
{
action?.Invoke();
}
catch (Exception e)
{
Debug.LogError($"[MCP] Error executing action on main thread: {e.Message}\n{e.StackTrace}");
}
}
}
}
/// <summary>
/// Enqueues an action to be executed on the main thread
/// </summary>
/// <param name="action">The action to execute</param>
public static void Enqueue(Action action)
{
if (action == null)
{
Debug.LogWarning("[MCP] Attempted to enqueue null action");
return;
}
lock (_queueLock)
{
_executionQueue.Enqueue(action);
}
}
/// <summary>
/// Enqueues a function to be executed on the main thread and waits for the result
/// </summary>
/// <typeparam name="T">Return type</typeparam>
/// <param name="func">The function to execute</param>
/// <returns>The result of the function</returns>
public static T EnqueueAndWait<T>(Func<T> func)
{
if (func == null)
{
Debug.LogWarning("[MCP] Attempted to enqueue null function");
return default(T);
}
T result = default(T);
Exception exception = null;
var waitHandle = new AutoResetEvent(false);
if (_verboseLogging)
{
Debug.Log($"[MCP] EnqueueAndWait<T>: Queuing function on thread {Thread.CurrentThread.ManagedThreadId}");
}
Enqueue(() =>
{
if (_verboseLogging)
{
Debug.Log($"[MCP] EnqueueAndWait<T>: Executing function on thread {Thread.CurrentThread.ManagedThreadId}");
}
try
{
result = func();
}
catch (Exception e)
{
exception = e;
Debug.LogError($"[MCP] EnqueueAndWait<T>: Exception during execution: {e.Message}");
}
finally
{
waitHandle.Set();
}
});
// Wait for execution to complete (with 30 second timeout)
if (_verboseLogging)
{
Debug.Log("[MCP] EnqueueAndWait<T>: Waiting for execution...");
}
bool signaled = waitHandle.WaitOne(30000);
if (!signaled)
{
Debug.LogError("[MCP] EnqueueAndWait<T>: Timeout waiting for main thread execution!");
throw new TimeoutException("Timeout waiting for main thread execution");
}
if (_verboseLogging)
{
Debug.Log("[MCP] EnqueueAndWait<T>: Execution completed");
}
if (exception != null)
{
throw exception;
}
return result;
}
/// <summary>
/// Enqueues an action to be executed on the main thread and waits for completion
/// </summary>
/// <param name="action">The action to execute</param>
public static void EnqueueAndWait(Action action)
{
if (action == null)
{
Debug.LogWarning("[MCP] Attempted to enqueue null action");
return;
}
Exception exception = null;
var waitHandle = new AutoResetEvent(false);
if (_verboseLogging)
{
Debug.Log($"[MCP] EnqueueAndWait(Action): Queuing action on thread {Thread.CurrentThread.ManagedThreadId}");
}
Enqueue(() =>
{
if (_verboseLogging)
{
Debug.Log($"[MCP] EnqueueAndWait(Action): Executing action on thread {Thread.CurrentThread.ManagedThreadId}");
}
try
{
action();
}
catch (Exception e)
{
exception = e;
Debug.LogError($"[MCP] EnqueueAndWait(Action): Exception during execution: {e.Message}");
}
finally
{
waitHandle.Set();
}
});
// Wait for execution to complete (with 30 second timeout)
if (_verboseLogging)
{
Debug.Log("[MCP] EnqueueAndWait(Action): Waiting for execution...");
}
bool signaled = waitHandle.WaitOne(30000);
if (!signaled)
{
Debug.LogError("[MCP] EnqueueAndWait(Action): Timeout waiting for main thread execution!");
throw new TimeoutException("Timeout waiting for main thread execution");
}
if (_verboseLogging)
{
Debug.Log("[MCP] EnqueueAndWait(Action): Execution completed");
}
if (exception != null)
{
throw exception;
}
}
/// <summary>
/// Checks if the current thread is the main thread
/// </summary>
/// <returns>True if on main thread, false otherwise</returns>
public static bool IsMainThread()
{
return Thread.CurrentThread.ManagedThreadId == 1;
}
/// <summary>
/// Enables or disables verbose logging
/// </summary>
/// <param name="enabled">True to enable verbose logging</param>
public static void SetVerboseLogging(bool enabled)
{
_verboseLogging = enabled;
Debug.Log($"[MCP] UnityMainThreadDispatcher verbose logging: {(_verboseLogging ? "ENABLED" : "DISABLED")}");
}
}
}