Răsfoiți Sursa

Refactoring ProjectLibrary import to accomodate a progress bar (WIP)

Marko Pintera 10 ani în urmă
părinte
comite
08c8ecf790

+ 1 - 1
BansheeCore/Source/Win32/BsWin32Platform.cpp

@@ -209,7 +209,7 @@ namespace BansheeEngine
 		iconinfo.hbmColor = hBitmap;
 		iconinfo.hbmColor = hBitmap;
 
 
 		mData->mCursor.cursor = CreateIconIndirect(&iconinfo);
 		mData->mCursor.cursor = CreateIconIndirect(&iconinfo);
-
+		
 		DeleteObject(hBitmap);          
 		DeleteObject(hBitmap);          
 		DeleteObject(hMonoBitmap);
 		DeleteObject(hMonoBitmap);
 
 

+ 3 - 3
BansheeEditor/Include/BsProjectLibrary.h

@@ -53,6 +53,7 @@ namespace BansheeEngine
 
 
 		void update();
 		void update();
 		void checkForModifications(const Path& path);
 		void checkForModifications(const Path& path);
+		void checkForModifications(const Path& path, bool import, Vector<Path>& dirtyResources);
 
 
 		const LibraryEntry* getRootEntry() const { return mRootEntry; }
 		const LibraryEntry* getRootEntry() const { return mRootEntry; }
 		LibraryEntry* findEntry(const Path& path) const;
 		LibraryEntry* findEntry(const Path& path) const;
@@ -70,6 +71,7 @@ namespace BansheeEngine
 		void copyEntry(const Path& oldPath, const Path& newPath, bool overwrite = true);
 		void copyEntry(const Path& oldPath, const Path& newPath, bool overwrite = true);
 		void deleteEntry(const Path& path);
 		void deleteEntry(const Path& path);
 		void reimport(const Path& path, const ImportOptionsPtr& importOptions = nullptr, bool forceReimport = false);
 		void reimport(const Path& path, const ImportOptionsPtr& importOptions = nullptr, bool forceReimport = false);
+		HResource load(const Path& path);
 
 
 		const Path& getResourcesFolder() const { return mResourcesFolder; }
 		const Path& getResourcesFolder() const { return mResourcesFolder; }
 
 
@@ -83,7 +85,6 @@ namespace BansheeEngine
 
 
 		ResourceManifestPtr mResourceManifest;
 		ResourceManifestPtr mResourceManifest;
 		DirectoryEntry* mRootEntry;
 		DirectoryEntry* mRootEntry;
-		FolderMonitor* mMonitor;
 		Path mProjectFolder;
 		Path mProjectFolder;
 		Path mResourcesFolder;
 		Path mResourcesFolder;
 
 
@@ -110,11 +111,10 @@ namespace BansheeEngine
 		void doOnEntryRemoved(const LibraryEntry* entry);
 		void doOnEntryRemoved(const LibraryEntry* entry);
 		void doOnEntryAdded(const LibraryEntry* entry);
 		void doOnEntryAdded(const LibraryEntry* entry);
 
 
-		void onMonitorFileModified(const Path& path);
-
 		Vector<Path> getImportDependencies(const ResourceEntry* entry);
 		Vector<Path> getImportDependencies(const ResourceEntry* entry);
 		void addDependencies(const ResourceEntry* entry);
 		void addDependencies(const ResourceEntry* entry);
 		void removeDependencies(const ResourceEntry* entry);
 		void removeDependencies(const ResourceEntry* entry);
+
 		void queueDependantForReimport(const ResourceEntry* entry);
 		void queueDependantForReimport(const ResourceEntry* entry);
 	};
 	};
 }
 }

+ 66 - 57
BansheeEditor/Source/BsProjectLibrary.cpp

@@ -9,7 +9,6 @@
 #include "BsImporter.h"
 #include "BsImporter.h"
 #include "BsImportOptions.h"
 #include "BsImportOptions.h"
 #include "BsFileSerializer.h"
 #include "BsFileSerializer.h"
-#include "BsFolderMonitor.h"
 #include "BsDebug.h"
 #include "BsDebug.h"
 #include "BsProjectLibraryEntries.h"
 #include "BsProjectLibraryEntries.h"
 #include "BsResource.h"
 #include "BsResource.h"
@@ -35,11 +34,11 @@ namespace BansheeEngine
 	{ }
 	{ }
 
 
 	ProjectLibrary::ResourceEntry::ResourceEntry()
 	ProjectLibrary::ResourceEntry::ResourceEntry()
-		:lastUpdateTime(0)
+		: lastUpdateTime(0)
 	{ }
 	{ }
 
 
 	ProjectLibrary::ResourceEntry::ResourceEntry(const Path& path, const WString& name, DirectoryEntry* parent)
 	ProjectLibrary::ResourceEntry::ResourceEntry(const Path& path, const WString& name, DirectoryEntry* parent)
-		:LibraryEntry(path, name, parent, LibraryEntryType::File), lastUpdateTime(0)
+		: LibraryEntry(path, name, parent, LibraryEntryType::File), lastUpdateTime(0)
 	{ }
 	{ }
 
 
 	ProjectLibrary::DirectoryEntry::DirectoryEntry()
 	ProjectLibrary::DirectoryEntry::DirectoryEntry()
