#if ENABLE_MONO && (DEVELOPMENT_BUILD || UNITY_EDITOR) using System; using System.Runtime.CompilerServices; using System.Threading; using UnityEngine; namespace SingularityGroup.HotReload { #if UNITY_EDITOR [UnityEditor.InitializeOnLoad] #endif static class ThreadUtility { /// /// Run code on Unity's main thread /// /// /// This field is set early in [InitializeOnLoadMethod] in the editor and [RuntimeInitializeOnLoad] in playmode / for player builds, so your code assume it is already set. /// #if UNITY_EDITOR static SynchronizationContext _cachedMainContext; public static SynchronizationContext MainContext { get { if(_cachedMainContext != null) { return _cachedMainContext; } return EditorFallbackContext.I; } private set { _cachedMainContext = value; } } class EditorFallbackContext : SynchronizationContext { public static readonly EditorFallbackContext I = new EditorFallbackContext(); EditorFallbackContext() { } public override void Send(SendOrPostCallback d, object state) { UnityEditor.EditorApplication.delayCall += () => d(state); } public override void Post(SendOrPostCallback d, object state) { UnityEditor.EditorApplication.delayCall += () => d(state); } } #else public static SynchronizationContext MainContext {get; private set;} #endif public static int mainThreadId {get; private set;} #if UNITY_EDITOR static ThreadUtility() { #else [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void InitMainThread() { #endif MainContext = SynchronizationContext.Current; mainThreadId = Thread.CurrentThread.ManagedThreadId; } [MethodImpl(MethodImplOptions.NoInlining)] public static void InitEditor() { //trigger static constructor } public static bool ShouldLogException(Exception ex) { AggregateException agg; while((agg = ex as AggregateException) != null) { ex = agg.InnerException; } if(ex is ThreadAbortException) { return false; } return true; } public static void LogException(Exception ex, CancellationToken token = default(CancellationToken)) { if(ShouldLogException(ex) && !token.IsCancellationRequested) { Log.Exception(ex); } } public static void RunOnMainThread(Action action, CancellationToken token = default(CancellationToken)) { if(Thread.CurrentThread.ManagedThreadId == mainThreadId) { action(); } else { MainContext.Post(_ => { if(!token.IsCancellationRequested) { action(); } }, null); } } public static SwitchToMainThreadAwaitable SwitchToMainThread() { return new SwitchToMainThreadAwaitable(); } public static CancellableSwitchToMainThreadAwaitable SwitchToMainThread(CancellationToken token) { return new CancellableSwitchToMainThreadAwaitable(token); } public static SwitchToThreadPoolAwaitable SwitchToThreadPool() { return new SwitchToThreadPoolAwaitable(); } public static CancellableSwitchToThreadPoolAwaitable SwitchToThreadPool(CancellationToken token) { return new CancellableSwitchToThreadPoolAwaitable(token); } } struct SwitchToMainThreadAwaitable { public Awaiter GetAwaiter() => new Awaiter(); public struct Awaiter : ICriticalNotifyCompletion { static readonly SendOrPostCallback switchToCallback = Callback; public bool IsCompleted => Thread.CurrentThread.ManagedThreadId == ThreadUtility.mainThreadId; public void GetResult() { } public void OnCompleted(Action continuation) { ThreadUtility.MainContext.Post(switchToCallback, continuation); } public void UnsafeOnCompleted(Action continuation) { ThreadUtility.MainContext.Post(switchToCallback, continuation); } static void Callback(object state) { var continuation = (Action)state; continuation(); } } } struct CancellableSwitchToMainThreadAwaitable { readonly CancellationToken token; public CancellableSwitchToMainThreadAwaitable(CancellationToken token) { this.token = token; } public Awaiter GetAwaiter() => new Awaiter(token); public struct Awaiter : ICriticalNotifyCompletion { readonly CancellationToken token; public Awaiter(CancellationToken token) { this.token = token; } public bool IsCompleted => Thread.CurrentThread.ManagedThreadId == ThreadUtility.mainThreadId; public void GetResult() { } public void OnCompleted(Action continuation) { UnsafeOnCompleted(continuation); } public void UnsafeOnCompleted(Action continuation) { var tokenCopy = this.token; ThreadUtility.MainContext.Post(o => { if(!tokenCopy.IsCancellationRequested) { continuation(); } }, null); } } } struct CancellableSwitchToThreadPoolAwaitable { readonly CancellationToken token; public CancellableSwitchToThreadPoolAwaitable(CancellationToken token) { this.token = token; } public Awaiter GetAwaiter() => new Awaiter(token); public struct Awaiter : ICriticalNotifyCompletion { readonly CancellationToken token; public Awaiter(CancellationToken token) { this.token = token; } public bool IsCompleted => false; public void GetResult() { } public void OnCompleted(Action continuation) { ThreadPool.UnsafeQueueUserWorkItem(Callback, continuation); } public void UnsafeOnCompleted(Action continuation) { ThreadPool.UnsafeQueueUserWorkItem(Callback, continuation); } void Callback(object state) { token.ThrowIfCancellationRequested(); var continuation = (Action)state; continuation(); } } } struct SwitchToThreadPoolAwaitable { public Awaiter GetAwaiter() => new Awaiter(); public struct Awaiter : ICriticalNotifyCompletion { static readonly WaitCallback switchToCallback = Callback; public bool IsCompleted => false; public void GetResult() { } public void OnCompleted(Action continuation) { ThreadPool.UnsafeQueueUserWorkItem(switchToCallback, continuation); } public void UnsafeOnCompleted(Action continuation) { ThreadPool.UnsafeQueueUserWorkItem(switchToCallback, continuation); } static void Callback(object state) { var continuation = (Action)state; continuation(); } } } } #endif