Start of async asset loading

- Refactored content manager and asset loaders
- Asset placeholder
- Error asset
This commit is contained in:
Simon Lübeß
2023-03-18 18:59:30 +01:00
parent 7b6662cbc4
commit 50714976e9
11 changed files with 329 additions and 321 deletions
+8 -47
View File
@@ -48,54 +48,15 @@ class EffectAssetLoader : IAssetLoader //, IReloadingAssetLoader
Effect effect = new Effect(file, assetIdentifier, contentManager);
return effect;
}
/*StreamReader reader = scope .(file);
public Asset GetPlaceholderAsset(Type assetType)
{
return default;
}
String text = scope .();
reader.ReadToEnd(text);
MaterialFile materialFile = scope .();
var result = Bon.Deserialize<MaterialFile>(ref materialFile, text);
if (result case .Err)
{
Log.EngineLogger.Error("Failed to load material.");
return null;
// TODO: return error material
}
Effect fx = new Effect(materialFile.Effect);
Material material = new Material(fx);
for (let (slotName, textureIdentifier) in materialFile.Textures)
{
Texture texture = contentManager.LoadAsset(textureIdentifier) as Texture;
if (texture == null)
{
Log.EngineLogger.Error("Failed to load texture.");
// TODO: LoadAsset should return an error texture.
}
material.SetTexture(slotName, texture);
}
fx.ReleaseRef();
/*for (let (slotName, textureIdentifier) in materialFile.Variables)
{
if (texture == null)
{
Log.EngineLogger.Error("Failed to load texture.");
// TODO: LoadAsset should return an error texture.
}
material.SetVariable(slotName, );
}*/
return material; //ModelLoader.LoadMesh(file, subAsset.Value, 0);*/
public Asset GetErrorAsset(Type assetType)
{
return default;
}
}
+15 -50
View File
@@ -339,10 +339,9 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
Log.EngineLogger.Error("Failed to load material.");
Debug.SafeBreak();
return null;
// TODO: return error material
}
Effect fx = Content.GetAsset<Effect>(contentManager.LoadAsset(materialFile.Effect), contentManager);//new Effect(materialFile.Effect);
Effect fx = Content.GetAsset<Effect>(contentManager.LoadAsset(materialFile.Effect, true), contentManager);
Material material = new Material(fx);
@@ -353,7 +352,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
if (texture.IsInvalid)
{
Log.EngineLogger.Error($"Failed to load texture \"{textureIdentifier}\".");
// TODO: LoadAsset should return an error texture.
}
material.SetTexture(slotName, texture);
@@ -377,7 +375,7 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
material.SetVariable(slotName, value);
case .None:
default:
Log.EngineLogger.Error("Errorre");
Log.EngineLogger.Error($"Unknown variable type of variable {slotName}: {variableValue}");
}
@@ -402,8 +400,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
materialFile.Textures = new .();
materialFile.Variables = new .();
//material.SetTexture();
for (let (slotName, texture) in material.[Friend]_textures)
{
Texture textureAsset = texture.Get();
@@ -419,7 +415,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
for (let (name, arguments) in effect.[Friend]_variableDescriptions)
{
VariableValue variableValue = .None;
//Object variantValue = null;
let variable = effect.Variables[name];
@@ -445,7 +440,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
value = ColorRGBA.LinearToSRGB((ColorRGBA)value);
variableValue = .ColorRGBA(value);
//variantValue = new box value;
}
}
else if (variable.Type == .Float && variable.Rows == 1)
@@ -465,48 +459,6 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
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(new String(name), variableValue);
@@ -523,4 +475,17 @@ class MaterialAssetLoader : IAssetLoader, IAssetSaver //, IReloadingAssetLoader
return .Ok;
}
Material _placeholder;
Material _error;
public Asset GetPlaceholderAsset(Type assetType)
{
return default;
}
public Asset GetErrorAsset(Type assetType)
{
return default;
}
}
+12 -2
View File
@@ -44,8 +44,18 @@ class ModelAssetLoader : IAssetLoader //, IReloadingAssetLoader
public Asset LoadAsset(Stream file, AssetLoaderConfig config, StringView assetIdentifier, StringView? subAsset, IContentManager contentManager)
{
Log.EngineLogger.Assert(subAsset != null);
//Log.EngineLogger.Assert(subAsset != null);
return ModelLoader.LoadMesh(file, subAsset.Value, 0);
return ModelLoader.LoadMesh(file, subAsset ?? assetIdentifier, 0);
}
public Asset GetPlaceholderAsset(Type assetType)
{
return default;
}
public Asset GetErrorAsset(Type assetType)
{
return default;
}
}
+88 -69
View File
@@ -243,65 +243,19 @@ class EditorTextureAssetLoader : IAssetLoader//, IReloadingAssetLoader
case .PNG:
texture = LoadPng(data, config);
case .Unknown:
Runtime.FatalError("Unknown image format.");
Log.EngineLogger.Error("Unknown texture format.");
texture = null;
}
Log.EngineLogger.AssertDebug(texture != null);
SetSampler(texture, config);
if (texture != null)
{
SetSampler(texture, config);
texture.[Friend]Complete = true;
}
return texture;
}
public void ReloadAsset(AssetFile assetFile, Stream data)
{
Texture reloadingTexture = assetFile.LoadedAsset as Texture;
if (reloadingTexture == null)
{
Log.EngineLogger.Error($"{nameof(Self)}: Requested reload of \"{assetFile.FilePath}\" but it's not a Texture!");
return;
}
EditorTextureAssetLoaderConfig config = assetFile.AssetConfig.Config as EditorTextureAssetLoaderConfig;
if (config == null)
{
Log.EngineLogger.Error($"{nameof(Self)}: Config of asset \"{assetFile.FilePath}\" doesn't have the correct type!");
return;
}
switch(GetTextureType(data))
{
case .DDS:
ReloadDds(reloadingTexture as Texture2D, data, config);
case .PNG:
ReloadPng(reloadingTexture as Texture2D, data, config);
case .Unknown:
Runtime.FatalError("Unknown image format.");
}
SetSampler(reloadingTexture, config);
}
private static void SetSampler(Texture texture, EditorTextureAssetLoaderConfig config)
{
using (SamplerState samplerState = SamplerStateManager.GetSampler(config.SamplerStateDescription))
{
texture.SamplerState = samplerState;
}
}
private static void ReloadPng(Texture2D reloadingTexture, Stream data, EditorTextureAssetLoaderConfig config)
{
Debug.Profiler.ProfileResourceFunction!();
using (Texture2D newTexture = LoadPng(data, config))
{
reloadingTexture.[Friend]SneakySwappyTexture(newTexture);
}
}
private static Texture2D LoadPng(Stream data, EditorTextureAssetLoaderConfig config)
{
Debug.Profiler.ProfileResourceFunction!();
@@ -313,40 +267,105 @@ class EditorTextureAssetLoader : IAssetLoader//, IReloadingAssetLoader
if (result case .Err(let err))
{
Log.EngineLogger.Error($"Failed to read data from stream. Texture: Error: {err}");
return null;
}
uint8* rawData = null;
defer
{
if (rawData != null)
LodePng.LodePng.Free(rawData);
}
uint8* rawData = ?;
uint32 width = 0, height = 0;
uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count);
{
Debug.Profiler.ProfileResourceScope!("LodePng.LodePng.Decode32");
uint32 errorCode = LodePng.LodePng.Decode32(&rawData, &width, &height, pngData.Ptr, (.)pngData.Count);
if (errorCode != 0)
{
Log.EngineLogger.Error($"Failed to decode PNG file {errorCode}.");
return null;
}
}
Log.EngineLogger.Assert(errorCode == 0, "Failed to load png File");
// TODO: load as SRGB because PNGs are usually not stored as linear
//Texture2DDesc desc = .(width, height, srgb? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable);
Texture2DDesc desc = .(width, height, config.IsSRGB ? .R8G8B8A8_UNorm_SRGB : .R8G8B8A8_UNorm, 1, 1, .Immutable);
Texture2D texture = new Texture2D(desc);
texture.SetData<Color>((.)rawData);
// TODO: Generate mip maps
LodePng.LodePng.Free(rawData);
return texture;
}
private static void ReloadDds(Texture2D reloadingTexture, Stream data, EditorTextureAssetLoaderConfig config)
{
using (Texture2D newTexture = new [Friend]Texture2D(data))
{
reloadingTexture.[Friend]SneakySwappyTexture(newTexture);
}
}
private static Texture LoadDds(Stream data, EditorTextureAssetLoaderConfig config)
{
// TODO: Move the loading of Dds files here.
Texture2D texture = new [Friend]Texture2D(data);
return texture;
}
private static void SetSampler(Texture texture, EditorTextureAssetLoaderConfig config)
{
using (SamplerState samplerState = SamplerStateManager.GetSampler(config.SamplerStateDescription))
{
texture.SamplerState = samplerState;
}
}
private static Texture2D _placeholder2D;
private static Texture2D _error2D;
public Asset GetPlaceholderAsset(Type assetType)
{
switch (assetType)
{
case typeof(Texture2D):
fallthrough;
default:
if (_placeholder2D == null)
{
Texture2DDesc desc = .(1, 1, .R8G8B8A8_UNorm, 1, 1, .Immutable, .None);
_placeholder2D = new Texture2D(desc);
_placeholder2D.SamplerState = SamplerStateManager.PointWrap;
Color color = Color.Cyan;
_placeholder2D.SetData<Color>(&color);
Content.ManageAsset(_placeholder2D);
_placeholder2D.ReleaseRef();
_placeholder2D.[Friend]Complete = false;
}
return _placeholder2D;
}
}
public Asset GetErrorAsset(Type assetType)
{
switch (assetType)
{
case typeof(Texture2D):
fallthrough;
default:
if (_error2D == null)
{
Texture2DDesc desc = .(2, 2, .R8G8B8A8_UNorm, 1, 1, .Immutable, .None);
_error2D = new Texture2D(desc);
_error2D.SamplerState = SamplerStateManager.PointWrap;
Color[4] color = .(Color.HotPink, Color.Black, Color.Black, Color.HotPink);
_error2D.SetData<Color>(&color);
Content.ManageAsset(_error2D);
_error2D.ReleaseRef();
_placeholder2D.[Friend]Complete = true;
}
return _error2D;
}
}
}
+184 -44
View File
@@ -8,6 +8,8 @@ using GlitchyEngine.Content;
using GlitchyEditor.Assets;
using GlitchyEngine;
using System.Linq;
using System.Threading.Tasks;
using internal GlitchyEngine.Content.Asset;
namespace GlitchyEditor;
@@ -100,6 +102,8 @@ class EditorContentManager : IContentManager
public void Update()
{
SwapInLoadedAssets();
if (!_reloadQueue.IsEmpty)
{
for (AssetHandle handle in _reloadQueue)
@@ -112,6 +116,40 @@ class EditorContentManager : IContentManager
_assetHierarchy.Update();
}
/// Replaces placeholders with the loaded assets
private void SwapInLoadedAssets()
{
// Don't take the lock if we have nothing to do.
if (_finishedEntries.Count == 0)
return;
using (_finishedEntriesLock.Enter())
{
while (_finishedEntries.Count > 0)
{
let (placeholder, asset) = _finishedEntries[0];
delete placeholder.LoadingTask;
if (asset == null)
placeholder.PlaceholderType = .Error;
else
{
// TODO: I'm not sure whether AssetFiles are guaranteed to persist.
// Get the reference here because placeholder wont survive SwapAsset.
AssetFile file = placeholder.AssetFile;
file.[Friend]_loadedAsset = asset;
SwapAsset(placeholder, asset);
// SwapAsset increases RefCount, but this scope also holds a reference.
asset.ReleaseRef();
}
_finishedEntries.RemoveAtFast(0);
}
}
}
public IAssetLoader GetDefaultAssetLoader(StringView fileExtension)
{
if (_defaultAssetLoaders.TryGetValue(fileExtension, let value))
@@ -131,9 +169,11 @@ class EditorContentManager : IContentManager
public void RegisterAssetLoader<T>() where T : new, class, IAssetLoader
{
//Log.EngineLogger.AssertDebug(!_assetLoaders.Any((l) => l.GetType() == typeof(T)), "Asset loader already registered.");
// Log.EngineLogger.AssertDebug(!_assetLoaders.Any((l) => l.GetType() == typeof(T)), "Asset loader already registered.");
_assetLoaders.Add(new T());
T assetLoader = new T();
_assetLoaders.Add(assetLoader);
for (StringView ext in T.FileExtensions)
_supportedExtensions.Add(new String(ext));
@@ -196,13 +236,21 @@ class EditorContentManager : IContentManager
{
return _identiferToHandle.ContainsKey(identifier);
}
public Asset GetAsset(Type assetType, AssetHandle handle)
{
Asset asset = null;
_handleToAsset.TryGetValue(handle, out asset);
if (var placeholder = asset as PlaceholderAsset)
{
if (placeholder.PlaceholderType == .Loading)
return placeholder.AssetLoader.GetPlaceholderAsset(assetType);
else if (placeholder.PlaceholderType == .Error)
return placeholder.AssetLoader.GetErrorAsset(assetType);
}
if (assetType == null)
{
return asset;
@@ -221,26 +269,26 @@ class EditorContentManager : IContentManager
private void ReloadAsset(AssetHandle handle)
{
Asset asset = null;
Debug.Profiler.ProfileResourceFunction!();
Asset oldAsset = null;
if (!_handleToAsset.TryGetValue(handle, out asset))
if (!_handleToAsset.TryGetValue(handle, out oldAsset))
{
Log.EngineLogger.Error("Can't reload! No asset exists for handle.");
return;
}
Log.EngineLogger.AssertDebug(asset != null);
Log.EngineLogger.AssertDebug(oldAsset != null);
StringView oldIdentifier = asset.Identifier;
StringView oldIdentifier = oldAsset.Identifier;
GetResourceAndSubassetName(oldIdentifier, let resourceName, let subassetName);
String filePath = scope .();
GetResourceFilePath(resourceName, filePath);
//filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
Result<TreeNode<AssetNode>> resultNode = AssetHierarchy.GetNodeFromPath(filePath);
if (resultNode case .Err)
@@ -255,35 +303,28 @@ class EditorContentManager : IContentManager
Stream stream = GetStream(filePath);
// TODO: Add async loading!
Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
delete stream;
if (loadedAsset == null)
return;
loadedAsset.Identifier = oldIdentifier;
loadedAsset.[Friend]_handle = handle;
// Remove asset
_identiferToHandle.Remove(oldIdentifier);
Asset oldAsset = _handleToAsset[handle];
oldAsset.ReleaseRef();
_handleToAsset[handle] = loadedAsset;
_identiferToHandle.Add(loadedAsset.Identifier, handle);
file.[Friend]_loadedAsset = loadedAsset;
SwapAsset(oldAsset, loadedAsset);
// SwapAsset increases RefCount, but this scope also holds a reference.
loadedAsset.ReleaseRef();
}
/// Returns the resource name and, if it exists, the subasset name.
private void GetResourceAndSubassetName(StringView identifier, out StringView resourceName, out StringView? subassetName)
{
// Find subasset name
int poundIndex = identifier.IndexOf('#');
resourceName = poundIndex == -1 ? identifier : identifier.Substring(0, poundIndex);
subassetName = identifier.Substring(poundIndex + 1);
resourceName = (poundIndex != -1) ? identifier.Substring(0, poundIndex) : identifier;
subassetName = (poundIndex != -1) ? identifier.Substring(poundIndex + 1) : null;
}
private void GetResourceFilePath(StringView resourceName, String filePath)
@@ -293,25 +334,66 @@ class EditorContentManager : IContentManager
Path.Fixup(filePath);
}
public AssetHandle LoadAsset(StringView identifier)
private enum PlaceholderType
{
Loading,
Error
}
private class PlaceholderAsset : Asset
{
public AssetFile AssetFile {get; private set;}
public Task LoadingTask {get;set;}
public IAssetLoader AssetLoader {get; private set;}
public PlaceholderType PlaceholderType {get; set;}
public this(AssetFile assetFile, IAssetLoader assetLoader, PlaceholderType placeholderType)
{
AssetFile = assetFile;
AssetLoader = assetLoader;
PlaceholderType = placeholderType;
}
}
private append Monitor _finishedEntriesLock = .();
private append List<(PlaceholderAsset placeholder, Asset newAsset)> _finishedEntries = .();
private class MissingAsset : Asset {}
public class AssetIdentifier
{
public const char8 DirectorySeparatorChar = '/';
public static void Fixup(String assetIdentifier)
{
const String DotSeperator = $".{DirectorySeparatorChar}";
const String SeperatorDot = $"{DirectorySeparatorChar}.";
assetIdentifier.Replace('\\', DirectorySeparatorChar);
assetIdentifier.Replace(DotSeperator, "");
assetIdentifier.Replace(SeperatorDot, "");
if (assetIdentifier.StartsWith(DirectorySeparatorChar))
assetIdentifier.Remove(0, 1);
}
}
public AssetHandle LoadAsset(StringView identifier, bool blocking = false)
{
Debug.Profiler.ProfileResourceFunction!();
// Todo: How strict should we be on paths?
String fixedIdentifier = scope String(identifier);
// TODO: Fixup is more of a filesystem thingy... we might want to use our own function
Path.Fixup(fixedIdentifier);
AssetIdentifier.Fixup(fixedIdentifier);
if (_identiferToHandle.TryGetValue(fixedIdentifier, let asset))
{
return asset;
}
GetResourceAndSubassetName(fixedIdentifier, let resourceName, let subassetName);
String filePath = scope .();
GetResourceFilePath(resourceName, filePath);
//filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
Result<TreeNode<AssetNode>> resultNode = AssetHierarchy.GetNodeFromPath(filePath);
if (resultNode case .Err)
@@ -327,14 +409,37 @@ class EditorContentManager : IContentManager
// TODO: what are we supposed to do if we don't find a loader? Sure not crash...
Log.EngineLogger.AssertDebug(assetLoader != null);
Stream stream = GetStream(filePath);
Asset loadedAsset;
Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
delete stream;
// TODO: Support lazy loading for all asset types
if (!(assetLoader is EditorTextureAssetLoader) || blocking)
{
Stream stream = GetStream(filePath);
if (loadedAsset == null)
return .Invalid;
loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
delete stream;
if (loadedAsset == null)
return .Invalid;
}
else
{
PlaceholderAsset placeholder = new PlaceholderAsset(file, assetLoader, .Loading);
String filePath2 = new String(filePath);
String newResourceName = new String(resourceName);
String newSesourceName = subassetName == null ? null : new String(subassetName.Value);
placeholder.LoadingTask = new Task(new () => {
AsyncLoadAsset(placeholder, filePath2, assetLoader, file,
newResourceName, newSesourceName);
});
ThreadPool.QueueUserWorkItem(placeholder.LoadingTask);
loadedAsset = placeholder;
}
loadedAsset.Identifier = fixedIdentifier;
AssetHandle handle = ManageAsset(loadedAsset);
@@ -349,6 +454,25 @@ class EditorContentManager : IContentManager
return handle;
}
private void AsyncLoadAsset(PlaceholderAsset placeholder, String filePath, IAssetLoader assetLoader, AssetFile file, String resourceName, String subassetName)
{
Debug.Profiler.ProfileResourceFunction!();
Stream stream = GetStream(filePath);
Asset loadedAsset = assetLoader.LoadAsset(stream, file.AssetConfig.Config, resourceName, subassetName, this);
delete stream;
delete filePath;
delete resourceName;
delete subassetName;
using (_finishedEntriesLock.Enter())
{
_finishedEntries.Add((placeholder, loadedAsset));
}
}
/// Gets the asset loader that has to be used for the given file.
IAssetLoader GetAssetLoader(AssetFile file)
{
@@ -483,9 +607,10 @@ class EditorContentManager : IContentManager
AssetHandle handle = .();
// Generate until we find a unique key (shouldn't happen too often)
while (_handleToAsset.ContainsKey(handle))
while (handle.IsInvalid || _handleToAsset.ContainsKey(handle))
{
handle = .();
Log.EngineLogger.Trace("Handle was invalid or already taken.");
// TODO: perhaps test how often this happens.
// If this happens too often we could use a different random generator
}
@@ -500,9 +625,26 @@ class EditorContentManager : IContentManager
return handle;
}
private void SwapAsset(Asset oldAsset, Asset newAsset)
{
newAsset.Identifier = oldAsset.Identifier;
newAsset._contentManager = this;
newAsset._handle = oldAsset.Handle;
_handleToAsset[oldAsset.Handle] = newAsset;
if (_identiferToHandle.ContainsKey(oldAsset.Identifier))
{
_identiferToHandle.Remove(oldAsset.Identifier);
_identiferToHandle.Add(newAsset.Identifier, newAsset.Handle);
}
newAsset.AddRef();
oldAsset.ReleaseRef();
}
public void UnmanageAsset(AssetHandle handle)
{
//Log.EngineLogger.AssertDebug(_handles.ContainsValue(handle), "Handle isn't managed by this content manager.");
Log.EngineLogger.AssertDebug(_handleToAsset.ContainsKey(handle), "Handle doesn't correspond to an asset.");
Asset asset = _handleToAsset[handle];
@@ -520,16 +662,14 @@ class EditorContentManager : IContentManager
/// Note: This will not release any assets.
private void UnmanageAllAssets()
{
for (let (_, assetHandle) in _identiferToHandle)
for (let (handle, _) in _handleToAsset)
{
UnmanageAsset(assetHandle);
UnmanageAsset(handle);
}
}
public void UpdateAssetIdentifier(Asset asset, StringView oldIdentifier, StringView newIdentifier)
public void AssetMoved(Asset asset, StringView oldIdentifier, StringView newIdentifier)
{
// TODO: this is much harder with asste handles that are basically hashed identifiers!
Runtime.NotImplemented();
if (oldIdentifier == newIdentifier)
+1 -1
View File
@@ -25,7 +25,7 @@ namespace GlitchyEditor
public this(String texturePath, Vector2 iconSize)
{
_texture = Content.LoadAsset(texturePath);//new Texture2D(texturePath);
_texture = Content.LoadAsset(texturePath, null, true);
Vector2 pen = .();