@@ -54,15 +53,6 @@ namespace BansheeEngine
 	{
 	{
 		mResourcesFolder = mProjectFolder;
 		mResourcesFolder = mProjectFolder;
 		mResourcesFolder.append(RESOURCES_DIR);
 		mResourcesFolder.append(RESOURCES_DIR);
-		mMonitor = bs_new<FolderMonitor>();
-
-		FolderChange folderChanges = (FolderChange)((UINT32)FolderChange::FileName | (UINT32)FolderChange::DirName | 
-				(UINT32)FolderChange::Creation | (UINT32)FolderChange::LastWrite);
-		mMonitor->startMonitor(mResourcesFolder, true, folderChanges);
-
-		mMonitor->onAdded.connect(std::bind(&ProjectLibrary::onMonitorFileModified, this, _1));
-		mMonitor->onRemoved.connect(std::bind(&ProjectLibrary::onMonitorFileModified, this, _1));
-		mMonitor->onModified.connect(std::bind(&ProjectLibrary::onMonitorFileModified, this, _1));
 
 
 		load();
 		load();
 
 
@@ -70,25 +60,18 @@ namespace BansheeEngine
 			mResourceManifest = ResourceManifest::create("ProjectLibrary");
 			mResourceManifest = ResourceManifest::create("ProjectLibrary");
 
 
 		gResources().registerResourceManifest(mResourceManifest);
 		gResources().registerResourceManifest(mResourceManifest);
-
-		checkForModifications(mResourcesFolder);
 	}
 	}
 
 
 	ProjectLibrary::~ProjectLibrary()
 	ProjectLibrary::~ProjectLibrary()
 	{
 	{
 		save();
 		save();
 
 
-		mMonitor->stopMonitorAll();
-		bs_delete(mMonitor);
-
 		if(mRootEntry != nullptr)
 		if(mRootEntry != nullptr)
 			deleteDirectoryInternal(mRootEntry);
 			deleteDirectoryInternal(mRootEntry);
 	}
 	}
 
 
 	void ProjectLibrary::update()
 	void ProjectLibrary::update()
 	{
 	{
-		mMonitor->_update();
-
 		while (!mReimportQueue.empty())
 		while (!mReimportQueue.empty())
 		{
 		{
 			Path toReimport = *mReimportQueue.begin();
 			Path toReimport = *mReimportQueue.begin();
@@ -105,6 +88,12 @@ namespace BansheeEngine
 	}
 	}
 
 
 	void ProjectLibrary::checkForModifications(const Path& fullPath)
 	void ProjectLibrary::checkForModifications(const Path& fullPath)
+	{
+		Vector<Path> dirtyResources;
+		checkForModifications(fullPath, true, dirtyResources);
+	}
+
+	void ProjectLibrary::checkForModifications(const Path& fullPath, bool import, Vector<Path>& dirtyResources)
 	{
 	{
 		if (!mResourcesFolder.includes(fullPath))
 		if (!mResourcesFolder.includes(fullPath))
 			return; // Folder not part of our resources path, so no modifications
 			return; // Folder not part of our resources path, so no modifications
@@ -131,26 +120,32 @@ namespace BansheeEngine
 
 
 			if(FileSystem::isFile(pathToSearch))
 			if(FileSystem::isFile(pathToSearch))
 			{
 			{
-				addResourceInternal(entryParent, pathToSearch);
+				if (import)
+					addResourceInternal(entryParent, pathToSearch);
+				
+				dirtyResources.push_back(pathToSearch);
 			}
 			}
 			else if(FileSystem::isDirectory(pathToSearch))
 			else if(FileSystem::isDirectory(pathToSearch))
 			{
 			{
 				addDirectoryInternal(entryParent, pathToSearch);
 				addDirectoryInternal(entryParent, pathToSearch);
 
 
-				if(newHierarchyParent == nullptr)
-					checkForModifications(pathToSearch);
+				checkForModifications(pathToSearch);
 			}
 			}
-
-			if(newHierarchyParent != nullptr)
-				checkForModifications(newHierarchyParent->path);
 		}
 		}
 		else if(entry->type == LibraryEntryType::File)
 		else if(entry->type == LibraryEntryType::File)
 		{
 		{
 			if(FileSystem::isFile(entry->path))
 			if(FileSystem::isFile(entry->path))
 			{
 			{
 				ResourceEntry* resEntry = static_cast<ResourceEntry*>(entry);
 				ResourceEntry* resEntry = static_cast<ResourceEntry*>(entry);
-				reimportResourceInternal(resEntry);
-				queueDependantForReimport(resEntry);
+
+				if (import)
+				{
+					reimportResourceInternal(resEntry);
+					queueDependantForReimport(resEntry);
+				}
+
+				if (!isUpToDate(resEntry))
+					dirtyResources.push_back(entry->path);
 			}
 			}
 			else
 			else
 			{
 			{
@@ -220,8 +215,14 @@ namespace BansheeEngine
 
 
 							if(existingEntry != nullptr)
 							if(existingEntry != nullptr)
 							{
 							{
-								reimportResourceInternal(existingEntry);
-								queueDependantForReimport(existingEntry);
+								if (import)
+								{
+									reimportResourceInternal(existingEntry);
+									queueDependantForReimport(existingEntry);
+								}
+
+								if (!isUpToDate(existingEntry))
+									dirtyResources.push_back(existingEntry->path);
 							}
 							}
 							else
 							else
 							{
 							{
@@ -733,9 +734,6 @@ namespace BansheeEngine
 				}
 				}
 
 
 				doOnEntryAdded(oldEntry);
 				doOnEntryAdded(oldEntry);
-
-				if(newHierarchyParent != nullptr)
-					checkForModifications(newHierarchyParent->path);
 			}
 			}
 		}
 		}
 		else // Moving from outside of the Resources folder (likely adding a new resource)
 		else // Moving from outside of the Resources folder (likely adding a new resource)
@@ -871,6 +869,19 @@ namespace BansheeEngine
 		}
 		}
 	}
 	}
 
 
