Add deterministic disposal for textures in IResourceCache (#6483)

* Add deterministic disposal for textures in IResourceCache

* Review

* Thanks PJB

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>

* Review

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
This commit is contained in:
B_Kirill
2026-05-08 19:43:32 +10:00
committed by GitHub
parent 302d240a54
commit 448d202a16
4 changed files with 65 additions and 6 deletions
@@ -0,0 +1,12 @@
namespace Robust.Client.ResourceManagement;
/// <summary>
/// Defines type-level metadata for resource types.
/// </summary>
public interface IBaseResource
{
/// <summary>
/// Whether resources of this type can be deterministically removed from the cache.
/// </summary>
static virtual bool CanBeRemoved => false;
}
@@ -28,6 +28,10 @@ public interface IResourceCache : IResourceManager
bool TryGetResource(AudioStream stream, [NotNullWhen(true)] out AudioResource? resource);
bool TryRemoveResource<T>(string path) where T : BaseResource, IBaseResource, new();
bool TryRemoveResource<T>(ResPath path) where T : BaseResource, IBaseResource, new();
void ReloadResource<T>(string path)
where T : BaseResource, new();
@@ -7,7 +7,6 @@ using System.Runtime.CompilerServices;
using Robust.Client.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Client.ResourceManagement;
@@ -107,6 +106,28 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
return true;
}
public bool TryRemoveResource<T>(string path) where T : BaseResource, IBaseResource, new()
=> TryRemoveResource<T>(new ResPath(path));
public bool TryRemoveResource<T>(ResPath path) where T : BaseResource, IBaseResource, new()
{
if (!T.CanBeRemoved)
throw new NotSupportedException($"Resource type '{typeof(T)}' does not support deterministic removal.");
if (new T().Fallback == path)
return false;
var cache = GetTypeData<T>();
if (!cache.Resources.Remove(path, out var resource))
return false;
cache.NonExistent.Remove(path);
resource.Dispose();
return true;
}
public void ReloadResource<T>(string path) where T : BaseResource, new()
{
ReloadResource<T>(new ResPath(path));
@@ -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,22 @@ using YamlDotNet.RepresentationModel;
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;
public Texture Texture => _texture;
public override ResPath? Fallback => 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 +117,14 @@ namespace Robust.Client.ResourceManagement
public override void Reload(IDependencyCollection dependencies, ResPath path, CancellationToken ct = default)
{
ObjectDisposedException.ThrowIf(_disposed, this);
var data = new LoadStepData {Path = path};
LoadTextureParameters(dependencies.Resolve<IResourceManager>(), data);
LoadPreTextureData(dependencies.Resolve<IResourceManager>(), 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 +140,15 @@ namespace Robust.Client.ResourceManagement
data.Image.Dispose();
}
public override void Dispose()
{
if (_disposed) return;
_disposed = true;
_texture?.Dispose();
base.Dispose();
}
internal sealed class LoadStepData
{
public ResPath Path = default!;