Created
October 3, 2019 16:46
-
-
Save ogxd/12f57c062e902c67f083857f325e8ca0 to your computer and use it in GitHub Desktop.
Dispatcher for Unity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Concurrent; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using UnityEditor; | |
using UnityEngine; | |
using UnityEngine.Experimental.LowLevel; | |
#pragma warning disable CS1998 | |
public static class Dispatcher | |
{ | |
#region Yield Instructions | |
public abstract class YieldInstruction | |
{ | |
public virtual async Task<bool> Dispatch(IEnumerator enumerator) | |
{ | |
return true; | |
} | |
} | |
#region Go Main Thread | |
public class GoMainThreadDispatch : YieldInstruction | |
{ | |
public override async Task<bool> Dispatch(IEnumerator enumerator) | |
{ | |
pendingActions.Enqueue(() => MoveNext(enumerator)); | |
return true; | |
} | |
} | |
public static GoMainThreadDispatch GoMainThread() | |
{ | |
return new GoMainThreadDispatch(); | |
} | |
#endregion | |
#region Go Thread Pool | |
public class GoThreadPoolDispatch : YieldInstruction | |
{ | |
public override async Task<bool> Dispatch(IEnumerator enumerator) | |
{ | |
ThreadPool.QueueUserWorkItem(new WaitCallback((o) => MoveNext(enumerator))); | |
return true; | |
} | |
} | |
public static GoThreadPoolDispatch GoThreadPool() | |
{ | |
return new GoThreadPoolDispatch(); | |
} | |
#endregion | |
#region Wait Seconds | |
public class WaitForSecondsDispatch : YieldInstruction | |
{ | |
internal readonly float seconds; | |
internal WaitForSecondsDispatch(float seconds) | |
{ | |
this.seconds = seconds; | |
} | |
public override async Task<bool> Dispatch(IEnumerator enumerator) | |
{ | |
await Task.Delay(Mathf.RoundToInt(((WaitForSecondsDispatch)enumerator.Current).seconds * 1000)); | |
MoveNext(enumerator); | |
return true; | |
} | |
} | |
public static WaitForSecondsDispatch WaitForSeconds(float seconds) | |
{ | |
return new WaitForSecondsDispatch(seconds); | |
} | |
#endregion | |
#region Sleep Seconds | |
public class SleepForSecondsDispatch : YieldInstruction | |
{ | |
internal readonly float seconds; | |
internal SleepForSecondsDispatch(float seconds) | |
{ | |
this.seconds = seconds; | |
} | |
public override async Task<bool> Dispatch(IEnumerator enumerator) | |
{ | |
Thread.Sleep(Mathf.RoundToInt(((SleepForSecondsDispatch)enumerator.Current).seconds * 1000)); | |
MoveNext(enumerator); | |
return true; | |
} | |
} | |
public static SleepForSecondsDispatch SleepForSeconds(float seconds) | |
{ | |
return new SleepForSecondsDispatch(seconds); | |
} | |
#endregion | |
#endregion | |
static Dispatcher() | |
{ | |
Initialize(); | |
} | |
private static void Initialize() | |
{ | |
// Connect to game loop | |
PlayerLoopSystem playerLoop = PlayerLoop.GetDefaultPlayerLoop(); | |
var newSystemList = new PlayerLoopSystem[playerLoop.subSystemList.Length + 1]; | |
Array.Copy(playerLoop.subSystemList, 0, newSystemList, 1, playerLoop.subSystemList.Length); | |
newSystemList[0] = new PlayerLoopSystem { type = typeof(PlayerLoopSystem.UpdateFunction), updateDelegate = Update }; | |
playerLoop.subSystemList = newSystemList; | |
PlayerLoop.SetPlayerLoop(playerLoop); | |
// Connect to editor loop | |
#if UNITY_EDITOR | |
EditorApplication.update += Update; | |
#endif | |
} | |
/// <summary> | |
/// Pending actions to dispatch in the main thread. | |
/// </summary> | |
private static ConcurrentQueue<Action> pendingActions = new ConcurrentQueue<Action>(); | |
/// <summary> | |
/// Update callback, connected to editor loop and game loop, in order to be able to dispatch | |
/// instructions in the main thread in editor or play mode. | |
/// </summary> | |
private static void Update() | |
{ | |
while (pendingActions.Count != 0) { | |
if (pendingActions.TryDequeue(out Action action)) { | |
action?.Invoke(); | |
} | |
} | |
} | |
/// <summary> | |
/// Starts a coroutine. | |
/// Similar to Unity's coroutine, except that it can switch threads. | |
/// Yield instructions to use are all static methods in the Dispatcher class. | |
/// </summary> | |
/// <param name="enumerable"></param> | |
public static void StartCoroutine(IEnumerator enumerable) | |
{ | |
MoveNext(enumerable); | |
} | |
/// <summary> | |
/// Move to the next IEnumerator yield instruction. | |
/// </summary> | |
/// <param name="enumerator"></param> | |
private static async void MoveNext(IEnumerator enumerator) | |
{ | |
if (enumerator.MoveNext()) { | |
var actionDispatcher = enumerator.Current as YieldInstruction; | |
if (actionDispatcher != null) { | |
await actionDispatcher.Dispatch(enumerator); | |
} else { | |
Debug.LogWarning($"Yield '{enumerator.Current}' not recognized"); | |
} | |
} else { | |
return; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DispatcherExample | |
{ | |
[MenuItem("Dispatcher/Test")] | |
static void DispatcherTest() | |
{ | |
Dispatcher.StartCoroutine(CoroutineAsync()); | |
} | |
static IEnumerator CoroutineAsync() | |
{ | |
Debug.Log("1: " + Thread.CurrentThread.Name); | |
yield return Dispatcher.GoThreadPool(); | |
Debug.Log("2: " + Thread.CurrentThread.Name); | |
yield return Dispatcher.GoMainThread(); | |
Debug.Log("3: " + Thread.CurrentThread.Name); | |
yield return Dispatcher.WaitForSeconds(3); | |
Debug.Log("4: " + Thread.CurrentThread.Name); | |
yield return Dispatcher.SleepForSeconds(3); | |
Debug.Log("5: " + Thread.CurrentThread.Name); | |
yield return Dispatcher.GoThreadPool(); | |
Debug.Log("6: " + Thread.CurrentThread.Name); | |
yield return Dispatcher.WaitForSeconds(3); | |
Debug.Log("7: " + Thread.CurrentThread.Name); | |
yield break; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment