2016-10-19 2 views
2

Ich brauche eine Thread-Safe-Klasse für File System Watcher im Unity Editor zu verwenden, weiß ich bereits, dass Threading ist nicht möglich, aus Korotinen, aber ich wusste nicht, dass Threading nicht war erlaubt auch im Editor.Thread-sicher File System Watcher für Unity Editor

So gibt es mein Fehler:

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

ich volle Stack-Trace kopiert bewusst alle Helfer zu machen, wo das Problem sein kann. Weil ich nach einer Lösung gesucht, wie jeder sicher FWS Gewinde, und ja, es ist ein, aber nur für .NET 4, und ich brauche einen für .NET 2

Dies ist mein Code:

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!"); 
} 

Es gibt nichts besonderes, wie Sie sehen können ...

die FSW ich dies sah, war: https://gist.githubusercontent.com/bradsjm/2c839912294d0e2c008a/raw/c4a5c3d920ab46fdaa53b0e111e0d1204b1fe903/FileSystemWatcher.cs

mit dieser Mein Ziel ist es einfach, ich habe eine DLL getrennt von meinem aktuellen Unity-Projekt, die Idee ist einfach Ich möchte alles automatisch von Unity neu kompilieren, wenn sich etwas ändert aus dem Projekt der DLL ist geändert, aber das kann ich wegen Threads nicht erreichen, also was kann ich tun? Gibt es eine Alternative, um Dateien zu hören, die mit Unity kompatibel sind?

Danke.

Antwort

1

Ich löste es mit Hilfe von @Kay, danke @Kay!

Ich wollte eine allgemeinere Antwort geben, also entschied ich mich, meine eigene Klasse zu machen, um das zu erreichen, was ich wollte. Und das ist das Ergebnis:

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; 
     } 
    } 
} 

Hauptklassencode:

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; 
      } 
     } 
    } 
} 

Das, was ich getan habe, ist sehr einfach. Ich habe nur eine generische Klasse erstellt, die eine FSW-Instanz oder was auch immer Sie hören möchten. Einmal erstellt, lege ich die Ereignisse an, die nur den Bool aktivieren, den @Kay mir vorgeschlagen hat, und auch die aufgerufene Methode, um genau zu wissen, welche Methode aufgerufen wurde.

Später in der Hauptklasse führt foreach eine Schleife jede Methode aus, die jede Sekunde aufgelistet wird, wenn eine Änderung erkannt wurde und die mit der Zeichenfolge verknüpfte Methode aufgerufen wird.

+0

Ich denke, schlanker wäre es, benutzerdefinierte SynchronizingObject zuweisen. – aeroson

2

Aus meiner Erfahrung können Sie Threads verwenden, aber Sie müssen darauf achten, dass der Zugriff auf Unity-Klassen nur vom Hauptthread ausgeführt wird. Mein Vorschlag ist es, die Kontrolle an den Haupt-Thread zu übergeben, wann immer Ihr Watchdog alarmiert.

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(); 
    } 
} 

Polling ist natürlich irgendwie böse und hässlich. Im Allgemeinen bevorzuge ich ereignisbasierte Ansätze. Aber in diesem speziellen Fall sehe ich keine Chance, die Hauptthread-Regel zu betrügen.

+0

Also, das Problem ruft Methoden von Unity in anderen Thread nicht? Ich muss testen, aber danke! – z3nth10n