+	HResource ProjectLibrary::load(const Path& path)
+	{
+		LibraryEntry* entry = findEntry(path);
+
+		if (entry == nullptr || entry->type == LibraryEntryType::Directory)
+			return HResource();
+
+		ResourceEntry* resEntry = static_cast<ResourceEntry*>(entry);
+		String resUUID = resEntry->meta->getUUID();
+
+		return Resources::instance().loadFromUUID(resUUID);
+	}
+
 	void ProjectLibrary::createInternalParentHierarchy(const Path& fullPath, DirectoryEntry** newHierarchyRoot, DirectoryEntry** newHierarchyLeaf)
 	void ProjectLibrary::createInternalParentHierarchy(const Path& fullPath, DirectoryEntry** newHierarchyRoot, DirectoryEntry** newHierarchyLeaf)
 	{
 	{
 		Path parentPath = fullPath;
 		Path parentPath = fullPath;
@@ -928,19 +939,6 @@ namespace BansheeEngine
 		return fullPath.getWExtension() == L".meta";
 		return fullPath.getWExtension() == L".meta";
 	}
 	}
 
 
-	void ProjectLibrary::onMonitorFileModified(const Path& path)
-	{
-		if(!isMeta(path))
-			checkForModifications(path);
-		else
-		{
-			Path resourcePath = path;
-			resourcePath.setExtension(L"");
-
-			checkForModifications(resourcePath);
-		}
-	}
-
 	void ProjectLibrary::save()
 	void ProjectLibrary::save()
 	{
 	{
 		std::shared_ptr<ProjectLibraryEntries> libEntries = ProjectLibraryEntries::create(*mRootEntry);
 		std::shared_ptr<ProjectLibraryEntries> libEntries = ProjectLibraryEntries::create(*mRootEntry);
@@ -999,26 +997,37 @@ namespace BansheeEngine
 				if(child->type == LibraryEntryType::File)
 				if(child->type == LibraryEntryType::File)
 				{
 				{
 					ResourceEntry* resEntry = static_cast<ResourceEntry*>(child);
 					ResourceEntry* resEntry = static_cast<ResourceEntry*>(child);
+					bool doAddDependencies = true;
 
 
-					if(resEntry->meta == nullptr)
+					if (FileSystem::isFile(resEntry->path))
 					{
 					{
-						Path metaPath = resEntry->path;
-						metaPath.setFilename(metaPath.getWFilename() + L".meta");
-
-						if(FileSystem::isFile(metaPath))
+						if (resEntry->meta == nullptr)
 						{
 						{
-							FileDecoder fs(metaPath);
-							std::shared_ptr<IReflectable> loadedMeta = fs.decode();
+							Path metaPath = resEntry->path;
+							metaPath.setFilename(metaPath.getWFilename() + L".meta");
+
+							if (FileSystem::isFile(metaPath))
+							{
+								FileDecoder fs(metaPath);
+								std::shared_ptr<IReflectable> loadedMeta = fs.decode();
 
 
-							if(loadedMeta != nullptr && loadedMeta->isDerivedFrom(ProjectResourceMeta::getRTTIStatic()))
+								if (loadedMeta != nullptr && loadedMeta->isDerivedFrom(ProjectResourceMeta::getRTTIStatic()))
+								{
+									ProjectResourceMetaPtr resourceMeta = std::static_pointer_cast<ProjectResourceMeta>(loadedMeta);
+									resEntry->meta = resourceMeta;
+								}
+							}
+							else
 							{
 							{
-								ProjectResourceMetaPtr resourceMeta = std::static_pointer_cast<ProjectResourceMeta>(loadedMeta);
-								resEntry->meta = resourceMeta;
+								LOGWRN("Missing meta file: " + metaPath.toString() + ". Triggering reimport.");
+								reimportResourceInternal(resEntry);
+								doAddDependencies = false;
 							}
 							}
 						}
 						}
 					}
 					}
 
 
-					addDependencies(resEntry);
+					if (doAddDependencies)
+						addDependencies(resEntry);
 				}
 				}
 				else if(child->type == LibraryEntryType::Directory)
 				else if(child->type == LibraryEntryType::Directory)
 				{
 				{
@@ -1103,7 +1112,7 @@ namespace BansheeEngine
 			curDependencies.erase(iterRemove, curDependencies.end());
 			curDependencies.erase(iterRemove, curDependencies.end());
 		}
 		}
 	}
 	}
-	
+
 	void ProjectLibrary::queueDependantForReimport(const ResourceEntry* entry)
 	void ProjectLibrary::queueDependantForReimport(const ResourceEntry* entry)
 	{
 	{
 		auto iterFind = mDependencies.find(entry->path);
 		auto iterFind = mDependencies.find(entry->path);

+ 4 - 2
BansheeUtility/Include/BsTime.h

@@ -73,6 +73,10 @@ namespace BansheeEngine
 		 */
 		 */
 		void update();
 		void update();
 
 
+		/**
+		 * @brief	Multiply with time in microseconds to get a time in seconds.
+		 */
+		static const double MICROSEC_TO_SEC;
 	private:
 	private:
 		float mFrameDelta; /**< Frame delta in seconds */
 		float mFrameDelta; /**< Frame delta in seconds */
 		float mTimeSinceStart; /**< Time since start in seconds */
 		float mTimeSinceStart; /**< Time since start in seconds */
@@ -83,8 +87,6 @@ namespace BansheeEngine
 		unsigned long mCurrentFrame;
 		unsigned long mCurrentFrame;
 
 
 		Timer* mTimer;
 		Timer* mTimer;
-
-		static const double MICROSEC_TO_SEC;
 	};
 	};
 
 
 	BS_UTILITY_EXPORT Time& gTime();
 	BS_UTILITY_EXPORT Time& gTime();

