Skip to content

Commit 830b97d

Browse files
committed
Merge branch 'shortcuts' into merge
2 parents b5d22b2 + 66c60cf commit 830b97d

6 files changed

Lines changed: 164 additions & 36 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Tool created mainly to solve old problem with reloading [native plugins](https:/
3434
- 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).
3535
- Options are accessible via `DllManipulatorScript` editor or window.
3636
- Although this tool presumably works in built game, it's intended to be used in editor.
37+
- Get callbacks in C# when the dll load state has changed with attributes like `[NativeDllLoadedTrigger]`, see `Attributes.cs` for more information
38+
- Unload and load all DLLs via shortcut `Alt+D` and `Alt+Shfit+D` respectively. Editable in the Shortcut Manager for 2019.1+
3739

3840
## Limitations
3941
- 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: 89 additions & 24 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;
@@ -42,10 +43,12 @@ public partial class DllManipulator
4243
private static List<NativeFunction> _mockedNativeFunctions = new List<NativeFunction>();
4344
private static int _createdDelegateTypes = 0;
4445
private static int _lastNativeCallIndex = 0; //Use with synchronization
45-
private static List<MethodInfo> _customLoadedTriggers = null;
46-
private static List<MethodInfo> _customBeforeUnloadTriggers = null;
47-
private static List<MethodInfo> _customAfterUnloadTriggers = null;
48-
46+
47+
private static List<Tuple<MethodInfo, bool>> _customLoadedTriggers = null; //List of callbacks to run, whether to run them on the main thread.
48+
private static List<Tuple<MethodInfo, bool>> _customBeforeUnloadTriggers = null;
49+
private static List<Tuple<MethodInfo, bool>> _customAfterUnloadTriggers = null;
50+
private static ConcurrentQueue<Tuple<MethodInfo, object[]>> _mainThreadTriggerQueue = new ConcurrentQueue<Tuple<MethodInfo, object[]>>();
51+
4952
/// <summary>
5053
/// Initialization.
5154
/// Finds and mocks relevant native function declarations.
@@ -91,17 +94,16 @@ internal static void Initialize(int unityMainThreadId, string assetsPath)
9194
if (Options.mockAllNativeFunctions || method.IsDefined(typeof(MockNativeDeclarationAttribute)) || method.DeclaringType.IsDefined(typeof(MockNativeDeclarationsAttribute)))
9295
MockNativeFunction(method);
9396
}
94-
else if(method.IsDefined(typeof(NativeDllLoadedTriggerAttribute)))
95-
{
96-
RegisterTriggerMethod(method, ref _customLoadedTriggers);
97-
}
98-
else if (method.IsDefined(typeof(NativeDllBeforeUnloadTriggerAttribute)))
97+
else
9998
{
100-
RegisterTriggerMethod(method, ref _customBeforeUnloadTriggers);
101-
}
102-
else if (method.IsDefined(typeof(NativeDllAfterUnloadTriggerAttribute)))
103-
{
104-
RegisterTriggerMethod(method, ref _customAfterUnloadTriggers);
99+
if (method.IsDefined(typeof(NativeDllLoadedTriggerAttribute)))
100+
RegisterTriggerMethod(method, ref _customLoadedTriggers, method.GetCustomAttribute<NativeDllLoadedTriggerAttribute>().UseMainThreadQueue);
101+
102+
if (method.IsDefined(typeof(NativeDllBeforeUnloadTriggerAttribute)))
103+
RegisterTriggerMethod(method, ref _customBeforeUnloadTriggers, method.GetCustomAttribute<NativeDllBeforeUnloadTriggerAttribute>().UseMainThreadQueue);
104+
105+
if (method.IsDefined(typeof(NativeDllAfterUnloadTriggerAttribute)))
106+
RegisterTriggerMethod(method, ref _customAfterUnloadTriggers, method.GetCustomAttribute<NativeDllAfterUnloadTriggerAttribute>().UseMainThreadQueue);
105107
}
106108
}
107109
}
@@ -111,18 +113,33 @@ internal static void Initialize(int unityMainThreadId, string assetsPath)
111113
LoadAll();
112114
}
113115

114-
private static void RegisterTriggerMethod(MethodInfo method, ref List<MethodInfo> triggersList)
116+
/// <summary>
117+
/// Will unload/forget all dll's and reset the state
118+
/// </summary>
119+
public static void Reset()
120+
{
121+
UnloadAll();
122+
ForgetAllDlls();
123+
ClearCrashLogs();
124+
125+
_customLoadedTriggers?.Clear();
126+
_customAfterUnloadTriggers?.Clear();
127+
_customBeforeUnloadTriggers?.Clear();
128+
}
129+
130+
private static void RegisterTriggerMethod(MethodInfo method, ref List<Tuple<MethodInfo, bool>> triggersList, bool runOnMainThread)
115131
{
116132
var parameters = method.GetParameters();
117-
if (parameters.Length == 0 || parameters.Length == 1 && parameters[0].ParameterType == typeof(NativeDll))
133+
if (parameters.Length == 0 || parameters.Length == 1 && parameters[0].ParameterType == typeof(NativeDll)
134+
|| parameters.Length == 2 && parameters[0].ParameterType == typeof(NativeDll) && parameters[1].ParameterType == typeof(int))
118135
{
119136
if (triggersList == null)
120-
triggersList = new List<MethodInfo>(2);
121-
triggersList.Add(method);
137+
triggersList = new List<Tuple<MethodInfo, bool>>();
138+
triggersList.Add(new Tuple<MethodInfo, bool>(method, runOnMainThread));
122139
}
123140
else
124141
{
125-
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}");
142+
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}");
126143
}
127144
}
128145

