Skip to content

Instantly share code, notes, and snippets.

@ogxd
Created October 3, 2019 16:46
Show Gist options
  • Save ogxd/12f57c062e902c67f083857f325e8ca0 to your computer and use it in GitHub Desktop.
Save ogxd/12f57c062e902c67f083857f325e8ca0 to your computer and use it in GitHub Desktop.
Dispatcher for Unity
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;
}
}
}
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