Pārlūkot izejas kodu

Work on ProjectLibrary

Marko Pintera 12 gadi atpakaļ
vecāks
revīzija
c22edb2db2

+ 1 - 0
BansheeEngine.sln

@@ -46,6 +46,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 		MonoIntegrationGuide.txt = MonoIntegrationGuide.txt
 		Notes.txt = Notes.txt
 		Opts.txt = Opts.txt
+		ProjectLibrary.txt = ProjectLibrary.txt
 		RenderOperation.txt = RenderOperation.txt
 		ResourceBundles.txt = ResourceBundles.txt
 		SpriteTexture.txt = SpriteTexture.txt

+ 5 - 0
CamelotClient/CamelotClient.vcxproj

@@ -280,6 +280,9 @@
     <ClInclude Include="Include\BsGUIWindowFrameWidget.h" />
     <ClInclude Include="Include\BsGUIWindowDropArea.h" />
     <ClInclude Include="Include\BsMainEditorWindow.h" />
+    <ClInclude Include="Include\BsProjectLibrary.h" />
+    <ClInclude Include="Include\BsResourceMeta.h" />
+    <ClInclude Include="Include\BsResourceMetaRTTI.h" />
     <ClInclude Include="Include\CmDebugCamera.h" />
     <ClInclude Include="Include\CmTestTextSprite.h" />
     <ClInclude Include="Include\DbgEditorWidget1.h" />
@@ -311,6 +314,8 @@
     <ClCompile Include="Source\BsGUIWindowFrameWidget.cpp" />
     <ClCompile Include="Source\BsGUIWindowDropArea.cpp" />
     <ClCompile Include="Source\BsMainEditorWindow.cpp" />
+    <ClCompile Include="Source\BsProjectLibrary.cpp" />
+    <ClCompile Include="Source\BsResourceMeta.cpp" />
     <ClCompile Include="Source\BsUndoRedo.cpp" />
     <ClCompile Include="Source\CmDebugCamera.cpp" />
     <ClCompile Include="Source\BsEditorApplication.cpp" />

+ 15 - 0
CamelotClient/CamelotClient.vcxproj.filters

@@ -126,6 +126,15 @@
     <ClInclude Include="Include\BsGUITreeView.h">
       <Filter>Header Files\Editor</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsProjectLibrary.h">
+      <Filter>Header Files\Editor</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\BsResourceMeta.h">
+      <Filter>Header Files\Editor</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\BsResourceMetaRTTI.h">
+      <Filter>Header Files\Editor</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="stdafx.cpp">
@@ -215,5 +224,11 @@
     <ClCompile Include="Source\BsGUITreeView.cpp">
       <Filter>Source Files\Editor</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsProjectLibrary.cpp">
+      <Filter>Source Files\Editor</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\BsResourceMeta.cpp">
+      <Filter>Source Files\Editor</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 3 - 0
CamelotClient/Include/BsEditorApplication.h

@@ -20,6 +20,9 @@ namespace BansheeEditor
 
 		void runMainLoop();
 
+		bool isProjectLoaded() const;
+		const CM::WString& getActiveProjectPath() const;
+		CM::WString getResourcesFolderPath() const;
 	private:
 		RenderSystemPlugin mActiveRSPlugin;
 

+ 8 - 0
CamelotClient/Include/BsEditorPrerequisites.h

@@ -22,10 +22,18 @@ namespace BansheeEditor
 	class GUISceneTreeView;
 	class GUITreeViewEditBox;
 	class EditorCommand;
+	class ResourceMeta;
+
+	typedef std::shared_ptr<ResourceMeta> ResourceMetaPtr;
 
 	enum class DragAndDropType
 	{
 		EditorWidget = 10000,
 		SceneObject = 10001
 	};
+
+	enum TypeID_BansheeEditor
+	{
+		TID_ResourceMeta = 40000
+	};
 }

+ 36 - 0
CamelotClient/Include/BsProjectLibrary.h

@@ -0,0 +1,36 @@
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "CmModule.h"
+#include "CmPath.h"
+
+namespace BansheeEditor
+{
+	class ProjectLibrary : public CM::Module<ProjectLibrary>
+	{
+		struct LibraryEntry;
+		struct ResourceEntry;
+		struct DirectoryEntry;
+
+	public:
+		ProjectLibrary();
+		~ProjectLibrary();
+
+		void checkForModifications(const CM::WString& folder);
+
+	private:
+		static const CM::WString INTERNAL_RESOURCES_DIR;
+
+		DirectoryEntry* mRootEntry;
+
+		void addResource(DirectoryEntry* parent, const CM::WPath& filePath);
+		void reimportResource(ResourceEntry* resource);
+
+		void deleteResource(ResourceEntry* resource);
+		void deleteDirectory(DirectoryEntry* directory);
+
+		bool isUpToDate(ResourceEntry* resource) const;
+
+		DirectoryEntry* findDirectoryEntry(const CM::WString& folder) const;
+	};
+}

+ 35 - 0
CamelotClient/Include/BsResourceMeta.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "CmIReflectable.h"
+
+namespace BansheeEditor
+{
+	class ResourceMeta : public CM::IReflectable
+	{
+	private:
+		struct ConstructPrivately {};
+
+	public:
+		explicit ResourceMeta(const ConstructPrivately&);
+
+		static ResourceMetaPtr create(const CM::String& uuid, const CM::ImportOptionsPtr& importOptions);
+
+		const CM::String& getUUID() const { return mUUID; }
+		const CM::ImportOptionsPtr& getImportOptions() const { return mImportOptions; }
+
+	private:
+		CM::String mUUID;
+		CM::ImportOptionsPtr mImportOptions;
+
+		/************************************************************************/
+		/* 								RTTI		                     		*/
+		/************************************************************************/
+		static ResourceMetaPtr createEmpty();
+
+	public:
+		friend class ResourceMetaRTTI;
+		static CM::RTTITypeBase* getRTTIStatic();
+		virtual CM::RTTITypeBase* getRTTI() const;	
+	};
+}

+ 42 - 0
CamelotClient/Include/BsResourceMetaRTTI.h

@@ -0,0 +1,42 @@
+#pragma once
+
+#include "CmPrerequisites.h"
+#include "CmRTTIType.h"
+#include "BsResourceMeta.h"
+#include "CmImportOptions.h"
+
+namespace BansheeEditor
+{
+	class ResourceMetaRTTI : public CM::RTTIType<ResourceMeta, CM::IReflectable, ResourceMetaRTTI>
+	{
+	private:
+		CM::String& getUUID(ResourceMeta* obj) { return obj->mUUID; }
+		void setUUID(ResourceMeta* obj, CM::String& val) { obj->mUUID = val; } 
+
+		CM::ImportOptionsPtr getImportOptions(ResourceMeta* obj) { return obj->mImportOptions; }
+		void setImportOptions(ResourceMeta* obj, CM::ImportOptionsPtr val) { obj->mImportOptions = val; }
+
+	public:
+		ResourceMetaRTTI()
+		{
+			addPlainField("mUUID", 0, &ResourceMetaRTTI::getUUID, &ResourceMetaRTTI::setUUID);
+			addReflectablePtrField("mImportOptions", 1, &ResourceMetaRTTI::getImportOptions, &ResourceMetaRTTI::setImportOptions);
+		}
+
+		virtual const CM::String& getRTTIName()
+		{
+			static CM::String name = "ResourceMeta";
+			return name;
+		}
+
+		virtual CM::UINT32 getRTTIId()
+		{
+			return TID_ResourceMeta;
+		}
+
+		virtual std::shared_ptr<CM::IReflectable> newRTTIObject()
+		{
+			return ResourceMeta::createEmpty();
+		}
+	};
+}

+ 23 - 0
CamelotClient/Source/BsEditorApplication.cpp

@@ -23,6 +23,7 @@
 #include "BsRenderable.h"
 #include "BsDbgTestGameObjectRef.h"
 #include "BsVirtualInput.h"
+#include "CmWin32FolderMonitor.h"
 
 using namespace CamelotFramework;
 using namespace BansheeEngine;
@@ -227,6 +228,11 @@ namespace BansheeEditor
 		
 		testModelGO->destroy();
 
+		//Win32FolderMonitor* folderMonitor = cm_new<Win32FolderMonitor>();
+
+		//FolderChange folderChanges = (FolderChange)((UINT32)FolderChange::FileName | (UINT32)FolderChange::DirName | 
+		//	(UINT32)FolderChange::Creation | (UINT32)FolderChange::LastWrite);
+		//folderMonitor->startMonitor(L"D:\\TestDetect", true, folderChanges);
 
 		HTexture dbgCursor = static_resource_cast<Texture>(Importer::instance().import(L"C:\\CursorDbg.psd"));
 		PixelDataPtr cursorPixelData = dbgCursor->allocateSubresourceBuffer(0);
@@ -310,6 +316,23 @@ namespace BansheeEditor
 		EditorWindowManager::instance().update();	
 	}
 
