#include "BsProjectLibrary.h" #include "BsFileSystem.h" #include "BsException.h" #include "BsResources.h" #include "BsResourceManifest.h" #include "BsImporter.h" #include "BsProjectResourceMeta.h" #include "BsResources.h" #include "BsImporter.h" #include "BsImportOptions.h" #include "BsFileSerializer.h" #include "BsDebug.h" #include "BsProjectLibraryEntries.h" #include "BsResource.h" #include "BsEditorApplication.h" #include "BsShader.h" #include using namespace std::placeholders; namespace BansheeEngine { const Path ProjectLibrary::RESOURCES_DIR = L"Resources\\"; const Path ProjectLibrary::INTERNAL_RESOURCES_DIR = PROJECT_INTERNAL_DIR + GAME_RESOURCES_FOLDER_NAME; const WString ProjectLibrary::LIBRARY_ENTRIES_FILENAME = L"ProjectLibrary.asset"; const WString ProjectLibrary::RESOURCE_MANIFEST_FILENAME = L"ResourceManifest.asset"; ProjectLibrary::LibraryEntry::LibraryEntry() :parent(nullptr), type(LibraryEntryType::Directory) { } ProjectLibrary::LibraryEntry::LibraryEntry(const Path& path, const WString& name, DirectoryEntry* parent, LibraryEntryType type) :path(path), parent(parent), type(type), elementName(name) { } ProjectLibrary::ResourceEntry::ResourceEntry() : lastUpdateTime(0) { } ProjectLibrary::ResourceEntry::ResourceEntry(const Path& path, const WString& name, DirectoryEntry* parent) : LibraryEntry(path, name, parent, LibraryEntryType::File), lastUpdateTime(0) { } ProjectLibrary::DirectoryEntry::DirectoryEntry() { } ProjectLibrary::DirectoryEntry::DirectoryEntry(const Path& path, const WString& name, DirectoryEntry* parent) :LibraryEntry(path, name, parent, LibraryEntryType::Directory) { } ProjectLibrary::ProjectLibrary() : mRootEntry(nullptr), mIsLoaded(false) { mRootEntry = bs_new(mResourcesFolder, mResourcesFolder.getWTail(), nullptr); } ProjectLibrary::~ProjectLibrary() { clearEntries(); } void ProjectLibrary::checkForModifications(const Path& fullPath) { Vector dirtyResources; checkForModifications(fullPath, true, dirtyResources); } void ProjectLibrary::checkForModifications(const Path& fullPath, bool import, Vector& dirtyResources) { if (!mResourcesFolder.includes(fullPath)) return; // Folder not part of our resources path, so no modifications if(mRootEntry == nullptr) { mRootEntry = bs_new(mResourcesFolder, mResourcesFolder.getWTail(), nullptr); } Path pathToSearch = fullPath; LibraryEntry* entry = findEntry(pathToSearch); if (entry == nullptr) // File could be new, try to find parent directory entry { if (FileSystem::exists(pathToSearch)) { if (isMeta(pathToSearch)) { Path sourceFilePath = pathToSearch; sourceFilePath.setExtension(L""); if (!FileSystem::isFile(sourceFilePath)) { LOGWRN("Found a .meta file without a corresponding resource. Deleting."); FileSystem::remove(pathToSearch); } } else { Path parentDirPath = pathToSearch.getParent(); entry = findEntry(parentDirPath); // Cannot find parent directory. Create the needed hierarchy. DirectoryEntry* entryParent = nullptr; DirectoryEntry* newHierarchyParent = nullptr; if (entry == nullptr) createInternalParentHierarchy(pathToSearch, &newHierarchyParent, &entryParent); else entryParent = static_cast(entry); if (FileSystem::isFile(pathToSearch)) { if (import) addResourceInternal(entryParent, pathToSearch); dirtyResources.push_back(pathToSearch); } else if (FileSystem::isDirectory(pathToSearch)) { addDirectoryInternal(entryParent, pathToSearch); checkForModifications(pathToSearch, import, dirtyResources); } } } } else if(entry->type == LibraryEntryType::File) { if(FileSystem::isFile(entry->path)) { ResourceEntry* resEntry = static_cast(entry); if (import) reimportResourceInternal(resEntry); if (!isUpToDate(resEntry)) dirtyResources.push_back(entry->path); } else { deleteResourceInternal(static_cast(entry)); } } else if(entry->type == LibraryEntryType::Directory) // Check folder and all subfolders for modifications { if(!FileSystem::isDirectory(entry->path)) { deleteDirectoryInternal(static_cast(entry)); } else { Stack todo; todo.push(static_cast(entry)); Vector childFiles; Vector childDirectories; Vector existingEntries; Vector 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) { if(isMeta(filePath)) { Path sourceFilePath = filePath; sourceFilePath.setExtension(L""); if(!FileSystem::isFile(sourceFilePath)) { LOGWRN("Found a .meta file without a corresponding resource. Deleting."); FileSystem::remove(filePath); } } else { 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(child); break; } idx++; } if(existingEntry != nullptr) { if (import) reimportResourceInternal(existingEntry); if (!isUpToDate(existingEntry)) dirtyResources.push_back(existingEntry->path); } else { if (import) addResourceInternal(currentDir, filePath); dirtyResources.push_back(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(child); break; } idx++; } if(existingEntry == nullptr) addDirectoryInternal(currentDir, dirPath); } { for(UINT32 i = 0; i < (UINT32)existingEntries.size(); i++) { if(existingEntries[i]) continue; toDelete.push_back(currentDir->mChildren[i]); } for(auto& child : toDelete) { if(child->type == LibraryEntryType::Directory) deleteDirectoryInternal(static_cast(child)); else if(child->type == LibraryEntryType::File) deleteResourceInternal(static_cast(child)); } toDelete.clear(); } for(auto& child : currentDir->mChildren) { if(child->type == LibraryEntryType::Directory) todo.push(static_cast(child)); } } } } } ProjectLibrary::ResourceEntry* ProjectLibrary::addResourceInternal(DirectoryEntry* parent, const Path& filePath, const ImportOptionsPtr& importOptions, bool forceReimport) { ResourceEntry* newResource = bs_new(filePath, filePath.getWTail(), parent); parent->mChildren.push_back(newResource); reimportResourceInternal(newResource, importOptions, forceReimport); onEntryAdded(newResource->path); return newResource; } ProjectLibrary::DirectoryEntry* ProjectLibrary::addDirectoryInternal(DirectoryEntry* parent, const Path& dirPath) { DirectoryEntry* newEntry = bs_new(dirPath, dirPath.getWTail(), parent); parent->mChildren.push_back(newEntry); onEntryAdded(newEntry->path); return newEntry; } void ProjectLibrary::deleteResourceInternal(ResourceEntry* resource) { if(resource->meta != nullptr) { String uuid = resource->meta->getUUID(); Path path; if (mResourceManifest->uuidToFilePath(uuid, path)) { if(FileSystem::isFile(path)) FileSystem::remove(path); mResourceManifest->unregisterResource(uuid); } mUUIDToPath.erase(uuid); } Path metaPath = getMetaPath(resource->path); 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); Path originalPath = resource->path; onEntryRemoved(originalPath); removeDependencies(resource); bs_delete(resource); reimportDependants(originalPath); } void ProjectLibrary::deleteDirectoryInternal(DirectoryEntry* directory) { if(directory == mRootEntry) mRootEntry = nullptr; Vector childrenToDestroy = directory->mChildren; for(auto& child : childrenToDestroy) { if(child->type == LibraryEntryType::Directory) deleteDirectoryInternal(static_cast(child)); else deleteResourceInternal(static_cast(child)); } DirectoryEntry* parent = directory->parent; if(parent != nullptr) { auto findIter = std::find_if(parent->mChildren.begin(), parent->mChildren.end(), [&] (const LibraryEntry* entry) { return entry == directory; }); parent->mChildren.erase(findIter); } onEntryRemoved(directory->path); bs_delete(directory); } void ProjectLibrary::reimportResourceInternal(ResourceEntry* resource, const ImportOptionsPtr& importOptions, bool forceReimport) { Path metaPath = resource->path; metaPath.setFilename(metaPath.getWFilename() + L".meta"); if(resource->meta == nullptr) { if(FileSystem::isFile(metaPath)) { FileDecoder fs(metaPath); std::shared_ptr loadedMeta = fs.decode(); if(loadedMeta != nullptr && loadedMeta->isDerivedFrom(ProjectResourceMeta::getRTTIStatic())) { ProjectResourceMetaPtr resourceMeta = std::static_pointer_cast(loadedMeta); resource->meta = resourceMeta; mUUIDToPath[resourceMeta->getUUID()] = resource->path; } } } if (!isUpToDate(resource) || forceReimport) { // Note: If resource is native we just copy it to the internal folder. We could avoid the copy and // load the resource directly from the Resources folder but that requires complicating library code. bool isNativeResource = isNative(resource); ImportOptionsPtr curImportOptions = nullptr; if (importOptions == nullptr && !isNativeResource) { if (resource->meta != nullptr) curImportOptions = resource->meta->getImportOptions(); else curImportOptions = Importer::instance().createImportOptions(resource->path); } else curImportOptions = importOptions; bool unloadWhenDone = false; HResource importedResource; if (isNativeResource) { // If meta exists make sure it is registered in the manifest before load, otherwise it will get assigned a new UUID. // This can happen if library isn't properly saved before exiting the application. if (resource->meta != nullptr) { mResourceManifest->registerResource(resource->meta->getUUID(), resource->path); unloadWhenDone = !gResources().isLoaded(resource->meta->getUUID()); } // Don't load dependencies because we don't need them, but also because they might not be in the manifest // which would screw up their UUIDs. importedResource = gResources().load(resource->path, false); } if(resource->meta == nullptr) { if (!isNativeResource) { importedResource = Importer::instance().import(resource->path, curImportOptions); unloadWhenDone = true; } if (importedResource != nullptr) { ResourceMetaDataPtr subMeta = importedResource->getMetaData(); UINT32 typeId = importedResource->getTypeId(); resource->meta = ProjectResourceMeta::create(importedResource.getUUID(), typeId, subMeta, curImportOptions); } else resource->meta = ProjectResourceMeta::create(importedResource.getUUID(), 0, nullptr, curImportOptions); FileEncoder fs(metaPath); fs.encode(resource->meta.get()); mUUIDToPath[resource->meta->getUUID()] = resource->path; } else { removeDependencies(resource); if (!isNativeResource) { importedResource = gResources()._getResourceHandle(resource->meta->getUUID()); if (importedResource == nullptr) { importedResource = HResource(resource->meta->getUUID()); unloadWhenDone = true; } Importer::instance().reimport(importedResource, resource->path, curImportOptions); } } addDependencies(resource); if (importedResource != nullptr) { Path internalResourcesPath = mProjectFolder; internalResourcesPath.append(INTERNAL_RESOURCES_DIR); if (!FileSystem::isDirectory(internalResourcesPath)) FileSystem::createDir(internalResourcesPath); internalResourcesPath.setFilename(toWString(importedResource.getUUID()) + L".asset"); gResources().save(importedResource, internalResourcesPath, true); if (unloadWhenDone) gResources().unload(importedResource); mResourceManifest->registerResource(importedResource.getUUID(), internalResourcesPath); } resource->lastUpdateTime = std::time(nullptr); onEntryImported(resource->path); reimportDependants(resource->path); } } bool ProjectLibrary::isUpToDate(ResourceEntry* resource) const { if(resource->meta == nullptr) return false; Path internalPath; if(!mResourceManifest->uuidToFilePath(resource->meta->getUUID(), internalPath)) return false; if(!FileSystem::isFile(internalPath)) return false; std::time_t lastModifiedTime = FileSystem::getLastModifiedTime(resource->path); return lastModifiedTime <= resource->lastUpdateTime; } Vector ProjectLibrary::search(const WString& pattern) { return search(pattern, {}); } Vector ProjectLibrary::search(const WString& pattern, const Vector& typeIds) { Vector foundEntries; std::wregex escape(L"[.^$|()\\[\\]{}*+?\\\\]"); WString replace(L"\\\\&"); WString escapedPattern = std::regex_replace(pattern, escape, replace, std::regex_constants::match_default | std::regex_constants::format_sed); std::wregex wildcard(L"\\\\\\*"); WString wildcardReplace(L".*"); WString searchPattern = std::regex_replace(escapedPattern, wildcard, L".*"); std::wregex searchRegex(searchPattern, std::regex_constants::ECMAScript | std::regex_constants::icase); Stack todo; todo.push(mRootEntry); while (!todo.empty()) { DirectoryEntry* dirEntry = todo.top(); todo.pop(); for (auto& child : dirEntry->mChildren) { if (std::regex_match(child->elementName, searchRegex)) { if (typeIds.size() == 0) foundEntries.push_back(child); else { if (child->type == LibraryEntryType::File) { ResourceEntry* childResEntry = static_cast(child); for (auto& typeId : typeIds) { if (childResEntry->meta != nullptr && childResEntry->meta->getTypeID() == typeId) { foundEntries.push_back(child); break; } } } } } if (child->type == LibraryEntryType::Directory) { DirectoryEntry* childDirEntry = static_cast(child); todo.push(childDirEntry); } } } std::sort(foundEntries.begin(), foundEntries.end(), [&](const LibraryEntry* a, const LibraryEntry* b) { return a->elementName.compare(b->elementName) < 0; }); return foundEntries; } ProjectLibrary::LibraryEntry* ProjectLibrary::findEntry(const Path& path) const { Path fullPath = path; if (fullPath.isAbsolute()) { if (!mResourcesFolder.includes(fullPath)) return nullptr; } else fullPath.makeAbsolute(mResourcesFolder); Path relPath = fullPath.getRelative(mRootEntry->path); UINT32 numElems = relPath.getNumDirectories() + (relPath.isFile() ? 1 : 0); UINT32 idx = 0; LibraryEntry* current = mRootEntry; while (current != nullptr) { if (idx == numElems) return current; WString curElem; if (relPath.isFile() && idx == (numElems - 1)) curElem = relPath.getWFilename(); else curElem = relPath[idx]; if (current->type == LibraryEntryType::Directory) { DirectoryEntry* dirEntry = static_cast(current); current = nullptr; for (auto& child : dirEntry->mChildren) { if (Path::comparePathElem(curElem, child->elementName)) { idx++; current = child; break; } } } else break; } return nullptr; } Path ProjectLibrary::uuidToPath(const String& uuid) const { auto iterFind = mUUIDToPath.find(uuid); if (iterFind != mUUIDToPath.end()) return iterFind->second; return Path::BLANK; } void ProjectLibrary::createEntry(const HResource& resource, const Path& path) { if (resource == nullptr) return; Path assetPath = path; if (path.isAbsolute()) { if (!getResourcesFolder().includes(path)) return; assetPath = path.getRelative(getResourcesFolder()); } LibraryEntry* existingEntry = findEntry(assetPath); if (existingEntry != nullptr) { LOGWRN("Resource already exists at the specified path : " + assetPath.toString() + ". Unable to save."); return; } resource->setName(path.getWFilename(false)); Path absPath = assetPath.getAbsolute(getResourcesFolder()); Resources::instance().save(resource, absPath, false); checkForModifications(absPath); } void ProjectLibrary::saveEntry(const HResource& resource) { if (resource == nullptr) return; Path filePath = uuidToPath(resource.getUUID()); filePath.makeAbsolute(getResourcesFolder()); Resources::instance().save(resource, filePath, true); checkForModifications(filePath); } void ProjectLibrary::createFolderEntry(const Path& path) { Path fullPath = path; if (fullPath.isAbsolute()) { if (!mResourcesFolder.includes(fullPath)) return; } else fullPath.makeAbsolute(mResourcesFolder); if (FileSystem::isDirectory(fullPath)) return; // Already exists FileSystem::createDir(fullPath); Path parentPath = fullPath.getParent(); DirectoryEntry* newEntryParent = nullptr; LibraryEntry* newEntryParentLib = findEntry(parentPath); if (newEntryParentLib != nullptr) { assert(newEntryParentLib->type == LibraryEntryType::Directory); newEntryParent = static_cast(newEntryParentLib); } DirectoryEntry* newHierarchyParent = nullptr; if (newEntryParent == nullptr) // New path parent doesn't exist, so we need to create the hierarchy createInternalParentHierarchy(fullPath, &newHierarchyParent, &newEntryParent); addDirectoryInternal(newEntryParent, fullPath); } void ProjectLibrary::moveEntry(const Path& oldPath, const Path& newPath, bool overwrite) { Path oldFullPath = oldPath; if (!oldFullPath.isAbsolute()) oldFullPath.makeAbsolute(mResourcesFolder); Path newFullPath = newPath; if (!newFullPath.isAbsolute()) newFullPath.makeAbsolute(mResourcesFolder); Path parentPath = newFullPath.getParent(); if (!FileSystem::isDirectory(parentPath)) { LOGWRN("Move operation failed. Destination directory \"" + parentPath.toString() + "\" doesn't exist."); return; } if(FileSystem::isFile(oldFullPath) || FileSystem::isDirectory(oldFullPath)) FileSystem::move(oldFullPath, newFullPath, overwrite); Path oldMetaPath = getMetaPath(oldFullPath); Path newMetaPath = getMetaPath(newFullPath); LibraryEntry* oldEntry = findEntry(oldFullPath); if(oldEntry != nullptr) // Moving from the Resources folder { // Moved outside of Resources, delete entry & meta file if (!mResourcesFolder.includes(newFullPath)) { if(oldEntry->type == LibraryEntryType::File) deleteResourceInternal(static_cast(oldEntry)); else if(oldEntry->type == LibraryEntryType::Directory) deleteDirectoryInternal(static_cast(oldEntry)); } else // Just moving internally { onEntryRemoved(oldEntry->path); ResourceEntry* resEntry = nullptr; if (oldEntry->type == LibraryEntryType::File) { resEntry = static_cast(oldEntry); removeDependencies(resEntry); } if(FileSystem::isFile(oldMetaPath)) FileSystem::move(oldMetaPath, newMetaPath); DirectoryEntry* parent = oldEntry->parent; auto findIter = std::find(parent->mChildren.begin(), parent->mChildren.end(), oldEntry); if(findIter != parent->mChildren.end()) parent->mChildren.erase(findIter); Path parentPath = newFullPath.getParent(); DirectoryEntry* newEntryParent = nullptr; LibraryEntry* newEntryParentLib = findEntry(parentPath); if(newEntryParentLib != nullptr) { assert(newEntryParentLib->type == LibraryEntryType::Directory); newEntryParent = static_cast(newEntryParentLib); } DirectoryEntry* newHierarchyParent = nullptr; if(newEntryParent == nullptr) // New path parent doesn't exist, so we need to create the hierarchy createInternalParentHierarchy(newFullPath, &newHierarchyParent, &newEntryParent); newEntryParent->mChildren.push_back(oldEntry); oldEntry->parent = newEntryParent; oldEntry->path = newFullPath; oldEntry->elementName = newFullPath.getWTail(); if(oldEntry->type == LibraryEntryType::Directory) // Update child paths { Stack todo; todo.push(oldEntry); while(!todo.empty()) { LibraryEntry* curEntry = todo.top(); todo.pop(); DirectoryEntry* curDirEntry = static_cast(curEntry); for(auto& child : curDirEntry->mChildren) { child->path = child->parent->path; child->path.append(child->elementName); if(child->type == LibraryEntryType::Directory) todo.push(child); } } } onEntryAdded(oldEntry->path); if (resEntry != nullptr) { reimportDependants(oldFullPath); reimportDependants(newFullPath); } } } else // Moving from outside of the Resources folder (likely adding a new resource) { checkForModifications(newFullPath); } } void ProjectLibrary::copyEntry(const Path& oldPath, const Path& newPath, bool overwrite) { Path oldFullPath = oldPath; if (!oldFullPath.isAbsolute()) oldFullPath.makeAbsolute(mResourcesFolder); Path newFullPath = newPath; if (!newFullPath.isAbsolute()) newFullPath.makeAbsolute(mResourcesFolder); if (!FileSystem::exists(oldFullPath)) return; FileSystem::copy(oldFullPath, newFullPath, overwrite); // Copying a file/folder outside of the Resources folder, no special handling needed if (!mResourcesFolder.includes(newFullPath)) return; Path parentPath = newFullPath.getParent(); DirectoryEntry* newEntryParent = nullptr; LibraryEntry* newEntryParentLib = findEntry(parentPath); if (newEntryParentLib != nullptr) { assert(newEntryParentLib->type == LibraryEntryType::Directory); newEntryParent = static_cast(newEntryParentLib); } // If the source is outside of Resources folder, just plain import the copy LibraryEntry* oldEntry = findEntry(oldFullPath); if (oldEntry == nullptr) { checkForModifications(newFullPath); return; } // Both source and destination are within Resources folder, need to preserve import options on the copies LibraryEntry* newEntry = nullptr; if (FileSystem::isFile(newFullPath)) { assert(oldEntry->type == LibraryEntryType::File); ResourceEntry* oldResEntry = static_cast(oldEntry); ImportOptionsPtr importOptions; if (oldResEntry->meta != nullptr) importOptions = oldResEntry->meta->getImportOptions(); newEntry = addResourceInternal(newEntryParent, newFullPath, importOptions, true); } else { assert(oldEntry->type == LibraryEntryType::File); DirectoryEntry* oldDirEntry = static_cast(oldEntry); DirectoryEntry* newDirEntry = addDirectoryInternal(newEntryParent, newFullPath); newEntry = newDirEntry; Stack> todo; todo.push(std::make_tuple(oldDirEntry, newDirEntry)); while (!todo.empty()) { auto current = todo.top(); todo.pop(); DirectoryEntry* sourceDir = std::get<0>(current); DirectoryEntry* destDir = std::get<1>(current); for (auto& child : sourceDir->mChildren) { Path childDestPath = destDir->path; childDestPath.append(child->path.getWTail()); if (child->type == LibraryEntryType::File) { ResourceEntry* childResEntry = static_cast(child); ImportOptionsPtr importOptions; if (childResEntry->meta != nullptr) importOptions = childResEntry->meta->getImportOptions(); addResourceInternal(destDir, childDestPath, importOptions, true); } else // Directory { DirectoryEntry* childSourceDirEntry = static_cast(child); DirectoryEntry* childDestDirEntry = addDirectoryInternal(destDir, childDestPath); todo.push(std::make_tuple(childSourceDirEntry, childSourceDirEntry)); } } } } } void ProjectLibrary::deleteEntry(const Path& path) { Path fullPath = path; if (!fullPath.isAbsolute()) fullPath.makeAbsolute(mResourcesFolder); if(FileSystem::exists(fullPath)) FileSystem::remove(fullPath); LibraryEntry* entry = findEntry(fullPath); if(entry != nullptr) { if(entry->type == LibraryEntryType::File) deleteResourceInternal(static_cast(entry)); else if(entry->type == LibraryEntryType::Directory) deleteDirectoryInternal(static_cast(entry)); } } void ProjectLibrary::reimport(const Path& path, const ImportOptionsPtr& importOptions, bool forceReimport) { LibraryEntry* entry = findEntry(path); if (entry != nullptr) { if (entry->type == LibraryEntryType::File) { ResourceEntry* resEntry = static_cast(entry); reimportResourceInternal(resEntry, importOptions, forceReimport); } } } void ProjectLibrary::setIncludeInBuild(const Path& path, bool include) { LibraryEntry* entry = findEntry(path); if (entry == nullptr || entry->type == LibraryEntryType::Directory) return; ResourceEntry* resEntry = static_cast(entry); if (resEntry->meta == nullptr) return; resEntry->meta->setIncludeInBuild(include); Path metaPath = resEntry->path; metaPath.setFilename(metaPath.getWFilename() + L".meta"); FileEncoder fs(metaPath); fs.encode(resEntry->meta.get()); } Vector ProjectLibrary::getResourcesForBuild() const { Vector output; Stack todo; todo.push(mRootEntry); while (!todo.empty()) { DirectoryEntry* directory = todo.top(); todo.pop(); for (auto& child : directory->mChildren) { if (child->type == LibraryEntryType::File) { ResourceEntry* resEntry = static_cast(child); if (resEntry->meta != nullptr && resEntry->meta->getIncludeInBuild()) output.push_back(resEntry); } else if (child->type == LibraryEntryType::Directory) { todo.push(static_cast(child)); } } } return output; } HResource ProjectLibrary::load(const Path& path) { LibraryEntry* entry = findEntry(path); if (entry == nullptr || entry->type == LibraryEntryType::Directory) return HResource(); ResourceEntry* resEntry = static_cast(entry); if (resEntry->meta == nullptr) return HResource(); String resUUID = resEntry->meta->getUUID(); return gResources().loadFromUUID(resUUID); } void ProjectLibrary::createInternalParentHierarchy(const Path& fullPath, DirectoryEntry** newHierarchyRoot, DirectoryEntry** newHierarchyLeaf) { Path parentPath = fullPath; DirectoryEntry* newEntryParent = nullptr; Stack parentPaths; do { Path newParentPath = parentPath.getParent(); if(newParentPath == parentPath) break; LibraryEntry* newEntryParentLib = findEntry(newParentPath); if(newEntryParentLib != nullptr) { assert(newEntryParentLib->type == LibraryEntryType::Directory); newEntryParent = static_cast(newEntryParentLib); break; } parentPaths.push(newParentPath); parentPath = newParentPath; } while (true); assert(newEntryParent != nullptr); // Must exist if(newHierarchyRoot != nullptr) *newHierarchyRoot = newEntryParent; while(!parentPaths.empty()) { Path curPath = parentPaths.top(); parentPaths.pop(); newEntryParent = addDirectoryInternal(newEntryParent, curPath); } if(newHierarchyLeaf != nullptr) *newHierarchyLeaf = newEntryParent; } Path ProjectLibrary::getMetaPath(const Path& path) const { Path metaPath = path; metaPath.setFilename(metaPath.getWFilename() + L".meta"); return metaPath; } bool ProjectLibrary::isMeta(const Path& fullPath) const { return fullPath.getWExtension() == L".meta"; } bool ProjectLibrary::isNative(ResourceEntry* resource) const { WString extension = resource->path.getWExtension(); return extension == L".asset" || extension == L".prefab"; } void ProjectLibrary::unloadLibrary() { if (!mIsLoaded) return; mProjectFolder = Path::BLANK; mResourcesFolder = Path::BLANK; clearEntries(); mRootEntry = bs_new(mResourcesFolder, mResourcesFolder.getWTail(), nullptr); mDependencies.clear(); gResources().unregisterResourceManifest(mResourceManifest); mResourceManifest = nullptr; mIsLoaded = false; } void ProjectLibrary::makeEntriesRelative() { // Make all paths relative before saving std::function makeRelative = [&](LibraryEntry* entry, const Path& root) { entry->path.makeRelative(root); if (entry->type == LibraryEntryType::Directory) { DirectoryEntry* dirEntry = static_cast(entry); for (auto& child : dirEntry->mChildren) makeRelative(child, root); } }; Path root = getResourcesFolder(); makeRelative(mRootEntry, root); } void ProjectLibrary::makeEntriesAbsolute() { std::function makeAbsolute = [&](LibraryEntry* entry, const Path& root) { entry->path.makeAbsolute(root); if (entry->type == LibraryEntryType::Directory) { DirectoryEntry* dirEntry = static_cast(entry); for (auto& child : dirEntry->mChildren) makeAbsolute(child, root); } }; Path root = getResourcesFolder(); makeAbsolute(mRootEntry, root); } void ProjectLibrary::saveLibrary() { if (!mIsLoaded) return; // Make all paths relative before saving makeEntriesRelative(); std::shared_ptr libEntries = ProjectLibraryEntries::create(*mRootEntry); Path libraryEntriesPath = mProjectFolder; libraryEntriesPath.append(PROJECT_INTERNAL_DIR); libraryEntriesPath.append(LIBRARY_ENTRIES_FILENAME); FileEncoder fs(libraryEntriesPath); fs.encode(libEntries.get()); // Restore absolute entry paths makeEntriesAbsolute(); Path resourceManifestPath = mProjectFolder; resourceManifestPath.append(PROJECT_INTERNAL_DIR); resourceManifestPath.append(RESOURCE_MANIFEST_FILENAME); ResourceManifest::save(mResourceManifest, resourceManifestPath, mProjectFolder); } void ProjectLibrary::loadLibrary() { unloadLibrary(); mProjectFolder = gEditorApplication().getProjectPath(); mResourcesFolder = mProjectFolder; mResourcesFolder.append(RESOURCES_DIR); mRootEntry = bs_new(mResourcesFolder, mResourcesFolder.getWTail(), nullptr); Path libraryEntriesPath = mProjectFolder; libraryEntriesPath.append(PROJECT_INTERNAL_DIR); libraryEntriesPath.append(LIBRARY_ENTRIES_FILENAME); if(FileSystem::exists(libraryEntriesPath)) { FileDecoder fs(libraryEntriesPath); std::shared_ptr libEntries = std::static_pointer_cast(fs.decode()); *mRootEntry = libEntries->getRootEntry(); for(auto& child : mRootEntry->mChildren) child->parent = mRootEntry; mRootEntry->parent = nullptr; } // Entries are stored relative to project folder, but we want their absolute paths now makeEntriesAbsolute(); // Load resource manifest Path resourceManifestPath = mProjectFolder; resourceManifestPath.append(PROJECT_INTERNAL_DIR); resourceManifestPath.append(RESOURCE_MANIFEST_FILENAME); if (FileSystem::exists(resourceManifestPath)) mResourceManifest = ResourceManifest::load(resourceManifestPath, mProjectFolder); else mResourceManifest = ResourceManifest::create("ProjectLibrary"); gResources().registerResourceManifest(mResourceManifest); // Load all meta files Stack todo; todo.push(mRootEntry); Vector deletedEntries; while(!todo.empty()) { DirectoryEntry* curDir = todo.top(); todo.pop(); for(auto& child : curDir->mChildren) { if(child->type == LibraryEntryType::File) { ResourceEntry* resEntry = static_cast(child); if (FileSystem::isFile(resEntry->path)) { if (resEntry->meta == nullptr) { Path metaPath = resEntry->path; metaPath.setFilename(metaPath.getWFilename() + L".meta"); if (FileSystem::isFile(metaPath)) { FileDecoder fs(metaPath); std::shared_ptr loadedMeta = fs.decode(); if (loadedMeta != nullptr && loadedMeta->isDerivedFrom(ProjectResourceMeta::getRTTIStatic())) { ProjectResourceMetaPtr resourceMeta = std::static_pointer_cast(loadedMeta); resEntry->meta = resourceMeta; } } } if (resEntry->meta != nullptr) mUUIDToPath[resEntry->meta->getUUID()] = resEntry->path; addDependencies(resEntry); } else deletedEntries.push_back(resEntry); } else if(child->type == LibraryEntryType::Directory) { if (FileSystem::isDirectory(child->path)) todo.push(static_cast(child)); else deletedEntries.push_back(child); } } } // Remove entries that no longer have corresponding files for (auto& deletedEntry : deletedEntries) { if (deletedEntry->type == LibraryEntryType::File) { ResourceEntry* resEntry = static_cast(deletedEntry); deleteResourceInternal(resEntry); } else { DirectoryEntry* dirEntry = static_cast(deletedEntry); deleteDirectoryInternal(dirEntry); } } // Clean up internal library folder from obsolete files Path internalResourcesFolder = mProjectFolder; internalResourcesFolder.append(INTERNAL_RESOURCES_DIR); Vector toDelete; auto processFile = [&](const Path& file) { String uuid = file.getFilename(false); if (mUUIDToPath.find(uuid) == mUUIDToPath.end()) { mResourceManifest->unregisterResource(uuid); toDelete.push_back(file); } return true; }; FileSystem::iterate(internalResourcesFolder, processFile); for (auto& entry : toDelete) FileSystem::remove(entry); mIsLoaded = true; } void ProjectLibrary::clearEntries() { if (mRootEntry == nullptr) return; std::function deleteRecursive = [&](LibraryEntry* entry) { if (entry->type == LibraryEntryType::Directory) { DirectoryEntry* dirEntry = static_cast(entry); for (auto& child : dirEntry->mChildren) deleteRecursive(child); } bs_delete(entry); }; deleteRecursive(mRootEntry); mRootEntry = nullptr; } Vector ProjectLibrary::getImportDependencies(const ResourceEntry* entry) { Vector output; if (entry->meta == nullptr) return output; if (entry->meta->getTypeID() == TID_Shader) { SPtr metaData = std::static_pointer_cast(entry->meta->getResourceMetaData()); for (auto& include : metaData->includes) output.push_back(include); } return output; } void ProjectLibrary::addDependencies(const ResourceEntry* entry) { Vector dependencies = getImportDependencies(entry); for (auto& dependency : dependencies) mDependencies[dependency].push_back(entry->path); } void ProjectLibrary::removeDependencies(const ResourceEntry* entry) { Vector dependencies = getImportDependencies(entry); for (auto& dependency : dependencies) { Vector& curDependencies = mDependencies[dependency]; auto iterRemove = std::remove_if(curDependencies.begin(), curDependencies.end(), [&](const Path& x) { return x == entry->path; }); curDependencies.erase(iterRemove, curDependencies.end()); } } void ProjectLibrary::reimportDependants(const Path& entryPath) { auto iterFind = mDependencies.find(entryPath); if (iterFind == mDependencies.end()) return; // Make a copy since we might modify this list during reimport Vector dependencies = iterFind->second; for (auto& dependency : dependencies) { LibraryEntry* entry = findEntry(dependency); if (entry != nullptr && entry->type == LibraryEntryType::File) { ResourceEntry* resEntry = static_cast(entry); ImportOptionsPtr importOptions; if (resEntry->meta != nullptr) importOptions = resEntry->meta->getImportOptions(); reimportResourceInternal(resEntry, importOptions, true); } } } BS_ED_EXPORT ProjectLibrary& gProjectLibrary() { return ProjectLibrary::instance(); } }