diff --git a/Robust.Client/ResourceManagement/BaseResource.cs b/Robust.Client/ResourceManagement/BaseResource.cs
index 4bc1b30fb8b..8d6314e074d 100644
--- a/Robust.Client/ResourceManagement/BaseResource.cs
+++ b/Robust.Client/ResourceManagement/BaseResource.cs
@@ -8,13 +8,8 @@ namespace Robust.Client.ResourceManagement;
///
/// Base resource for the cache.
///
-public abstract class BaseResource : IDisposable
+public abstract class BaseResource : IBaseResource, IDisposable
{
- ///
- /// Fallback resource path if this one does not exist.
- ///
- public virtual ResPath? Fallback => null;
-
///
/// Disposes this resource.
///
diff --git a/Robust.Client/ResourceManagement/IBaseResource.cs b/Robust.Client/ResourceManagement/IBaseResource.cs
new file mode 100644
index 00000000000..4e4e9574b2c
--- /dev/null
+++ b/Robust.Client/ResourceManagement/IBaseResource.cs
@@ -0,0 +1,19 @@
+using Robust.Shared.Utility;
+
+namespace Robust.Client.ResourceManagement;
+
+///
+/// Defines type-level metadata for resource types.
+///
+public interface IBaseResource
+{
+ ///
+ /// Whether resources of this type can be deterministically removed from the cache.
+ ///
+ static virtual bool CanBeRemoved => false;
+
+ ///
+ /// The fallback path for this resource type (if any).
+ ///
+ static virtual ResPath? FallbackPath => null;
+}
diff --git a/Robust.Client/ResourceManagement/IResourceCache.cs b/Robust.Client/ResourceManagement/IResourceCache.cs
index 16a2532c71d..bde0d63b29a 100644
--- a/Robust.Client/ResourceManagement/IResourceCache.cs
+++ b/Robust.Client/ResourceManagement/IResourceCache.cs
@@ -15,10 +15,10 @@ namespace Robust.Client.ResourceManagement;
public interface IResourceCache : IResourceManager
{
T GetResource(string path, bool useFallback = true)
- where T : BaseResource, new();
+ where T : BaseResource, IBaseResource, new();
T GetResource(ResPath path, bool useFallback = true)
- where T : BaseResource, new();
+ where T : BaseResource, IBaseResource, new();
bool TryGetResource(string path, [NotNullWhen(true)] out T? resource)
where T : BaseResource, new();
@@ -28,6 +28,10 @@ bool TryGetResource(ResPath path, [NotNullWhen(true)] out T? resource)
bool TryGetResource(AudioStream stream, [NotNullWhen(true)] out AudioResource? resource);
+ bool TryRemoveResource(string path) where T : BaseResource, IBaseResource, new();
+
+ bool TryRemoveResource(ResPath path) where T : BaseResource, IBaseResource, new();
+
void ReloadResource(string path)
where T : BaseResource, new();
@@ -41,7 +45,7 @@ void CacheResource(ResPath path, T resource)
where T : BaseResource, new();
T GetFallback()
- where T : BaseResource, new();
+ where T : BaseResource, IBaseResource, new();
IEnumerable> GetAllResources() where T : BaseResource, new();
diff --git a/Robust.Client/ResourceManagement/ResourceCache.cs b/Robust.Client/ResourceManagement/ResourceCache.cs
index cdcea4fccfe..8b9d5b70615 100644
--- a/Robust.Client/ResourceManagement/ResourceCache.cs
+++ b/Robust.Client/ResourceManagement/ResourceCache.cs
@@ -7,7 +7,6 @@
using Robust.Client.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
-using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Client.ResourceManagement;
@@ -20,12 +19,12 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
private readonly Dictionary _cachedResources = new();
private readonly Dictionary _fallbacks = new();
- public T GetResource(string path, bool useFallback = true) where T : BaseResource, new()
+ public T GetResource(string path, bool useFallback = true) where T : BaseResource, IBaseResource, new()
{
return GetResource(new ResPath(path), useFallback);
}
- public T GetResource(ResPath path, bool useFallback = true) where T : BaseResource, new()
+ public T GetResource(ResPath path, bool useFallback = true) where T : BaseResource, IBaseResource, new()
{
var cache = GetTypeData();
if (cache.Resources.TryGetValue(path, out var cached))
@@ -43,11 +42,11 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
}
catch (Exception e)
{
- if (useFallback && resource.Fallback != null)
+ if (useFallback && T.FallbackPath != null)
{
Sawmill.Error(
$"Exception while loading resource {typeof(T)} at '{path}', resorting to fallback.\n{Environment.StackTrace}\n{e}");
- return GetResource(resource.Fallback.Value, false);
+ return GetResource(T.FallbackPath.Value, false);
}
else
{
@@ -107,6 +106,29 @@ public bool TryGetResource(AudioStream stream, [NotNullWhen(true)] out AudioReso
return true;
}
+ public bool TryRemoveResource(string path) where T : BaseResource, IBaseResource, new()
+ => TryRemoveResource(new ResPath(path));
+
+ public bool TryRemoveResource(ResPath path) where T : BaseResource, IBaseResource, new()
+ {
+ if (!T.CanBeRemoved)
+ throw new NotSupportedException($"Resource type '{typeof(T)}' does not support deterministic removal.");
+
+ if (T.FallbackPath == path)
+ return false;
+
+ var cache = GetTypeData();
+
+ if (!cache.Resources.TryGetValue(path, out var resource))
+ return false;
+
+ cache.Resources.Remove(path);
+ cache.NonExistent.Remove(path);
+ resource.Dispose();
+
+ return true;
+ }
+
public void ReloadResource(string path) where T : BaseResource, new()
{
ReloadResource(new ResPath(path));
@@ -153,20 +175,19 @@ public bool TryGetResource(AudioStream stream, [NotNullWhen(true)] out AudioReso
GetTypeData().Resources[path] = resource;
}
- public T GetFallback() where T : BaseResource, new()
+ public T GetFallback() where T : BaseResource, IBaseResource, new()
{
if (_fallbacks.TryGetValue(typeof(T), out var fallback))
{
return (T) fallback;
}
- var res = new T();
- if (res.Fallback == null)
+ if (T.FallbackPath == null)
{
throw new InvalidOperationException($"Resource of type '{typeof(T)}' has no fallback.");
}
- fallback = GetResource(res.Fallback.Value, useFallback: false);
+ fallback = GetResource(T.FallbackPath.Value, useFallback: false);
_fallbacks.Add(typeof(T), fallback);
return (T) fallback;
}
diff --git a/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs b/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs
index 0880f8e9518..5fd015da841 100644
--- a/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs
+++ b/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs
@@ -18,9 +18,9 @@ namespace Robust.Client.ResourceManagement
/// Handles the loading code for RSI files.
/// See for the RSI API itself.
///
- public sealed class RSIResource : BaseResource
+ public sealed class RSIResource : BaseResource, IBaseResource
{
- public override ResPath? Fallback => new("/Textures/error.rsi");
+ static ResPath? IBaseResource.FallbackPath => new("/Textures/error.rsi");
public RSI RSI { get; private set; } = default!;
diff --git a/Robust.Client/ResourceManagement/ResourceTypes/TextureResource.cs b/Robust.Client/ResourceManagement/ResourceTypes/TextureResource.cs
index 8b5fb6f96df..8babbe6b9da 100644
--- a/Robust.Client/ResourceManagement/ResourceTypes/TextureResource.cs
+++ b/Robust.Client/ResourceManagement/ResourceTypes/TextureResource.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Shared.ContentPack;
@@ -13,12 +14,23 @@
namespace Robust.Client.ResourceManagement
{
- public sealed class TextureResource : BaseResource
+ public sealed class TextureResource : BaseResource, IBaseResource
{
private OwnedTexture _texture = default!;
- public override ResPath? Fallback => new("/Textures/noSprite.png");
+ private bool _disposed;
+ private readonly Lock _lock = new();
- public Texture Texture => _texture;
+ static ResPath? IBaseResource.FallbackPath => new("/Textures/noSprite.png");
+ static bool IBaseResource.CanBeRemoved => true;
+
+ public Texture Texture
+ {
+ get
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
+ return _texture;
+ }
+ }
public override void Load(IDependencyCollection dependencies, ResPath path)
{
@@ -106,12 +118,14 @@ internal void LoadFinish(IResourceCache cache, LoadStepData data)
public override void Reload(IDependencyCollection dependencies, ResPath path, CancellationToken ct = default)
{
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
var data = new LoadStepData {Path = path};
LoadTextureParameters(dependencies.Resolve(), data);
LoadPreTextureData(dependencies.Resolve(), data);
- if (data.Image.Width == Texture.Width && data.Image.Height == Texture.Height)
+ if (data.Image.Width == _texture.Width && data.Image.Height == _texture.Height)
{
// Dimensions match, rewrite texture in place.
_texture.SetSubImage(Vector2i.Zero, data.Image);
@@ -127,6 +141,18 @@ public override void Reload(IDependencyCollection dependencies, ResPath path, Ca
data.Image.Dispose();
}
+ public override void Dispose()
+ {
+ lock (_lock)
+ {
+ if (_disposed) return;
+ _disposed = true;
+ _texture?.Dispose();
+ }
+
+ base.Dispose();
+ }
+
internal sealed class LoadStepData
{
public ResPath Path = default!;