+	bool EditorApplication::isProjectLoaded() const
+	{
+		return true; // TODO - DEBUG ONLY
+	}
+
+	const WString& EditorApplication::getActiveProjectPath() const
+	{
+		static WString dummyProjectPath = L"D:\\DummyBansheeProject";
+
+		return dummyProjectPath;
+	}
+
+	WString EditorApplication::getResourcesFolderPath() const
+	{
+		return getActiveProjectPath() + L"\\Resources";
+	}
+
 	const String& EditorApplication::getLibraryNameForRenderSystem(RenderSystemPlugin plugin)
 	{
 		static String DX11Name = "CamelotD3D11RenderSystem";

+ 345 - 0
CamelotClient/Source/BsProjectLibrary.cpp

@@ -0,0 +1,345 @@
+#include "BsProjectLibrary.h"
+#include "BsEditorApplication.h"
+#include "CmPath.h"
+#include "CmFileSystem.h"
+#include "CmException.h"
+#include "CmResources.h"
+#include "CmResourceManifest.h"
+#include "CmImporter.h"
+#include "BsResourceMeta.h"
+#include "CmResources.h"
+#include "CmImporter.h"
+#include "CmImportOptions.h"
+#include "CmFileSerializer.h"
+
+using namespace CamelotFramework;
+using namespace BansheeEngine;
+
+namespace BansheeEditor
+{
+	const WString ProjectLibrary::INTERNAL_RESOURCES_DIR = L"Internal/Resources";
+
+	enum class LibraryEntryType
+	{
+		File,
+		Directory
+	};
+
+	struct ProjectLibrary::LibraryEntry
+	{
+		LibraryEntryType type;
+		WPath path;
+
+		DirectoryEntry* parent;
+	};
+
+	struct ProjectLibrary::ResourceEntry : public ProjectLibrary::LibraryEntry
+	{
+		ResourceMetaPtr meta;
+		std::time_t lastUpdateTime;
+	};
+
+	struct ProjectLibrary::DirectoryEntry : public ProjectLibrary::LibraryEntry
+	{
+		Vector<LibraryEntry*>::type mChildren;
+	};
+
+	ProjectLibrary::ProjectLibrary()
+		:mRootEntry(nullptr)
+	{
+
+	}
+
+	ProjectLibrary::~ProjectLibrary()
+	{
+		if(mRootEntry != nullptr)
+			deleteDirectory(mRootEntry);
+	}
+
+	void ProjectLibrary::checkForModifications(const CM::WString& folder)
+	{
+		if(!PathUtil::includes(folder, EditorApplication::instance().getResourcesFolderPath()))
+			return; // Folder not part of our resources path, so no modifications
+
+		if(mRootEntry == nullptr)
+		{
+			mRootEntry = cm_new<DirectoryEntry>();
+			mRootEntry->path = toPath(EditorApplication::instance().getResourcesFolderPath());
+		}
+
+		DirectoryEntry* directoryEntry = findDirectoryEntry(folder);
+		if(directoryEntry == nullptr)
+		{
+			CM_EXCEPT(InternalErrorException, "Attempting to check for modifications on a folder that is not yet part " \
+				"of the project library. Maybe check for modifications on the parent folder first? Folder: \"" + toString(folder) + "\"");
+		}
+
+		Stack<DirectoryEntry*>::type todo;
+		todo.push(directoryEntry);
+
+		Vector<WPath>::type childFiles;
+		Vector<WPath>::type childDirectories;
+		Vector<bool>::type existingEntries;
+		Vector<LibraryEntry*>::type toDelete;
+
+		while(!todo.empty())
+		{
+			DirectoryEntry* currentDir = todo.top();
+			todo.pop();
+
+			existingEntries.clear();
+			existingEntries.resize(currentDir->mChildren.size());
+			for(UINT32 i = 0; i < (UINT32)currentDir->mChildren.size(); i++)
+				existingEntries[i] = false;
+
+			childFiles.clear();
+			childDirectories.clear();
+
+			FileSystem::getChildren(currentDir->path, childFiles, childDirectories);
+			
+			for(auto& filePath : childFiles)
+			{
+				ResourceEntry* existingEntry = nullptr;
+				UINT32 idx = 0;
+				for(auto& child : currentDir->mChildren)
+				{
+					if(child->type == LibraryEntryType::File && child->path == filePath)
+					{
+						existingEntries[idx] = true;
+						existingEntry = static_cast<ResourceEntry*>(child);
+						break;
+					}
+
+					idx++;
+				}
+
+				if(existingEntry != nullptr)
+				{
+					reimportResource(existingEntry);
+				}
+				else
+				{
+					addResource(currentDir, filePath);
+				}
+			}
+
+			for(auto& dirPath : childDirectories)
+			{
+				DirectoryEntry* existingEntry = nullptr;
+				UINT32 idx = 0;
+				for(auto& child : currentDir->mChildren)
+				{
+					if(child->type == LibraryEntryType::Directory && child->path == dirPath)
+					{
+						existingEntries[idx] = true;
+						existingEntry = static_cast<DirectoryEntry*>(child);
+						break;
+					}
+
+					idx++;
+				}
+
+				if(existingEntry == nullptr)
+				{
+					DirectoryEntry* newEntry = cm_new<DirectoryEntry>();
+					newEntry->path = dirPath;
+					newEntry->type = LibraryEntryType::Directory;
+					newEntry->parent = currentDir;
+
+					currentDir->mChildren.push_back(newEntry);
+				}
+			}
+
+			{
+				UINT32 idx = 0;
+				toDelete.clear();
+				for(auto& child : currentDir->mChildren)
+				{
+					if(existingEntries[idx])
+						continue;
+
+					toDelete.push_back(child);
+
+					idx++;
+				}
+
+				for(auto& child : toDelete)
+				{
+					if(child->type == LibraryEntryType::Directory)
+						deleteDirectory(static_cast<DirectoryEntry*>(child));
+					else if(child->type == LibraryEntryType::File)
+						deleteResource(static_cast<ResourceEntry*>(child));
+				}
+			}
+
+			for(auto& child : currentDir->mChildren)
+			{
+				if(child->type == LibraryEntryType::Directory)
+					todo.push(static_cast<DirectoryEntry*>(child));
+			}
+		}
+	}
+
+	void ProjectLibrary::addResource(DirectoryEntry* parent, const CM::WPath& filePath)
+	{
+		ResourceEntry* newResource = cm_new<ResourceEntry>();
+		newResource->type = LibraryEntryType::File;
+		newResource->path = filePath;
+		newResource->parent = parent;
+
+		parent->mChildren.push_back(newResource);
+
+		reimportResource(newResource);
+	}
+
+	void ProjectLibrary::reimportResource(ResourceEntry* resource)
+	{
+		WString ext = toWString(PathUtil::getExtension(resource->path));
+		WPath metaPath = resource->path;
+		PathUtil::replaceExtension(metaPath, ext + L".meta");
+
+		ext = ext.substr(1, ext.size() - 1); // Remove the .
+		if(!Importer::instance().supportsFileType(ext))
+			return;
+
+		if(resource->meta == nullptr)
+		{
+			FileSerializer fs;
+			if(FileSystem::isFile(metaPath))
+			{
+				std::shared_ptr<IReflectable> loadedMeta = fs.decode(toWString(metaPath));
+
+				if(loadedMeta != nullptr && loadedMeta->isDerivedFrom(ResourceMeta::getRTTIStatic()))
+				{
+					ResourceMetaPtr resourceMeta = std::static_pointer_cast<ResourceMeta>(loadedMeta);
+					resource->meta = resourceMeta;
+				}
+			}
+		}
+
+		if(!isUpToDate(resource))
+		{
+			ImportOptionsPtr importOptions = nullptr;
+			
+			if(resource->meta != nullptr)
+				importOptions = resource->meta->getImportOptions();
+			else
+				importOptions = Importer::instance().createImportOptions(toWString(resource->path));
+
+			HResource importedResource = Importer::instance().import(toWString(resource->path), importOptions);
+
+			if(resource->meta == nullptr)
+			{
+				resource->meta = ResourceMeta::create(importedResource.getUUID(), importOptions);
+				FileSerializer fs;
+				fs.encode(resource->meta.get(), toWString(metaPath)); // TODO - Make it accept WPath
+			}
+
+			WPath internalResourcesPath = toPath(EditorApplication::instance().getActiveProjectPath()) / toPath(INTERNAL_RESOURCES_DIR);
+			if(!FileSystem::isDirectory(internalResourcesPath))
+				FileSystem::createDir(internalResourcesPath);
+
+			internalResourcesPath /= toPath(toWString(importedResource.getUUID()) + L".asset");
+
+			gResources().save(importedResource, toWString(internalResourcesPath), true);
+			resource->lastUpdateTime = std::time(nullptr);
+		}
+	}
+
+	void ProjectLibrary::deleteResource(ResourceEntry* resource)
+	{
+		ResourceManifestPtr manifest = gResources().getResourceManifest();
+
+		if(resource->meta != nullptr)
+		{
+			if(manifest->uuidExists(resource->meta->getUUID()))
+			{
+				const WString& path = manifest->uuidToFilePath(resource->meta->getUUID());
+				if(FileSystem::isFile(path))
+					FileSystem::remove(path);
+			}
+
+			WString ext = toWString(PathUtil::getExtension(resource->path));
+			WPath metaPath = resource->path;
+			PathUtil::replaceExtension(metaPath, ext + L".meta");
+
+			if(FileSystem::isFile(metaPath))
+				FileSystem::remove(metaPath);
+		}
+
+		DirectoryEntry* parent = resource->parent;
+		auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(), 
+			[&] (const LibraryEntry* entry) { return entry == resource; });
+
+		parent->mChildren.erase(findIter);
+
+		cm_delete(resource);
+	}
+
+	void ProjectLibrary::deleteDirectory(DirectoryEntry* directory)
+	{
+		if(directory == mRootEntry)
+			mRootEntry = nullptr;
+
+		for(auto& child : directory->mChildren)
+		{
+			if(child->type == LibraryEntryType::Directory)
+				deleteDirectory(static_cast<DirectoryEntry*>(child));
+			else
+				deleteResource(static_cast<ResourceEntry*>(child));
+		}
+
+		DirectoryEntry* parent = directory->parent;
+		auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(), 
+			[&] (const LibraryEntry* entry) { return entry == directory; });
+
+		parent->mChildren.erase(findIter);
+
+		cm_delete(directory);
+	}
+
+	bool ProjectLibrary::isUpToDate(ResourceEntry* resource) const
+	{
+		if(resource->meta == nullptr)
+			return false;
+
+		ResourceManifestPtr manifest = gResources().getResourceManifest();
+
+		if(!manifest->uuidExists(resource->meta->getUUID()))
+			return false;
+
+		const WString& path = manifest->uuidToFilePath(resource->meta->getUUID());
+		if(!FileSystem::isFile(path))
+			return false;
+
+		std::time_t lastModifiedTime = FileSystem::getLastModifiedTime(path);
+		return lastModifiedTime <= resource->lastUpdateTime;
+	}
+
+	ProjectLibrary::DirectoryEntry* ProjectLibrary::findDirectoryEntry(const CM::WString& folder) const
+	{
+		WPath pathToSearch = toPath(folder);
+
+		Stack<DirectoryEntry*>::type todo;
+		todo.push(mRootEntry);
+
+		while(!todo.empty())
+		{
+			DirectoryEntry* currentDir = todo.top();
+			todo.pop();
+
+			for(auto& child : currentDir->mChildren)
+			{
+				if(child->type == LibraryEntryType::Directory)
+				{
+					if(pathToSearch == child->path)
+						return static_cast<DirectoryEntry*>(child);
+
+					todo.push(static_cast<DirectoryEntry*>(child));
+				}
+			}
+		}
+
+		return nullptr;
+	}
+}

