Skip to content

Commit 39c4fce

Browse files
committed
Custom triggers optional execute on main thread
Allow custom triggers to be executed on the main thread in a queue. We need this internally to repaint the editor GUI as it uses the Unity API.
1 parent 1a33f4d commit 39c4fce

5 files changed

Lines changed: 78 additions & 23 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Tool created mainly to solve old problem with reloading [native plugins](https:/
3030
- If something is not working, first check out available options (and read their descriptions), then [report an issue](https://github.com/mcpiroman/UnityNativeTool/issues/new).
3131
- Options are accessible via `DllManipulatorScript` editor or window.
3232
- Although this tool presumably works in built game, it's intended to be used in editor.
33+
- Get callbacks in C# when the dll load state has changed with attributes like `[NativeDllLoadedTrigger]`, see `Attributes.cs` for more information
3334

3435
## Limitations
3536
- Marshaling parameter attributes other than `[MarshalAs]`, `[In]` and `[Out]` are not supported.

scripts/Attributes.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,24 @@ public class DisableMockingAttribute : Attribute
3030

3131
}
3232

33+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
34+
public class TriggerAttribute : Attribute
35+
{
36+
/// <summary>
37+
/// Should the method always be executed on the main thread, to allow use of the Unity API.
38+
/// Note: this means the method is not immediately executed but put in a queue.
39+
/// For consistent behaviour, the method is put in the queue even if it is triggered from the main thread.
40+
/// </summary>
41+
public bool UseMainThreadQueue = false;
42+
}
43+
3344
/// <summary>
3445
/// Methods with this attribute are called directly after a native DLL has been loaded.
3546
/// Such method must be <see langword="static"/> and either have no parameters or one parameter of type <see cref="NativeDll"/>
3647
/// which indicates the state of the dll being loaded. Please treat this parameter as readonly.
3748
/// </summary>
3849
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
39-
public class NativeDllLoadedTriggerAttribute : Attribute
50+
public class NativeDllLoadedTriggerAttribute : TriggerAttribute
4051
{
4152

4253
}
@@ -47,7 +58,7 @@ public class NativeDllLoadedTriggerAttribute : Attribute
4758
/// which indicates the state of the dll being unloaded. Please treat this parameter as readonly.
4859
/// </summary>
4960
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
50-
public class NativeDllBeforeUnloadTriggerAttribute : Attribute
61+
public class NativeDllBeforeUnloadTriggerAttribute : TriggerAttribute
5162
{
5263

5364
}
@@ -58,7 +69,7 @@ public class NativeDllBeforeUnloadTriggerAttribute : Attribute
5869
/// which indicates the state of the dll being unloaded. Please treat this parameter as readonly.
5970
/// </summary>
6071
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
61-
public class NativeDllAfterUnloadTriggerAttribute : Attribute
72+
public class NativeDllAfterUnloadTriggerAttribute : TriggerAttribute
6273
{
6374

6475
}

scripts/DllManipulator.cs

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.Linq;
45
using System.Reflection;
@@ -30,10 +31,12 @@ public partial class DllManipulator
3031
private static List<NativeFunction> _mockedNativeFunctions = new List<NativeFunction>();
3132
private static int _createdDelegateTypes = 0;
3233
private static int _lastNativeCallIndex = 0; //Use with synchronization
33-
private static List<MethodInfo> _customLoadedTriggers = null;
34-
private static List<MethodInfo> _customBeforeUnloadTriggers = null;
35-
private static List<MethodInfo> _customAfterUnloadTriggers = null;
36-
34+
35+
private static List<Tuple<MethodInfo, bool>> _customLoadedTriggers = null; //List of callbacks to run, whether to run them on the main thread.
36+
private static List<Tuple<MethodInfo, bool>> _customBeforeUnloadTriggers = null;
37+
private static List<Tuple<MethodInfo, bool>> _customAfterUnloadTriggers = null;
38+
private static ConcurrentQueue<Tuple<MethodInfo, object[]>> _mainThreadTriggerQueue = new ConcurrentQueue<Tuple<MethodInfo, object[]>>();
39+
3740
/// <summary>
3841
/// Initialization.
3942
/// Finds and mocks relevant native function declarations.
@@ -84,13 +87,13 @@ internal static void Initialize(int unityMainThreadId, string assetsPath)
8487
else
8588
{
8689
if (method.IsDefined(typeof(NativeDllLoadedTriggerAttribute)))
87-
RegisterTriggerMethod(method, ref _customLoadedTriggers);
90+
RegisterTriggerMethod(method, ref _customLoadedTriggers, method.GetCustomAttribute<NativeDllLoadedTriggerAttribute>().UseMainThreadQueue);
8891

8992
if (method.IsDefined(typeof(NativeDllBeforeUnloadTriggerAttribute)))
90-
RegisterTriggerMethod(method, ref _customBeforeUnloadTriggers);
93+
RegisterTriggerMethod(method, ref _customBeforeUnloadTriggers, method.GetCustomAttribute<NativeDllBeforeUnloadTriggerAttribute>().UseMainThreadQueue);
9194

9295
if (method.IsDefined(typeof(NativeDllAfterUnloadTriggerAttribute)))
93-
RegisterTriggerMethod(method, ref _customAfterUnloadTriggers);
96+
RegisterTriggerMethod(method, ref _customAfterUnloadTriggers, method.GetCustomAttribute<NativeDllAfterUnloadTriggerAttribute>().UseMainThreadQueue);
9497
}
9598
}
9699
}
@@ -113,19 +116,20 @@ public static void Reset()
113116
_customAfterUnloadTriggers?.Clear();
114117
_customBeforeUnloadTriggers?.Clear();
115118
}
116-
117-
private static void RegisterTriggerMethod(MethodInfo method, ref List<MethodInfo> triggersList)
119+
120+
private static void RegisterTriggerMethod(MethodInfo method, ref List<Tuple<MethodInfo, bool>> triggersList, bool runOnMainThread)
118121
{
119122
var parameters = method.GetParameters();
120-
if (parameters.Length == 0 || parameters.Length == 1 && parameters[0].ParameterType == typeof(NativeDll))
123+
if (parameters.Length == 0 || parameters.Length == 1 && parameters[0].ParameterType == typeof(NativeDll)
124+
|| parameters.Length == 2 && parameters[0].ParameterType == typeof(NativeDll) && parameters[1].ParameterType == typeof(int))
121125
{
122126
if (triggersList == null)
123-
triggersList = new List<MethodInfo>(2);
124-
triggersList.Add(method);
127+
triggersList = new List<Tuple<MethodInfo, bool>>();
128+
triggersList.Add(new Tuple<MethodInfo, bool>(method, runOnMainThread));
125129
}
126130
else
127131
{
128-
Debug.LogError($"Trigger method must either take no parameters or one parameter of type {nameof(NativeDll)}. Violation on method {method.Name} in {method.DeclaringType.FullName}");
132+
Debug.LogError($"Trigger method must either take no parameters or one parameter of type {nameof(NativeDll)} or two of type {nameof(NativeDll)} and int. Violation on method {method.Name} in {method.DeclaringType.FullName}");
129133
}
130134
}
131135