+ 58 - 0
MBansheeEditor/FolderMonitor.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    public class FolderMonitor : ScriptObject
+    {
+        public Action<string> OnModified;
+        public Action<string> OnAdded;
+        public Action<string> OnRemoved;
+        public Action<string, string> OnRenamed;
+
+        public FolderMonitor(string folderToMonitor)
+        {
+            Internal_CreateInstance(this, folderToMonitor);
+        }
+
+        public void Destroy()
+        {
+            Internal_Destroy(mCachedPtr);
+        }
+
+        private void Internal_DoOnModified(string path)
+        {
+            if (OnModified != null)
+                OnModified(path);
+        }
+
+        private void Internal_DoOnAdded(string path)
+        {
+            if (OnAdded != null)
+                OnAdded(path);
+        }
+
+        private void Internal_DoOnRemoved(string path)
+        {
+            if (OnRemoved != null)
+                OnRemoved(path);
+        }
+
+        private void Internal_DoOnRenamed(string from, string to)
+        {
+            if (OnRenamed != null)
+                OnRenamed(from, to);
+        }
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_CreateInstance(FolderMonitor instance, string folder);
+
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern void Internal_Destroy(IntPtr thisPtr);
+    }
+}

+ 1 - 0
MBansheeEditor/MBansheeEditor.csproj

@@ -55,6 +55,7 @@
     <Compile Include="DialogBox.cs" />
     <Compile Include="DialogBox.cs" />
     <Compile Include="DragDrop.cs" />
     <Compile Include="DragDrop.cs" />
     <Compile Include="DropDownWindow.cs" />
     <Compile Include="DropDownWindow.cs" />
+    <Compile Include="FolderMonitor.cs" />
     <Compile Include="ProjectDropTarget.cs" />
     <Compile Include="ProjectDropTarget.cs" />
     <Compile Include="OSDropTarget.cs" />
     <Compile Include="OSDropTarget.cs" />
     <Compile Include="EditorApplication.cs" />
     <Compile Include="EditorApplication.cs" />

+ 8 - 0
MBansheeEditor/ProjectLibrary.cs

@@ -13,6 +13,11 @@ namespace BansheeEditor
         public static event Action<string> OnEntryAdded;
         public static event Action<string> OnEntryAdded;
         public static event Action<string> OnEntryRemoved;
         public static event Action<string> OnEntryRemoved;
 
 
+        public static string[] Refresh(bool import = false)
+        {
+            return Internal_Refresh(import);
+        }
+
         public static void Create(Resource resource, string path)
         public static void Create(Resource resource, string path)
         {
         {
             if (Path.IsPathRooted(path))
             if (Path.IsPathRooted(path))
@@ -96,6 +101,9 @@ namespace BansheeEditor
                 OnEntryRemoved(path);
                 OnEntryRemoved(path);
         }
         }
 
 
+        [MethodImpl(MethodImplOptions.InternalCall)]
+        private static extern string[] Internal_Refresh(bool import);
+
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_Create(Resource resource, string path);
         private static extern void Internal_Create(Resource resource, string path);
 
 

+ 7 - 1
MBansheeEditor/ProjectWindow.cs

@@ -302,6 +302,7 @@ namespace BansheeEditor
 
 
         private ProjectViewType viewType = ProjectViewType.Grid32;
         private ProjectViewType viewType = ProjectViewType.Grid32;
 
 
+        private bool requiresRefresh;
         private string currentDirectory = "";
         private string currentDirectory = "";
         private List<string> selectionPaths = new List<string>();
         private List<string> selectionPaths = new List<string>();
         private int selectionAnchorStart = -1;
         private int selectionAnchorStart = -1;
@@ -892,12 +893,15 @@ namespace BansheeEditor
                 contentScrollArea.VerticalScroll += scrollPct * Time.FrameDelta;
                 contentScrollArea.VerticalScroll += scrollPct * Time.FrameDelta;
             }
             }
 
 
+            if (requiresRefresh)
+                Refresh();
+
             dropTarget.Update();
             dropTarget.Update();
         }
         }
 
 
         private void OnEntryChanged(string entry)
         private void OnEntryChanged(string entry)
         {
         {
-            Refresh();
+            requiresRefresh = true;
         }
         }
 
 
         private void ScrollToEntry(string path)
         private void ScrollToEntry(string path)