+ 41 - 0
CamelotClient/Source/BsResourceMeta.cpp

@@ -0,0 +1,41 @@
+#include "BsResourceMeta.h"
+#include "BsResourceMetaRTTI.h"
+
+using namespace BansheeEngine;
+using namespace CamelotFramework;
+
+namespace BansheeEditor
+{
+	ResourceMeta::ResourceMeta(const ConstructPrivately& dummy)
+	{
+
+	}
+
+	ResourceMetaPtr ResourceMeta::create(const String& uuid, const ImportOptionsPtr& importOptions)
+	{
+		ResourceMetaPtr meta = cm_shared_ptr<ResourceMeta>(ConstructPrivately());
+		meta->mUUID = uuid;
+		meta->mImportOptions = importOptions;
+
+		return meta;
+	}
+
+	ResourceMetaPtr ResourceMeta::createEmpty()
+	{
+		return cm_shared_ptr<ResourceMeta>(ConstructPrivately());
+	}
+
+	/************************************************************************/
+	/* 								RTTI		                     		*/
+	/************************************************************************/
+
+	RTTITypeBase* ResourceMeta::getRTTIStatic()
+	{
+		return ResourceMetaRTTI::instance();
+	}
+
+	RTTITypeBase* ResourceMeta::getRTTI() const
+	{
+		return ResourceMeta::getRTTIStatic();
+	}
+}

