Asset renaming + AssetHierarchy: major refactoring for file tree construction

+ AssetFile: Added "AssetConfigPath" Property
+ Less crashy, more cleaner
This commit is contained in:
Simon Lübeß
2023-08-01 23:56:52 +02:00
parent 51441f0e70
commit 6c4ac923c6
4 changed files with 194 additions and 165 deletions
+1
View File
@@ -40,6 +40,7 @@ class AssetFile
public StringView FilePath => _path;
public StringView Identifier => _identifier;
public StringView AssetConfigPath => _assetConfigPath;
public const String ConfigFileExtension = ".ass";
+131 -154
View File
@@ -157,7 +157,7 @@ class AssetHierarchy
}
}
StringView assetDescriptorPath = assetNode.AssetFile?.FilePath ?? "";
StringView assetDescriptorPath = assetNode.AssetFile?.AssetConfigPath ?? "";
// If it exists, also try to delete the .ass file
if (File.Exists(assetDescriptorPath))
@@ -169,6 +169,27 @@ class AssetHierarchy
}
}
/// Renames the given assetNode to the specified name.
public void RenameFile(AssetNode assetNode, StringView fileName)
{
String directory = scope .();
if (Path.GetDirectoryPath(assetNode.Path, directory) case .Err)
{
Log.EngineLogger.Error($"Rename failed. Couldn't get directory for path \"{assetNode.Path}\".");
return;
}
String newName = scope .();
Path.Combine(newName, directory, fileName);
if (File.Move(assetNode.Path, newName) case .Err(let error))
{
Log.EngineLogger.Error($"Couldn't rename file from {assetNode.Path} to {newName} ({error}).");
}
// We don't need to rename the .ass file, because the filesystem watcher will handle this for us!
}
/// Sets path to the directory that contains the engine assets.
public void SetResourcesDirectory(StringView fileName)
{
@@ -281,6 +302,8 @@ class AssetHierarchy
Log.EngineLogger.Trace($"File renamed (From \"{oldName}\" to \"{newName}\")");
FileRenamed(oldName, newName);
_fileSystemDirty = true;
});
fsw.StartRaisingEvents();
@@ -361,191 +384,137 @@ class AssetHierarchy
AssetIdentifier.Fixup(assetNode->Identifier);
}
/// Rebuilds the asset file hierarchy.
private void UpdateFiles()
{
Log.EngineLogger.Trace($"Updating asset hierarchy");
void HandleFile(AssetNode node)
{
node.AssetFile = new AssetFile(_contentManager, node.Identifier, node.Path, node.IsDirectory);
}
/// Determines the files that belong to the given directory and adds them to the tree.
void AddFilesOfDirectory(TreeNode<AssetNode> directory)
{
// Filter that accepts all files.
String filter = scope $"{directory->Path}/*";
// Buffer used to hold the path of the files iterated below.
String filepathBuffer = scope String(256);
// Buffer used to hold the file extension of the files iterated below.
String extensionBuffer = scope String(16);
for (var entry in Directory.Enumerate(filter, .Files))
{
entry.GetFilePath(filepathBuffer..Clear());
Path.GetExtension(filepathBuffer, .. extensionBuffer..Clear());
filepathBuffer.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
// Ignore meta files.
if (extensionBuffer.Equals(AssetFile.ConfigFileExtension, .OrdinalIgnoreCase))
continue;
TreeNode<AssetNode> treeNode = directory.Children.Where(scope (node) => node.Value.Path == filepathBuffer).FirstOrDefault();
if (treeNode == null)
{
AssetNode assetNode = new AssetNode();
assetNode.Name = new String();
Path.GetFileName(filepathBuffer, assetNode.Name);
assetNode.Path = new String(filepathBuffer);
assetNode.IsDirectory = false;
treeNode = directory.AddChild(assetNode);
DetermineIdentifier(treeNode);
_pathToAssetNode.Add(assetNode.Path, treeNode);
_identifierToAssetNode.Add(assetNode.Identifier, treeNode);
//GrabSubAssets(node);
HandleFile(treeNode.Value);
_handleToAssetNode.Add(assetNode.AssetFile.AssetConfig.AssetHandle, treeNode);
Log.EngineLogger.Trace($"Created file node for: \"{assetNode.Path}\"");
}
}
}
/// Scans the tree for orphaned nodes and removes them.
void RemoveOrphanedEntries(TreeNode<AssetNode> node)
{
for (TreeNode<AssetNode> child in node.Children)
{
if (!Directory.Exists(child->Path) && !File.Exists(child->Path))
{
Log.EngineLogger.Trace($"Removed orphaned node for: \"{child->Path}\"");
Log.EngineLogger.Trace($"Removing orphaned node for: \"{child->Path}\"");
@child.Remove();
// Remove entire subtree from all maps
child.ForEach(scope (node) => {
_pathToAssetNode.Remove(node->Path);
_identifierToAssetNode.Remove(node->Identifier);
if (node->AssetFile != null)
{
_handleToAssetNode.Remove(node->AssetFile.AssetConfig.AssetHandle);
}
Log.EngineLogger.Trace($"Removed orphaned node from tree {node->Path} ({node->AssetFile?.AssetConfig.AssetHandle})");
});
DeleteTreeAndChildren!(child);
}
}
}
/// Adds the given directory to the specified tree.
/// Recursively adds all Files and Subdirectories.
void AddDirectoryToTree(String path, TreeNode<AssetNode> parentNode)
{
path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
// Try to find the node for the specified path in the given parent
TreeNode<AssetNode> treeNode = parentNode.Children.Where(scope (node) => node.Value.Path == path).FirstOrDefault();
// Create new Node for the Directory, if no TreeNode exists.
if (treeNode == null)
{
AssetNode assetNode = new AssetNode();
assetNode.Path = new String(path);
assetNode.Name = new String();
//assetNode.Identifier = new String();
assetNode.IsDirectory = true;
Path.GetFileName(assetNode.Path, assetNode.Name);
/*// Get the Identifier which is simply the path relative to the asste root (either Resources- or Assets-Folder)
if (parentNode == _resourcesDirectoryNode || parentNode.IsChildOf(_resourcesDirectoryNode))
{
Path.GetRelativePath(assetNode.Path, _resourcesDirectoryNode->Path, assetNode.Identifier);
assetNode.Identifier.Insert(0, "Resources/");
}
else
{
Path.GetRelativePath(assetNode.Path, _assetsDirectoryNode->Path, assetNode.Identifier);
assetNode.Identifier.Insert(0, "Assets/");
RemoveOrphanedEntries(child);
}
}
}
AssetIdentifier.Fixup(assetNode.Identifier);*/
treeNode = parentNode.AddChild(assetNode);
/// Rebuilds the asset file hierarchy.
private void UpdateFiles()
{
Log.EngineLogger.Trace($"Updating asset hierarchy");
// Cleanup entire tree first to avoid
RemoveOrphanedEntries(_assetRootNode);
/// Creates a TreeNode for the given file path.
/// @param fileName The name of the file/directory to add to the tree.
/// @param isDirectory If True, the Entry will be added as a directory
/// @param parentNode The node that is the parentNode in the tree
TreeNode<AssetNode> CreateTreeNode(String filePath, bool isDirectory, TreeNode<AssetNode> parentNode)
{
AssetNode assetNode = new AssetNode();
assetNode.Path = new String(filePath);
assetNode.Name = new String();
assetNode.IsDirectory = isDirectory;
Path.GetFileName(filePath, assetNode.Name);
TreeNode<AssetNode> treeNode = parentNode.AddChild(assetNode);
DetermineIdentifier(treeNode);
_pathToAssetNode.Add(assetNode.Path, treeNode);
// No Identifiers for Directories, because we can't use directories as Assets anyways...
//_identifierToAssetNode.Add(assetNode.Identifier, treeNode);
_identifierToAssetNode.Add(assetNode.Identifier, treeNode);
Log.EngineLogger.Trace($"Created directory node for: \"{assetNode.Path}\"");
}
String directoryNameBuffer = scope String(256);
// Filter that finds all entries of a directory.
String filter = scope $"{path}/*";
for (var directory in Directory.Enumerate(filter, .Directories))
// Only files get AssetFile and AssetHandle
if (!isDirectory)
{
directory.GetFilePath(directoryNameBuffer..Clear());
// TODO: Subassets
//GrabSubAssets(node);
AddDirectoryToTree(directoryNameBuffer, treeNode);
// TODO: Apparently directories were supposed to get an AssetFile? Makes sense, we wanted to have settings for directories, too!
treeNode->AssetFile = new AssetFile(_contentManager, assetNode.Identifier, assetNode.Path, assetNode.IsDirectory);
_handleToAssetNode.Add(assetNode.AssetFile.AssetConfig.AssetHandle, treeNode);
}
RemoveOrphanedEntries(treeNode);
AddFilesOfDirectory(treeNode);
Log.EngineLogger.Trace($"Created {(isDirectory ? "directory" : "file")} node for: \"{assetNode.Path}\"");
return treeNode;
}
/// If necessary adds a tree node for the given file path. For Directories also adds files and subdirectories.
/// @param fileName The name of the file/directory to add to the tree.
/// @param isDirectory If True, the Entry will be added as a directory
/// @param parentNode The node that is the parentNode in the tree
void AddEntryToTree(String filePath, bool isDirectory, TreeNode<AssetNode> parentNode)
{
// Try to find the node for the specified path in the given parent
TreeNode<AssetNode> treeNode = parentNode.Children.Where(scope (node) => node.Value.Path == filePath).FirstOrDefault();
// If no node was found -> create one
if (treeNode == null)
treeNode = CreateTreeNode(filePath, isDirectory, parentNode);
if (isDirectory)
AddSubdirectoriesAndFilesToTree(treeNode);
}
/// Adds the subdirectories of the given directory to the tree
/// @param directoryNode The node of the directory whose subdirectories and files will be added.
void AddSubdirectoriesAndFilesToTree(TreeNode<AssetNode> directoryNode)
{
// Filter that finds all entries of a directory.
String filter = scope $"{directoryNode->Path}/*";
String filePathBuffer = scope String(256);
String fileExtensionBuffer = scope String(16);
for (var entry in Directory.Enumerate(filter, .Directories | .Files))
{
entry.GetFilePath(filePathBuffer..Clear());
filePathBuffer.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
if (!entry.IsDirectory)
{
// For files we have to check whether or not the file ends with .ass
fileExtensionBuffer.Clear();
Path.GetExtension(filePathBuffer, fileExtensionBuffer);
// Ignore meta files.
if (fileExtensionBuffer.Equals(AssetFile.ConfigFileExtension, .OrdinalIgnoreCase))
continue;
}
AddEntryToTree(filePathBuffer, entry.IsDirectory, directoryNode);
}
}
if (!AssetsDirectory.IsWhiteSpace)
{
String filter = scope $"{AssetsDirectory}/*";
String directoryNameBuffer = scope String(256);
for (var directory in Directory.Enumerate(filter, .Directories))
{
directory.GetFilePath(directoryNameBuffer..Clear());
AddDirectoryToTree(directoryNameBuffer, _assetsDirectoryNode);
}
RemoveOrphanedEntries(_assetsDirectoryNode);
AddFilesOfDirectory(_assetsDirectoryNode);
}
AddSubdirectoriesAndFilesToTree(_assetsDirectoryNode);
if (!ResourcesDirectory.IsWhiteSpace)
{
String filter = scope $"{ResourcesDirectory}/*";
String directoryNameBuffer = scope String(256);
for (var directory in Directory.Enumerate(filter, .Directories))
{
directory.GetFilePath(directoryNameBuffer..Clear());
AddDirectoryToTree(directoryNameBuffer, _resourcesDirectoryNode);
}
RemoveOrphanedEntries(_resourcesDirectoryNode);
AddFilesOfDirectory(_resourcesDirectoryNode);
}
//*String filter = scope $"{AssetsDirectory}/*";
/*
String directoryNameBuffer = scope String(256);
for (var directory in Directory.Enumerate(filter, .Directories))
{
directory.GetFilePath(directoryNameBuffer..Clear());
AddDirectoryToTree(directoryNameBuffer, _assetHierarchy);
}
RemoveOrphanedEntries(_assetHierarchy);*/
AddSubdirectoriesAndFilesToTree(_resourcesDirectoryNode);
_fileSystemDirty = false;
}
@@ -620,27 +589,35 @@ class AssetHierarchy
return;
}
// Save the old identifier
String oldIdentifier = scope .(node->Identifier);
_pathToAssetNode.Remove(oldFileNameWithContentRoot);
_identifierToAssetNode.Remove(node->Identifier);
node->Path.Set(newFileNameWithContentRoot);
_pathToAssetNode.Add(node->Path, node);
// Updates node->Identifier
DetermineIdentifier(node);
_identifierToAssetNode.Add(node->Identifier, node);
node->Name.Clear();
Path.GetFileName(newFileNameWithContentRoot, node->Name);
String oldIdentifier = scope .(node->AssetFile.[Friend]_identifier);
// Directories have no AssetFile?
if (node->AssetFile != null)
{
node->AssetFile.[Friend]_path.Set(node->Path);
node->AssetFile.[Friend]_identifier.Set(newFilePath);
AssetIdentifier.Fixup(node->AssetFile.[Friend]_identifier);
node->AssetFile.[Friend]_assetConfigPath..Set(node->Path).Append(AssetFile.ConfigFileExtension);
}
// Fire renamed event (e.g. because EditorContentManager needs to know)
OnFileRenamed(node.Value, oldIdentifier);
}
@@ -6,6 +6,7 @@ using GlitchyEngine.Collections;
using System.Collections;
using GlitchyEngine.Renderer;
using GlitchyEngine.Math;
using GlitchyEngine.Content;
using GlitchyEngine;
using GlitchyEditor.Assets;
@@ -48,6 +49,9 @@ namespace GlitchyEditor.EditWindows
private append String _selectedFile = .();
private append String _assetToRename = .();
private char8[128] _renameFileNameBuffer;
public static SubTexture2D s_FolderTexture;
public static SubTexture2D s_FileTexture;
@@ -467,7 +471,44 @@ namespace GlitchyEditor.EditWindows
EntryDoubleClicked(entry);
}
if (_assetToRename == entry->Path)
{
ImGui.PushItemWidth((.)IconSize.X);
if (ImGui.InputText("##renameBox", &_renameFileNameBuffer, _renameFileNameBuffer.Count - 1, .AutoSelectAll | .EnterReturnsTrue))
{
}
ImGui.PopItemWidth();
if (ImGui.IsKeyDown(.Escape))
{
_assetToRename.Clear();
}
else if (ImGui.IsItemDeactivatedAfterEdit())
{
StringView newName = StringView(&_renameFileNameBuffer);
if (!newName.IsWhiteSpace)
_manager.AssetHierarchy.RenameFile(entry.Value, newName);
_assetToRename.Clear();
}
else if (ImGui.IsItemDeactivated())
{
_assetToRename.Clear();
}
// TODO: Textbox
// TODO: Save when focus lost
// TODO: Abort on escape
//ImGui.TextUnformatted(entry->Name);
}
else
{
ImGui.TextUnformatted(entry->Name);
}
bool wantsDelete = false;
@@ -575,6 +616,16 @@ namespace GlitchyEditor.EditWindows
}
}
ImGui.Separator();
if (ImGui.MenuItem("Rename"))
{
// Copy the path, just for the rare case that fileOrFolder gets deleted
_assetToRename.Set(fileOrFolder->Path);
fileOrFolder->Name.CopyTo(_renameFileNameBuffer);
}
if (ImGui.MenuItem("Delete"))
{
wantsDelete = true;
+1 -1
View File
@@ -54,7 +54,7 @@ class EditorContentManager : IContentManager
public void OnFileRenamed(AssetNode assetNode, StringView oldIdentifier)
{
// Asset isn't loaded so we don't need to reload it.
if (assetNode.AssetFile.LoadedAsset == null)
if (assetNode.AssetFile?.LoadedAsset == null)
return;
Asset asset = assetNode.AssetFile.LoadedAsset;