// %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); } } } }