@@ -522,20 +526,41 @@ internal static void LoadTargetFunction(NativeFunction nativeFunction, bool igno
522526
}
523527
}
524528

525-
private static void InvokeCustomTriggers(List<MethodInfo> triggers, NativeDll dll)
529+
private static void InvokeCustomTriggers(List<Tuple<MethodInfo, bool>> triggers, NativeDll dll)
526530
{
527531
if (triggers == null)
528532
return;
529533

530-
foreach(var triggerMethod in triggers)
534+
foreach(var (methodInfo, useMainThreadQueue) in triggers)
531535
{
532-
if (triggerMethod.GetParameters().Length == 1)
533-
triggerMethod.Invoke(null, new object[] { dll });
536+
object[] args;
537+
538+
// Determine args for method
539+
if (methodInfo.GetParameters().Length == 2)
540+
args = new object[] { dll, _unityMainThreadId };
541+
else if (methodInfo.GetParameters().Length == 1)
542+
args = new object[] { dll };
543+
else
544+
args = Array.Empty<object>();
545+
546+
// Execute now or queue to the main thread
547+
if (useMainThreadQueue /*&& Thread.CurrentThread.ManagedThreadId != _unityMainThreadId*/)
548+
_mainThreadTriggerQueue.Enqueue(new Tuple<MethodInfo, object[]>(methodInfo, args));
534549
else
535-
triggerMethod.Invoke(null, Array.Empty<object>());
550+
methodInfo.Invoke(null, args);
536551
}
537552
}
538553

554+
/// <summary>
555+
/// Executes queued methods.
556+
/// Should be called from the main thread in Update.
557+
/// </summary>
558+
public static void InvokeMainThreadQueue()
559+
{
560+
while (_mainThreadTriggerQueue.TryDequeue(out var action))
561+
action.Item1.Invoke(null, action.Item2);
562+
}
563+
539564
/// <summary>
540565
/// Logs native function's call to file. If that file exists, it is overwritten. One file is maintained for each thread.
541566
/// Note: This method is being called by dynamically generated code. Be careful when changing its signature.

scripts/DllManipulatorScript.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ private void OnEnable()
5757

5858
if(EditorApplication.isPlaying || Options.enableInEditMode)
5959
Initialize();
60+
61+
if(!EditorApplication.isPlaying && Options.enableInEditMode)
62+
EditorApplication.update += Update;
63+
6064
#else
6165
if (Options.onlyInEditor)
6266
return;
@@ -83,6 +87,20 @@ private void Initialize()
8387
initTimer.Stop();
8488
InitializationTime = initTimer.Elapsed;
8589
}
90+
91+
/// <summary>
92+
/// Note: also called in edit mode if Options.enableInEditMode is set.
93+
/// </summary>
94+
private void Update()
95+
{
96+
DllManipulator.InvokeMainThreadQueue();
97+
}
98+
99+
private void OnDisable()
100+
{
101+
if(!EditorApplication.isPlaying && Options.enableInEditMode)
102+
EditorApplication.update -= Update;
103+
}
86104

87105
private void OnDestroy()
88106
{

scripts/Editor/DllManipulatorEditor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,8 @@ public static void UnloadAll()
348348
DllManipulator.UnloadAll();
349349
}
350350

351-
[NativeDllLoadedTrigger]
352-
[NativeDllAfterUnloadTrigger]
351+
[NativeDllLoadedTrigger(UseMainThreadQueue = true)]
352+
[NativeDllAfterUnloadTrigger(UseMainThreadQueue = true)]
353353
public static void RepaintAll()
354354
{
355355
RepaintAllEditors.Invoke();

0 commit comments

Comments
 (0)