Unity 编辑器的线程安全文件系统观察器
Thread safe File System Watcher for Unity Editor
我需要一个线程安全的 class 供 File System Watcher 在 Unity Editor 中使用,我已经知道线程不可能脱离协程,但我不知道也不允许线程在编辑器中。
所以,我的错误是:
get_isEditor can only be called from the main thread. Constructors and
field initializers will be executed from the loading thread when
loading a scene. Don't use this function in the constructor or field
initializers, instead move initialization code to the Awake or Start
function. 0x0000000140E431ED (Unity) StackWalker::GetCurrentCallstack
0x0000000140E44EE1 (Unity) StackWalker::ShowCallstack
0x00000001405FC603 (Unity) GetStacktrace 0x00000001405F97FE (Unity)
DebugStringToFile 0x00000001405F9C5C (Unity) DebugStringToFile
0x000000014035F7B3 (Unity) ThreadAndSerializationSafeCheckReportError
0x0000000140E7B988 (Unity) Application_Get_Custom_PropIsEditor
0x0000000015AC46AA (Mono JIT Code) (wrapper managed-to-native)
UnityEngine.Application:get_isEditor () 0x0000000015AC42FE (Mono JIT
Code) [Helpers.cs:585] Lerp2API.DebugHandler.Debug:Log (object)
0x0000000015AC41C2 (Mono JIT Code) [Helpers.cs:578]
Lerp2API.DebugHandler.Debug:Log (string) 0x0000000015AC40F7 (Mono JIT
Code) [LerpedEditorCore.cs:101]
Lerp2APIEditor.LerpedEditorCore:Recompile
(object,System.IO.FileSystemEventArgs) 0x0000000015AC3F2D (Mono JIT
Code) (wrapper runtime-invoke)
:runtime_invoke_void__this___object_object
(object,intptr,intptr,intptr) 0x00007FFB400A519B (mono) [mini.c:4937]
mono_jit_runtime_invoke 0x00007FFB3FFF84FD (mono) [object.c:2623]
mono_runtime_invoke 0x00007FFB3FFFE8F7 (mono) [object.c:3827]
mono_runtime_invoke_array 0x00007FFB3FFFEBCC (mono) [object.c:5457]
mono_message_invoke 0x00007FFB4001EB8B (mono) [threadpool.c:1019]
mono_async_invoke 0x00007FFB4001F5E2 (mono) [threadpool.c:1455]
async_invoke_thread 0x00007FFB4002329F (mono) [threads.c:685]
start_wrapper 0x00007FFB400D78C9 (mono) [win32_threads.c:599]
thread_start 0x00007FFB77FC8364 (KERNEL32) BaseThreadInitThunk
我复制了完整的堆栈跟踪以了解可能出现问题的任何助手。因为,我搜索了一个解决方案,就像任何线程安全的 FWS,是的,有一个,但仅适用于 .NET 4,我需要一个适用于 .NET 2
这是我的代码:
using System.IO; //class, namespace, redundant info...
private static FileSystemWatcher m_Watcher;
[InitializeOnLoadMethod]
static void HookWatcher()
{
m_Watcher = new FileSystemWatcher("path", "*.cs");
m_Watcher.NotifyFilter = NotifyFilters.LastWrite;
m_Watcher.IncludeSubdirectories = true;
//m_Watcher.Created += new FileSystemEventHandler(); //Add to the solution before compile
//m_Watcher.Renamed += new FileSystemEventHandler(); //Rename to the solution before compile
//m_Watcher.Deleted += new FileSystemEventHandler(); //Remove to the solution before compile
m_Watcher.Changed += Recompile;
m_Watcher.EnableRaisingEvents = true;
}
private static void Recompile(object sender, FileSystemEventArgs e)
{
Debug.Log("Origin files has been changed!");
}
如您所见,没有什么特别的...
我这样做的目的很简单,我有一个从我当前的 Unity 项目中分离出来的 DLL,想法很简单,我想在 DLL 的项目发生任何变化时自动从 Unity 重新编译所有内容,但是我由于线程无法实现,那我该怎么办?是否有任何替代方案可以监听与 Unity 兼容的文件?
谢谢。
根据我的经验,您可以使用线程,但您必须注意只能从主线程访问 Unity 类。我的建议是当你的看门狗发出警报时,将控制权移交给主线程。
static bool _triggerRecompile = false;
[InitializeOnLoadMethod]
static void HookWatcher()
{
m_Watcher = new FileSystemWatcher("path", "*.cs");
// ....
m_Watcher.Changed += Recompile;
EditorApplication.update += OnEditorApplicationUpdate;
}
private static void Recompile(object sender, FileSystemEventArgs e)
{
bool _triggerRecompile = true;
// Never call any Unity classes as we are not in the main thread
}
static void OnEditorApplicationUpdate ()
{
// note that this is called very often (100/sec)
if (_triggerRecompile)
{
_triggerRecompile = false;
Debug.Log("Origin files has been changed!");
DoRecompile();
}
}
轮询当然有点讨厌和丑陋。一般来说,我更喜欢基于事件的方法。但在这种特殊情况下,我认为没有机会欺骗主线程规则。
在@Kay的帮助下解决了,谢谢@Kay!
我想做一个更通用的答案,所以我决定自己做一个 class 来实现我想要的。这是结果:
using System;
using System.IO;
using System.Collections.Generic;
namespace Lerp2APIEditor.Utility
{
public class LerpedThread<T>
{
public T value = default(T);
public bool isCalled = false;
public string methodCalled = "";
public Dictionary<string, Action> matchedMethods = new Dictionary<string, Action>();
public FileSystemWatcher FSW
{
get
{
return (FileSystemWatcher)(object)value;
}
}
public LerpedThread(string name, FSWParams pars)
{
if(typeof(T) == typeof(FileSystemWatcher))
{
FileSystemWatcher watcher = new FileSystemWatcher(pars.path, pars.filter);
watcher.NotifyFilter = pars.notifiers;
watcher.IncludeSubdirectories = pars.includeSubfolders;
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnCreated);
watcher.Deleted += new FileSystemEventHandler(OnDeleted);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
ApplyChanges(watcher);
}
}
private void OnChanged(object source, FileSystemEventArgs e)
{
methodCalled = "OnChanged";
isCalled = true;
}
private void OnCreated(object source, FileSystemEventArgs e)
{
methodCalled = "OnCreated";
isCalled = true;
}
private void OnDeleted(object source, FileSystemEventArgs e)
{
methodCalled = "OnDeleted";
isCalled = true;
}
private void OnRenamed(object source, RenamedEventArgs e)
{
methodCalled = "OnRenamed";
isCalled = true;
}
public void StartFSW()
{
FSW.EnableRaisingEvents = true;
}
public void CancelFSW()
{
FSW.EnableRaisingEvents = false;
}
public void ApplyChanges<T1>(T1 obj)
{
value = (T)(object)obj;
}
}
public class FSWParams
{
public string path,
filter;
public NotifyFilters notifiers;
public bool includeSubfolders;
public FSWParams(string p, string f, NotifyFilters nf, bool isf)
{
path = p;
filter = f;
notifiers = nf;
includeSubfolders = isf;
}
}
}
主要class代码:
namespace Lerp2APIEditor
{
public class LerpedEditorCore
{
private static LerpedThread<FileSystemWatcher> m_Watcher;
[InitializeOnLoadMethod]
static void HookWatchers()
{
EditorApplication.update += OnEditorApplicationUpdate;
m_Watcher.matchedMethods.Add("OnChanged", () => {
Debug.Log("Origin files has been changed!");
});
m_Watcher.StartFSW();
}
static void OnEditorApplicationUpdate()
{
if(EditorApplication.timeSinceStartup > nextSeek)
{
if (m_Watcher.isCalled)
{
foreach (KeyValuePair<string, Action> kv in m_Watcher.matchedMethods)
if (m_Watcher.methodCalled == kv.Key)
kv.Value();
m_Watcher.isCalled = false;
}
nextSeek = EditorApplication.timeSinceStartup + threadSeek;
}
}
}
}
我做的事情很简单。我只创建了一个通用的 class 来创建一个 FSW 实例或任何你想听的东西。一次创建,我附上了只激活 bool @Kay 建议我使用的事件,以及调用的方法以确切知道调用了什么方法。
稍后在主 class 中,如果检测到更改,则 foreach 每秒循环列出的每个方法,并调用链接到字符串的方法。
我需要一个线程安全的 class 供 File System Watcher 在 Unity Editor 中使用,我已经知道线程不可能脱离协程,但我不知道也不允许线程在编辑器中。
所以,我的错误是:
get_isEditor can only be called from the main thread. Constructors and field initializers will be executed from the loading thread when loading a scene. Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function. 0x0000000140E431ED (Unity) StackWalker::GetCurrentCallstack 0x0000000140E44EE1 (Unity) StackWalker::ShowCallstack 0x00000001405FC603 (Unity) GetStacktrace 0x00000001405F97FE (Unity) DebugStringToFile 0x00000001405F9C5C (Unity) DebugStringToFile 0x000000014035F7B3 (Unity) ThreadAndSerializationSafeCheckReportError 0x0000000140E7B988 (Unity) Application_Get_Custom_PropIsEditor 0x0000000015AC46AA (Mono JIT Code) (wrapper managed-to-native) UnityEngine.Application:get_isEditor () 0x0000000015AC42FE (Mono JIT Code) [Helpers.cs:585] Lerp2API.DebugHandler.Debug:Log (object) 0x0000000015AC41C2 (Mono JIT Code) [Helpers.cs:578] Lerp2API.DebugHandler.Debug:Log (string) 0x0000000015AC40F7 (Mono JIT Code) [LerpedEditorCore.cs:101] Lerp2APIEditor.LerpedEditorCore:Recompile (object,System.IO.FileSystemEventArgs) 0x0000000015AC3F2D (Mono JIT Code) (wrapper runtime-invoke) :runtime_invoke_void__this___object_object (object,intptr,intptr,intptr) 0x00007FFB400A519B (mono) [mini.c:4937] mono_jit_runtime_invoke 0x00007FFB3FFF84FD (mono) [object.c:2623] mono_runtime_invoke 0x00007FFB3FFFE8F7 (mono) [object.c:3827] mono_runtime_invoke_array 0x00007FFB3FFFEBCC (mono) [object.c:5457] mono_message_invoke 0x00007FFB4001EB8B (mono) [threadpool.c:1019] mono_async_invoke 0x00007FFB4001F5E2 (mono) [threadpool.c:1455] async_invoke_thread 0x00007FFB4002329F (mono) [threads.c:685] start_wrapper 0x00007FFB400D78C9 (mono) [win32_threads.c:599] thread_start 0x00007FFB77FC8364 (KERNEL32) BaseThreadInitThunk
我复制了完整的堆栈跟踪以了解可能出现问题的任何助手。因为,我搜索了一个解决方案,就像任何线程安全的 FWS,是的,有一个,但仅适用于 .NET 4,我需要一个适用于 .NET 2
这是我的代码:
using System.IO; //class, namespace, redundant info...
private static FileSystemWatcher m_Watcher;
[InitializeOnLoadMethod]
static void HookWatcher()
{
m_Watcher = new FileSystemWatcher("path", "*.cs");
m_Watcher.NotifyFilter = NotifyFilters.LastWrite;
m_Watcher.IncludeSubdirectories = true;
//m_Watcher.Created += new FileSystemEventHandler(); //Add to the solution before compile
//m_Watcher.Renamed += new FileSystemEventHandler(); //Rename to the solution before compile
//m_Watcher.Deleted += new FileSystemEventHandler(); //Remove to the solution before compile
m_Watcher.Changed += Recompile;
m_Watcher.EnableRaisingEvents = true;
}
private static void Recompile(object sender, FileSystemEventArgs e)
{
Debug.Log("Origin files has been changed!");
}
如您所见,没有什么特别的...
我这样做的目的很简单,我有一个从我当前的 Unity 项目中分离出来的 DLL,想法很简单,我想在 DLL 的项目发生任何变化时自动从 Unity 重新编译所有内容,但是我由于线程无法实现,那我该怎么办?是否有任何替代方案可以监听与 Unity 兼容的文件?
谢谢。
根据我的经验,您可以使用线程,但您必须注意只能从主线程访问 Unity 类。我的建议是当你的看门狗发出警报时,将控制权移交给主线程。
static bool _triggerRecompile = false;
[InitializeOnLoadMethod]
static void HookWatcher()
{
m_Watcher = new FileSystemWatcher("path", "*.cs");
// ....
m_Watcher.Changed += Recompile;
EditorApplication.update += OnEditorApplicationUpdate;
}
private static void Recompile(object sender, FileSystemEventArgs e)
{
bool _triggerRecompile = true;
// Never call any Unity classes as we are not in the main thread
}
static void OnEditorApplicationUpdate ()
{
// note that this is called very often (100/sec)
if (_triggerRecompile)
{
_triggerRecompile = false;
Debug.Log("Origin files has been changed!");
DoRecompile();
}
}
轮询当然有点讨厌和丑陋。一般来说,我更喜欢基于事件的方法。但在这种特殊情况下,我认为没有机会欺骗主线程规则。
在@Kay的帮助下解决了,谢谢@Kay!
我想做一个更通用的答案,所以我决定自己做一个 class 来实现我想要的。这是结果:
using System;
using System.IO;
using System.Collections.Generic;
namespace Lerp2APIEditor.Utility
{
public class LerpedThread<T>
{
public T value = default(T);
public bool isCalled = false;
public string methodCalled = "";
public Dictionary<string, Action> matchedMethods = new Dictionary<string, Action>();
public FileSystemWatcher FSW
{
get
{
return (FileSystemWatcher)(object)value;
}
}
public LerpedThread(string name, FSWParams pars)
{
if(typeof(T) == typeof(FileSystemWatcher))
{
FileSystemWatcher watcher = new FileSystemWatcher(pars.path, pars.filter);
watcher.NotifyFilter = pars.notifiers;
watcher.IncludeSubdirectories = pars.includeSubfolders;
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnCreated);
watcher.Deleted += new FileSystemEventHandler(OnDeleted);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
ApplyChanges(watcher);
}
}
private void OnChanged(object source, FileSystemEventArgs e)
{
methodCalled = "OnChanged";
isCalled = true;
}
private void OnCreated(object source, FileSystemEventArgs e)
{
methodCalled = "OnCreated";
isCalled = true;
}
private void OnDeleted(object source, FileSystemEventArgs e)
{
methodCalled = "OnDeleted";
isCalled = true;
}
private void OnRenamed(object source, RenamedEventArgs e)
{
methodCalled = "OnRenamed";
isCalled = true;
}
public void StartFSW()
{
FSW.EnableRaisingEvents = true;
}
public void CancelFSW()
{
FSW.EnableRaisingEvents = false;
}
public void ApplyChanges<T1>(T1 obj)
{
value = (T)(object)obj;
}
}
public class FSWParams
{
public string path,
filter;
public NotifyFilters notifiers;
public bool includeSubfolders;
public FSWParams(string p, string f, NotifyFilters nf, bool isf)
{
path = p;
filter = f;
notifiers = nf;
includeSubfolders = isf;
}
}
}
主要class代码:
namespace Lerp2APIEditor
{
public class LerpedEditorCore
{
private static LerpedThread<FileSystemWatcher> m_Watcher;
[InitializeOnLoadMethod]
static void HookWatchers()
{
EditorApplication.update += OnEditorApplicationUpdate;
m_Watcher.matchedMethods.Add("OnChanged", () => {
Debug.Log("Origin files has been changed!");
});
m_Watcher.StartFSW();
}
static void OnEditorApplicationUpdate()
{
if(EditorApplication.timeSinceStartup > nextSeek)
{
if (m_Watcher.isCalled)
{
foreach (KeyValuePair<string, Action> kv in m_Watcher.matchedMethods)
if (m_Watcher.methodCalled == kv.Key)
kv.Value();
m_Watcher.isCalled = false;
}
nextSeek = EditorApplication.timeSinceStartup + threadSeek;
}
}
}
}
我做的事情很简单。我只创建了一个通用的 class 来创建一个 FSW 实例或任何你想听的东西。一次创建,我附上了只激活 bool @Kay 建议我使用的事件,以及调用的方法以确切知道调用了什么方法。
稍后在主 class 中,如果检测到更改,则 foreach 每秒循环列出的每个方法,并调用链接到字符串的方法。