+ 14 - 1
CamelotCore/Include/CmFontImportOptionsRTTI.h

@@ -9,12 +9,25 @@ namespace CamelotFramework
 	class CM_EXPORT FontImportOptionsRTTI : public RTTIType<FontImportOptions, IReflectable, FontImportOptionsRTTI>
 	{
 	private:
+		Vector<UINT32>::type& getFontSizes(FontImportOptions* obj) { return obj->mFontSizes; }
+		void setFontSizes(FontImportOptions* obj, Vector<UINT32>::type& value) { obj->mFontSizes = value; }
 
+		Vector<std::pair<UINT32, UINT32>>::type& getCharIndexRanges(FontImportOptions* obj) { return obj->mCharIndexRanges; }
+		void setCharIndexRanges(FontImportOptions* obj, Vector<std::pair<UINT32, UINT32>>::type& value) { obj->mCharIndexRanges = value; }
+
+		UINT32& getDPI(FontImportOptions* obj) { return obj->mDPI; }
+		void setDPI(FontImportOptions* obj, UINT32& value) { obj->mDPI = value; }
+
+		bool& getAntialiasing(FontImportOptions* obj) { return obj->mAntialiasing; }
+		void setAntialiasing(FontImportOptions* obj, bool& value) { obj->mAntialiasing = value; }
 
 	public:
 		FontImportOptionsRTTI()
 		{
-
+			addPlainField("mFontSizes", 0, &FontImportOptionsRTTI::getFontSizes, &FontImportOptionsRTTI::setFontSizes);
+			addPlainField("mCharIndexRanges", 1, &FontImportOptionsRTTI::getCharIndexRanges, &FontImportOptionsRTTI::setCharIndexRanges);
+			addPlainField("mDPI", 2, &FontImportOptionsRTTI::getDPI, &FontImportOptionsRTTI::setDPI);
+			addPlainField("mAntialiasing", 3, &FontImportOptionsRTTI::getAntialiasing, &FontImportOptionsRTTI::setAntialiasing);
 		}
 
 		virtual const String& getRTTIName()

+ 2 - 1
CamelotCore/Include/CmPrerequisites.h

@@ -285,7 +285,8 @@ namespace CamelotFramework
 		TID_MeshBase = 1065,
 		TID_GameObjectHandleBase = 1066,
 		TID_ResourceManifest = 1067,
-		TID_ResourceManifestEntry = 1068
+		TID_ResourceManifestEntry = 1068,
+		TID_STDPAIR = 1069
 	};
 }
 

