diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b044bda --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +LICENSE.meta +README.md.meta diff --git a/README.md b/README.md index 47b5815..96ddab6 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,12 @@ A thread-safe way of dispatching IEnumerator functions to the main thread in unity. Useful for calling UI functions and other actions that Unity limits to the main thread from different threads. Initially written for Firebase Unity but now used across the board! -### Version -1.0 - Tested and functional in one or more production applications, including those from major companies. +## Version +1.0 - Tested and functional in one or more production applications, including those from major companies. -### Installation +1.1 - Added a static interface for easier useage. + +## Installation No dependencies needed other than Unity. This script was created in Unity 5.3, and has been tested in 5.3, 5.4, and 5.5 by the creators, but is being used across many more versions. If something breaks, let us know! @@ -13,7 +15,7 @@ No dependencies needed other than Unity. This script was created in Unity 5.3, a 2. Download the UnityMainThreadDispatcher.cs script and add it to your prefab 3. You can now dispatch objects to the main thread in Unity. -### Usage +## Usage ```C# public IEnumerator ThisWillBeExecutedOnTheMainThread() { Debug.Log ("This is executed from the main thread"); @@ -26,20 +28,34 @@ No dependencies needed other than Unity. This script was created in Unity 5.3, a OR ```C# - UnityMainThreadDispatcher.Instance().Enqueue(() => Debug.Log ("This is executed from the main thread")); + UnityMainThreadDispatcher.Instance().Enqueue(() => Debug.Log("This is executed from the main thread")); ``` -### Development -Want to contribute? Great! If you find a bug or want to make improvements, simply fork the repo and make a pull request with your changes on your own fork. +OR -Also - I'm looking for additional maintainers who are still actively using this in production as I now run a startup and am not actively doing engineering work anymore. +```C# + UnityMainThreadDispatcher.Dispatch(() => Debug.Log("This is executed from the main thread")); +``` -### Author -@PimDeWitte +OR + +```C# + var result = UnityMainThreadDispatcher.Dispatch(() => Resource.Load("test").text); +``` +OR +```C# + var result = await UnityMainThreadDispatcher.DispatchAsync(() => Resource.Load("test").text); +``` +## Development +Want to contribute? Great! If you find a bug or want to make improvements, simply fork the repo and make a pull request with your changes on your own fork. + +Also - I'm looking for additional maintainers who are still actively using this in production as I now run a startup and am not actively doing engineering work anymore. +## Author +@PimDeWitte - +[@rwindegger](https://www.windegger.wtf "Windegger Rene") diff --git a/UnityMainThreadDispatcher.cs b/UnityMainThreadDispatcher.cs index 7a4f783..4ba8ec3 100644 --- a/UnityMainThreadDispatcher.cs +++ b/UnityMainThreadDispatcher.cs @@ -1,5 +1,5 @@ /* -Copyright 2015 Pim de Witte All Rights Reserved. +Copyright 2015-2020 Pim de Witte and Rene Windegger All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,102 +18,236 @@ limitations under the License. using System.Collections; using System.Collections.Generic; using System; +using System.Threading; using System.Threading.Tasks; -/// Author: Pim de Witte (pimdewitte.com) and contributors, https://github.com/PimDeWitte/UnityMainThreadDispatcher +/// Author: Pim de Witte (pimdewitte.com), Rene Windegger (https://www.windegger.wtf), and contributors, https://github.com/rwindegger/UnityMainThreadDispatcher /// /// A thread-safe class which holds a queue with actions to execute on the next Update() method. It can be used to make calls to the main thread for /// things such as UI Manipulation in Unity. It was developed for use in combination with the Firebase Unity plugin, which uses separate threads for event handling /// -public class UnityMainThreadDispatcher : MonoBehaviour { - - private static readonly Queue _executionQueue = new Queue(); - - public void Update() { - lock(_executionQueue) { - while (_executionQueue.Count > 0) { - _executionQueue.Dequeue().Invoke(); - } - } - } - - /// - /// Locks the queue and adds the IEnumerator to the queue - /// - /// IEnumerator function that will be executed from the main thread. - public void Enqueue(IEnumerator action) { - lock (_executionQueue) { - _executionQueue.Enqueue (() => { - StartCoroutine (action); - }); - } - } - - /// - /// Locks the queue and adds the Action to the queue - /// - /// function that will be executed from the main thread. - public void Enqueue(Action action) - { - Enqueue(ActionWrapper(action)); - } - - /// - /// Locks the queue and adds the Action to the queue, returning a Task which is completed when the action completes - /// - /// function that will be executed from the main thread. - /// A Task that can be awaited until the action completes - public Task EnqueueAsync(Action action) - { - var tcs = new TaskCompletionSource(); - - void WrappedAction() { - try - { - action(); - tcs.TrySetResult(true); - } catch (Exception ex) - { - tcs.TrySetException(ex); - } - } - - Enqueue(ActionWrapper(WrappedAction)); - return tcs.Task; - } - - - IEnumerator ActionWrapper(Action a) - { - a(); - yield return null; - } - - - private static UnityMainThreadDispatcher _instance = null; - - public static bool Exists() { - return _instance != null; - } - - public static UnityMainThreadDispatcher Instance() { - if (!Exists ()) { - throw new Exception ("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene."); - } - return _instance; - } - - - void Awake() { - if (_instance == null) { - _instance = this; - DontDestroyOnLoad(this.gameObject); - } - } - - void OnDestroy() { - _instance = null; - } - - +public class UnityMainThreadDispatcher : MonoBehaviour +{ + private static readonly Queue _executionQueue = new Queue(); + private static UnityMainThreadDispatcher _instance = null; + private static Thread _mainThread = null; + + void Awake() + { + if (_instance == null) + { + _mainThread = Thread.CurrentThread; + _instance = this; + DontDestroyOnLoad(this.gameObject); + } + } + + void Update() + { + // Make local copy to avoid holding the lock while the actions execute. + Queue queueCopy; + lock (_executionQueue) + { + queueCopy = new Queue(_executionQueue); + _executionQueue.Clear(); + } + while (queueCopy.Count > 0) + { + queueCopy.Dequeue().Invoke(); + } + } + + void OnDestroy() + { + _instance = null; + } + + /// + /// Checks if a Dispatcher exists. + /// + /// Either true of false depending if the Dispatcher exists. + public static bool Exists() + { + return _instance != null; + } + + /// + /// The current dispatcher. + /// + /// The dispatcher + public static UnityMainThreadDispatcher Instance() + { + if (!Exists()) + { + throw new Exception("UnityMainThreadDispatcher could not find the UnityMainThreadDispatcher object. Please ensure you have added the MainThreadExecutor Prefab to your scene."); + } + + return _instance; + } + + /// + /// Locks the queue and adds the IEnumerator to the queue + /// + /// IEnumerator function that will be executed from the main thread. + public void Enqueue(IEnumerator action) + { + lock (_executionQueue) + { + _executionQueue.Enqueue(() => + { + StartCoroutine(action); + }); + } + } + + /// + /// Locks the queue and adds the Action to the queue + /// + /// Function that will be executed from the main thread. + public void Enqueue(Action action) + { + IEnumerator ActionWrapper(Action a) + { + a(); + yield return null; + } + + Enqueue(ActionWrapper(action)); + } + + /// + /// Locks the queue and adds the Action to the queue, returning a Task which is completed when the action completes + /// + /// Function that will be executed from the main thread. + /// A Task that can be awaited until the action completes + public Task EnqueueAsync(Action action) + { + var tcs = new TaskCompletionSource(); + + void WrappedAction() + { + try + { + action(); + tcs.TrySetResult(true); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + } + + Enqueue(WrappedAction); + return tcs.Task; + } + + /// + /// Executes the function on the main thread and returns the result to the caller. + /// + /// The result type. + /// The function that will be executed on the main thread. + /// The result of the executed function. + public static T Dispatch(Func func) + { + var obj = default(T); + if (Thread.CurrentThread == _mainThread) + { + obj = func(); + } + else + { + bool finished = false; + Instance().Enqueue(() => + { + obj = func(); + finished = true; + }); + while (!finished) Thread.Yield(); + } + + return obj; + } + + /// + /// Executes the function on the main thread and returns the result to the caller. + /// + /// The function that will be executed on the main thread. + public static void Dispatch(Action func) + { + if (Thread.CurrentThread == _mainThread) + { + func(); + } + else + { + Instance().Enqueue(func); + } + } + + /// + /// Locks the queue and adds the Action to the queue, returning a Task which is completed when the action completes + /// + /// Function that will be executed from the main thread. + /// A Task that can be awaited until the action completes + public static Task DispatchAsync(Func func) + { + var tcs = new TaskCompletionSource(); + + void WrappedAction() + { + try + { + tcs.TrySetResult(func()); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + } + + if (Thread.CurrentThread == _mainThread) + { + WrappedAction(); + } + else + { + Instance().Enqueue(WrappedAction); + } + + return tcs.Task; + } + + /// + /// Locks the queue and adds the Action to the queue, returning a Task which is completed when the action completes + /// + /// Function that will be executed from the main thread. + /// A Task that can be awaited until the action completes + public static Task DispatchAsync(Action func) + { + var tcs = new TaskCompletionSource(); + + void WrappedAction() + { + try + { + func(); + tcs.TrySetResult(true); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + } + + if (Thread.CurrentThread == _mainThread) + { + WrappedAction(); + } + else + { + Instance().Enqueue(WrappedAction); + } + + return tcs.Task; + } }