@@ -519,20 +536,41 @@ internal static void LoadTargetFunction(NativeFunction nativeFunction, bool igno
519536
}
520537
}
521538

522-
private static void InvokeCustomTriggers(List<MethodInfo> triggers, NativeDll dll)
539+
private static void InvokeCustomTriggers(List<Tuple<MethodInfo, bool>> triggers, NativeDll dll)
523540
{
524541
if (triggers == null)
525542
return;
526543

527-
foreach(var triggerMethod in triggers)
544+
foreach(var (methodInfo, useMainThreadQueue) in triggers)
528545
{
529-
if (triggerMethod.GetParameters().Length == 1)
530-
triggerMethod.Invoke(null, new object[] { dll });
546+
object[] args;
547+
548+
// Determine args for method
549+
if (methodInfo.GetParameters().Length == 2)
550+
args = new object[] { dll, _unityMainThreadId };
551+
else if (methodInfo.GetParameters().Length == 1)
552+
args = new object[] { dll };
531553
else
532-
triggerMethod.Invoke(null, Array.Empty<object>());
554+
args = Array.Empty<object>();
555+
556+
// Execute now or queue to the main thread
557+
if (useMainThreadQueue /*&& Thread.CurrentThread.ManagedThreadId != _unityMainThreadId*/)
558+
_mainThreadTriggerQueue.Enqueue(new Tuple<MethodInfo, object[]>(methodInfo, args));
559+
else
560+
methodInfo.Invoke(null, args);
533561
}
534562
}
535563

564+
/// <summary>
565+
/// Executes queued methods.
566+
/// Should be called from the main thread in Update.
567+
/// </summary>
568+
public static void InvokeMainThreadQueue()
569+
{
570+
while (_mainThreadTriggerQueue.TryDequeue(out var action))
571+
action.Item1.Invoke(null, action.Item2);
572+
}
573+
536574
/// <summary>
537575
/// Logs native function's call to file. If that file exists, it is overwritten. One file is maintained for each thread.
538576
/// Note: This method is being called by dynamically generated code. Be careful when changing its signature.
@@ -664,6 +702,33 @@ public class DllManipulatorOptions
664702
public bool mockAllNativeFunctions;
665703
public bool onlyInEditor;
666704
public bool enableInEditMode;
705+
706+
public DllManipulatorOptions CloneTo(DllManipulatorOptions other)
707+
{
708+
other.dllPathPattern = dllPathPattern;
709+
other.assemblyNames = (string[]) assemblyNames.Clone();
710+
other.loadingMode = loadingMode;
711+
other.posixDlopenFlags = posixDlopenFlags;
712+
other.threadSafe = threadSafe;
713+
other.enableCrashLogs = enableCrashLogs;
714+
other.crashLogsDir = crashLogsDir;
715+
other.crashLogsStackTrace = crashLogsStackTrace;
716+
other.mockAllNativeFunctions = mockAllNativeFunctions;
717+
other.onlyInEditor = onlyInEditor;
718+
other.enableInEditMode = enableInEditMode;
719+
720+
return other;
721+
}
722+
723+
public bool Equals(DllManipulatorOptions other)
724+
{
725+
return other.dllPathPattern == dllPathPattern && other.assemblyNames.SequenceEqual(assemblyNames) &&
726+
other.loadingMode == loadingMode && other.posixDlopenFlags == posixDlopenFlags &&
727+
other.threadSafe == threadSafe && other.enableCrashLogs == enableCrashLogs &&
728+
other.crashLogsDir == crashLogsDir && other.crashLogsStackTrace == crashLogsStackTrace &&
729+
other.mockAllNativeFunctions == mockAllNativeFunctions && other.onlyInEditor == onlyInEditor &&
730+
other.enableInEditMode == enableInEditMode;
731+
}
667732
}
668733

669734
public enum DllLoadingMode

scripts/DllManipulatorScript.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ private void OnEnable()
4747
{
4848
if (EditorApplication.isPlaying)
4949
Destroy(gameObject);
50-
else
50+
else if(_singletonInstance != this)
5151
enabled = false; //Don't destroy as the user may be editing a Prefab
5252
return;
5353
}
@@ -58,6 +58,10 @@ private void OnEnable()
5858

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