@@ -929,6 +933,8 @@ namespace BansheeEditor
 
 
         private void Refresh()
         private void Refresh()
         {
         {
+            requiresRefresh = false;
+
             LibraryEntry[] entriesToDisplay = new LibraryEntry[0];
             LibraryEntry[] entriesToDisplay = new LibraryEntry[0];
             if (IsSearchActive)
             if (IsSearchActive)
             {
             {

+ 56 - 0
SBansheeEditor/Include/BsScriptFolderMonitor.h

@@ -0,0 +1,56 @@
+#pragma once
+
+#include "BsScriptEditorPrerequisites.h"
+#include "BsScriptObject.h"
+#include "BsFolderMonitor.h"
+#include "BsModule.h"
+
+namespace BansheeEngine
+{
+	class BS_SCR_BED_EXPORT ScriptFolderMonitor : public ScriptObject <ScriptFolderMonitor>
+	{
+	public:
+		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "FolderMonitor")
+
+	private:
+		friend class ScriptFolderMonitorManager;
+
+		static void internal_CreateInstance(MonoObject* instance, MonoString* folder);
+		static void internal_Destroy(ScriptFolderMonitor* thisPtr);
+
+		ScriptFolderMonitor(MonoObject* instance, FolderMonitor* monitor);
+		~ScriptFolderMonitor();
+
+		void update();
+		void destroy();
+
+		void onMonitorFileModified(const Path& path);
+		void onMonitorFileAdded(const Path& path);
+		void onMonitorFileRemoved(const Path& path);
+		void onMonitorFileRenamed(const Path& from, const Path& to);
+
+		typedef void(__stdcall *OnModifiedThunkDef) (MonoObject*, MonoString*, MonoException**);
+		typedef void(__stdcall *OnRenamedThunkDef) (MonoObject*, MonoString*, MonoString*, MonoException**);
+
+		static OnModifiedThunkDef OnModifiedThunk;
+		static OnModifiedThunkDef OnAddedThunk;
+		static OnModifiedThunkDef OnRemovedThunk;
+		static OnRenamedThunkDef OnRenamedThunk;
+
+		FolderMonitor* mMonitor;
+	};
+
+	class BS_SCR_BED_EXPORT ScriptFolderMonitorManager : public Module<ScriptFolderMonitorManager>
+	{
+	public:
+		void update();
+
+	private:
+		friend class ScriptFolderMonitor;
+
+		void _registerMonitor(ScriptFolderMonitor* monitor);
+		void _unregisterMonitor(ScriptFolderMonitor* monitor);
+
+		UnorderedSet<ScriptFolderMonitor*> mMonitors;
+	};
+}

+ 1 - 0
SBansheeEditor/Include/BsScriptProjectLibrary.h

@@ -16,6 +16,7 @@ namespace BansheeEngine
 		void static shutDown();
 		void static shutDown();
 
 
 	private:
 	private:
+		static MonoArray* internal_Refresh(bool import);
 		static void internal_Create(MonoObject* resource, MonoString* path);
 		static void internal_Create(MonoObject* resource, MonoString* path);
 		static MonoObject* internal_Load(MonoString* path);
 		static MonoObject* internal_Load(MonoString* path);
 		static void internal_Save(MonoObject* resource);
 		static void internal_Save(MonoObject* resource);

+ 2 - 0
SBansheeEditor/SBansheeEditor.vcxproj

@@ -241,6 +241,7 @@
     <ClInclude Include="Include\BsScriptCodeEditor.h" />
     <ClInclude Include="Include\BsScriptCodeEditor.h" />
     <ClInclude Include="Include\BsScriptDragDropManager.h" />
     <ClInclude Include="Include\BsScriptDragDropManager.h" />
     <ClInclude Include="Include\BsScriptDropDownWindow.h" />
     <ClInclude Include="Include\BsScriptDropDownWindow.h" />
+    <ClInclude Include="Include\BsScriptFolderMonitor.h" />
     <ClInclude Include="Include\BsScriptOSDropTarget.h" />
     <ClInclude Include="Include\BsScriptOSDropTarget.h" />
     <ClInclude Include="Include\BsScriptEditorApplication.h" />
     <ClInclude Include="Include\BsScriptEditorApplication.h" />
     <ClInclude Include="Include\BsScriptEditorBuiltin.h" />
     <ClInclude Include="Include\BsScriptEditorBuiltin.h" />
@@ -286,6 +287,7 @@
     <ClCompile Include="Source\BsGUIResourceField.cpp" />
     <ClCompile Include="Source\BsGUIResourceField.cpp" />
     <ClCompile Include="Source\BsScriptBrowseDialog.cpp" />
     <ClCompile Include="Source\BsScriptBrowseDialog.cpp" />
     <ClCompile Include="Source\BsScriptDropDownWindow.cpp" />
     <ClCompile Include="Source\BsScriptDropDownWindow.cpp" />
+    <ClCompile Include="Source\BsScriptFolderMonitor.cpp" />
     <ClCompile Include="Source\BsScriptOSDropTarget.cpp" />
     <ClCompile Include="Source\BsScriptOSDropTarget.cpp" />
     <ClCompile Include="Source\BsScriptEditorApplication.cpp" />
     <ClCompile Include="Source\BsScriptEditorApplication.cpp" />
     <ClCompile Include="Source\BsScriptEditorBuiltin.cpp" />
     <ClCompile Include="Source\BsScriptEditorBuiltin.cpp" />

+ 6 - 0
SBansheeEditor/SBansheeEditor.vcxproj.filters

@@ -144,6 +144,9 @@
     <ClInclude Include="Include\BsScriptOSDropTarget.h">
     <ClInclude Include="Include\BsScriptOSDropTarget.h">
       <Filter>Header Files</Filter>
       <Filter>Header Files</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\BsScriptFolderMonitor.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsScriptEditorPlugin.cpp">
     <ClCompile Include="Source\BsScriptEditorPlugin.cpp">
@@ -275,5 +278,8 @@
     <ClCompile Include="Source\BsScriptOSDropTarget.cpp">
     <ClCompile Include="Source\BsScriptOSDropTarget.cpp">
       <Filter>Source Files</Filter>
       <Filter>Source Files</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\BsScriptFolderMonitor.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 4 - 0
SBansheeEditor/Source/BsEditorScriptManager.cpp

@@ -11,6 +11,7 @@
 #include "BsScriptDragDropManager.h"
 #include "BsScriptDragDropManager.h"
 #include "BsScriptProjectLibrary.h"
 #include "BsScriptProjectLibrary.h"
 #include "BsMenuItemManager.h"
 #include "BsMenuItemManager.h"
+#include "BsScriptFolderMonitor.h"
 #include "BsTime.h"
 #include "BsTime.h"
 #include "BsMath.h"
 #include "BsMath.h"
 #include "BsEditorApplication.h"
 #include "BsEditorApplication.h"
@@ -32,6 +33,7 @@ namespace BansheeEngine
 		ScriptDragDropManager::startUp();
 		ScriptDragDropManager::startUp();
 		ScriptProjectLibrary::startUp();
 		ScriptProjectLibrary::startUp();
 		MenuItemManager::startUp(ScriptAssemblyManager::instance());
 		MenuItemManager::startUp(ScriptAssemblyManager::instance());
+		ScriptFolderMonitorManager::startUp();
 
 
 		mOnDomainLoadConn = ScriptObjectManager::instance().onRefreshDomainLoaded.connect(std::bind(&EditorScriptManager::loadMonoTypes, this));
 		mOnDomainLoadConn = ScriptObjectManager::instance().onRefreshDomainLoaded.connect(std::bind(&EditorScriptManager::loadMonoTypes, this));
 		mOnAssemblyRefreshDoneConn = ScriptObjectManager::instance().onRefreshComplete.connect(std::bind(&EditorScriptManager::onAssemblyRefreshDone, this));
 		mOnAssemblyRefreshDoneConn = ScriptObjectManager::instance().onRefreshComplete.connect(std::bind(&EditorScriptManager::onAssemblyRefreshDone, this));
@@ -47,6 +49,7 @@ namespace BansheeEngine
 		mOnDomainLoadConn.disconnect();
 		mOnDomainLoadConn.disconnect();
 		mOnAssemblyRefreshDoneConn.disconnect();
 		mOnAssemblyRefreshDoneConn.disconnect();
 
 
+		ScriptFolderMonitorManager::shutDown();
 		MenuItemManager::shutDown();
 		MenuItemManager::shutDown();
 		ScriptProjectLibrary::shutDown();
 		ScriptProjectLibrary::shutDown();
 		ScriptDragDropManager::shutDown();
 		ScriptDragDropManager::shutDown();
@@ -76,6 +79,7 @@ namespace BansheeEngine
 
 
 		ScriptGizmoManager::instance().update();
 		ScriptGizmoManager::instance().update();
 		ScriptDragDropManager::instance().update();
 		ScriptDragDropManager::instance().update();
+		ScriptFolderMonitorManager::instance().update();
 	}
 	}
 
 
 	void EditorScriptManager::debug_refreshAssembly()
 	void EditorScriptManager::debug_refreshAssembly()

+ 143 - 0
SBansheeEditor/Source/BsScriptFolderMonitor.cpp

@@ -0,0 +1,143 @@
+#include "BsScriptFolderMonitor.h"
+#include "BsScriptMeta.h"
+#include "BsMonoField.h"
+#include "BsMonoClass.h"
+#include "BsMonoManager.h"
+#include "BsMonoMethod.h"
+#include "BsMonoUtil.h"
+
+using namespace std::placeholders;
+
+namespace BansheeEngine
+{
+	ScriptFolderMonitor::OnModifiedThunkDef ScriptFolderMonitor::OnModifiedThunk;
+	ScriptFolderMonitor::OnModifiedThunkDef ScriptFolderMonitor::OnAddedThunk;
+	ScriptFolderMonitor::OnModifiedThunkDef ScriptFolderMonitor::OnRemovedThunk;
+	ScriptFolderMonitor::OnRenamedThunkDef ScriptFolderMonitor::OnRenamedThunk;
+
+	ScriptFolderMonitor::ScriptFolderMonitor(MonoObject* instance, FolderMonitor* monitor)
+		:ScriptObject(instance), mMonitor(monitor)
+	{
+		if (mMonitor != nullptr)
+		{
+			ScriptFolderMonitorManager::instance()._registerMonitor(this);
+
+			mMonitor->onAdded.connect(std::bind(&ScriptFolderMonitor::onMonitorFileAdded, this, _1));
+			mMonitor->onRemoved.connect(std::bind(&ScriptFolderMonitor::onMonitorFileRemoved, this, _1));
+			mMonitor->onModified.connect(std::bind(&ScriptFolderMonitor::onMonitorFileModified, this, _1));
+			mMonitor->onRenamed.connect(std::bind(&ScriptFolderMonitor::onMonitorFileRenamed, this, _1, _2));
+		}
+	}
+
+	ScriptFolderMonitor::~ScriptFolderMonitor()
+	{
+		destroy();
+	}
+
+	void ScriptFolderMonitor::initRuntimeData()
+	{
+		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptFolderMonitor::internal_CreateInstance);
+		metaData.scriptClass->addInternalCall("Internal_Destroy", &ScriptFolderMonitor::internal_Destroy);
+
+		OnModifiedThunk = (OnModifiedThunkDef)metaData.scriptClass->getMethod("Internal_DoOnModified", 1)->getThunk();
+		OnAddedThunk = (OnModifiedThunkDef)metaData.scriptClass->getMethod("Internal_DoOnAdded", 1)->getThunk();
+		OnRemovedThunk = (OnModifiedThunkDef)metaData.scriptClass->getMethod("Internal_DoOnRemoved", 1)->getThunk();
+		OnRenamedThunk = (OnRenamedThunkDef)metaData.scriptClass->getMethod("Internal_DoOnRenamed", 2)->getThunk();
+	}
+
+	void ScriptFolderMonitor::internal_CreateInstance(MonoObject* instance, MonoString* folder)
+	{
+		FolderMonitor* monitor = nullptr;
+		if (folder != nullptr)
+		{
+			Path folderPath = MonoUtil::monoToWString(folder);
+
+			FolderChange folderChanges = (FolderChange)((UINT32)FolderChange::FileName | (UINT32)FolderChange::DirName |
+				(UINT32)FolderChange::Creation | (UINT32)FolderChange::LastWrite);
+
+			monitor = bs_new<FolderMonitor>();
+			monitor->startMonitor(folderPath, true, folderChanges);
+		}
+
+		new (bs_alloc<ScriptFolderMonitor>()) ScriptFolderMonitor(instance, monitor);
+	}
+
+	void ScriptFolderMonitor::internal_Destroy(ScriptFolderMonitor* thisPtr)
+	{
+		thisPtr->destroy();
+	}
+
+	void ScriptFolderMonitor::onMonitorFileModified(const Path& path)
+	{
+		MonoString* monoPath = MonoUtil::wstringToMono(MonoManager::instance().getDomain(), path.toWString());
+
+		MonoException* exception = nullptr;
+		OnModifiedThunk(getManagedInstance(), monoPath, &exception);
+
+		MonoUtil::throwIfException(exception);
+	}
+
+	void ScriptFolderMonitor::onMonitorFileAdded(const Path& path)
+	{
+		MonoString* monoPath = MonoUtil::wstringToMono(MonoManager::instance().getDomain(), path.toWString());
+
+		MonoException* exception = nullptr;
+		OnAddedThunk(getManagedInstance(), monoPath, &exception);
+
+		MonoUtil::throwIfException(exception);
+	}
+
+	void ScriptFolderMonitor::onMonitorFileRemoved(const Path& path)
+	{
+		MonoString* monoPath = MonoUtil::wstringToMono(MonoManager::instance().getDomain(), path.toWString());
+
+		MonoException* exception = nullptr;
+		OnRemovedThunk(getManagedInstance(), monoPath, &exception);
+
+		MonoUtil::throwIfException(exception);
+	}
+
+	void ScriptFolderMonitor::onMonitorFileRenamed(const Path& from, const Path& to)
+	{
+		MonoString* monoPathFrom = MonoUtil::wstringToMono(MonoManager::instance().getDomain(), from.toWString());
+		MonoString* monoPathTo = MonoUtil::wstringToMono(MonoManager::instance().getDomain(), to.toWString());
+
+		MonoException* exception = nullptr;
+		OnRenamedThunk(getManagedInstance(), monoPathFrom, monoPathTo, &exception);
+
+		MonoUtil::throwIfException(exception);
+	}
+
+	void ScriptFolderMonitor::update()
+	{
+		mMonitor->_update();
+	}
+
+	void ScriptFolderMonitor::destroy()
+	{
+		if (mMonitor != nullptr)
+		{
+			mMonitor->stopMonitorAll();
+			bs_delete(mMonitor);
+			mMonitor = nullptr;
+
+			ScriptFolderMonitorManager::instance()._unregisterMonitor(this);
+		}
+	}
+
+	void ScriptFolderMonitorManager::update()
+	{
+		for (auto& monitor : mMonitors)
+			monitor->update();
+	}
+
+	void ScriptFolderMonitorManager::_registerMonitor(ScriptFolderMonitor* monitor)
+	{
+		mMonitors.insert(monitor);
+	}
+
+	void ScriptFolderMonitorManager::_unregisterMonitor(ScriptFolderMonitor* monitor)
+	{
+		mMonitors.erase(monitor);
+	}
+}

