mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Basic material serialisation
This commit is contained in:
@@ -4,3 +4,6 @@ WorkspaceFolders = {GlitchyEngine = ["GlitchyEngine", "GlitchLog", "GlitchyEngin
|
|||||||
|
|
||||||
[Workspace]
|
[Workspace]
|
||||||
StartupProject = "GlitchyEditor"
|
StartupProject = "GlitchyEditor"
|
||||||
|
|
||||||
|
[Configs.Debug.Win64]
|
||||||
|
AllocStackTraceDepth = 12
|
||||||
|
|||||||
@@ -1,7 +1,28 @@
|
|||||||
{
|
{
|
||||||
Effect = "content/Shaders/myEffect.hlsl",
|
Effect = "content/Shaders/myEffect.hlsl",
|
||||||
Textures =
|
Textures = [
|
||||||
[
|
"AlbedoTexture": "Textures/TestMat/rustediron2_albedo.png"
|
||||||
"AlbedoTexture": "Textures/TestMat/rustediron2_albedo.png"
|
],
|
||||||
]
|
Variables = [
|
||||||
|
"AlbedoColor": .ColorRGBA{
|
||||||
|
Value = {
|
||||||
|
R = 1,
|
||||||
|
G = 1,
|
||||||
|
B = 1,
|
||||||
|
A = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"NormalScaling": .Float2{
|
||||||
|
Value = {
|
||||||
|
X = 1,
|
||||||
|
Y = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MetallicFactor": .Float{
|
||||||
|
Value = 1
|
||||||
|
},
|
||||||
|
"RoughnessFactor": .Float{
|
||||||
|
Value = 1
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
using GlitchyEngine;
|
||||||
|
using GlitchyEngine.Collections;
|
||||||
|
using GlitchyEngine.Renderer;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Assets;
|
||||||
|
|
||||||
|
public class AssetNode
|
||||||
|
{
|
||||||
|
public String Name ~ delete _;
|
||||||
|
public String Path ~ delete _;
|
||||||
|
|
||||||
|
public bool IsDirectory;
|
||||||
|
|
||||||
|
public AssetFile AssetFile ~ delete _;
|
||||||
|
|
||||||
|
public List<SubAsset> SubAssets ~ {
|
||||||
|
SubAssets?.ClearAndDeleteItems();
|
||||||
|
delete SubAssets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Texture2D PreviewImage ~ _?.ReleaseRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SubAsset
|
||||||
|
{
|
||||||
|
public AssetNode Asset;
|
||||||
|
public String Name ~ delete _;
|
||||||
|
//public String AssetInternalPath ~ delete _;
|
||||||
|
|
||||||
|
public Texture2D PreviewImage ~ _?.ReleaseRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssetHierarchy
|
||||||
|
{
|
||||||
|
FileSystemWatcher fsw ~ {
|
||||||
|
_.StopRaisingEvents();
|
||||||
|
delete _;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool _fileSystemDirty = false;
|
||||||
|
|
||||||
|
internal TreeNode<AssetNode> _assetHierarchy = null ~ DeleteTreeAndChildren!(_);
|
||||||
|
private append Dictionary<StringView, TreeNode<AssetNode>> _pathToAssetNode = .();
|
||||||
|
|
||||||
|
private append String _contentDirectory = .();
|
||||||
|
|
||||||
|
private EditorContentManager _contentManager;
|
||||||
|
|
||||||
|
public StringView ContentDirectory
|
||||||
|
{
|
||||||
|
get => _contentDirectory;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
_contentDirectory.Clear();
|
||||||
|
_contentDirectory.Append(value);
|
||||||
|
Path.Fixup(_contentDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public this(EditorContentManager contentManager)
|
||||||
|
{
|
||||||
|
_contentManager = contentManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContentDirectory(StringView contentDirectory)
|
||||||
|
{
|
||||||
|
ContentDirectory = contentDirectory;
|
||||||
|
|
||||||
|
_fileSystemDirty = true;
|
||||||
|
|
||||||
|
SetupFileSystemWatcher();
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the FSW for the current ContentDirectory and registers the events.
|
||||||
|
private void SetupFileSystemWatcher()
|
||||||
|
{
|
||||||
|
delete fsw;
|
||||||
|
fsw = new FileSystemWatcher(_contentDirectory);
|
||||||
|
fsw.IncludeSubdirectories = true;
|
||||||
|
|
||||||
|
fsw.OnChanged.Add(new (filename) => {
|
||||||
|
// Note: Gets fired for a directory if a file inside it is created/removed
|
||||||
|
|
||||||
|
Log.EngineLogger.Trace($"File content changed (\"{filename}\")");
|
||||||
|
//_fileSystemDirty = true;
|
||||||
|
|
||||||
|
FileContentChanged(filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
fsw.OnCreated.Add(new (filename) => {
|
||||||
|
Log.EngineLogger.Trace($"File created (\"{filename}\")");
|
||||||
|
|
||||||
|
_fileSystemDirty = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
fsw.OnDeleted.Add(new (filename) => {
|
||||||
|
Log.EngineLogger.Trace($"File deleted (\"{filename}\")");
|
||||||
|
|
||||||
|
_fileSystemDirty = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
fsw.OnRenamed.Add(new (oldName, newName) => {
|
||||||
|
Log.EngineLogger.Trace($"File renamed (From \"{oldName}\" to \"{newName}\")");
|
||||||
|
|
||||||
|
_fileSystemDirty = true;
|
||||||
|
/*String contentFilePath = scope String();
|
||||||
|
|
||||||
|
Path.InternalCombine(contentFilePath, ContentDirectory, oldName);
|
||||||
|
|
||||||
|
//_fileSystemDirty = true;
|
||||||
|
TreeNode<AssetNode> fileNode = GetNodeFromPath(contentFilePath);
|
||||||
|
fileNode->*/
|
||||||
|
});
|
||||||
|
|
||||||
|
fsw.StartRaisingEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the tree node for the given filePath or null, if the file/directory doesn't exist.
|
||||||
|
/// @param filePath the path for which to return the tree node.
|
||||||
|
/// @remarks Do not hold a reference to the TreeNode because it can become invalid when the file hierarchy changes.
|
||||||
|
public Result<TreeNode<AssetNode>> GetNodeFromPath(StringView filePath)
|
||||||
|
{
|
||||||
|
if (_pathToAssetNode.TryGetValue(filePath, let treeNode))
|
||||||
|
{
|
||||||
|
return treeNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FileExists(StringView filePath)
|
||||||
|
{
|
||||||
|
return _pathToAssetNode.ContainsKey(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
// TODO: do we really need to do this in the update loop?
|
||||||
|
if (_fileSystemDirty)
|
||||||
|
{
|
||||||
|
UpdateFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Rebuilds the asset file hierarchy.
|
||||||
|
private void UpdateFiles()
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Trace($"Updating asset hierarchy");
|
||||||
|
|
||||||
|
if (_assetHierarchy == null)
|
||||||
|
{
|
||||||
|
// TODO: move to init?
|
||||||
|
|
||||||
|
_assetHierarchy = new TreeNode<AssetNode>(new AssetNode());
|
||||||
|
_assetHierarchy->Path = new String(ContentDirectory);
|
||||||
|
_assetHierarchy->Name = new String("Content");
|
||||||
|
|
||||||
|
Log.EngineLogger.Trace($"Created directory node for: \"{_assetHierarchy->Path}\"");
|
||||||
|
|
||||||
|
_pathToAssetNode.Add(ContentDirectory, _assetHierarchy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleFile(AssetNode node)
|
||||||
|
{
|
||||||
|
String identifier = scope .(node.Path.Length);
|
||||||
|
Path.GetRelativePath(node.Path, _contentDirectory, identifier);
|
||||||
|
|
||||||
|
node.AssetFile = new AssetFile(_contentManager, 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());
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
filepathBuffer.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
assetNode.Path = new String(filepathBuffer);
|
||||||
|
assetNode.IsDirectory = false;
|
||||||
|
|
||||||
|
treeNode = directory.AddChild(assetNode);
|
||||||
|
_pathToAssetNode.Add(assetNode.Path, treeNode);
|
||||||
|
|
||||||
|
//GrabSubAssets(node);
|
||||||
|
HandleFile(treeNode.Value);
|
||||||
|
|
||||||
|
Log.EngineLogger.Trace($"Created file node for: \"{assetNode.Path}\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveOrphanedEntries(TreeNode<AssetNode> node)
|
||||||
|
{
|
||||||
|
/// Removes the node and its children from _pathToAssetNode
|
||||||
|
void RemoveSubtree(TreeNode<AssetNode> tree)
|
||||||
|
{
|
||||||
|
_pathToAssetNode.Remove(tree->Path);
|
||||||
|
|
||||||
|
for (var child in tree.Children)
|
||||||
|
{
|
||||||
|
RemoveSubtree(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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}\"");
|
||||||
|
|
||||||
|
@child.Remove();
|
||||||
|
|
||||||
|
RemoveSubtree(child);
|
||||||
|
|
||||||
|
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.IsDirectory = true;
|
||||||
|
Path.GetFileName(assetNode.Path, assetNode.Name);
|
||||||
|
|
||||||
|
treeNode = parentNode.AddChild(assetNode);
|
||||||
|
_pathToAssetNode.Add(assetNode.Path, 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))
|
||||||
|
{
|
||||||
|
directory.GetFilePath(directoryNameBuffer..Clear());
|
||||||
|
|
||||||
|
AddDirectoryToTree(directoryNameBuffer, treeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddFilesOfDirectory(treeNode);
|
||||||
|
|
||||||
|
RemoveOrphanedEntries(treeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
String filter = scope $"{ContentDirectory}/*";
|
||||||
|
|
||||||
|
String directoryNameBuffer = scope String(256);
|
||||||
|
|
||||||
|
for (var directory in Directory.Enumerate(filter, .Directories))
|
||||||
|
{
|
||||||
|
directory.GetFilePath(directoryNameBuffer..Clear());
|
||||||
|
|
||||||
|
AddDirectoryToTree(directoryNameBuffer, _assetHierarchy);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveOrphanedEntries(_assetHierarchy);
|
||||||
|
|
||||||
|
_fileSystemDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FileContentChanged(StringView fileName)
|
||||||
|
{
|
||||||
|
var fileName;
|
||||||
|
|
||||||
|
// Config files aren't really tracked but changing them effectively changes the corresponding file
|
||||||
|
// so we fire the event for them.
|
||||||
|
if (fileName.EndsWith(AssetFile.ConfigFileExtension))
|
||||||
|
fileName.RemoveFromEnd(AssetFile.ConfigFileExtension.Length);
|
||||||
|
|
||||||
|
String fileNameWithContentRoot = scope .();
|
||||||
|
Path.InternalCombine(fileNameWithContentRoot, _contentDirectory, fileName);
|
||||||
|
|
||||||
|
var nodeResult = GetNodeFromPath(fileNameWithContentRoot);
|
||||||
|
|
||||||
|
TreeNode<AssetNode> node = null;
|
||||||
|
|
||||||
|
if (!(nodeResult case .Ok(out node)))
|
||||||
|
{
|
||||||
|
// This happens, when we create new files.
|
||||||
|
Log.EngineLogger.Trace($"Could not find node for file \"{fileNameWithContentRoot}\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't fire event for directories.
|
||||||
|
if (node->IsDirectory)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OnFileContentChanged(node.Value);
|
||||||
|
|
||||||
|
// TODO: Handle file changes (reload asset, etc...)
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void FileContentChangedFunc(AssetNode node);
|
||||||
|
|
||||||
|
public Event<FileContentChangedFunc> OnFileContentChanged ~ _.Dispose();
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using GlitchyEngine.Content;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.Assets;
|
||||||
|
|
||||||
|
interface IAssetSaver
|
||||||
|
{
|
||||||
|
Result<void> EditorSaveAsset(Stream file, Asset asset, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager);
|
||||||
|
}
|
||||||
@@ -7,11 +7,26 @@ using GlitchyEngine;
|
|||||||
using GlitchyEngine.Renderer;
|
using GlitchyEngine.Renderer;
|
||||||
using ImGui;
|
using ImGui;
|
||||||
using GlitchyEngine.Math;
|
using GlitchyEngine.Math;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Bon.Integrated;
|
||||||
|
|
||||||
namespace GlitchyEditor.Assets;
|
namespace GlitchyEditor.Assets;
|
||||||
|
|
||||||
class MaterialAssetPropertiesEditor : AssetPropertiesEditor
|
class MaterialAssetPropertiesEditor : AssetPropertiesEditor
|
||||||
{
|
{
|
||||||
|
public static bool TryGetValue(Dictionary<String, Variant> parameters, String name, out Variant value)
|
||||||
|
{
|
||||||
|
if (parameters.TryGetValue(name, let param))
|
||||||
|
{
|
||||||
|
value = param;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = ?;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mixin DropAssetTarget<T>() where T : Asset
|
mixin DropAssetTarget<T>() where T : Asset
|
||||||
{
|
{
|
||||||
Asset asset = null;
|
Asset asset = null;
|
||||||
@@ -83,18 +98,6 @@ class MaterialAssetPropertiesEditor : AssetPropertiesEditor
|
|||||||
|
|
||||||
private void ShowVariables(Material material, Effect effect)
|
private void ShowVariables(Material material, Effect effect)
|
||||||
{
|
{
|
||||||
bool TryGetValue(Dictionary<String, Variant> parameters, String name, out Variant value)
|
|
||||||
{
|
|
||||||
if (parameters.TryGetValue(name, let param))
|
|
||||||
{
|
|
||||||
value = param;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = ?;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let (name, arguments) in effect.[Friend]_variableDescriptions)
|
for (let (name, arguments) in effect.[Friend]_variableDescriptions)
|
||||||
{
|
{
|
||||||
@@ -194,16 +197,123 @@ class MaterialAssetLoaderConfig : AssetLoaderConfig
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BonTarget]
|
||||||
|
public enum VariableValue
|
||||||
|
{
|
||||||
|
case Float(float Value);
|
||||||
|
case Float2(Vector2 Value);
|
||||||
|
case Float3(Vector3 Value);
|
||||||
|
case Float4(Vector4 Value);
|
||||||
|
case Int(int Value);
|
||||||
|
case Int2(Int2 Value);
|
||||||
|
case Int3(Int3 Value);
|
||||||
|
case Int4(Int4 Value);
|
||||||
|
case ColorRGB(ColorRGB Value);
|
||||||
|
case ColorRGBA(ColorRGBA Value);
|
||||||
|
case None;
|
||||||
|
|
||||||
|
/*static this()
|
||||||
|
{
|
||||||
|
gBonEnv.typeHandlers.Add(typeof(Self),
|
||||||
|
((.)new => VariableValueSerialize, (.)new => VariableValueDeserialize));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void VariableValueSerialize(BonWriter writer, ValueView value, BonEnvironment env)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Assert(value.type == typeof(Self));
|
||||||
|
|
||||||
|
let variableValue = value.Get<Self>();
|
||||||
|
|
||||||
|
writer.Type(variableValue)
|
||||||
|
using (writer.ObjectBlock())
|
||||||
|
{
|
||||||
|
Serialize.Value(writer, nameof(MaterialFile.Effect), materialFile.Effect, env);
|
||||||
|
Serialize.Value(writer, nameof(MaterialFile.Textures), materialFile.Textures, env);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result<void> VariableValueDeserialize(BonReader reader, ValueView val, BonEnvironment env)
|
||||||
|
{
|
||||||
|
return .Ok;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
[BonTarget]
|
[BonTarget]
|
||||||
class MaterialFile
|
class MaterialFile
|
||||||
{
|
{
|
||||||
public String Effect ~ delete _;
|
public String Effect ~ delete _;
|
||||||
|
|
||||||
public Dictionary<String, String> Textures ~ DeleteDictionaryAndKeysAndValues!(_);
|
public Dictionary<String, String> Textures ~ DeleteDictionaryAndKeysAndValues!(_);
|
||||||
//public Dictionary<String, Object> Variables;
|
public Dictionary<String, VariableValue> Variables ~
|
||||||
|
{
|
||||||
|
if (_ != null)
|
||||||
|
{
|
||||||
|
for (var entry in _)
|
||||||
|
{
|
||||||
|
delete entry.key;
|
||||||
|
//delete entry.value;
|
||||||
|
/*if (entry.value.HasValue)
|
||||||
|
entry.value->Dispose();*/
|
||||||
|
}
|
||||||
|
|
||||||
|
delete _;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*static this()
|
||||||
|
{
|
||||||
|
gBonEnv.typeHandlers.Add(typeof(Self),
|
||||||
|
((.)new => MaterialSerialize, (.)new => MaterialDeserialize));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MaterialSerialize(BonWriter writer, ValueView value, BonEnvironment env)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Assert(value.type == typeof(Self));
|
||||||
|
|
||||||
|
let materialFile = value.Get<Self>();
|
||||||
|
|
||||||
|
using (writer.ObjectBlock())
|
||||||
|
{
|
||||||
|
Serialize.Value(writer, nameof(MaterialFile.Effect), materialFile.Effect, env);
|
||||||
|
Serialize.Value(writer, nameof(MaterialFile.Textures), materialFile.Textures, env);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SerializeVariablesDictionary(BonWriter writer, MaterialFile materialFile, BonEnvironment env)
|
||||||
|
{
|
||||||
|
using (writer.ArrayBlock())
|
||||||
|
{
|
||||||
|
for (let (name, value) in materialFile.Variables)
|
||||||
|
{
|
||||||
|
let keyVal = ValueView(typeof(String), name);
|
||||||
|
Serialize.Value(writer, keyVal, env);
|
||||||
|
writer.Pair();
|
||||||
|
|
||||||
|
ValueView valueVal;// = ValueView(, entriesPtr + (currentIndex * entryStride) + entryValueOffset);
|
||||||
|
switch(value.GetType())
|
||||||
|
{
|
||||||
|
case typeof(ColorRGBA):
|
||||||
|
writer.Identifier("ColorRGBA");
|
||||||
|
default:
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Serialize.Value(writer, valueVal, env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result<void> MaterialDeserialize(BonReader reader, ValueView val, BonEnvironment env)
|
||||||
|
{
|
||||||
|
return .Ok;
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
class MaterialAssetLoader : IAssetLoader //, IReloadingAssetLoader
|
class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
|
||||||
{
|
{
|
||||||
private static readonly List<StringView> _fileExtensions = new .(){".mat"} ~ delete _;
|
private static readonly List<StringView> _fileExtensions = new .(){".mat"} ~ delete _;
|
||||||
|
|
||||||
@@ -229,6 +339,7 @@ class MaterialAssetLoader : IAssetLoader //, IReloadingAssetLoader
|
|||||||
if (result case .Err)
|
if (result case .Err)
|
||||||
{
|
{
|
||||||
Log.EngineLogger.Error("Failed to load material.");
|
Log.EngineLogger.Error("Failed to load material.");
|
||||||
|
Debug.SafeBreak();
|
||||||
return null;
|
return null;
|
||||||
// TODO: return error material
|
// TODO: return error material
|
||||||
}
|
}
|
||||||
@@ -243,7 +354,7 @@ class MaterialAssetLoader : IAssetLoader //, IReloadingAssetLoader
|
|||||||
{
|
{
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
Log.EngineLogger.Error("Failed to load texture.");
|
Log.EngineLogger.Error($"Failed to load texture \"{textureIdentifier}\".");
|
||||||
// TODO: LoadAsset should return an error texture.
|
// TODO: LoadAsset should return an error texture.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,17 +364,167 @@ class MaterialAssetLoader : IAssetLoader //, IReloadingAssetLoader
|
|||||||
|
|
||||||
fx.ReleaseRef();
|
fx.ReleaseRef();
|
||||||
|
|
||||||
/*for (let (slotName, textureIdentifier) in materialFile.Variables)
|
for (let (slotName, variableValue) in materialFile.Variables)
|
||||||
{
|
{
|
||||||
if (texture == null)
|
switch (variableValue)
|
||||||
{
|
{
|
||||||
Log.EngineLogger.Error("Failed to load texture.");
|
case .ColorRGBA(let value):
|
||||||
// TODO: LoadAsset should return an error texture.
|
material.SetVariable(slotName, value);
|
||||||
|
case .ColorRGB(let value):
|
||||||
|
material.SetVariable(slotName, value);
|
||||||
|
case .Float(let value):
|
||||||
|
material.SetVariable(slotName, value);
|
||||||
|
case .Float2(let value):
|
||||||
|
material.SetVariable(slotName, value);
|
||||||
|
case .Float3(let value):
|
||||||
|
material.SetVariable(slotName, value);
|
||||||
|
case .Float4(let value):
|
||||||
|
material.SetVariable(slotName, value);
|
||||||
|
case .None:
|
||||||
|
default:
|
||||||
|
Log.EngineLogger.Error("Errorre");
|
||||||
}
|
}
|
||||||
|
|
||||||
material.SetVariable(slotName, );
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return material; //ModelLoader.LoadMesh(file, subAsset.Value, 0);
|
}
|
||||||
|
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result<void> EditorSaveAsset(Stream file, Asset asset, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
|
||||||
|
{
|
||||||
|
Material material = asset as Material;
|
||||||
|
|
||||||
|
if (material == null)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error("Asset must be a Material!");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialFile materialFile = scope .();
|
||||||
|
|
||||||
|
materialFile.Effect = new String(material.Effect.Identifier);
|
||||||
|
materialFile.Textures = new .();
|
||||||
|
materialFile.Variables = new .();
|
||||||
|
|
||||||
|
//material.SetTexture();
|
||||||
|
|
||||||
|
for (let (slotName, textureViewBinding) in material.[Friend]_textures)
|
||||||
|
{
|
||||||
|
//materialFile.Textures.Add(slotName, textureViewBinding.)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Effect effect = material.Effect;
|
||||||
|
|
||||||
|
if (effect == null)
|
||||||
|
return .Ok;
|
||||||
|
|
||||||
|
for (let (name, arguments) in effect.[Friend]_variableDescriptions)
|
||||||
|
{
|
||||||
|
VariableValue variableValue = .None;
|
||||||
|
//Object variantValue = null;
|
||||||
|
|
||||||
|
let variable = effect.Variables[name];
|
||||||
|
|
||||||
|
bool hasPreviewType = MaterialAssetPropertiesEditor.TryGetValue(arguments, "Type", var previewType);
|
||||||
|
|
||||||
|
if (hasPreviewType && previewType.Get<String>() == "Color")
|
||||||
|
{
|
||||||
|
Log.EngineLogger.AssertDebug(variable.Type == .Float && variable.Rows == 1);
|
||||||
|
|
||||||
|
if (variable.Columns == 3)
|
||||||
|
{
|
||||||
|
material.GetVariable<ColorRGB>(variable.Name, var value);
|
||||||
|
|
||||||
|
value = ColorRGB.LinearToSRGB((ColorRGB)value);
|
||||||
|
|
||||||
|
//variantValue = new box value;
|
||||||
|
variableValue = .ColorRGB(value);
|
||||||
|
}
|
||||||
|
else if (variable.Columns == 4)
|
||||||
|
{
|
||||||
|
material.GetVariable<ColorRGBA>(variable.Name, var value);
|
||||||
|
|
||||||
|
value = ColorRGBA.LinearToSRGB((ColorRGBA)value);
|
||||||
|
|
||||||
|
variableValue = .ColorRGBA(value);
|
||||||
|
//variantValue = new box value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (variable.Type == .Float && variable.Rows == 1)
|
||||||
|
{
|
||||||
|
switch (variable.Columns)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
material.GetVariable<float>(variable.Name, let value);
|
||||||
|
variableValue = .Float(value);
|
||||||
|
case 2:
|
||||||
|
material.GetVariable<Vector2>(variable.Name, let value);
|
||||||
|
variableValue = .Float2(value);
|
||||||
|
case 3:
|
||||||
|
material.GetVariable<Vector3>(variable.Name, let value);
|
||||||
|
variableValue = .Float3(value);
|
||||||
|
case 4:
|
||||||
|
material.GetVariable<Vector4>(variable.Name, let value);
|
||||||
|
variableValue = .Float4(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*bool hasMin = TryGetValue(arguments, "Min", var min);
|
||||||
|
bool hasMax = TryGetValue(arguments, "Max", var max);
|
||||||
|
|
||||||
|
for (int r < variable.Rows)
|
||||||
|
{
|
||||||
|
switch (variable.Columns)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
material.GetVariable<float>(variable.Name, var value);
|
||||||
|
|
||||||
|
float[1] minV = hasMin ? min.Get<float[1]>() : .(float.MinValue);
|
||||||
|
float[1] maxV = hasMax ? max.Get<float[1]>() : .(float.MaxValue);
|
||||||
|
|
||||||
|
if (ImGui.EditVector<1>(displayName, ref *(float[1]*)&value, .(), 0.1f, 100.0f, minV, maxV))
|
||||||
|
material.SetVariable(variable.Name, value);
|
||||||
|
case 2:
|
||||||
|
material.GetVariable<Vector2>(variable.Name, var value);
|
||||||
|
|
||||||
|
Vector2 minV = hasMin ? min.Get<Vector2>() : .(float.MinValue);
|
||||||
|
Vector2 maxV = hasMax ? max.Get<Vector2>() : .(float.MaxValue);
|
||||||
|
|
||||||
|
if (ImGui.EditVector2(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
|
||||||
|
material.SetVariable(variable.Name, value);
|
||||||
|
case 3:
|
||||||
|
material.GetVariable<Vector3>(variable.Name, var value);
|
||||||
|
|
||||||
|
Vector3 minV = hasMin ? min.Get<Vector3>() : .(float.MinValue);
|
||||||
|
Vector3 maxV = hasMax ? max.Get<Vector3>() : .(float.MaxValue);
|
||||||
|
|
||||||
|
if (ImGui.EditVector3(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
|
||||||
|
material.SetVariable(variable.Name, value);
|
||||||
|
case 4:
|
||||||
|
material.GetVariable<Vector4>(variable.Name, var value);
|
||||||
|
|
||||||
|
Vector4 minV = hasMin ? min.Get<Vector4>() : .(float.MinValue);
|
||||||
|
Vector4 maxV = hasMax ? max.Get<Vector4>() : .(float.MaxValue);
|
||||||
|
|
||||||
|
if (ImGui.EditVector4(displayName, ref value, .Zero, 0.1f, 100.0f, minV, maxV))
|
||||||
|
material.SetVariable(variable.Name, value);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
materialFile.Variables.Add(name, variableValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
String text = scope .();
|
||||||
|
|
||||||
|
gBonEnv.serializeFlags |= .IncludeDefault | .Verbose;
|
||||||
|
|
||||||
|
Bon.Serialize<MaterialFile>(materialFile, text);
|
||||||
|
|
||||||
|
StreamWriter writer = scope .(file, .UTF8, 1024);
|
||||||
|
writer.Write(text);
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using System;
|
||||||
|
namespace Bon.Integrated;
|
||||||
|
|
||||||
|
extension Serialize
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ using System.Collections;
|
|||||||
using GlitchyEngine.Renderer;
|
using GlitchyEngine.Renderer;
|
||||||
using GlitchyEngine.Math;
|
using GlitchyEngine.Math;
|
||||||
using GlitchyEngine;
|
using GlitchyEngine;
|
||||||
|
using GlitchyEditor.Assets;
|
||||||
|
|
||||||
namespace GlitchyEditor.EditWindows
|
namespace GlitchyEditor.EditWindows
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class PropertiesWindow : EditorWindow
|
|||||||
|
|
||||||
private append String _selectedFileName = .();
|
private append String _selectedFileName = .();
|
||||||
|
|
||||||
private Asset _currentAsset ~ _.ReleaseRef();
|
private Asset _currentAsset ~ _?.ReleaseRef();
|
||||||
|
|
||||||
public this(Editor editor)
|
public this(Editor editor)
|
||||||
{
|
{
|
||||||
@@ -104,6 +104,11 @@ class PropertiesWindow : EditorWindow
|
|||||||
|
|
||||||
_currentPropertiesEditor.ShowEditor();
|
_currentPropertiesEditor.ShowEditor();
|
||||||
|
|
||||||
|
if (ImGui.Button("Save Asset"))
|
||||||
|
{
|
||||||
|
_editor.ContentManager.SaveAsset(_currentAsset);
|
||||||
|
}
|
||||||
|
|
||||||
if (!assetFile.AssetConfig.Config.Changed)
|
if (!assetFile.AssetConfig.Config.Changed)
|
||||||
{
|
{
|
||||||
ImGui.BeginDisabled();
|
ImGui.BeginDisabled();
|
||||||
|
|||||||
@@ -73,340 +73,6 @@ namespace GlitchyEditor;
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
public class AssetNode
|
|
||||||
{
|
|
||||||
public String Name ~ delete _;
|
|
||||||
public String Path ~ delete _;
|
|
||||||
|
|
||||||
public bool IsDirectory;
|
|
||||||
|
|
||||||
public AssetFile AssetFile ~ delete _;
|
|
||||||
|
|
||||||
public List<SubAsset> SubAssets ~ {
|
|
||||||
SubAssets?.ClearAndDeleteItems();
|
|
||||||
delete SubAssets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Texture2D PreviewImage ~ _?.ReleaseRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SubAsset
|
|
||||||
{
|
|
||||||
public AssetNode Asset;
|
|
||||||
public String Name ~ delete _;
|
|
||||||
//public String AssetInternalPath ~ delete _;
|
|
||||||
|
|
||||||
public Texture2D PreviewImage ~ _?.ReleaseRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
class AssetHierarchy
|
|
||||||
{
|
|
||||||
FileSystemWatcher fsw ~ {
|
|
||||||
_.StopRaisingEvents();
|
|
||||||
delete _;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool _fileSystemDirty = false;
|
|
||||||
|
|
||||||
internal TreeNode<AssetNode> _assetHierarchy = null ~ DeleteTreeAndChildren!(_);
|
|
||||||
private append Dictionary<StringView, TreeNode<AssetNode>> _pathToAssetNode = .();
|
|
||||||
|
|
||||||
private append String _contentDirectory = .();
|
|
||||||
|
|
||||||
private EditorContentManager _contentManager;
|
|
||||||
|
|
||||||
public StringView ContentDirectory
|
|
||||||
{
|
|
||||||
get => _contentDirectory;
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
_contentDirectory.Clear();
|
|
||||||
_contentDirectory.Append(value);
|
|
||||||
Path.Fixup(_contentDirectory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public this(EditorContentManager contentManager)
|
|
||||||
{
|
|
||||||
_contentManager = contentManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetContentDirectory(StringView contentDirectory)
|
|
||||||
{
|
|
||||||
ContentDirectory = contentDirectory;
|
|
||||||
|
|
||||||
_fileSystemDirty = true;
|
|
||||||
|
|
||||||
SetupFileSystemWatcher();
|
|
||||||
|
|
||||||
Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initializes the FSW for the current ContentDirectory and registers the events.
|
|
||||||
private void SetupFileSystemWatcher()
|
|
||||||
{
|
|
||||||
delete fsw;
|
|
||||||
fsw = new FileSystemWatcher(_contentDirectory);
|
|
||||||
fsw.IncludeSubdirectories = true;
|
|
||||||
|
|
||||||
fsw.OnChanged.Add(new (filename) => {
|
|
||||||
// Note: Gets fired for a directory if a file inside it is created/removed
|
|
||||||
|
|
||||||
Log.EngineLogger.Trace($"File content changed (\"{filename}\")");
|
|
||||||
//_fileSystemDirty = true;
|
|
||||||
|
|
||||||
FileContentChanged(filename);
|
|
||||||
});
|
|
||||||
|
|
||||||
fsw.OnCreated.Add(new (filename) => {
|
|
||||||
Log.EngineLogger.Trace($"File created (\"{filename}\")");
|
|
||||||
|
|
||||||
_fileSystemDirty = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
fsw.OnDeleted.Add(new (filename) => {
|
|
||||||
Log.EngineLogger.Trace($"File deleted (\"{filename}\")");
|
|
||||||
|
|
||||||
_fileSystemDirty = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
fsw.OnRenamed.Add(new (oldName, newName) => {
|
|
||||||
Log.EngineLogger.Trace($"File renamed (From \"{oldName}\" to \"{newName}\")");
|
|
||||||
|
|
||||||
_fileSystemDirty = true;
|
|
||||||
/*String contentFilePath = scope String();
|
|
||||||
|
|
||||||
Path.InternalCombine(contentFilePath, ContentDirectory, oldName);
|
|
||||||
|
|
||||||
//_fileSystemDirty = true;
|
|
||||||
TreeNode<AssetNode> fileNode = GetNodeFromPath(contentFilePath);
|
|
||||||
fileNode->*/
|
|
||||||
});
|
|
||||||
|
|
||||||
fsw.StartRaisingEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the tree node for the given filePath or null, if the file/directory doesn't exist.
|
|
||||||
/// @param filePath the path for which to return the tree node.
|
|
||||||
/// @remarks Do not hold a reference to the TreeNode because it can become invalid when the file hierarchy changes.
|
|
||||||
public Result<TreeNode<AssetNode>> GetNodeFromPath(StringView filePath)
|
|
||||||
{
|
|
||||||
if (_pathToAssetNode.TryGetValue(filePath, let treeNode))
|
|
||||||
{
|
|
||||||
return treeNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return .Err;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FileExists(StringView filePath)
|
|
||||||
{
|
|
||||||
return _pathToAssetNode.ContainsKey(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update()
|
|
||||||
{
|
|
||||||
// TODO: do we really need to do this in the update loop?
|
|
||||||
if (_fileSystemDirty)
|
|
||||||
{
|
|
||||||
UpdateFiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Rebuilds the asset file hierarchy.
|
|
||||||
private void UpdateFiles()
|
|
||||||
{
|
|
||||||
Log.EngineLogger.Trace($"Updating asset hierarchy");
|
|
||||||
|
|
||||||
if (_assetHierarchy == null)
|
|
||||||
{
|
|
||||||
// TODO: move to init?
|
|
||||||
|
|
||||||
_assetHierarchy = new TreeNode<AssetNode>(new AssetNode());
|
|
||||||
_assetHierarchy->Path = new String(ContentDirectory);
|
|
||||||
_assetHierarchy->Name = new String("Content");
|
|
||||||
|
|
||||||
Log.EngineLogger.Trace($"Created directory node for: \"{_assetHierarchy->Path}\"");
|
|
||||||
|
|
||||||
_pathToAssetNode.Add(ContentDirectory, _assetHierarchy);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleFile(AssetNode node)
|
|
||||||
{
|
|
||||||
String identifier = scope .(node.Path.Length);
|
|
||||||
Path.GetRelativePath(node.Path, _contentDirectory, identifier);
|
|
||||||
|
|
||||||
node.AssetFile = new AssetFile(_contentManager, 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());
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
filepathBuffer.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
|
||||||
|
|
||||||
assetNode.Path = new String(filepathBuffer);
|
|
||||||
assetNode.IsDirectory = false;
|
|
||||||
|
|
||||||
treeNode = directory.AddChild(assetNode);
|
|
||||||
_pathToAssetNode.Add(assetNode.Path, treeNode);
|
|
||||||
|
|
||||||
//GrabSubAssets(node);
|
|
||||||
HandleFile(treeNode.Value);
|
|
||||||
|
|
||||||
Log.EngineLogger.Trace($"Created file node for: \"{assetNode.Path}\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RemoveOrphanedEntries(TreeNode<AssetNode> node)
|
|
||||||
{
|
|
||||||
/// Removes the node and its children from _pathToAssetNode
|
|
||||||
void RemoveSubtree(TreeNode<AssetNode> tree)
|
|
||||||
{
|
|
||||||
_pathToAssetNode.Remove(tree->Path);
|
|
||||||
|
|
||||||
for (var child in tree.Children)
|
|
||||||
{
|
|
||||||
RemoveSubtree(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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}\"");
|
|
||||||
|
|
||||||
@child.Remove();
|
|
||||||
|
|
||||||
RemoveSubtree(child);
|
|
||||||
|
|
||||||
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.IsDirectory = true;
|
|
||||||
Path.GetFileName(assetNode.Path, assetNode.Name);
|
|
||||||
|
|
||||||
treeNode = parentNode.AddChild(assetNode);
|
|
||||||
_pathToAssetNode.Add(assetNode.Path, 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))
|
|
||||||
{
|
|
||||||
directory.GetFilePath(directoryNameBuffer..Clear());
|
|
||||||
|
|
||||||
AddDirectoryToTree(directoryNameBuffer, treeNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddFilesOfDirectory(treeNode);
|
|
||||||
|
|
||||||
RemoveOrphanedEntries(treeNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
String filter = scope $"{ContentDirectory}/*";
|
|
||||||
|
|
||||||
String directoryNameBuffer = scope String(256);
|
|
||||||
|
|
||||||
for (var directory in Directory.Enumerate(filter, .Directories))
|
|
||||||
{
|
|
||||||
directory.GetFilePath(directoryNameBuffer..Clear());
|
|
||||||
|
|
||||||
AddDirectoryToTree(directoryNameBuffer, _assetHierarchy);
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveOrphanedEntries(_assetHierarchy);
|
|
||||||
|
|
||||||
_fileSystemDirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FileContentChanged(StringView fileName)
|
|
||||||
{
|
|
||||||
var fileName;
|
|
||||||
|
|
||||||
// Config files aren't really tracked but changing them effectively changes the corresponding file
|
|
||||||
// so we fire the event for them.
|
|
||||||
if (fileName.EndsWith(AssetFile.ConfigFileExtension))
|
|
||||||
fileName.RemoveFromEnd(AssetFile.ConfigFileExtension.Length);
|
|
||||||
|
|
||||||
String fileNameWithContentRoot = scope .();
|
|
||||||
Path.InternalCombine(fileNameWithContentRoot, _contentDirectory, fileName);
|
|
||||||
|
|
||||||
var nodeResult = GetNodeFromPath(fileNameWithContentRoot);
|
|
||||||
|
|
||||||
TreeNode<AssetNode> node = null;
|
|
||||||
|
|
||||||
if (!(nodeResult case .Ok(out node)))
|
|
||||||
{
|
|
||||||
// This happens, when we create new files.
|
|
||||||
Log.EngineLogger.Trace($"Could not find node for file \"{fileNameWithContentRoot}\"");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't fire event for directories.
|
|
||||||
if (node->IsDirectory)
|
|
||||||
return;
|
|
||||||
|
|
||||||
OnFileContentChanged(node.Value);
|
|
||||||
|
|
||||||
// TODO: Handle file changes (reload asset, etc...)
|
|
||||||
}
|
|
||||||
|
|
||||||
public delegate void FileContentChangedFunc(AssetNode node);
|
|
||||||
|
|
||||||
public Event<FileContentChangedFunc> OnFileContentChanged ~ _.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
class EditorContentManager : IContentManager
|
class EditorContentManager : IContentManager
|
||||||
{
|
{
|
||||||
private append String _contentDirectory = .();
|
private append String _contentDirectory = .();
|
||||||
@@ -602,7 +268,6 @@ class EditorContentManager : IContentManager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//AssetFile file = scope .(this, filePath, false);
|
|
||||||
AssetFile file = resultNode->Value.AssetFile;
|
AssetFile file = resultNode->Value.AssetFile;
|
||||||
|
|
||||||
IAssetLoader assetLoader = null;
|
IAssetLoader assetLoader = null;
|
||||||
@@ -628,6 +293,9 @@ class EditorContentManager : IContentManager
|
|||||||
|
|
||||||
delete stream;
|
delete stream;
|
||||||
|
|
||||||
|
if (loadedAsset == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
//String identifierString = new .(identifier);
|
//String identifierString = new .(identifier);
|
||||||
//_identifiers.Add(identifierString);
|
//_identifiers.Add(identifierString);
|
||||||
|
|
||||||
@@ -642,10 +310,91 @@ class EditorContentManager : IContentManager
|
|||||||
return loadedAsset;
|
return loadedAsset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Saves the asset.
|
||||||
|
public Result<void> SaveAsset(Asset asset)
|
||||||
|
{
|
||||||
|
// Find subasset name
|
||||||
|
int poundIndex = asset.Identifier.IndexOf('#');
|
||||||
|
|
||||||
|
StringView resourceName = poundIndex == -1 ? asset.Identifier : asset.Identifier.Substring(0, poundIndex);
|
||||||
|
StringView? subassetName = asset.Identifier.Substring(poundIndex + 1);
|
||||||
|
|
||||||
|
String filePath = scope String(resourceName.Length + _contentDirectory.Length + 2);
|
||||||
|
Path.Combine(filePath, _contentDirectory, resourceName);
|
||||||
|
|
||||||
|
Path.Fixup(filePath);
|
||||||
|
|
||||||
|
//filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
TreeNode<AssetNode> assetNode = Try!(AssetHierarchy.GetNodeFromPath(filePath));
|
||||||
|
|
||||||
|
AssetFile file = assetNode->AssetFile;
|
||||||
|
|
||||||
|
IAssetLoader assetLoader = null;
|
||||||
|
|
||||||
|
String loaderTypeName = scope .(128);
|
||||||
|
|
||||||
|
for (IAssetLoader loader in _assetLoaders)
|
||||||
|
{
|
||||||
|
loader.GetType().GetName(loaderTypeName..Clear());
|
||||||
|
|
||||||
|
if (loaderTypeName == file.AssetConfig.AssetLoader)
|
||||||
|
{
|
||||||
|
assetLoader = loader;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IAssetSaver assetSaver = assetLoader as IAssetSaver;
|
||||||
|
|
||||||
|
if (assetSaver == null)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Error("The asset loader can't save!");
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream stream = OpenStream(filePath, false, true);
|
||||||
|
|
||||||
|
assetSaver.EditorSaveAsset(stream, asset, file.AssetConfig.Config, resourceName, subassetName, this);
|
||||||
|
|
||||||
|
delete stream;
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream OpenStream(StringView assetIdentifier, bool openOnly, bool truncate = false)
|
||||||
|
{
|
||||||
|
var assetIdentifier;
|
||||||
|
|
||||||
|
if (!assetIdentifier.StartsWith(_contentDirectory))
|
||||||
|
{
|
||||||
|
String filePath = scope:: String(assetIdentifier.Length + _contentDirectory.Length + 2);
|
||||||
|
Path.Combine(filePath, _contentDirectory, assetIdentifier);
|
||||||
|
|
||||||
|
assetIdentifier = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileStream fs = new FileStream();
|
||||||
|
|
||||||
|
FileMode fileMode = openOnly ? FileMode.Open : FileMode.OpenOrCreate;
|
||||||
|
|
||||||
|
if (truncate)
|
||||||
|
fileMode |= .Truncate;
|
||||||
|
|
||||||
|
var result = fs.Open(assetIdentifier, fileMode, openOnly ? .Read : .ReadWrite, .ReadWrite);
|
||||||
|
|
||||||
|
if (result case .Err)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return fs;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: probably not needed
|
// TODO: probably not needed
|
||||||
public Stream GetStream(StringView assetIdentifier)
|
public Stream GetStream(StringView assetIdentifier)
|
||||||
{
|
{
|
||||||
var assetIdentifier;
|
return OpenStream(assetIdentifier, true);
|
||||||
|
|
||||||
|
/*var assetIdentifier;
|
||||||
|
|
||||||
if (!assetIdentifier.StartsWith(_contentDirectory))
|
if (!assetIdentifier.StartsWith(_contentDirectory))
|
||||||
{
|
{
|
||||||
@@ -661,7 +410,7 @@ class EditorContentManager : IContentManager
|
|||||||
if (result case .Err)
|
if (result case .Err)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return fs;
|
return fs;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ManageAsset(Asset asset)
|
public void ManageAsset(Asset asset)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class Asset : RefCounter
|
|||||||
_contentManager?.UnmanageAsset(this);
|
_contentManager?.UnmanageAsset(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment)
|
static void AssetSerialize(BonWriter writer, ValueView value, BonEnvironment environment, SerializeValueState state)
|
||||||
{
|
{
|
||||||
Log.EngineLogger.Assert(value.type == typeof(Asset));
|
Log.EngineLogger.Assert(value.type == typeof(Asset));
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class Asset : RefCounter
|
|||||||
writer.String(identifier);
|
writer.String(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result<void> AssetDeserialize(BonReader reader, ValueView value, BonEnvironment environment)//, DeserializeFieldState state)
|
static Result<void> AssetDeserialize(BonReader reader, ValueView value, BonEnvironment environment, DeserializeValueState state)
|
||||||
{
|
{
|
||||||
Log.EngineLogger.Assert(value.type == typeof(Asset));
|
Log.EngineLogger.Assert(value.type == typeof(Asset));
|
||||||
|
|
||||||
|
|||||||
@@ -36,14 +36,14 @@ namespace GlitchyEngine.Core
|
|||||||
return (int)_uuid;
|
return (int)_uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Serialize(BonWriter writer, ValueView val, BonEnvironment env)
|
static void Serialize(BonWriter writer, ValueView val, BonEnvironment env, SerializeValueState state)
|
||||||
{
|
{
|
||||||
UUID uuid = *(UUID*)val.dataPtr;
|
UUID uuid = *(UUID*)val.dataPtr;
|
||||||
|
|
||||||
Bon.Integrated.Serialize.[Friend]Integer(typeof(uint64), writer, ValueView(typeof(uint64), &uuid._uuid));
|
Bon.Integrated.Serialize.[Friend]Integer(typeof(uint64), writer, ValueView(typeof(uint64), &uuid._uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<void> Deserialize(BonReader reader, ValueView val, BonEnvironment env)
|
public static Result<void> Deserialize(BonReader reader, ValueView val, BonEnvironment env, DeserializeValueState state)
|
||||||
{
|
{
|
||||||
Bon.Integrated.Deserialize.[Friend]Integer!(typeof(uint64), reader, val);
|
Bon.Integrated.Deserialize.[Friend]Integer!(typeof(uint64), reader, val);
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace GlitchyEngine.World
|
|||||||
|
|
||||||
String buffer = scope String();
|
String buffer = scope String();
|
||||||
let writer = scope BonWriter(buffer, true);
|
let writer = scope BonWriter(buffer, true);
|
||||||
Serialize.Start(writer);
|
var length = Serialize.Start(writer);
|
||||||
|
|
||||||
gBonEnv.serializeFlags |= .IncludeDefault | .Verbose;
|
gBonEnv.serializeFlags |= .IncludeDefault | .Verbose;
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ namespace GlitchyEngine.World
|
|||||||
writer.EntryEnd();
|
writer.EntryEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
Serialize.End(writer);
|
Serialize.End(writer, length);
|
||||||
|
|
||||||
String targetDirectory = Path.GetDirectoryPath(filePath, .. scope String());
|
String targetDirectory = Path.GetDirectoryPath(filePath, .. scope String());
|
||||||
Directory.CreateDirectory(targetDirectory);
|
Directory.CreateDirectory(targetDirectory);
|
||||||
|
|||||||
Vendored
+1
-1
Submodule GlitchyEngine/vendor/bon updated: 928cfa14ea...0c80e365cd
Reference in New Issue
Block a user