// %BANNER_BEGIN%
// ---------------------------------------------------------------------
// %COPYRIGHT_BEGIN%
// Copyright (c) (2018-2022) Magic Leap, Inc. All Rights Reserved.
// Use of this file is governed by the Software License Agreement, located here: https://www.magicleap.com/software-license-agreement-ml2
// Terms and conditions applicable to third-party materials accompanying this distribution may also be found in the top-level NOTICE file appearing herein.
// %COPYRIGHT_END%
// ---------------------------------------------------------------------
// %BANNER_END%
namespace UnityEngine.XR.MagicLeap.Native
{
using System;
using System.Collections.Concurrent;
using System.Threading;
///
/// Handles dispatching calls from the Magic Leap native thread to the Unity thread
///
public class MLThreadDispatch
{
///
/// A concurrent queue handles the dispatched callbacks in a thread-safe way.
///
private static ConcurrentQueue actionQueue = new ConcurrentQueue();
///
/// The concurrent queue for actions to execute on the main thread.
///
private static ConcurrentQueue mainActionQueue = new ConcurrentQueue();
///
/// The concurrent queue for actions to execute on the graphics thread.
///
private static ConcurrentQueue graphicsActionQueue = new ConcurrentQueue();
private static bool registeredForGraphicsCallbacks = false;
///
/// The worker thread
///
private static System.Threading.Thread thread = null;
///
/// The concurrent queue of itemized work items that will execute on the worker thread.
///
private static ConcurrentQueue> itemizedWork = new ConcurrentQueue>();
///
/// A method that schedules a callback on the worker thread.
///
/// Function to call. Return TRUE when processing is done, FALSE to be placed back in the queue to be called again at a later time.
public static void ScheduleWork(Func function)
{
itemizedWork.Enqueue(function);
}
///
/// A method that schedules a callback on the main thread.
///
/// A callback function to be called when the action is invoked
public static void ScheduleMain(System.Action callback)
{
if (MLDevice.Instance != null && MLDevice.MainThreadId != -1 && MLDevice.MainThreadId == System.Threading.Thread.CurrentThread.ManagedThreadId)
{
callback();
}
else
{
mainActionQueue.Enqueue(callback);
}
}
public static void ScheduleGraphics(System.Action callback)
{
graphicsActionQueue.Enqueue(callback);
if (!registeredForGraphicsCallbacks)
{
registeredForGraphicsCallbacks = true;
MLGraphicsHooks.OnPreBeginRenderFrame += ExecuteOnPreBeginRenderFrameTasks;
}
}
private static void ExecuteOnPreBeginRenderFrameTasks()
{
while (graphicsActionQueue.TryDequeue(out System.Action action))
{
action();
}
}
///
/// A method that queues an action without a payload
///
/// A callback function to be called when the delegate is invoked
public static void Call(System.Delegate callback)
{
if (callback != null)
{
System.Action call = delegate
{
DispatchNoPayload newDispatch = new DispatchNoPayload(Cast(callback));
actionQueue.Enqueue(newDispatch);
};
call();
}
}
///
/// A method that queues an action without a payload
///
/// A callback function to be called when the action is invoked
public static void Call(System.Action callback)
{
if (callback != null)
{
System.Action call = delegate
{
DispatchNoPayload newDispatch = new DispatchNoPayload(callback);
actionQueue.Enqueue(newDispatch);
};
call();
}
}
///
/// A template method that queues an action with a single payload
///
/// Payload type
/// Payload 1
/// A callback function to be called when the delegate is invoked
public static void Call(A a, System.Delegate callback)
{
if (callback != null)
{
System.Action call = delegate (A arg1)
{
DispatchPayload1 newDispatch = new DispatchPayload1(arg1, Cast>(callback));
actionQueue.Enqueue(newDispatch);
};
call(a);
}
}
///
/// A template method that queues an action with two payloads
///
/// First payload type
/// Second payload type
/// Payload 1
/// Payload 2
/// A callback function to be called when the delegate is invoked
public static void Call(A a, B b, System.Delegate callback)
{
if (callback != null)
{
System.Action call = delegate (A arg1, B arg2)
{
DispatchPayload2 newDispatch = new DispatchPayload2(arg1, arg2, Cast>(callback));
actionQueue.Enqueue(newDispatch);
};
call(a, b);
}
}
///
/// A template method that queues an action with three payloads
///
/// First payload type
/// Second payload type
/// Third payload type
/// Payload 1
/// Payload 2
/// Payload 3
/// A callback function to be called when the delegate is invoked
public static void Call(A a, B b, C c, System.Delegate callback)
{
if (callback != null)
{
System.Action call = delegate (A arg1, B arg2, C arg3)
{
DispatchPayload3 newDispatch = new DispatchPayload3(arg1, arg2, arg3, Cast>(callback));
actionQueue.Enqueue(newDispatch);
};
call(a, b, c);
}
}
///
/// A template method that queues an action with four payloads
///
/// First payload type
/// Second payload type
/// Third payload type
/// Forth payload type
/// Payload 1
/// Payload 2
/// Payload 3
/// Payload 4
/// A callback function to be called when the delegate is invoked
public static void Call(A a, B b, C c, D d, System.Delegate callback)
{
if (callback != null)
{
System.Action call = delegate (A arg1, B arg2, C arg3, D arg4)
{
DispatchPayload4 newDispatch = new DispatchPayload4(arg1, arg2, arg3, arg4, Cast>(callback));
actionQueue.Enqueue(newDispatch);
};
call(a, b, c, d);
}
}
///
/// A template method that queues an action with five payloads
///
/// First payload type
/// Second payload type
/// Third payload type
/// Forth payload type
/// Fifth payload type
/// Payload 1
/// Payload 2
/// Payload 3
/// Payload 4
/// Payload 5
/// A callback function to be called when the delegate is invoked
public static void Call(A a, B b, C c, D d, E e, System.Delegate callback)
{
if (callback != null)
{
System.Action call = delegate (A arg1, B arg2, C arg3, D arg4, E arg5)
{
DispatchPayload5 newDispatch = new DispatchPayload5(arg1, arg2, arg3, arg4, arg5, Cast>(callback));
actionQueue.Enqueue(newDispatch);
};
call(a, b, c, d, e);
}
}
///
/// A template method that queues an action with six payloads
///
/// First payload type
/// Second payload type
/// Third payload type
/// Forth payload type
/// Fifth payload type
/// Sixth payload type
/// Payload 1
/// Payload 2
/// Payload 3
/// Payload 4
/// Payload 5
/// Payload 6
/// A callback function to be called when the delegate is invoked
public static void Call(A a, B b, C c, D d, E e, F f, System.Delegate callback)
{
if (callback != null)
{
System.Action call = delegate (A arg1, B arg2, C arg3, D arg4, E arg5, F arg6)
{
DispatchPayload6 newDispatch = new DispatchPayload6(arg1, arg2, arg3, arg4, arg5, arg6, Cast>(callback));
actionQueue.Enqueue(newDispatch);
};
call(a, b, c, d, e, f);
}
}
///
/// Dispatch all queued items
///
public static void DispatchAll()
{
Dispatcher callbacks;
System.Action action;
while (actionQueue.TryDequeue(out callbacks))
{
callbacks.Dispatch();
}
while (mainActionQueue.TryDequeue(out action))
{
action();
}
if (thread == null && !itemizedWork.IsEmpty)
{
thread = new Thread(ExecuteBackgroundThread);
thread.Start();
}
}
///
/// Casts a delegate of unknown type to a delegate of a defined type.
///
/// Type of delegate to cast to
/// Original delegate
/// Returns a newly constructed delegate of the specified type or null if a conversion doesn't exist
private static T Cast(Delegate source) where T : class
{
if (source == null)
{
return null;
}
Delegate[] delegates = source.GetInvocationList();
if (delegates.Length > 1)
{
Delegate[] delegatesDest = new Delegate[delegates.Length];
for (int nn = 0; nn < delegates.Length; nn++)
{
delegatesDest[nn] = Delegate.CreateDelegate(typeof(T), delegates[nn].Target, delegates[nn].Method);
}
return Delegate.Combine(delegatesDest) as T;
}
return Delegate.CreateDelegate(typeof(T), delegates[0].Target, delegates[0].Method) as T;
}
///
/// Static method that executes the background worker thread.
///
/// Optional object
private static void ExecuteBackgroundThread(object obj)
{
Thread.CurrentThread.IsBackground = true;
while (true)
{
Func function;
if (itemizedWork.TryDequeue(out function))
{
bool result = function();
if (!result)
{
// Not done yet. Put it at the end of the queue to try again later.
itemizedWork.Enqueue(function);
}
}
else
{
// Yield a reasonable timeslice.
Thread.Sleep(5);
}
}
}
///
/// Defines a generic dispatching class.
///
private abstract class Dispatcher
{
///
/// Abstract dispatch method to be called when removing callbacks from the queue
///
public abstract void Dispatch();
}
///
/// Overloads the generic dispatcher to call back a method without a payload
///
private class DispatchNoPayload : Dispatcher
{
///
/// Stores the method to be dispatched
///
private System.Action action;
///
/// Initializes a new instance of the class with the supplied callback
///
/// Method to call back
public DispatchNoPayload(System.Action action)
{
this.action = action;
}
///
/// Dispatches the previously stored callback
///
public override void Dispatch()
{
this.action();
}
}
///
/// Overloads the generic dispatcher to call back a method with a single payload
///
/// Payload type
private class DispatchPayload1 : Dispatcher
{
///
/// Stores the method to be dispatched
///
private System.Action action;
///
/// Stores a copy or reference of the payload to dispatch
///
private T payload;
///
/// Initializes a new instance of the class. with the supplied callback and payload
///
/// Payload to dispatch
/// Method to call back
public DispatchPayload1(T payload, System.Action action)
{
this.payload = payload;
this.action = action;
}
///
/// Dispatches the previously stored callback with the supplied payload
///
public override void Dispatch()
{
this.action(this.payload);
}
}
///
/// Overloads the generic dispatcher to call back a method with two payloads
///
/// First payload type
/// Second payload type
private class DispatchPayload2 : Dispatcher
{
///
/// Stores the method to be dispatched
///
private System.Action action;
///
/// Stores a copy or reference of a payload to dispatch
///
private A payload1;
///
/// Stores a copy or reference of a payload to dispatch
///
private B payload2;
///
/// Initializes a new instance of the class with the supplied callback and payloads
///
/// First payload
/// Second payload
/// Method to dispatch
public DispatchPayload2(A payload1, B payload2, System.Action action)
{
this.payload1 = payload1;
this.payload2 = payload2;
this.action = action;
}
///
/// Dispatches the previously stored callback with the supplied payloads
///
public override void Dispatch()
{
this.action(this.payload1, this.payload2);
}
}
///
/// Overloads the generic dispatcher to call back a method with three payloads
///
/// First payload type
/// Second payload type
/// Third payload type
private class DispatchPayload3 : Dispatcher
{
///
/// Stores the method to be dispatched
///
private System.Action action;
///
/// Stores a copy or reference of a payload to dispatch
///
private A payload1;
///
/// Stores a copy or reference of a payload to dispatch
///
private B payload2;
///
/// Stores a copy or reference of a payload to dispatch
///
private C payload3;
///
/// Initializes a new instance of the class with the supplied callback and payloads
///
/// First payload
/// Second payload
/// Third payload
/// Method to dispatch
public DispatchPayload3(A payload1, B payload2, C payload3, System.Action action)
{
this.payload1 = payload1;
this.payload2 = payload2;
this.payload3 = payload3;
this.action = action;
}
///
/// Dispatches the previously stored callback with the supplied payloads
///
public override void Dispatch()
{
this.action(this.payload1, this.payload2, this.payload3);
}
}
///
/// Overloads the generic dispatcher to call back a method with four payloads
///
/// First payload type
/// Second payload type
/// Third payload type
/// Forth payload type
private class DispatchPayload4 : Dispatcher
{
///
/// Stores the method to be dispatched
///
private System.Action action;
///
/// Stores a copy or reference of a payload to dispatch
///
private A payload1;
///
/// Stores a copy or reference of a payload to dispatch
///
private B payload2;
///
/// Stores a copy or reference of a payload to dispatch
///
private C payload3;
///
/// Stores a copy or reference of a payload to dispatch
///
private D payload4;
///
/// Initializes a new instance of the class with the supplied callback and payloads
///
/// First payload
/// Second payload
/// Third payload
/// Forth payload
/// Method to dispatch
public DispatchPayload4(A payload1, B payload2, C payload3, D payload4, System.Action action)
{
this.payload1 = payload1;
this.payload2 = payload2;
this.payload3 = payload3;
this.payload4 = payload4;
this.action = action;
}
///
/// Dispatches the previously stored callback with the supplied payloads
///
public override void Dispatch()
{
this.action(this.payload1, this.payload2, this.payload3, this.payload4);
}
}
///
/// Overloads the generic dispatcher to call back a method with four payloads
///
/// First payload type
/// Second payload type
/// Third payload type
/// Forth payload type
/// Fifth payload type
private class DispatchPayload5 : Dispatcher
{
///
/// Stores the method to be dispatched
///
private System.Action action;
///
/// Stores a copy or reference of a payload to dispatch
///
private A payload1;
///
/// Stores a copy or reference of a payload to dispatch
///
private B payload2;
///
/// Stores a copy or reference of a payload to dispatch
///
private C payload3;
///
/// Stores a copy or reference of a payload to dispatch
///
private D payload4;
///
/// Stores a copy or reference of a payload to dispatch
///
private E payload5;
///
/// Initializes a new instance of the class with the supplied callback and payloads
///
/// First payload
/// Second payload
/// Third payload
/// Forth payload
/// Fifth payload
/// Method to dispatch
public DispatchPayload5(A payload1, B payload2, C payload3, D payload4, E payload5, System.Action action)
{
this.payload1 = payload1;
this.payload2 = payload2;
this.payload3 = payload3;
this.payload4 = payload4;
this.payload5 = payload5;
this.action = action;
}
///
/// Dispatches the previously stored callback with the supplied payloads
///
public override void Dispatch()
{
this.action(this.payload1, this.payload2, this.payload3, this.payload4, this.payload5);
}
}
///
/// Overloads the generic dispatcher to call back a method with four payloads
///
/// First payload type
/// Second payload type
/// Third payload type
/// Forth payload type
/// Fifth payload type
/// Sixth payload type
private class DispatchPayload6 : Dispatcher
{
///
/// Stores the method to be dispatched
///
private System.Action action;
///
/// Stores a copy or reference of a payload to dispatch
///
private A payload1;
///
/// Stores a copy or reference of a payload to dispatch
///
private B payload2;
///
/// Stores a copy or reference of a payload to dispatch
///
private C payload3;
///
/// Stores a copy or reference of a payload to dispatch
///
private D payload4;
///
/// Stores a copy or reference of a payload to dispatch
///
private E payload5;
///
/// Stores a copy or reference of a payload to dispatch
///
private F payload6;
///
/// Initializes a new instance of the class with the supplied callback and payloads
///
/// First payload
/// Second payload
/// Third payload
/// Forth payload
/// Fifth payload
/// Sixth payload
/// Method to dispatch
public DispatchPayload6(A payload1, B payload2, C payload3, D payload4, E payload5, F payload6, System.Action action)
{
this.payload1 = payload1;
this.payload2 = payload2;
this.payload3 = payload3;
this.payload4 = payload4;
this.payload5 = payload5;
this.payload6 = payload6;
this.action = action;
}
///
/// Dispatches the previously stored callback with the supplied payloads
///
public override void Dispatch()
{
this.action(this.payload1, this.payload2, this.payload3, this.payload4, this.payload5, this.payload6);
}
}
}
}