+ 17 - 10
SBansheeEditor/Source/BsScriptProjectLibrary.cpp

@@ -32,6 +32,7 @@ namespace BansheeEngine
 
 
 	void ScriptProjectLibrary::initRuntimeData()
 	void ScriptProjectLibrary::initRuntimeData()
 	{
 	{
+		metaData.scriptClass->addInternalCall("Internal_Refresh", &ScriptProjectLibrary::internal_Refresh);
 		metaData.scriptClass->addInternalCall("Internal_Create", &ScriptProjectLibrary::internal_Create);
 		metaData.scriptClass->addInternalCall("Internal_Create", &ScriptProjectLibrary::internal_Create);
 		metaData.scriptClass->addInternalCall("Internal_Load", &ScriptProjectLibrary::internal_Load);
 		metaData.scriptClass->addInternalCall("Internal_Load", &ScriptProjectLibrary::internal_Load);
 		metaData.scriptClass->addInternalCall("Internal_Save", &ScriptProjectLibrary::internal_Save);
 		metaData.scriptClass->addInternalCall("Internal_Save", &ScriptProjectLibrary::internal_Save);
@@ -51,6 +52,20 @@ namespace BansheeEngine
 		OnEntryRemovedThunk = (OnEntryChangedThunkDef)metaData.scriptClass->getMethod("Internal_DoOnEntryRemoved", 1)->getThunk();
 		OnEntryRemovedThunk = (OnEntryChangedThunkDef)metaData.scriptClass->getMethod("Internal_DoOnEntryRemoved", 1)->getThunk();
 	}
 	}
 
 