88106
private void OnDestroy()
89107
{
@@ -93,9 +111,8 @@ private void OnDestroy()
93111
//On Preloaded mode this leads to NullReferenceException, but on Lazy mode the DLL and function would be just reloaded so we would up with loaded DLL after game exit.
94112
//Thankfully thread safety with Lazy mode is not implemented yet.
95113

96-
DllManipulator.UnloadAll();
97-
DllManipulator.ForgetAllDlls();
98-
DllManipulator.ClearCrashLogs();
114+
if (DllManipulator.Options != null) // Check that we have initialized
115+
DllManipulator.Reset();
99116
_singletonInstance = null;
100117
}
101118
}

scripts/Editor/DllManipulatorEditor.cs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,23 @@ public class DllManipulatorEditor : Editor
6666
private string[] _allKnownAssemblies = null;
6767
private DateTime _lastKnownAssembliesRefreshTime;
6868

69+
/// <summary>
70+
/// To check if the options have change in order to set the object as dirty
71+
/// </summary>
72+
private DllManipulatorOptions _prevOptions = new DllManipulatorOptions();
73+
74+
public static event Action RepaintAllEditors = delegate {};
75+
6976
public DllManipulatorEditor()
7077
{
7178
EditorApplication.pauseStateChanged += _ => Repaint();
7279
EditorApplication.playModeStateChanged += _ => Repaint();
80+
RepaintAllEditors += Repaint;
81+
}
82+
83+
private void Awake()
84+
{
85+
((DllManipulatorScript)target).Options.CloneTo(_prevOptions);
7386
}
7487

7588
public override void OnInspectorGUI()
@@ -118,9 +131,9 @@ public override void OnInspectorGUI()
118131

119132

120133
bool unloadAll;
121-
if(EditorApplication.isPlaying && t.Options.threadSafe)
134+
if((EditorApplication.isPlaying || t.Options.enableInEditMode) && t.Options.threadSafe)
122135
unloadAll = GUILayout.Button(UNLOAD_ALL_DLLS_WITH_THREAD_SAFETY_GUI_CONTENT);
123-
else if (EditorApplication.isPlaying && !EditorApplication.isPaused && t.Options.loadingMode == DllLoadingMode.Preload)
136+
else if ((EditorApplication.isPlaying && !EditorApplication.isPaused || t.Options.enableInEditMode) && t.Options.loadingMode == DllLoadingMode.Preload)
124137
unloadAll = GUILayout.Button(UNLOAD_ALL_DLLS_IN_PLAY_PRELOADED_GUI_CONTENT);
125138
else
126139
unloadAll = GUILayout.Button("Unload all DLLs");
@@ -131,7 +144,7 @@ public override void OnInspectorGUI()
131144

132145
DrawUsedDlls(usedDlls);
133146
}
134-
else if(EditorApplication.isPlaying)
147+
else if(EditorApplication.isPlaying || t.Options.enableInEditMode)
135148
{
136149
GUILayout.BeginHorizontal();
137150
GUILayout.FlexibleSpace();
@@ -140,13 +153,23 @@ public override void OnInspectorGUI()
140153
GUILayout.EndHorizontal();
141154
}
142155

143-
if(EditorApplication.isPlaying && t.InitializationTime != null)
156+
if((EditorApplication.isPlaying || t.Options.enableInEditMode) && t.InitializationTime != null)
144157
{
145158
EditorGUILayout.Space();
146159
EditorGUILayout.Space();
147160
var time = t.InitializationTime.Value;
148161
EditorGUILayout.LabelField($"Initialized in: {(int)time.TotalSeconds}.{time.Milliseconds.ToString("D3")}s");
149162
}
163+
164+
// Set the target as dirty so changes can be saved, if there are changes
165+
if (GUI.changed)
166+
{
167+
if (!t.Options.Equals(_prevOptions))
168+
{
169+
t.Options.CloneTo(_prevOptions);
170+
EditorUtility.SetDirty(target);
171+
}
172+
}
150173
}
151174

152175
private void DrawUsedDlls(IList<NativeDllInfo> usedDlls)
@@ -362,5 +385,12 @@ public static void UnloadAll()
362385
{
363386
DllManipulator.UnloadAll();
364387
}
388+
389+
[NativeDllLoadedTrigger(UseMainThreadQueue = true)]
390+
[NativeDllAfterUnloadTrigger(UseMainThreadQueue = true)]
391+
public static void RepaintAll()
392+
{
393+
RepaintAllEditors.Invoke();
394+
}
365395
}
366396
}

scripts/Editor/DllManipulatorWindowEditor.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ namespace UnityNativeTool.Internal
66
{
77
public class DllManipulatorWindowEditor : EditorWindow
88
{
9+
private static EditorWindow window;
10+
911
[MenuItem("Window/Dll manipulator")]
1012
static void Init()
1113
{
12-
var window = GetWindow<DllManipulatorWindowEditor>();
14+
window = GetWindow<DllManipulatorWindowEditor>();
1315
window.Show();
16+
DllManipulatorEditor.RepaintAllEditors += window.Repaint;
1417
}
1518

1619
void OnGUI()

0 commit comments

Comments
 (0)