Skip to main content
Glama
UnityMainThreadDispatcher.cs7.9 kB
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")}"); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dsgarage/UniMCP4CC'

If you have feedback or need assistance with the MCP directory API, please join our Discord server