mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(resource): 添加资源管理系统和日志集成
- 实现了完整的资源管理系统,包括资源加载、缓存和卸载功能 - 添加了 IResourceHandle、IResourceLoader、IResourceManager 和 IResourceReleaseStrategy 接口定义 - 实现了 AutoReleaseStrategy 和 ManualReleaseStrategy 两种资源释放策略 - 创建了 ResourceCache 缓存系统和 ResourceHandle 资源句柄管理 - 在 ConfigurationManager 和 CoroutineScheduler 中集成了 ILogger 日志功能 - 添加了全面的 ResourceManagerTests 单元测试覆盖各种使用场景
This commit is contained in:
parent
d8cd22a424
commit
7919c93f44
38
GFramework.Core.Abstractions/resource/IResourceHandle.cs
Normal file
38
GFramework.Core.Abstractions/resource/IResourceHandle.cs
Normal file
@ -0,0 +1,38 @@
|
||||
namespace GFramework.Core.Abstractions.resource;
|
||||
|
||||
/// <summary>
|
||||
/// 资源句柄接口,用于管理资源的生命周期和引用
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
public interface IResourceHandle<out T> : IDisposable where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取资源实例
|
||||
/// </summary>
|
||||
T? Resource { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源路径
|
||||
/// </summary>
|
||||
string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源是否有效(未被释放)
|
||||
/// </summary>
|
||||
bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前引用计数
|
||||
/// </summary>
|
||||
int ReferenceCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 增加引用计数
|
||||
/// </summary>
|
||||
void AddReference();
|
||||
|
||||
/// <summary>
|
||||
/// 减少引用计数
|
||||
/// </summary>
|
||||
void RemoveReference();
|
||||
}
|
||||
35
GFramework.Core.Abstractions/resource/IResourceLoader.cs
Normal file
35
GFramework.Core.Abstractions/resource/IResourceLoader.cs
Normal file
@ -0,0 +1,35 @@
|
||||
namespace GFramework.Core.Abstractions.resource;
|
||||
|
||||
/// <summary>
|
||||
/// 资源加载器接口,用于加载特定类型的资源
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
public interface IResourceLoader<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// 同步加载资源
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>加载的资源实例</returns>
|
||||
T Load(string path);
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载资源
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>加载的资源实例</returns>
|
||||
Task<T> LoadAsync(string path);
|
||||
|
||||
/// <summary>
|
||||
/// 卸载资源
|
||||
/// </summary>
|
||||
/// <param name="resource">要卸载的资源</param>
|
||||
void Unload(T resource);
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否支持加载指定路径的资源
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>如果支持返回 true,否则返回 false</returns>
|
||||
bool CanLoad(string path);
|
||||
}
|
||||
89
GFramework.Core.Abstractions/resource/IResourceManager.cs
Normal file
89
GFramework.Core.Abstractions/resource/IResourceManager.cs
Normal file
@ -0,0 +1,89 @@
|
||||
using GFramework.Core.Abstractions.utility;
|
||||
|
||||
namespace GFramework.Core.Abstractions.resource;
|
||||
|
||||
/// <summary>
|
||||
/// 资源管理器接口,提供资源加载、缓存和卸载功能
|
||||
/// 线程安全:所有方法都是线程安全的
|
||||
/// </summary>
|
||||
public interface IResourceManager : IUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取已加载资源的数量
|
||||
/// </summary>
|
||||
int LoadedResourceCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 同步加载资源
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>资源实例,如果加载失败返回 null</returns>
|
||||
T? Load<T>(string path) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载资源
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>资源实例,如果加载失败返回 null</returns>
|
||||
Task<T?> LoadAsync<T>(string path) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源句柄(增加引用计数)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>资源句柄</returns>
|
||||
IResourceHandle<T>? GetHandle<T>(string path) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 卸载指定路径的资源
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>如果成功卸载返回 true,否则返回 false</returns>
|
||||
bool Unload(string path);
|
||||
|
||||
/// <summary>
|
||||
/// 卸载所有资源
|
||||
/// </summary>
|
||||
void UnloadAll();
|
||||
|
||||
/// <summary>
|
||||
/// 检查资源是否已加载
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>如果已加载返回 true,否则返回 false</returns>
|
||||
bool IsLoaded(string path);
|
||||
|
||||
/// <summary>
|
||||
/// 注册资源加载器
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
/// <param name="loader">资源加载器</param>
|
||||
void RegisterLoader<T>(IResourceLoader<T> loader) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 取消注册资源加载器
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
void UnregisterLoader<T>() where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 预加载资源(加载但不返回)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
/// <param name="path">资源路径</param>
|
||||
Task PreloadAsync<T>(string path) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已加载资源的路径
|
||||
/// </summary>
|
||||
IEnumerable<string> GetLoadedResourcePaths();
|
||||
|
||||
/// <summary>
|
||||
/// 设置资源释放策略
|
||||
/// </summary>
|
||||
/// <param name="strategy">资源释放策略</param>
|
||||
void SetReleaseStrategy(IResourceReleaseStrategy strategy);
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
namespace GFramework.Core.Abstractions.resource;
|
||||
|
||||
/// <summary>
|
||||
/// 资源释放策略接口
|
||||
/// 定义当资源引用计数变化时的处理行为
|
||||
/// </summary>
|
||||
public interface IResourceReleaseStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断是否应该释放资源
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <param name="refCount">当前引用计数</param>
|
||||
/// <returns>如果应该释放返回 true,否则返回 false</returns>
|
||||
bool ShouldRelease(string path, int refCount);
|
||||
}
|
||||
407
GFramework.Core.Tests/resource/ResourceManagerTests.cs
Normal file
407
GFramework.Core.Tests/resource/ResourceManagerTests.cs
Normal file
@ -0,0 +1,407 @@
|
||||
using System.IO;
|
||||
using GFramework.Core.Abstractions.resource;
|
||||
using GFramework.Core.resource;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.resource;
|
||||
|
||||
/// <summary>
|
||||
/// 测试用的简单资源类
|
||||
/// </summary>
|
||||
public class TestResource
|
||||
{
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public bool IsDisposed { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试用的资源加载器
|
||||
/// </summary>
|
||||
public class TestResourceLoader : IResourceLoader<TestResource>
|
||||
{
|
||||
private readonly Dictionary<string, string> _resourceData = new();
|
||||
|
||||
public TestResource Load(string path)
|
||||
{
|
||||
if (_resourceData.TryGetValue(path, out var content))
|
||||
{
|
||||
return new TestResource { Content = content };
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"Resource not found: {path}");
|
||||
}
|
||||
|
||||
public async Task<TestResource> LoadAsync(string path)
|
||||
{
|
||||
await Task.Delay(10); // 模拟异步加载
|
||||
return Load(path);
|
||||
}
|
||||
|
||||
public void Unload(TestResource resource)
|
||||
{
|
||||
resource.IsDisposed = true;
|
||||
}
|
||||
|
||||
public bool CanLoad(string path)
|
||||
{
|
||||
return _resourceData.ContainsKey(path);
|
||||
}
|
||||
|
||||
public void AddTestData(string path, string content)
|
||||
{
|
||||
_resourceData[path] = content;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ResourceManager 功能测试类
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class ResourceManagerTests
|
||||
{
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_resourceManager = new ResourceManager();
|
||||
_testLoader = new TestResourceLoader();
|
||||
_testLoader.AddTestData("test/resource1.txt", "Content 1");
|
||||
_testLoader.AddTestData("test/resource2.txt", "Content 2");
|
||||
_resourceManager.RegisterLoader(_testLoader);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_resourceManager.UnloadAll();
|
||||
}
|
||||
|
||||
private ResourceManager _resourceManager = null!;
|
||||
private TestResourceLoader _testLoader = null!;
|
||||
|
||||
[Test]
|
||||
public void Load_Should_Load_Resource()
|
||||
{
|
||||
var resource = _resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
|
||||
Assert.That(resource, Is.Not.Null);
|
||||
Assert.That(resource!.Content, Is.EqualTo("Content 1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Load_Should_Return_Cached_Resource()
|
||||
{
|
||||
var resource1 = _resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
var resource2 = _resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
|
||||
Assert.That(resource1, Is.SameAs(resource2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LoadAsync_Should_Load_Resource()
|
||||
{
|
||||
var resource = await _resourceManager.LoadAsync<TestResource>("test/resource1.txt");
|
||||
|
||||
Assert.That(resource, Is.Not.Null);
|
||||
Assert.That(resource!.Content, Is.EqualTo("Content 1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Load_Should_Throw_When_No_Loader_Registered()
|
||||
{
|
||||
_resourceManager.UnregisterLoader<TestResource>();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => { _resourceManager.Load<TestResource>("test/resource1.txt"); });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsLoaded_Should_Return_True_When_Resource_Loaded()
|
||||
{
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
|
||||
Assert.That(_resourceManager.IsLoaded("test/resource1.txt"), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsLoaded_Should_Return_False_When_Resource_Not_Loaded()
|
||||
{
|
||||
Assert.That(_resourceManager.IsLoaded("test/nonexistent.txt"), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unload_Should_Remove_Resource()
|
||||
{
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
|
||||
var unloaded = _resourceManager.Unload("test/resource1.txt");
|
||||
|
||||
Assert.That(unloaded, Is.True);
|
||||
Assert.That(_resourceManager.IsLoaded("test/resource1.txt"), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UnloadAll_Should_Remove_All_Resources()
|
||||
{
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
_resourceManager.Load<TestResource>("test/resource2.txt");
|
||||
|
||||
_resourceManager.UnloadAll();
|
||||
|
||||
Assert.That(_resourceManager.LoadedResourceCount, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LoadedResourceCount_Should_Return_Correct_Count()
|
||||
{
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
_resourceManager.Load<TestResource>("test/resource2.txt");
|
||||
|
||||
Assert.That(_resourceManager.LoadedResourceCount, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetLoadedResourcePaths_Should_Return_All_Paths()
|
||||
{
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
_resourceManager.Load<TestResource>("test/resource2.txt");
|
||||
|
||||
var paths = _resourceManager.GetLoadedResourcePaths().ToList();
|
||||
|
||||
Assert.That(paths, Has.Count.EqualTo(2));
|
||||
Assert.That(paths, Contains.Item("test/resource1.txt"));
|
||||
Assert.That(paths, Contains.Item("test/resource2.txt"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetHandle_Should_Return_Valid_Handle()
|
||||
{
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
|
||||
var handle = _resourceManager.GetHandle<TestResource>("test/resource1.txt");
|
||||
|
||||
Assert.That(handle, Is.Not.Null);
|
||||
Assert.That(handle!.IsValid, Is.True);
|
||||
Assert.That(handle.Resource, Is.Not.Null);
|
||||
Assert.That(handle.Path, Is.EqualTo("test/resource1.txt"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetHandle_Should_Return_Null_When_Resource_Not_Loaded()
|
||||
{
|
||||
var handle = _resourceManager.GetHandle<TestResource>("test/nonexistent.txt");
|
||||
|
||||
Assert.That(handle, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Handle_Dispose_Should_Decrease_Reference_Count()
|
||||
{
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
|
||||
var handle = _resourceManager.GetHandle<TestResource>("test/resource1.txt");
|
||||
handle!.Dispose();
|
||||
|
||||
// 资源应该仍然存在,因为还有一个初始引用
|
||||
Assert.That(_resourceManager.IsLoaded("test/resource1.txt"), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PreloadAsync_Should_Load_Resource()
|
||||
{
|
||||
await _resourceManager.PreloadAsync<TestResource>("test/resource1.txt");
|
||||
|
||||
Assert.That(_resourceManager.IsLoaded("test/resource1.txt"), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Load_Should_Throw_When_Path_Is_Null_Or_Empty()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => _resourceManager.Load<TestResource>(null!));
|
||||
Assert.Throws<ArgumentException>(() => _resourceManager.Load<TestResource>(""));
|
||||
Assert.Throws<ArgumentException>(() => _resourceManager.Load<TestResource>(" "));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterLoader_Should_Throw_When_Loader_Is_Null()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => { _resourceManager.RegisterLoader<TestResource>(null!); });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Concurrent_Load_Should_Be_Thread_Safe()
|
||||
{
|
||||
const int threadCount = 10;
|
||||
var tasks = new Task[threadCount];
|
||||
var exceptions = new List<Exception>();
|
||||
var resources = new TestResource?[threadCount];
|
||||
|
||||
for (var i = 0; i < threadCount; i++)
|
||||
{
|
||||
var index = i;
|
||||
tasks[i] = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
resources[index] = _resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lock (exceptions)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
|
||||
Assert.That(exceptions, Is.Empty);
|
||||
Assert.That(resources.All(r => r != null), Is.True);
|
||||
Assert.That(resources.All(r => r == resources[0]), Is.True, "所有线程应该获得同一个资源实例");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Concurrent_LoadAsync_Should_Be_Thread_Safe()
|
||||
{
|
||||
const int threadCount = 10;
|
||||
var tasks = new Task<TestResource?>[threadCount];
|
||||
|
||||
for (var i = 0; i < threadCount; i++)
|
||||
{
|
||||
tasks[i] = _resourceManager.LoadAsync<TestResource>("test/resource1.txt");
|
||||
}
|
||||
|
||||
var resources = await Task.WhenAll(tasks);
|
||||
|
||||
Assert.That(resources.All(r => r != null), Is.True);
|
||||
Assert.That(resources.All(r => r == resources[0]), Is.True, "所有任务应该获得同一个资源实例");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Concurrent_Load_Different_Resources_Should_Work()
|
||||
{
|
||||
const int threadCount = 2;
|
||||
var tasks = new Task[threadCount];
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
tasks[0] = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lock (exceptions)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tasks[1] = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
_resourceManager.Load<TestResource>("test/resource2.txt");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lock (exceptions)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
|
||||
Assert.That(exceptions, Is.Empty);
|
||||
Assert.That(_resourceManager.LoadedResourceCount, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ManualReleaseStrategy_Should_Not_Auto_Unload()
|
||||
{
|
||||
_resourceManager.SetReleaseStrategy(new ManualReleaseStrategy());
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
|
||||
using (var handle = _resourceManager.GetHandle<TestResource>("test/resource1.txt"))
|
||||
{
|
||||
handle?.Dispose();
|
||||
}
|
||||
|
||||
// 验证资源仍然在缓存中
|
||||
Assert.That(_resourceManager.LoadedResourceCount, Is.EqualTo(1));
|
||||
Assert.That(_resourceManager.IsLoaded("test/resource1.txt"), Is.True);
|
||||
|
||||
// 手动获取资源验证其未被卸载
|
||||
var cachedResource = _resourceManager.GetHandle<TestResource>("test/resource1.txt");
|
||||
Assert.That(cachedResource, Is.Not.Null);
|
||||
Assert.That(cachedResource!.Resource, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AutoReleaseStrategy_Should_Auto_Unload_When_RefCount_Zero()
|
||||
{
|
||||
_resourceManager.SetReleaseStrategy(new AutoReleaseStrategy());
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
var handle = _resourceManager.GetHandle<TestResource>("test/resource1.txt");
|
||||
|
||||
// 释放句柄
|
||||
handle!.Dispose();
|
||||
|
||||
// 自动释放策略:资源应该被自动卸载
|
||||
Assert.That(_resourceManager.IsLoaded("test/resource1.txt"), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AutoReleaseStrategy_Should_Not_Unload_With_Active_References()
|
||||
{
|
||||
_resourceManager.SetReleaseStrategy(new AutoReleaseStrategy());
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
var handle1 = _resourceManager.GetHandle<TestResource>("test/resource1.txt");
|
||||
var handle2 = _resourceManager.GetHandle<TestResource>("test/resource1.txt");
|
||||
|
||||
// 释放一个句柄
|
||||
handle1!.Dispose();
|
||||
|
||||
// 还有一个活跃引用,资源不应该被卸载
|
||||
Assert.That(_resourceManager.IsLoaded("test/resource1.txt"), Is.True);
|
||||
|
||||
// 释放第二个句柄
|
||||
handle2!.Dispose();
|
||||
|
||||
// 现在资源应该被自动卸载
|
||||
Assert.That(_resourceManager.IsLoaded("test/resource1.txt"), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetReleaseStrategy_Should_Throw_When_Strategy_Is_Null()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => { _resourceManager.SetReleaseStrategy(null!); });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Switch_Between_Release_Strategies()
|
||||
{
|
||||
// 开始使用手动释放策略
|
||||
_resourceManager.Load<TestResource>("test/resource1.txt");
|
||||
var handle1 = _resourceManager.GetHandle<TestResource>("test/resource1.txt");
|
||||
handle1!.Dispose();
|
||||
Assert.That(_resourceManager.IsLoaded("test/resource1.txt"), Is.True);
|
||||
|
||||
// 切换到自动释放策略
|
||||
_resourceManager.SetReleaseStrategy(new AutoReleaseStrategy());
|
||||
_resourceManager.Load<TestResource>("test/resource2.txt");
|
||||
var handle2 = _resourceManager.GetHandle<TestResource>("test/resource2.txt");
|
||||
handle2!.Dispose();
|
||||
Assert.That(_resourceManager.IsLoaded("test/resource2.txt"), Is.False);
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,8 @@ using System.IO;
|
||||
using System.Text.Json;
|
||||
using GFramework.Core.Abstractions.configuration;
|
||||
using GFramework.Core.Abstractions.events;
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.logging;
|
||||
|
||||
namespace GFramework.Core.configuration;
|
||||
|
||||
@ -32,6 +34,8 @@ public class ConfigurationManager : IConfigurationManager
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<string, object> _configs = new();
|
||||
|
||||
private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(ConfigurationManager));
|
||||
|
||||
/// <summary>
|
||||
/// 用于保护监听器列表的锁
|
||||
/// </summary>
|
||||
@ -289,7 +293,7 @@ public class ConfigurationManager : IConfigurationManager
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 防止监听器异常影响其他监听器
|
||||
Console.Error.WriteLine(
|
||||
_logger.Error(
|
||||
$"[ConfigurationManager] Error in config watcher for key '{key}': {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
using GFramework.Core.Abstractions.coroutine;
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.coroutine.instructions;
|
||||
using GFramework.Core.logging;
|
||||
|
||||
namespace GFramework.Core.coroutine;
|
||||
|
||||
@ -15,6 +17,7 @@ public sealed class CoroutineScheduler(
|
||||
byte instanceId = 1,
|
||||
int initialCapacity = 256)
|
||||
{
|
||||
private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CoroutineScheduler));
|
||||
private readonly Dictionary<CoroutineHandle, CoroutineMetadata> _metadata = new();
|
||||
private readonly Dictionary<string, HashSet<CoroutineHandle>> _tagged = new();
|
||||
private readonly ITimeSource _timeSource = timeSource ?? throw new ArgumentNullException(nameof(timeSource));
|
||||
@ -400,13 +403,13 @@ public sealed class CoroutineScheduler(
|
||||
catch (Exception callbackEx)
|
||||
{
|
||||
// 防止回调异常传播,记录到控制台
|
||||
Console.Error.WriteLine($"[CoroutineScheduler] Exception in error callback: {callbackEx}");
|
||||
_logger.Error($"[CoroutineScheduler] Exception in error callback: {callbackEx}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 输出到控制台作为后备
|
||||
Console.Error.WriteLine($"[CoroutineScheduler] Coroutine {handle} failed with exception: {ex}");
|
||||
_logger.Error($"[CoroutineScheduler] Coroutine {handle} failed with exception: {ex}");
|
||||
|
||||
// 完成协程
|
||||
Complete(slotIndex);
|
||||
|
||||
19
GFramework.Core/resource/AutoReleaseStrategy.cs
Normal file
19
GFramework.Core/resource/AutoReleaseStrategy.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using GFramework.Core.Abstractions.resource;
|
||||
|
||||
namespace GFramework.Core.resource;
|
||||
|
||||
/// <summary>
|
||||
/// 自动释放策略
|
||||
/// 当资源引用计数降为 0 时自动卸载资源
|
||||
/// </summary>
|
||||
public class AutoReleaseStrategy : IResourceReleaseStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断是否应该释放资源
|
||||
/// 当引用计数降为 0 时返回 true
|
||||
/// </summary>
|
||||
public bool ShouldRelease(string path, int refCount)
|
||||
{
|
||||
return refCount <= 0;
|
||||
}
|
||||
}
|
||||
19
GFramework.Core/resource/ManualReleaseStrategy.cs
Normal file
19
GFramework.Core/resource/ManualReleaseStrategy.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using GFramework.Core.Abstractions.resource;
|
||||
|
||||
namespace GFramework.Core.resource;
|
||||
|
||||
/// <summary>
|
||||
/// 手动释放策略
|
||||
/// 引用计数降为 0 时不自动卸载资源,需要手动调用 Unload
|
||||
/// </summary>
|
||||
public class ManualReleaseStrategy : IResourceReleaseStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 判断是否应该释放资源(始终返回 false)
|
||||
/// </summary>
|
||||
public bool ShouldRelease(string path, int refCount)
|
||||
{
|
||||
// 手动释放策略:永远不自动释放,由用户显式调用 Unload
|
||||
return false;
|
||||
}
|
||||
}
|
||||
211
GFramework.Core/resource/ResourceCache.cs
Normal file
211
GFramework.Core/resource/ResourceCache.cs
Normal file
@ -0,0 +1,211 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace GFramework.Core.resource;
|
||||
|
||||
/// <summary>
|
||||
/// 资源缓存条目
|
||||
/// </summary>
|
||||
internal sealed class ResourceCacheEntry(object resource, Type resourceType)
|
||||
{
|
||||
public object Resource { get; } = resource ?? throw new ArgumentNullException(nameof(resource));
|
||||
public Type ResourceType { get; } = resourceType ?? throw new ArgumentNullException(nameof(resourceType));
|
||||
public int ReferenceCount { get; set; }
|
||||
public DateTime LastAccessTime { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 资源缓存系统,管理已加载资源的缓存和引用计数
|
||||
/// 线程安全:所有公共方法都是线程安全的
|
||||
/// </summary>
|
||||
internal sealed class ResourceCache
|
||||
{
|
||||
/// <summary>
|
||||
/// Path 参数验证错误消息常量
|
||||
/// </summary>
|
||||
private const string PathCannotBeNullOrEmptyMessage = "Path cannot be null or whitespace.";
|
||||
|
||||
private readonly ConcurrentDictionary<string, ResourceCacheEntry> _cache = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取已缓存资源的数量
|
||||
/// </summary>
|
||||
public int Count => _cache.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 添加资源到缓存
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <param name="resource">资源实例</param>
|
||||
/// <returns>如果成功添加返回 true,如果已存在返回 false</returns>
|
||||
public bool Add<T>(string path, T resource) where T : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
ArgumentNullException.ThrowIfNull(resource);
|
||||
|
||||
var entry = new ResourceCacheEntry(resource, typeof(T));
|
||||
return _cache.TryAdd(path, entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中获取资源
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>资源实例,如果不存在或类型不匹配返回 null</returns>
|
||||
public T? Get<T>(string path) where T : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
if (_cache.TryGetValue(path, out var entry))
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
entry.LastAccessTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if (entry.Resource is T typedResource)
|
||||
{
|
||||
return typedResource;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查资源是否在缓存中
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>如果存在返回 true,否则返回 false</returns>
|
||||
public bool Contains(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
return _cache.ContainsKey(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从缓存中移除资源
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>如果成功移除返回 true,否则返回 false</returns>
|
||||
public bool Remove(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
return _cache.TryRemove(path, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有缓存
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加资源的引用计数
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>如果成功增加返回 true,如果资源不存在返回 false</returns>
|
||||
public bool AddReference(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
if (_cache.TryGetValue(path, out var entry))
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
entry.ReferenceCount++;
|
||||
entry.LastAccessTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 减少资源的引用计数
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>减少后的引用计数,如果资源不存在返回 -1</returns>
|
||||
public int RemoveReference(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
if (_cache.TryGetValue(path, out var entry))
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
entry.ReferenceCount--;
|
||||
return entry.ReferenceCount;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源的引用计数
|
||||
/// </summary>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <returns>引用计数,如果资源不存在返回 -1</returns>
|
||||
public int GetReferenceCount(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
if (_cache.TryGetValue(path, out var entry))
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return entry.ReferenceCount;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已缓存资源的路径
|
||||
/// </summary>
|
||||
/// <returns>资源路径集合</returns>
|
||||
public IEnumerable<string> GetAllPaths()
|
||||
{
|
||||
return _cache.Keys.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有引用计数为 0 的资源路径
|
||||
/// </summary>
|
||||
/// <returns>资源路径集合</returns>
|
||||
public IEnumerable<string> GetUnreferencedPaths()
|
||||
{
|
||||
var unreferencedPaths = new List<string>();
|
||||
|
||||
foreach (var kvp in _cache)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (kvp.Value.ReferenceCount <= 0)
|
||||
{
|
||||
unreferencedPaths.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unreferencedPaths;
|
||||
}
|
||||
}
|
||||
144
GFramework.Core/resource/ResourceHandle.cs
Normal file
144
GFramework.Core/resource/ResourceHandle.cs
Normal file
@ -0,0 +1,144 @@
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.Abstractions.resource;
|
||||
using GFramework.Core.logging;
|
||||
|
||||
namespace GFramework.Core.resource;
|
||||
|
||||
/// <summary>
|
||||
/// 资源句柄实现,管理资源的生命周期和引用计数
|
||||
/// 线程安全:所有公共方法都是线程安全的
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
internal sealed class ResourceHandle<T> : IResourceHandle<T> where T : class
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(ResourceHandle<T>));
|
||||
private readonly Action<string> _onDispose;
|
||||
private bool _disposed;
|
||||
private int _referenceCount;
|
||||
|
||||
/// <summary>
|
||||
/// 创建资源句柄
|
||||
/// </summary>
|
||||
/// <param name="resource">资源实例</param>
|
||||
/// <param name="path">资源路径</param>
|
||||
/// <param name="onDispose">释放时的回调</param>
|
||||
public ResourceHandle(T resource, string path, Action<string> onDispose)
|
||||
{
|
||||
Resource = resource ?? throw new ArgumentNullException(nameof(resource));
|
||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
||||
_onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
|
||||
_referenceCount = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源实例
|
||||
/// </summary>
|
||||
public T? Resource { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源路径
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源是否有效
|
||||
/// </summary>
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return !_disposed && Resource != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前引用计数
|
||||
/// </summary>
|
||||
public int ReferenceCount
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _referenceCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加引用计数
|
||||
/// </summary>
|
||||
public void AddReference()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(ResourceHandle<T>));
|
||||
|
||||
_referenceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 减少引用计数
|
||||
/// </summary>
|
||||
public void RemoveReference()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_referenceCount--;
|
||||
|
||||
if (_referenceCount <= 0)
|
||||
{
|
||||
DisposeInternal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源句柄
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_referenceCount--;
|
||||
|
||||
if (_referenceCount <= 0)
|
||||
{
|
||||
DisposeInternal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部释放方法(必须在锁内调用)
|
||||
/// </summary>
|
||||
private void DisposeInternal()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
_disposed = true;
|
||||
Resource = null;
|
||||
|
||||
try
|
||||
{
|
||||
_onDispose(Path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"[ResourceHandle] Error disposing resource '{Path}': {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
334
GFramework.Core/resource/ResourceManager.cs
Normal file
334
GFramework.Core/resource/ResourceManager.cs
Normal file
@ -0,0 +1,334 @@
|
||||
using System.Collections.Concurrent;
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.Abstractions.resource;
|
||||
using GFramework.Core.logging;
|
||||
|
||||
namespace GFramework.Core.resource;
|
||||
|
||||
/// <summary>
|
||||
/// 资源管理器实现,提供资源加载、缓存和卸载功能
|
||||
/// 线程安全:所有公共方法都是线程安全的
|
||||
/// </summary>
|
||||
public class ResourceManager : IResourceManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Path 参数验证错误消息常量
|
||||
/// </summary>
|
||||
private const string PathCannotBeNullOrEmptyMessage = "Path cannot be null or whitespace.";
|
||||
|
||||
private readonly ResourceCache _cache = new();
|
||||
private readonly ConcurrentDictionary<Type, object> _loaders = new();
|
||||
private readonly object _loadLock = new();
|
||||
private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(ResourceManager));
|
||||
private IResourceReleaseStrategy _releaseStrategy;
|
||||
|
||||
/// <summary>
|
||||
/// 创建资源管理器
|
||||
/// 默认使用手动释放策略
|
||||
/// </summary>
|
||||
public ResourceManager()
|
||||
{
|
||||
_releaseStrategy = new ManualReleaseStrategy();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取已加载资源的数量
|
||||
/// </summary>
|
||||
public int LoadedResourceCount => _cache.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 同步加载资源
|
||||
/// </summary>
|
||||
public T? Load<T>(string path) where T : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
// 检查缓存
|
||||
var cached = _cache.Get<T>(path);
|
||||
if (cached != null)
|
||||
{
|
||||
_cache.AddReference(path);
|
||||
return cached;
|
||||
}
|
||||
|
||||
// 加载资源
|
||||
lock (_loadLock)
|
||||
{
|
||||
// 双重检查
|
||||
cached = _cache.Get<T>(path);
|
||||
if (cached != null)
|
||||
{
|
||||
_cache.AddReference(path);
|
||||
return cached;
|
||||
}
|
||||
|
||||
var loader = GetLoader<T>();
|
||||
if (loader == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No loader registered for type {typeof(T).Name}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var resource = loader.Load(path);
|
||||
_cache.Add(path, resource);
|
||||
_cache.AddReference(path);
|
||||
return resource;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"[ResourceManager] Failed to load resource '{path}': {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载资源
|
||||
/// </summary>
|
||||
public async Task<T?> LoadAsync<T>(string path) where T : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
// 检查缓存
|
||||
var cached = _cache.Get<T>(path);
|
||||
if (cached != null)
|
||||
{
|
||||
_cache.AddReference(path);
|
||||
return cached;
|
||||
}
|
||||
|
||||
var loader = GetLoader<T>();
|
||||
if (loader == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No loader registered for type {typeof(T).Name}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var resource = await loader.LoadAsync(path);
|
||||
lock (_loadLock)
|
||||
{
|
||||
// 双重检查
|
||||
cached = _cache.Get<T>(path);
|
||||
if (cached != null)
|
||||
{
|
||||
// 已经被其他线程加载了,卸载当前加载的资源
|
||||
loader.Unload(resource);
|
||||
_cache.AddReference(path);
|
||||
return cached;
|
||||
}
|
||||
|
||||
_cache.Add(path, resource);
|
||||
_cache.AddReference(path);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"[ResourceManager] Failed to load resource '{path}': {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源句柄
|
||||
/// </summary>
|
||||
public IResourceHandle<T>? GetHandle<T>(string path) where T : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
var resource = _cache.Get<T>(path);
|
||||
if (resource == null)
|
||||
return null;
|
||||
|
||||
_cache.AddReference(path);
|
||||
return new ResourceHandle<T>(resource, path, HandleDispose);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卸载指定路径的资源
|
||||
/// </summary>
|
||||
public bool Unload(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
lock (_loadLock)
|
||||
{
|
||||
var resource = _cache.Get<object>(path);
|
||||
if (resource == null)
|
||||
return false;
|
||||
|
||||
// 检查引用计数
|
||||
var refCount = _cache.GetReferenceCount(path);
|
||||
if (refCount > 0)
|
||||
{
|
||||
_logger.Error(
|
||||
$"[ResourceManager] Cannot unload resource '{path}' with {refCount} active references");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 卸载资源
|
||||
UnloadResource(resource);
|
||||
|
||||
// 从缓存中移除
|
||||
return _cache.Remove(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卸载所有资源
|
||||
/// </summary>
|
||||
public void UnloadAll()
|
||||
{
|
||||
lock (_loadLock)
|
||||
{
|
||||
var paths = _cache.GetAllPaths().ToList();
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
var resource = _cache.Get<object>(path);
|
||||
if (resource != null)
|
||||
{
|
||||
UnloadResource(resource);
|
||||
}
|
||||
}
|
||||
|
||||
_cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查资源是否已加载
|
||||
/// </summary>
|
||||
public bool IsLoaded(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException(PathCannotBeNullOrEmptyMessage, nameof(path));
|
||||
|
||||
return _cache.Contains(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册资源加载器
|
||||
/// </summary>
|
||||
public void RegisterLoader<T>(IResourceLoader<T> loader) where T : class
|
||||
{
|
||||
if (loader == null)
|
||||
throw new ArgumentNullException(nameof(loader));
|
||||
|
||||
_loaders[typeof(T)] = loader;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消注册资源加载器
|
||||
/// </summary>
|
||||
public void UnregisterLoader<T>() where T : class
|
||||
{
|
||||
_loaders.TryRemove(typeof(T), out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 预加载资源
|
||||
/// </summary>
|
||||
public async Task PreloadAsync<T>(string path) where T : class
|
||||
{
|
||||
await LoadAsync<T>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已加载资源的路径
|
||||
/// </summary>
|
||||
public IEnumerable<string> GetLoadedResourcePaths()
|
||||
{
|
||||
return _cache.GetAllPaths();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置资源释放策略
|
||||
/// </summary>
|
||||
/// <param name="strategy">资源释放策略</param>
|
||||
public void SetReleaseStrategy(IResourceReleaseStrategy strategy)
|
||||
{
|
||||
_releaseStrategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定类型的资源加载器
|
||||
/// </summary>
|
||||
private IResourceLoader<T>? GetLoader<T>() where T : class
|
||||
{
|
||||
if (_loaders.TryGetValue(typeof(T), out var loader))
|
||||
{
|
||||
return loader as IResourceLoader<T>;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 卸载资源实例
|
||||
/// </summary>
|
||||
private void UnloadResource(object resource)
|
||||
{
|
||||
var resourceType = resource.GetType();
|
||||
|
||||
if (_loaders.TryGetValue(resourceType, out var loaderObj))
|
||||
{
|
||||
try
|
||||
{
|
||||
var unloadMethod = loaderObj.GetType().GetMethod("Unload");
|
||||
unloadMethod?.Invoke(loaderObj, new[] { resource });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"[ResourceManager] Error unloading resource: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 如果资源实现了 IDisposable,调用 Dispose
|
||||
if (resource is IDisposable disposable)
|
||||
{
|
||||
try
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error($"[ResourceManager] Error disposing resource: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 句柄释放时的回调
|
||||
/// </summary>
|
||||
private void HandleDispose(string path)
|
||||
{
|
||||
var refCount = _cache.RemoveReference(path);
|
||||
|
||||
// 使用策略模式判断是否应该释放资源
|
||||
if (_releaseStrategy.ShouldRelease(path, refCount))
|
||||
{
|
||||
lock (_loadLock)
|
||||
{
|
||||
// 双重检查引用计数,避免竞态条件
|
||||
var currentRefCount = _cache.GetReferenceCount(path);
|
||||
if (currentRefCount <= 0)
|
||||
{
|
||||
var resource = _cache.Get<object>(path);
|
||||
if (resource != null)
|
||||
{
|
||||
UnloadResource(resource);
|
||||
_cache.Remove(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user