+ 1 - 0
CamelotCore/Source/CmFontImportOptions.cpp

@@ -6,6 +6,7 @@ namespace CamelotFramework
 	FontImportOptions::FontImportOptions()
 		:mDPI(72), mAntialiasing(true)
 	{
+		mFontSizes.push_back(10);
 		mCharIndexRanges.push_back(std::make_pair(33, 166)); // Most used ASCII characters
 	}
 

+ 1 - 1
CamelotCore/Source/CmGpuProgramImporter.cpp

@@ -23,7 +23,7 @@ namespace CamelotFramework
 
 	HResource GpuProgramImporter::import(const WString& filePath, ConstImportOptionsPtr importOptions)
 	{
-		WString ext = Path::getExtension(filePath);
+		WString ext = PathUtil::getExtension(filePath);
 		ext = ext.substr(1, ext.size() - 1); // Remove the .
 
 		DataStreamPtr stream = FileSystem::openFile(filePath);

+ 1 - 1
CamelotCore/Source/CmImporter.cpp

@@ -108,7 +108,7 @@ namespace CamelotFramework
 
 	SpecificImporter* Importer::getImporterForFile(const WString& inputFilePath) const
 	{
-		WString ext = Path::getExtension(inputFilePath);
+		WString ext = PathUtil::getExtension(inputFilePath);
 		ext = ext.substr(1, ext.size() - 1); // Remove the .
 		if(!supportsFileType(ext))
 		{

+ 1 - 0
CamelotUtility/CamelotUtility.vcxproj

@@ -250,6 +250,7 @@
     <ClCompile Include="Source\CmDegree.cpp" />
     <ClCompile Include="Source\CmFrameAlloc.cpp" />
     <ClCompile Include="Source\CmMemorySerializer.cpp" />
+    <ClCompile Include="Source\CmPath.cpp" />
     <ClCompile Include="Source\CmRectF.cpp" />
     <ClCompile Include="Source\CmVector2I.cpp" />
     <ClCompile Include="Source\CmManagedDataBlock.cpp" />

+ 3 - 0
CamelotUtility/CamelotUtility.vcxproj.filters

@@ -371,5 +371,8 @@
     <ClCompile Include="Source\CmMemorySerializer.cpp">
       <Filter>Source Files\Serialization</Filter>
     </ClCompile>
+    <ClCompile Include="Source\CmPath.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 9 - 1
CamelotUtility/Include/CmFileSystem.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include "CmPrerequisitesUtil.h"
+#include "CmPath.h"
 
 namespace CamelotFramework
 {
@@ -13,12 +14,19 @@ namespace CamelotFramework
 		static UINT64 getFileSize(const WString& fullPath);
 
 		static void remove(const WString& fullPath, bool recursively = true);
+		static void remove(const WPath& fullPath, bool recursively = true);
+
 		static void createDir(const WString& fullPath);
+		static void createDir(const WPath& fullPath);
 
 		static bool isFile(const WString& fullPath);
+		static bool isFile(const WPath& fullPath);
+		
 		static bool isDirectory(const WString& fullPath);
+		static bool isDirectory(const WPath& fullPath);
 
-		static Vector<WString>::type getFiles(const WString& dirPath);
+		static void getChildren(const WPath& dirPath, Vector<WPath>::type& files, Vector<WPath>::type& directories);
+		static std::time_t getLastModifiedTime(const WString& fullPath);
 
 		static WString getWorkingDirectoryPath();
 		static WString getParentDirectory(const WString& path);

+ 41 - 1
CamelotUtility/Include/CmPath.h

@@ -5,10 +5,19 @@
 
 namespace CamelotFramework
 {
+	typedef boost::filesystem3::path Path;
+	typedef boost::filesystem3::wpath WPath;
+
+	Path CM_UTILITY_EXPORT toPath(const String& p);
+	WPath CM_UTILITY_EXPORT toPath(const WString& p);
+
+	String CM_UTILITY_EXPORT toString(const Path& p);
+	WString CM_UTILITY_EXPORT toWString(const WPath& p);
+
 	/**
 	 * @brief	Various string manipulations of file paths.
 	 */
-	class Path
+	class CM_UTILITY_EXPORT PathUtil
 	{
 	public:
 		static WString getExtension(const WString& path)
@@ -17,11 +26,42 @@ namespace CamelotFramework
 			return ext.wstring().c_str();
 		}
 
+		static WPath getExtension(const WPath& path)
+		{
+			return boost::filesystem3::extension(path);
+		}
+
 		static bool hasExtension(const WString& path, const WString& extension)
 		{
 			return getExtension(path) == extension;
 		}
 
+		static void replaceExtension(WPath& path, const WString& newExtension)
+		{
+			path.replace_extension(newExtension.c_str());
+		}
+
+		/**
+		 * @brief	Returns true if path child is included in path parent.
+		 * 			Both paths must be canonical.
+		 */
+		static bool includes(const WString& child, const WString& parent)
+		{
+			boost::filesystem3::wpath childPath = child.c_str();
+			boost::filesystem3::wpath parentPath = parent.c_str();
+
+			auto iterChild = childPath.begin();
+			auto iterParent = parentPath.begin();
+
+			for(; iterChild != childPath.end(); ++iterChild, ++iterParent)
+			{
+				if(*iterChild != *iterParent)
+					return false;
+			}
+
+			return true;
+		}
+
 		static WString combine(const WString& base, const WString& name)
 		{
 			if (base.empty())

+ 51 - 6
CamelotUtility/Include/CmRTTIPrerequisites.h

@@ -111,17 +111,20 @@ namespace CamelotFramework
 
 		static UINT32 fromMemory(std::vector<T, StdAlloc<T>>& data, char* memory)
 		{ 
-			UINT32 size;
-			memcpy(&size, memory, sizeof(UINT32)); 
+			UINT32 size = 0;
+			UINT32 numElements;
+			memcpy(&numElements, memory, sizeof(UINT32)); 
 			memory += sizeof(UINT32);
+			size += sizeof(UINT32);
 
-			for(UINT32 i = 0; i < size; i++)
+			for(UINT32 i = 0; i < numElements; i++)
 			{
 				T element;
 				UINT32 elementSize = RTTIPlainType<T>::fromMemory(element, memory);
 				data.push_back(element);
 
 				memory += elementSize;
+				size += elementSize;
 			}
 
 			return size;
@@ -167,11 +170,13 @@ namespace CamelotFramework
 
 		static UINT32 fromMemory(std::map<Key, Value, std::less<Key>, StdAlloc<std::pair<const Key, Value>>>& data, char* memory)
 		{ 
-			UINT32 size;
-			memcpy(&size, memory, sizeof(UINT32)); 
+			UINT32 size = 0;
+			UINT32 numElements;
+			memcpy(&numElements, memory, sizeof(UINT32)); 
 			memory += sizeof(UINT32);
+			size += sizeof(UINT32);
 
-			for(UINT32 i = 0; i < size; i++)
+			for(UINT32 i = 0; i < numElements; i++)
 			{
 				Key key;
 				UINT32 keySize = RTTIPlainType<Key>::fromMemory(key, memory);
@@ -182,6 +187,7 @@ namespace CamelotFramework
 				memory += valueSize;
 
 				data[key] = value; 
+				size += keySize + valueSize;
 			}
 
 			return size;
@@ -202,4 +208,43 @@ namespace CamelotFramework
 			return (UINT32)dataSize;
 		}	
 	}; 
+
+	template<class A, class B> struct RTTIPlainType<std::pair<A, B>>
+	{	
+		enum { id = TID_STDPAIR }; enum { hasDynamicSize = 1 };
+
+		static void toMemory(const std::pair<A, B>& data, char* memory)
+		{ 
+			UINT32 firstSize = RTTIPlainType<A>::getDynamicSize(data.first);
+			RTTIPlainType<A>::toMemory(data.first, memory);
+
+			memory += firstSize;
+
+			UINT32 secondSize = RTTIPlainType<B>::getDynamicSize(data.second);
+			RTTIPlainType<B>::toMemory(data.second, memory);
+
+			memory += secondSize;
+		}
+
+		static UINT32 fromMemory(std::pair<A, B>& data, char* memory)
+		{ 
+			UINT32 firstSize = RTTIPlainType<A>::fromMemory(data.first, memory);
+			memory += firstSize;
+
+			UINT32 secondSize = RTTIPlainType<B>::fromMemory(data.second, memory);
+			memory += secondSize;
+
+			return firstSize + secondSize;
+		}
+
+		static UINT32 getDynamicSize(const std::pair<A, B>& data)	
+		{ 
+			UINT64 dataSize = RTTIPlainType<A>::getDynamicSize(data.first);		
+			dataSize += RTTIPlainType<B>::getDynamicSize(data.second);
+
+			assert(dataSize <= std::numeric_limits<UINT32>::max());
+
+			return (UINT32)dataSize;
+		}	
+	}; 
 }

+ 41 - 5
CamelotUtility/Source/CmFileSystem.cpp

@@ -77,11 +77,19 @@ namespace CamelotFramework
 	void FileSystem::remove(const WString& fullPath, bool recursively)
 	{
 		if(recursively)
-			remove_all(fullPath.c_str());
+			boost::filesystem3::remove_all(fullPath.c_str());
 		else
 			boost::filesystem3::remove(fullPath.c_str());
 	}
 
+	void FileSystem::remove(const WPath& fullPath, bool recursively)
+	{
+		if(recursively)
+			boost::filesystem3::remove_all(fullPath);
+		else
+			boost::filesystem3::remove(fullPath);
+	}
+
 	bool FileSystem::isFile(const WString& fullPath)
 	{
 		if(exists(fullPath.c_str()) && !is_directory(fullPath.c_str()))
@@ -90,6 +98,14 @@ namespace CamelotFramework
 		return false;
 	}
 
+	bool FileSystem::isFile(const WPath& fullPath)
+	{
+		if(exists(fullPath) && !is_directory(fullPath))
+			return true;
+
+		return false;
+	}
+
 	bool FileSystem::isDirectory(const WString& fullPath)
 	{
 		if(exists(fullPath.c_str()) && is_directory(fullPath.c_str()))
@@ -98,25 +114,45 @@ namespace CamelotFramework
 		return false;
 	}
 
+	bool FileSystem::isDirectory(const WPath& fullPath)
+	{
+		if(exists(fullPath) && is_directory(fullPath))
+			return true;
+
+		return false;
+	}
+
 	void FileSystem::createDir(const WString& fullPath)
 	{
 		create_directory(fullPath.c_str());
 	}
 
-	Vector<WString>::type FileSystem::getFiles(const WString& dirPath)
+	void FileSystem::createDir(const WPath& fullPath)
+	{
+		create_directory(fullPath);
+	}
+
+	void FileSystem::getChildren(const WPath& dirPath, Vector<WPath>::type& files, Vector<WPath>::type& directories)
 	{
 		directory_iterator dirIter(dirPath.c_str());
 
 		Vector<WString>::type foundFiles;
 		while(dirIter != directory_iterator())
 		{
-			if(is_regular_file(dirIter->path()))
-				foundFiles.push_back(dirIter->path().wstring().c_str());
+			boost::filesystem3::path curPath = dirIter->path();
+
+			if(is_regular_file(curPath))
+				files.push_back(curPath);
+			else if(is_directory(curPath))
+				directories.push_back(curPath);
 
 			dirIter++;
 		}
+	}
 
-		return foundFiles;
+	std::time_t FileSystem::getLastModifiedTime(const WString& fullPath)
+	{
+		return last_write_time(fullPath.c_str());
 	}
 
 	WString FileSystem::getWorkingDirectoryPath()

+ 10 - 0
CamelotUtility/Source/CmPath.cpp

@@ -0,0 +1,10 @@
+#include "CmPath.h"
+
+namespace CamelotFramework
+{
+	Path toPath(const String& p) { return p.c_str(); }
+	WPath toPath(const WString& p) { return p.c_str(); }
+
+	String toString(const Path& p) { return String(p.string().c_str()); }
+	WString toWString(const WPath& p) { return WString(p.wstring().c_str()); }
+}

+ 39 - 0
ProjectLibrary.txt

@@ -0,0 +1,39 @@
+
+ ProjectLibrary
+  - Save/Load - saves/loads the hierarchy of ResourceEntries
+  - import(filePath)
+    - Check if we have an importer that supports the resource with the specified extension
+	- If we are okay then import the resource, otherwise do nothing and return false
+	  - Check if importer failed and report error if it did, also return false
+  - move(oldLocation, newLocation)
+    - Moves the resource from one location to another, first performs the move on the disk, then updates the hierarchy
+	 - Potentially suspends file monitoring since we don't need to detect these changes?
+  - remove(location, subfolders)
+    - Removes the resource from the file-system, and the rest is handled by the auto-update system
+  - monitoring
+    - Sets up a FileMonitor at the Resources folder, and calls add/move/remove/import whenever changes are reported
+
+ResourceManifest contains absolute file locations. What happens when the project moves?
+ - When saving ResourceManifest make sure to process all paths and make them relative.
+ - Then reverse that process on load
+
+What happens when importer fails importing a file?
+ - This can be one of the lasts steps, but I will likely need to go through all importers manually and set them up so they return proper error codes
+    and clean up properly on error.
+	- In fact they should use exceptions but those should be translated to error codes when returned to Importer
+	  - OR just write directly to debug log?
+	   - Probably log and error code, since I will need to inform the outside world that import failed somehow
+
+--------------------
+
+IMPLEMENTATION: First ensure scan and import works. Then work out how to deal with FileMonitor. And finally how to add/remove/move resources from outside.
+
+Don't forget meta files and a way to store import options.
+
+I'll likely need to update ResourceEntry in ProjectLibrary
+ - Replace UUID field with ResourceMeta class
+    - Contains UUID and ImportOptions
+	- Created whenever resource is imported
+	- Deleted when resource is deleted
+
+Make ProjectLibrary also check for .meta files that have no corresponding file and delete them (With a warning logged)