From 5c1c18a69727cbfaf7b1809eb634888df3c04f68 Mon Sep 17 00:00:00 2001 From: Rene Windegger Date: Sat, 21 Sep 2019 13:45:32 +0200 Subject: [PATCH 1/7] Added a Dispatch method that allows to get a return value from an executed function. Updated the readme to reflect the changes. Ordered and formated the actual code. --- README.md | 7 ++ UnityMainThreadDispatcher.cs | 177 ++++++++++++++++++++++------------- 2 files changed, 119 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 1ac8ca7..cc04ba5 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,13 @@ OR ```C# UnityMainThreadDispatcher.Instance().Enqueue(() => Debug.Log ("This is executed from the main thread")); ``` + +OR + +```C# + var result = UnityMainThreadDispatcher.Dispatch(() => 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. diff --git a/UnityMainThreadDispatcher.cs b/UnityMainThreadDispatcher.cs index 3b40ca9..5acf9e7 100644 --- a/UnityMainThreadDispatcher.cs +++ b/UnityMainThreadDispatcher.cs @@ -18,75 +18,122 @@ limitations under the License. using System.Collections; using System.Collections.Generic; using System; +using System.Threading; /// Author: Pim de Witte (pimdewitte.com) and contributors, https://github.com/PimDeWitte/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)); - } - 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() + { + lock (ExecutionQueue) + { + while (ExecutionQueue.Count > 0) + { + ExecutionQueue.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; + } + + private IEnumerator ActionWrapper(Action a) + { + a(); + yield return null; + } + + /// + /// 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)); + } + + /// + /// 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; + } } From f42b7a85d5f7a186af8b0c3cf3fddc0bc0450d24 Mon Sep 17 00:00:00 2001 From: Rene Windegger Date: Sat, 21 Sep 2019 13:55:33 +0200 Subject: [PATCH 2/7] Added a second overload for Dispatch that takes an Action as parameter. --- README.md | 6 ++++++ UnityMainThreadDispatcher.cs | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/README.md b/README.md index cc04ba5..f1ef9dc 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,12 @@ OR OR +```C# + UnityMainThreadDispatcher.Dispatch(() => Debug.Log ("This is executed from the main thread")); +``` + +OR + ```C# var result = UnityMainThreadDispatcher.Dispatch(() => Resource.Load("test").text); ``` diff --git a/UnityMainThreadDispatcher.cs b/UnityMainThreadDispatcher.cs index 5acf9e7..0dea2f1 100644 --- a/UnityMainThreadDispatcher.cs +++ b/UnityMainThreadDispatcher.cs @@ -136,4 +136,20 @@ public static T Dispatch(Func func) 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); + } + } } From c8827dab0a523aecaa3b91538e45566b8b2b0415 Mon Sep 17 00:00:00 2001 From: Alan Browning <8662070+omgpuppy@users.noreply.github.com> Date: Thu, 16 Jan 2020 11:57:10 -0800 Subject: [PATCH 3/7] Locally copy queue to avoid holding lock while Actions execute. --- UnityMainThreadDispatcher.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/UnityMainThreadDispatcher.cs b/UnityMainThreadDispatcher.cs index 3b40ca9..9101602 100644 --- a/UnityMainThreadDispatcher.cs +++ b/UnityMainThreadDispatcher.cs @@ -29,12 +29,18 @@ public class UnityMainThreadDispatcher : MonoBehaviour { private static readonly Queue _executionQueue = new Queue(); public void Update() { - lock(_executionQueue) { - while (_executionQueue.Count > 0) { - _executionQueue.Dequeue().Invoke(); - } - } - } + // 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(); + } + } /// /// Locks the queue and adds the IEnumerator to the queue From 42d0a64108d7757bc2f70815b5e4d7336c6164e7 Mon Sep 17 00:00:00 2001 From: Rene Windegger Date: Sat, 30 May 2020 21:02:35 +0200 Subject: [PATCH 4/7] Ignore LICENSE.meta and README.md.meta. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore 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 From cf8375f7d2548925e39c67018bc4996ce9689fee Mon Sep 17 00:00:00 2001 From: Rene Windegger Date: Sat, 30 May 2020 21:29:55 +0200 Subject: [PATCH 5/7] Renamed a local variable. --- UnityMainThreadDispatcher.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UnityMainThreadDispatcher.cs b/UnityMainThreadDispatcher.cs index b229a9d..672d920 100644 --- a/UnityMainThreadDispatcher.cs +++ b/UnityMainThreadDispatcher.cs @@ -28,7 +28,7 @@ limitations under the License. /// public class UnityMainThreadDispatcher : MonoBehaviour { - private static readonly Queue ExecutionQueue = new Queue(); + private static readonly Queue _executionQueue = new Queue(); private static UnityMainThreadDispatcher _instance = null; private static Thread _mainThread = null; @@ -97,9 +97,9 @@ private IEnumerator ActionWrapper(Action a) /// IEnumerator function that will be executed from the main thread. public void Enqueue(IEnumerator action) { - lock (ExecutionQueue) + lock (_executionQueue) { - ExecutionQueue.Enqueue(() => + _executionQueue.Enqueue(() => { StartCoroutine(action); }); From 1febf3c03d316474c752291414b8678c72335efc Mon Sep 17 00:00:00 2001 From: Rene Windegger Date: Sat, 30 May 2020 22:32:24 +0200 Subject: [PATCH 6/7] Added DispatchAsync. --- README.md | 24 ++++++---- UnityMainThreadDispatcher.cs | 89 +++++++++++++++++++++++++++++++----- 2 files changed, 94 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 833624d..6d0b4ae 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ 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. +1.1 - Added a static interface for easier useage. -### Installation +## 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 +14,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,13 +27,13 @@ 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")); ``` OR ```C# - UnityMainThreadDispatcher.Dispatch(() => Debug.Log ("This is executed from the main thread")); + UnityMainThreadDispatcher.Dispatch(() => Debug.Log("This is executed from the main thread")); ``` OR @@ -41,14 +42,21 @@ OR var result = UnityMainThreadDispatcher.Dispatch(() => Resource.Load("test").text); ``` -### Development +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 +## Author @PimDeWitte +[@rwindegger](https://www.windegger.wtf "Windegger Rene") diff --git a/UnityMainThreadDispatcher.cs b/UnityMainThreadDispatcher.cs index 672d920..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. @@ -21,7 +21,7 @@ limitations under the License. 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 @@ -85,12 +85,6 @@ public static UnityMainThreadDispatcher Instance() return _instance; } - private IEnumerator ActionWrapper(Action a) - { - a(); - yield return null; - } - /// /// Locks the queue and adds the IEnumerator to the queue /// @@ -109,16 +103,22 @@ public void Enqueue(IEnumerator action) /// /// Locks the queue and adds the Action to the queue /// - /// function that will be executed from the main thread. + /// 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. + /// Function that will be executed from the main thread. /// A Task that can be awaited until the action completes public Task EnqueueAsync(Action action) { @@ -137,7 +137,7 @@ void WrappedAction() } } - Enqueue(ActionWrapper(WrappedAction)); + Enqueue(WrappedAction); return tcs.Task; } @@ -183,4 +183,71 @@ public static void Dispatch(Action func) 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; + } } From 0a0d4c85ef21aa960e33278fe1bb5e86cdcf892c Mon Sep 17 00:00:00 2001 From: Rene Windegger Date: Sat, 30 May 2020 22:34:00 +0200 Subject: [PATCH 7/7] Updated Readme.md --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6d0b4ae..96ddab6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ A thread-safe way of dispatching IEnumerator functions to the main thread in uni ## Version 1.0 - Tested and functional in one or more production applications, including those from major companies. + 1.1 - Added a static interface for easier useage. ## Installation @@ -56,11 +57,5 @@ Also - I'm looking for additional maintainers who are still actively using this ## Author @PimDeWitte -[@rwindegger](https://www.windegger.wtf "Windegger Rene") - - - - - - +[@rwindegger](https://www.windegger.wtf "Windegger Rene")