+	MonoArray* ScriptProjectLibrary::internal_Refresh(bool import)
+	{
+		Vector<Path> dirtyResources;
+		ProjectLibrary::instance().checkForModifications(ProjectLibrary::instance().getResourcesFolder(), import, dirtyResources);
+
+		ScriptArray output = ScriptArray::create<WString>((UINT32)dirtyResources.size());
+		for (UINT32 i = 0; i < (UINT32)dirtyResources.size(); i++)
+		{
+			output.set(i, dirtyResources[i].toWString());
+		}
+
+		return output.getInternal();
+	}
+
 	void ScriptProjectLibrary::internal_Create(MonoObject* resource, MonoString* path)
 	void ScriptProjectLibrary::internal_Create(MonoObject* resource, MonoString* path)
 	{
 	{
 		ScriptResource* scrResource = ScriptResource::toNative(resource);
 		ScriptResource* scrResource = ScriptResource::toNative(resource);
@@ -63,19 +78,11 @@ namespace BansheeEngine
 	{
 	{
 		Path resourcePath = MonoUtil::monoToWString(path);
 		Path resourcePath = MonoUtil::monoToWString(path);
 
 
-		ProjectLibrary::LibraryEntry* entry = ProjectLibrary::instance().findEntry(resourcePath);
-
-		if (entry == nullptr || entry->type == ProjectLibrary::LibraryEntryType::Directory)
-			return nullptr;
-
-		ProjectLibrary::ResourceEntry* resEntry = static_cast<ProjectLibrary::ResourceEntry*>(entry);
-		String resUUID = resEntry->meta->getUUID();
-
-		HResource resource = Resources::instance().loadFromUUID(resUUID);
+		HResource resource = Resources::instance().load(resourcePath);
 		if (!resource)
 		if (!resource)
 			return nullptr;
 			return nullptr;
 
 
-		ScriptResourceBase* scriptResource = ScriptResourceManager::instance().getScriptResource(resUUID);
+		ScriptResourceBase* scriptResource = ScriptResourceManager::instance().getScriptResource(resource.getUUID());
 		if (scriptResource == nullptr)
 		if (scriptResource == nullptr)
 			scriptResource = ScriptResourceManager::instance().createScriptResource(resource);
 			scriptResource = ScriptResourceManager::instance().createScriptResource(resource);
 
 

+ 17 - 15
TODO.txt

@@ -16,23 +16,22 @@ TODO - Setting Material array parameters isn't possible from C#
 
 
 GUIResourceField doesn't distinguish between tex2d, tex3d and texcube.
 GUIResourceField doesn't distinguish between tex2d, tex3d and texcube.
 
 
+---------------------------------------------------------------------
+Extend ProjectLibrary.Refresh
+ - Called in EditorApplication on start or assembly refresh
+ - Calls checkForModifications(false) internally, retrieves dirty resources and adds them to a HashSet (if they're not already there)
+ - EditorApplication also subscribes to FolderMonitor and when it triggers asset modification call Refresh(false)
+ - In Update() go over all dirty resources and call checkForModifications(true) on that specific path
+   - Time-slice the import so UI has time to update
+   - Open/Close/Update the progress bar as necessary
+   - Update is called every frame by EditorApplication
+
+Modify ProjectWindow so it only does file-system copy/move operations for copy/paste
+and drag and drop. After which it calls ProjectLibrary.Refresh.
+
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 Project window
 Project window
 
 
-TODO - When importing/reimporting stuff there should be a progress bar
- - Extend project library reimport queue so it finds all dependant assets before starting import
- - Then it can trigger an event onImportStarted(numAssets)
- - And onImportDone(entry) for each reimported asset
- - Then have EditorApplication subscribe to those events and show a progress bar when needed
-   - TODO: I will need to either skip a frame, or force GUI to update meshes in order for the progress bar to update
- - PROBABLY: PRojectLibrary should skip import into time slices (e.g. 16ms). If import took longer than 16ms wait till next
-   frame to continue. This was progress bar can update normally.
- - TODO: The current import queue only deals with dependant resources. I should probably make it work even when
-   main resources are imported
-TODO - Might need to handle overwritting better when importing/moving
- - When pasting generate a unique filename if the same one already exists
- - Do the same when drag and dropping (either internal, or OS)
-
 Simple tasks:
 Simple tasks:
  - Hook up scene view drag and drop instantiation
  - Hook up scene view drag and drop instantiation
 
 
@@ -40,7 +39,10 @@ Later:
  - Hook up ping effect so it triggers when I select a resource or sceneobject
  - Hook up ping effect so it triggers when I select a resource or sceneobject
   - Add ping to SceneTreeView
   - Add ping to SceneTreeView
  - Consider delaying search until user stops pressing keys
  - Consider delaying search until user stops pressing keys
- - When interacting with directory bar the window should keep focus
+ - Save & restore scroll position when Refresh happens
+ - Might need to handle overwritting better when importing/moving
+   - When pasting generate a unique filename if the same one already exists
+   - Do the same when drag and dropping (either internal, or OS)
 
 
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 Resources
 Resources