Skip to content

Feat/configuration resource manager#68

Merged
GeWuYou merged 3 commits into
mainfrom
feat/configuration-resource-manager
Mar 5, 2026
Merged

Feat/configuration resource manager#68
GeWuYou merged 3 commits into
mainfrom
feat/configuration-resource-manager

Conversation

@GeWuYou
Copy link
Copy Markdown
Owner

@GeWuYou GeWuYou commented Mar 4, 2026

Summary by Sourcery

添加可配置的、线程安全的资源和配置管理工具,支持带引用计数的资源句柄和可插拔的释放策略,并与现有日志系统集成。

New Features:

  • 引入通用资源管理系统,支持加载器、缓存、带引用计数的句柄,以及可插拔的释放策略。
  • 添加配置管理器,以类型安全的键值配置为基础,支持 JSON/文件持久化和变更监听。
  • 为配置、资源管理、资源加载器、句柄以及释放策略定义抽象接口,以便在整个框架中复用。

Enhancements:

  • 将协程调度器的错误报告通过框架的日志基础设施进行路由,而不是直接输出到控制台。

Tests:

  • 添加全面的单元测试,覆盖资源管理器的加载、缓存、并发及释放策略行为。
  • 添加全面的单元测试,覆盖配置管理器的增删改查操作、监听器、持久化以及并发行为。
Original summary in English

Summary by Sourcery

Add configurable, thread-safe resource and configuration management utilities with reference-counted resource handles and pluggable release strategies, and integrate them with existing logging.

New Features:

  • Introduce a generic resource management system with loaders, caching, reference-counted handles, and pluggable release strategies.
  • Add a configuration manager for type-safe key/value configs with JSON/file persistence and change watchers.
  • Define abstractions for configuration, resource management, resource loaders, handles, and release strategies for use across the framework.

Enhancements:

  • Route coroutine scheduler error reporting through the framework logging infrastructure instead of writing directly to the console.

Tests:

  • Add comprehensive unit tests covering resource manager loading, caching, concurrency, and release strategies.
  • Add comprehensive unit tests covering configuration manager CRUD operations, watchers, persistence, and concurrency behavior.

GeWuYou added 2 commits March 4, 2026 12:40
- 实现了 IConfigurationManager 接口定义配置管理契约
- 创建 ConfigurationManager 类提供线程安全的配置存储和访问
- 添加配置的增删改查功能支持泛型类型转换
- 实现配置变更监听机制和取消注册功能
- 提供 JSON 格式导入导出和文件读写功能
- 添加完整的单元测试覆盖并发场景和边界条件
- 实现 ConfigWatcherUnRegister 类处理监听器注销逻辑
- 实现了完整的资源管理系统,包括资源加载、缓存和卸载功能
- 添加了 IResourceHandle、IResourceLoader、IResourceManager 和 IResourceReleaseStrategy 接口定义
- 实现了 AutoReleaseStrategy 和 ManualReleaseStrategy 两种资源释放策略
- 创建了 ResourceCache 缓存系统和 ResourceHandle 资源句柄管理
- 在 ConfigurationManager 和 CoroutineScheduler 中集成了 ILogger 日志功能
- 添加了全面的 ResourceManagerTests 单元测试覆盖各种使用场景
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 4, 2026

Reviewer's Guide

引入了一个全新的线程安全配置系统和资源管理子系统(带缓存、引用计数句柄以及可插拔释放策略),将它们接入现有抽象层和测试中,并将协程错误的 Console 日志替换为基于集中式 ILogger 的日志记录。

资源加载与自动释放策略的时序图

sequenceDiagram
    actor Client
    participant ResourceManager
    participant ResourceCache
    participant IResourceLoader_T
    participant ResourceHandle_T
    participant IResourceReleaseStrategy

    Client->>ResourceManager: LoadAsync_T_(path)
    ResourceManager->>ResourceCache: Get_T_(path)
    alt cached resource exists
        ResourceCache-->>ResourceManager: cachedResource
        ResourceManager->>ResourceCache: AddReference(path)
        ResourceManager-->>Client: cachedResource
    else not cached
        ResourceManager->>IResourceLoader_T: LoadAsync(path)
        IResourceLoader_T-->>ResourceManager: loadedResource
        ResourceManager->>ResourceCache: Get_T_(path)
        alt another thread cached
            ResourceCache-->>ResourceManager: existingResource
            ResourceManager->>IResourceLoader_T: Unload(loadedResource)
            ResourceManager->>ResourceCache: AddReference(path)
            ResourceManager-->>Client: existingResource
        else still not cached
            ResourceCache-->>ResourceManager: null
            ResourceManager->>ResourceCache: Add(path, loadedResource)
            ResourceManager->>ResourceCache: AddReference(path)
            ResourceManager-->>Client: loadedResource
        end
    end

    Client->>ResourceManager: GetHandle_T_(path)
    ResourceManager->>ResourceCache: Get_T_(path)
    ResourceCache-->>ResourceManager: resource
    ResourceManager->>ResourceCache: AddReference(path)
    ResourceManager-->>Client: ResourceHandle_T

    Client->>ResourceHandle_T: Dispose()
    ResourceHandle_T->>ResourceHandle_T: DisposeInternal()
    ResourceHandle_T->>ResourceManager: onDispose(path)
    ResourceManager->>ResourceCache: RemoveReference(path)
    ResourceCache-->>ResourceManager: refCount
    ResourceManager->>IResourceReleaseStrategy: ShouldRelease(path, refCount)
    alt strategy allows release
        IResourceReleaseStrategy-->>ResourceManager: true
        ResourceManager->>ResourceCache: Get_object_(path)
        ResourceCache-->>ResourceManager: resource
        ResourceManager->>IResourceLoader_T: Unload(resource)
        ResourceManager->>ResourceCache: Remove(path)
    else strategy keeps resource
        IResourceReleaseStrategy-->>ResourceManager: false
        note over ResourceManager: Resource remains cached
    end
Loading

新资源管理子系统的类图

classDiagram

class IResourceManager {
  <<interface>>
  +int LoadedResourceCount
  +T Load(string path)
  +Task~T~ LoadAsync(string path)
  +IResourceHandle~T~ GetHandle(string path)
  +bool Unload(string path)
  +void UnloadAll()
  +bool IsLoaded(string path)
  +void RegisterLoader(T loader)
  +void UnregisterLoader()
  +Task PreloadAsync(string path)
  +IEnumerable~string~ GetLoadedResourcePaths()
  +void SetReleaseStrategy(IResourceReleaseStrategy strategy)
}

class IResourceHandle_T {
  <<interface>>
  +T Resource
  +string Path
  +bool IsValid
  +int ReferenceCount
  +void AddReference()
  +void RemoveReference()
  +void Dispose()
}

class IResourceLoader_T {
  <<interface>>
  +T Load(string path)
  +Task~T~ LoadAsync(string path)
  +void Unload(T resource)
  +bool CanLoad(string path)
}

class IResourceReleaseStrategy {
  <<interface>>
  +bool ShouldRelease(string path, int refCount)
}

class ResourceManager {
  -ResourceCache _cache
  -ConcurrentDictionary~Type,object~ _loaders
  -object _loadLock
  -ILogger _logger
  -IResourceReleaseStrategy _releaseStrategy
  +int LoadedResourceCount
  +ResourceManager()
  +T Load(string path)
  +Task~T~ LoadAsync(string path)
  +IResourceHandle~T~ GetHandle(string path)
  +bool Unload(string path)
  +void UnloadAll()
  +bool IsLoaded(string path)
  +void RegisterLoader(T loader)
  +void UnregisterLoader()
  +Task PreloadAsync(string path)
  +IEnumerable~string~ GetLoadedResourcePaths()
  +void SetReleaseStrategy(IResourceReleaseStrategy strategy)
  -IResourceLoader~T~ GetLoader()
  -void UnloadResource(object resource)
  -void HandleDispose(string path)
}

class ResourceCache {
  -ConcurrentDictionary~string,ResourceCacheEntry~ _cache
  -object _lock
  +int Count
  +bool Add(string path, T resource)
  +T Get(string path)
  +bool Contains(string path)
  +bool Remove(string path)
  +void Clear()
  +bool AddReference(string path)
  +int RemoveReference(string path)
  +int GetReferenceCount(string path)
  +IEnumerable~string~ GetAllPaths()
  +IEnumerable~string~ GetUnreferencedPaths()
}

class ResourceCacheEntry {
  +object Resource
  +Type ResourceType
  +int ReferenceCount
  +DateTime LastAccessTime
}

class ResourceHandle_T {
  -object _lock
  -ILogger _logger
  -Action~string~ _onDispose
  -bool _disposed
  -int _referenceCount
  +T Resource
  +string Path
  +bool IsValid
  +int ReferenceCount
  +ResourceHandle(T resource, string path, Action~string~ onDispose)
  +void AddReference()
  +void RemoveReference()
  +void Dispose()
  -void DisposeInternal()
}

class AutoReleaseStrategy {
  +bool ShouldRelease(string path, int refCount)
}

class ManualReleaseStrategy {
  +bool ShouldRelease(string path, int refCount)
}

class TestResourceLoader {
}

IResourceManager <|.. ResourceManager
IResourceHandle_T <|.. ResourceHandle_T
IResourceLoader_T <|.. TestResourceLoader
IResourceReleaseStrategy <|.. AutoReleaseStrategy
IResourceReleaseStrategy <|.. ManualReleaseStrategy

ResourceManager *-- ResourceCache
ResourceManager o-- IResourceReleaseStrategy
ResourceManager o-- IResourceLoader_T
ResourceManager ..> ResourceHandle_T : creates
ResourceCache *-- ResourceCacheEntry
ResourceHandle_T ..> ResourceManager : onDispose callback
Loading

新配置管理子系统的类图

classDiagram

class IConfigurationManager {
  <<interface>>
  +int Count
  +T GetConfig(string key)
  +T GetConfig(string key, T defaultValue)
  +void SetConfig(string key, T value)
  +bool HasConfig(string key)
  +bool RemoveConfig(string key)
  +void Clear()
  +IUnRegister WatchConfig(string key, Action~T~ onChange)
  +void LoadFromJson(string json)
  +string SaveToJson()
  +void LoadFromFile(string path)
  +void SaveToFile(string path)
  +IEnumerable~string~ GetAllKeys()
}

class IUnRegister {
  <<interface>>
  +void UnRegister()
}

class ConfigurationManager {
  -ConcurrentDictionary~string,object~ _configs
  -ILogger _logger
  -object _watcherLock
  -ConcurrentDictionary~string,List_Delegate~ _watchers
  +int Count
  +T GetConfig(string key)
  +T GetConfig(string key, T defaultValue)
  +void SetConfig(string key, T value)
  +bool HasConfig(string key)
  +bool RemoveConfig(string key)
  +void Clear()
  +IUnRegister WatchConfig(string key, Action~T~ onChange)
  +void LoadFromJson(string json)
  +string SaveToJson()
  +void LoadFromFile(string path)
  +void SaveToFile(string path)
  +IEnumerable~string~ GetAllKeys()
  -void UnwatchConfig(string key, Action~T~ onChange)
  -void NotifyWatchers(string key, T newValue)
  -static T ConvertValue(object value)
}

class ConfigWatcherUnRegister {
  -Action _unRegisterAction
  +ConfigWatcherUnRegister(Action unRegisterAction)
  +void UnRegister()
}

IConfigurationManager <|.. ConfigurationManager
IUnRegister <|.. ConfigWatcherUnRegister

ConfigurationManager ..> ConfigWatcherUnRegister : returns
ConfigurationManager ..> IUnRegister
Loading

文件级变更

Change Details Files
将协程错误上报从 Console.Error 切换到框架级日志记录器。
  • 通过 LoggerFactoryResolver 将 ILogger 注入 CoroutineScheduler
  • 在错误处理逻辑中,将直接调用 Console.Error.WriteLine 替换为调用 _logger.Error
GFramework.Core/coroutine/CoroutineScheduler.cs
添加功能完善、线程安全的资源管理器,支持缓存、引用计数以及可插拔释放策略。
  • 基于 IResourceManager 实现带同步/异步加载、预加载、卸载、加载器注册,以及基于策略的自动/手动释放的 ResourceManager
  • 引入 ResourceCache,用于按路径追踪资源及其引用计数与最后访问时间
  • 实现 ResourceHandle<T>,封装资源及其路径、引用计数,并在释放时向 ResourceManager 发出回调
  • 添加 AutoReleaseStrategy 和 ManualReleaseStrategy 这两种 IResourceReleaseStrategy 的实现
  • 定义 IResourceManager、IResourceHandle<T>、IResourceLoader<T> 和 IResourceReleaseStrategy 抽象接口
GFramework.Core/resource/ResourceManager.cs
GFramework.Core/resource/ResourceCache.cs
GFramework.Core/resource/ResourceHandle.cs
GFramework.Core/resource/AutoReleaseStrategy.cs
GFramework.Core/resource/ManualReleaseStrategy.cs
GFramework.Core.Abstractions/resource/IResourceManager.cs
GFramework.Core.Abstractions/resource/IResourceHandle.cs
GFramework.Core.Abstractions/resource/IResourceLoader.cs
GFramework.Core.Abstractions/resource/IResourceReleaseStrategy.cs
引入线程安全的配置管理器,支持强类型访问、监听器以及 JSON/文件持久化。
  • 实现 ConfigurationManager,支持强类型 Get/Set、默认值、键枚举,并通过 ConcurrentDictionary 保证并发访问安全
  • 支持配置变更监听,包括注册/反注册,以及在锁保护下安全地调用回调
  • 使用 System.Text.Json 和文件系统辅助方法,为配置值增加 JSON 与文件持久化能力
  • 定义 IConfigurationManager 抽象接口,以及实现 IUnRegister 的辅助类 ConfigWatcherUnRegister
GFramework.Core/configuration/ConfigurationManager.cs
GFramework.Core/configuration/ConfigWatcherUnRegister.cs
GFramework.Core.Abstractions/configuration/IConfigurationManager.cs
为资源管理器和配置管理器添加全面的单元测试,包括并发与策略行为。
  • 创建 ResourceManagerTests,覆盖加载、缓存语义、句柄、预加载、卸载/全部卸载、释放策略以及多线程使用模式
  • 创建 ConfigurationManagerTests,覆盖强类型访问、默认值、监听(含多监听与反注册)、JSON/文件往返、校验以及并发 set/get/watch 操作
GFramework.Core.Tests/resource/ResourceManagerTests.cs
GFramework.Core.Tests/configuration/ConfigurationManagerTests.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the
    pull request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Original review guide in English

Reviewer's Guide

Introduces a new thread-safe configuration system and resource management subsystem (with caching, reference-counted handles, and pluggable release strategies), wires them into abstractions and tests, and replaces coroutine error Console logging with the central ILogger-based logging.

Sequence diagram for resource loading and auto release strategy

sequenceDiagram
    actor Client
    participant ResourceManager
    participant ResourceCache
    participant IResourceLoader_T
    participant ResourceHandle_T
    participant IResourceReleaseStrategy

    Client->>ResourceManager: LoadAsync_T_(path)
    ResourceManager->>ResourceCache: Get_T_(path)
    alt cached resource exists
        ResourceCache-->>ResourceManager: cachedResource
        ResourceManager->>ResourceCache: AddReference(path)
        ResourceManager-->>Client: cachedResource
    else not cached
        ResourceManager->>IResourceLoader_T: LoadAsync(path)
        IResourceLoader_T-->>ResourceManager: loadedResource
        ResourceManager->>ResourceCache: Get_T_(path)
        alt another thread cached
            ResourceCache-->>ResourceManager: existingResource
            ResourceManager->>IResourceLoader_T: Unload(loadedResource)
            ResourceManager->>ResourceCache: AddReference(path)
            ResourceManager-->>Client: existingResource
        else still not cached
            ResourceCache-->>ResourceManager: null
            ResourceManager->>ResourceCache: Add(path, loadedResource)
            ResourceManager->>ResourceCache: AddReference(path)
            ResourceManager-->>Client: loadedResource
        end
    end

    Client->>ResourceManager: GetHandle_T_(path)
    ResourceManager->>ResourceCache: Get_T_(path)
    ResourceCache-->>ResourceManager: resource
    ResourceManager->>ResourceCache: AddReference(path)
    ResourceManager-->>Client: ResourceHandle_T

    Client->>ResourceHandle_T: Dispose()
    ResourceHandle_T->>ResourceHandle_T: DisposeInternal()
    ResourceHandle_T->>ResourceManager: onDispose(path)
    ResourceManager->>ResourceCache: RemoveReference(path)
    ResourceCache-->>ResourceManager: refCount
    ResourceManager->>IResourceReleaseStrategy: ShouldRelease(path, refCount)
    alt strategy allows release
        IResourceReleaseStrategy-->>ResourceManager: true
        ResourceManager->>ResourceCache: Get_object_(path)
        ResourceCache-->>ResourceManager: resource
        ResourceManager->>IResourceLoader_T: Unload(resource)
        ResourceManager->>ResourceCache: Remove(path)
    else strategy keeps resource
        IResourceReleaseStrategy-->>ResourceManager: false
        note over ResourceManager: Resource remains cached
    end
Loading

Class diagram for the new resource management subsystem

classDiagram

class IResourceManager {
  <<interface>>
  +int LoadedResourceCount
  +T Load(string path)
  +Task~T~ LoadAsync(string path)
  +IResourceHandle~T~ GetHandle(string path)
  +bool Unload(string path)
  +void UnloadAll()
  +bool IsLoaded(string path)
  +void RegisterLoader(T loader)
  +void UnregisterLoader()
  +Task PreloadAsync(string path)
  +IEnumerable~string~ GetLoadedResourcePaths()
  +void SetReleaseStrategy(IResourceReleaseStrategy strategy)
}

class IResourceHandle_T {
  <<interface>>
  +T Resource
  +string Path
  +bool IsValid
  +int ReferenceCount
  +void AddReference()
  +void RemoveReference()
  +void Dispose()
}

class IResourceLoader_T {
  <<interface>>
  +T Load(string path)
  +Task~T~ LoadAsync(string path)
  +void Unload(T resource)
  +bool CanLoad(string path)
}

class IResourceReleaseStrategy {
  <<interface>>
  +bool ShouldRelease(string path, int refCount)
}

class ResourceManager {
  -ResourceCache _cache
  -ConcurrentDictionary~Type,object~ _loaders
  -object _loadLock
  -ILogger _logger
  -IResourceReleaseStrategy _releaseStrategy
  +int LoadedResourceCount
  +ResourceManager()
  +T Load(string path)
  +Task~T~ LoadAsync(string path)
  +IResourceHandle~T~ GetHandle(string path)
  +bool Unload(string path)
  +void UnloadAll()
  +bool IsLoaded(string path)
  +void RegisterLoader(T loader)
  +void UnregisterLoader()
  +Task PreloadAsync(string path)
  +IEnumerable~string~ GetLoadedResourcePaths()
  +void SetReleaseStrategy(IResourceReleaseStrategy strategy)
  -IResourceLoader~T~ GetLoader()
  -void UnloadResource(object resource)
  -void HandleDispose(string path)
}

class ResourceCache {
  -ConcurrentDictionary~string,ResourceCacheEntry~ _cache
  -object _lock
  +int Count
  +bool Add(string path, T resource)
  +T Get(string path)
  +bool Contains(string path)
  +bool Remove(string path)
  +void Clear()
  +bool AddReference(string path)
  +int RemoveReference(string path)
  +int GetReferenceCount(string path)
  +IEnumerable~string~ GetAllPaths()
  +IEnumerable~string~ GetUnreferencedPaths()
}

class ResourceCacheEntry {
  +object Resource
  +Type ResourceType
  +int ReferenceCount
  +DateTime LastAccessTime
}

class ResourceHandle_T {
  -object _lock
  -ILogger _logger
  -Action~string~ _onDispose
  -bool _disposed
  -int _referenceCount
  +T Resource
  +string Path
  +bool IsValid
  +int ReferenceCount
  +ResourceHandle(T resource, string path, Action~string~ onDispose)
  +void AddReference()
  +void RemoveReference()
  +void Dispose()
  -void DisposeInternal()
}

class AutoReleaseStrategy {
  +bool ShouldRelease(string path, int refCount)
}

class ManualReleaseStrategy {
  +bool ShouldRelease(string path, int refCount)
}

class TestResourceLoader {
}

IResourceManager <|.. ResourceManager
IResourceHandle_T <|.. ResourceHandle_T
IResourceLoader_T <|.. TestResourceLoader
IResourceReleaseStrategy <|.. AutoReleaseStrategy
IResourceReleaseStrategy <|.. ManualReleaseStrategy

ResourceManager *-- ResourceCache
ResourceManager o-- IResourceReleaseStrategy
ResourceManager o-- IResourceLoader_T
ResourceManager ..> ResourceHandle_T : creates
ResourceCache *-- ResourceCacheEntry
ResourceHandle_T ..> ResourceManager : onDispose callback
Loading

Class diagram for the new configuration management subsystem

classDiagram

class IConfigurationManager {
  <<interface>>
  +int Count
  +T GetConfig(string key)
  +T GetConfig(string key, T defaultValue)
  +void SetConfig(string key, T value)
  +bool HasConfig(string key)
  +bool RemoveConfig(string key)
  +void Clear()
  +IUnRegister WatchConfig(string key, Action~T~ onChange)
  +void LoadFromJson(string json)
  +string SaveToJson()
  +void LoadFromFile(string path)
  +void SaveToFile(string path)
  +IEnumerable~string~ GetAllKeys()
}

class IUnRegister {
  <<interface>>
  +void UnRegister()
}

class ConfigurationManager {
  -ConcurrentDictionary~string,object~ _configs
  -ILogger _logger
  -object _watcherLock
  -ConcurrentDictionary~string,List_Delegate~ _watchers
  +int Count
  +T GetConfig(string key)
  +T GetConfig(string key, T defaultValue)
  +void SetConfig(string key, T value)
  +bool HasConfig(string key)
  +bool RemoveConfig(string key)
  +void Clear()
  +IUnRegister WatchConfig(string key, Action~T~ onChange)
  +void LoadFromJson(string json)
  +string SaveToJson()
  +void LoadFromFile(string path)
  +void SaveToFile(string path)
  +IEnumerable~string~ GetAllKeys()
  -void UnwatchConfig(string key, Action~T~ onChange)
  -void NotifyWatchers(string key, T newValue)
  -static T ConvertValue(object value)
}

class ConfigWatcherUnRegister {
  -Action _unRegisterAction
  +ConfigWatcherUnRegister(Action unRegisterAction)
  +void UnRegister()
}

IConfigurationManager <|.. ConfigurationManager
IUnRegister <|.. ConfigWatcherUnRegister

ConfigurationManager ..> ConfigWatcherUnRegister : returns
ConfigurationManager ..> IUnRegister
Loading

File-Level Changes

Change Details Files
Switch coroutine error reporting from Console.Error to framework logger.
  • Inject ILogger into CoroutineScheduler via LoggerFactoryResolver
  • Replace direct Console.Error.WriteLine calls in error handling with _logger.Error invocations
GFramework.Core/coroutine/CoroutineScheduler.cs
Add fully featured, thread-safe resource manager with caching, reference counting, and pluggable release strategies.
  • Implement ResourceManager with sync/async load, preload, unload, loader registration, and strategy-based auto/manual release using IResourceManager
  • Introduce ResourceCache to track resources by path with reference counts and last-access time
  • Implement ResourceHandle to encapsulate a resource, its path, reference counting, and disposal callback to ResourceManager
  • Add AutoReleaseStrategy and ManualReleaseStrategy implementations of IResourceReleaseStrategy
  • Define IResourceManager, IResourceHandle, IResourceLoader, and IResourceReleaseStrategy abstractions
GFramework.Core/resource/ResourceManager.cs
GFramework.Core/resource/ResourceCache.cs
GFramework.Core/resource/ResourceHandle.cs
GFramework.Core/resource/AutoReleaseStrategy.cs
GFramework.Core/resource/ManualReleaseStrategy.cs
GFramework.Core.Abstractions/resource/IResourceManager.cs
GFramework.Core.Abstractions/resource/IResourceHandle.cs
GFramework.Core.Abstractions/resource/IResourceLoader.cs
GFramework.Core.Abstractions/resource/IResourceReleaseStrategy.cs
Introduce a thread-safe configuration manager with typed access, watchers, and JSON/file persistence.
  • Implement ConfigurationManager with typed Get/Set, default values, key enumeration, and concurrent access using ConcurrentDictionary
  • Support config change watchers with registration/unregistration and safe callback invocation guarded by locks
  • Add JSON and file persistence for configuration values using System.Text.Json and filesystem helpers
  • Define IConfigurationManager abstraction and ConfigWatcherUnRegister helper implementing IUnRegister
GFramework.Core/configuration/ConfigurationManager.cs
GFramework.Core/configuration/ConfigWatcherUnRegister.cs
GFramework.Core.Abstractions/configuration/IConfigurationManager.cs
Add comprehensive unit tests for resource and configuration managers, including concurrency and strategy behavior.
  • Create ResourceManagerTests covering loading, caching semantics, handles, preloading, unload/unloadAll, release strategies, and multithreaded usage patterns
  • Create ConfigurationManagerTests covering typed access, defaults, watchers (including multiple and unregistration), JSON/file round-trip, validation, and concurrent set/get/watch operations
GFramework.Core.Tests/resource/ResourceManagerTests.cs
GFramework.Core.Tests/configuration/ConfigurationManagerTests.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@deepsource-io
Copy link
Copy Markdown

deepsource-io Bot commented Mar 4, 2026

DeepSource Code Review

We reviewed changes in d88aa12...fa4c7ec on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade  

Focus Area: Reliability
Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
C# Mar 5, 2026 12:27a.m. Review ↗
Secrets Mar 5, 2026 12:27a.m. Review ↗

_configManager.SetConfig("int.key", 42);
_configManager.SetConfig("string.key", "hello");
_configManager.SetConfig("bool.key", true);
_configManager.SetConfig("double.key", 3.14);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use literals that can be replaced with the ones from `Math` class


Constants such as pi, e, and tau are already defined in the Math class. Consider using them instead of manually defining the literal yourself. This makes it easier to read and follow the code.

Assert.That(_configManager.GetConfig<int>("int.key"), Is.EqualTo(42));
Assert.That(_configManager.GetConfig<string>("string.key"), Is.EqualTo("hello"));
Assert.That(_configManager.GetConfig<bool>("bool.key"), Is.True);
Assert.That(_configManager.GetConfig<double>("double.key"), Is.EqualTo(3.14).Within(0.001));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use literals that can be replaced with the ones from `Math` class


Constants such as pi, e, and tau are already defined in the Math class. Consider using them instead of manually defining the literal yourself. This makes it easier to read and follow the code.

[Test]
public void LoadFromFile_Should_Throw_When_File_Not_Exists()
{
Assert.Throws<FileNotFoundException>(() => { _configManager.LoadFromFile("nonexistent.json"); });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lambda's body can be simplified


If your lambda's body has a single statement, consider refactoring it to move away from block syntax to expression body. Doing so makes your code easier to read.

Comment on lines +289 to +292
lock (exceptions)
{
exceptions.Add(ex);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock held in an improper manner


Trying to acquire a lock on a local variable is error-prone as different threads may end up locking on different instances of the same variable. Instead, you should be locking on a class field that is preferably designated as private and readonly, i.e. a dedicated object. Microsoft guidelines explicitly state that you should avoid locking on:

Comment on lines +329 to +332
lock (exceptions)
{
exceptions.Add(ex);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock held in an improper manner


Trying to acquire a lock on a local variable is error-prone as different threads may end up locking on different instances of the same variable. Instead, you should be locking on a class field that is preferably designated as private and readonly, i.e. a dedicated object. Microsoft guidelines explicitly state that you should avoid locking on:

Comment on lines +248 to +251
lock (exceptions)
{
exceptions.Add(ex);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock held in an improper manner


Trying to acquire a lock on a local variable is error-prone as different threads may end up locking on different instances of the same variable. Instead, you should be locking on a class field that is preferably designated as private and readonly, i.e. a dedicated object. Microsoft guidelines explicitly state that you should avoid locking on:

}

[Test]
public async Task Concurrent_LoadAsync_Should_Be_Thread_Safe()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method with return type `Task` does not follow the naming convention


The consensus in .NET is to have names of methods dealing with asynchronous operations suffixed with Async. One such example is Stream.ReadAsync from System.IO. Doing so improves readability and provides crucial information at a glance.

Comment on lines +298 to +301
lock (exceptions)
{
exceptions.Add(ex);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock held in an improper manner


Trying to acquire a lock on a local variable is error-prone as different threads may end up locking on different instances of the same variable. Instead, you should be locking on a class field that is preferably designated as private and readonly, i.e. a dedicated object. Microsoft guidelines explicitly state that you should avoid locking on:

Comment on lines +316 to +319
lock (exceptions)
{
exceptions.Add(ex);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock held in an improper manner


Trying to acquire a lock on a local variable is error-prone as different threads may end up locking on different instances of the same variable. Instead, you should be locking on a class field that is preferably designated as private and readonly, i.e. a dedicated object. Microsoft guidelines explicitly state that you should avoid locking on:

[Test]
public void SetReleaseStrategy_Should_Throw_When_Strategy_Is_Null()
{
Assert.Throws<ArgumentNullException>(() => { _resourceManager.SetReleaseStrategy(null!); });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lambda's body can be simplified


If your lambda's body has a single statement, consider refactoring it to move away from block syntax to expression body. Doing so makes your code easier to read.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并给出了一些整体性的反馈:

  • 在 ConfigurationManager.SetConfig 中,使用 AddOrUpdate 意味着 oldValue 实际上是更新后的新值,因此相等性检查几乎总是会通过,监听器也就永远不会被通知;建议在更新前先获取旧值(例如通过在更新前使用 TryGetValue),这样就可以正确检测到变更并触发通知。
  • ConfigWatcherUnRegister 被声明在 GFramework.Core.Abstractions.configuration 命名空间下,但实现是在 core 项目中;将它的命名空间(或物理位置)与实际所在的程序集对齐,可以避免跨层的混淆。
给 AI 代理的提示
请根据这次代码评审中的评论进行修改:

## 整体评审意见
- 在 ConfigurationManager.SetConfig 中,使用 AddOrUpdate 意味着 `oldValue` 实际上是更新后的新值,因此相等性检查几乎总是会通过,监听器也就永远不会被通知;建议在更新前先获取旧值(例如通过在更新前使用 TryGetValue),这样就可以正确检测到变更并触发通知。
- ConfigWatcherUnRegister 被声明在 GFramework.Core.Abstractions.configuration 命名空间下,但实现是在 core 项目中;将它的命名空间(或物理位置)与实际所在的程序集对齐,可以避免跨层的混淆。

## 单条评论

### 评论 1
<location path="GFramework.Core/configuration/ConfigurationManager.cs" line_range="90-98" />
<code_context>
+    /// <summary>
+    ///     设置指定键的配置值
+    /// </summary>
+    public void SetConfig<T>(string key, T value)
+    {
+        if (string.IsNullOrWhiteSpace(key))
+            throw new ArgumentException(KeyCannotBeNullOrEmptyMessage, nameof(key));
+
+        var oldValue = _configs.AddOrUpdate(key, value!, (_, _) => value!);
+
+        // 触发监听器
+        if (!EqualityComparer<object>.Default.Equals(oldValue, value))
+        {
+            NotifyWatchers(key, value);
</code_context>
<issue_to_address>
**issue (bug_risk):** SetConfig 永远不会通知监听器,因为 `oldValue` 实际上是 AddOrUpdate 返回的新值。

`ConcurrentDictionary.AddOrUpdate` 会返回更新后的值,因此 `oldValue` 始终是新值,这样比较就永远不会触发 `NotifyWatchers`。你需要访问更新前的值。例如,可以在调用 `AddOrUpdate` 之前用 `TryGetValue` 获取旧值,或者在更新后执行一次 `TryGetValue`,并将那个保存下来的旧值与新值进行比较,然后再决定是否通知监听器。
</issue_to_address>

### 评论 2
<location path="GFramework.Core/resource/ResourceManager.cs" line_range="72-81" />
<code_context>
+    {
+        var tempFile = Path.GetTempFileName();
+
+        try
+        {
+            _configManager.SetConfig("key1", "value1");
</code_context>
<issue_to_address>
**suggestion:** 在 Load/LoadAsync 中只记录异常消息会丢失堆栈信息,从而使排查加载失败的原因更加困难。

在 `Load``LoadAsync` 中,目前的 catch 代码块只记录 `ex.Message`,然后返回 null。请改为记录完整的异常信息(使用合适的 ILogger 重载),例如:

```csharp
catch (Exception ex)
{
    _logger.Error(ex, $"[ResourceManager] Failed to load resource '{path}'");
    return null;
}
```

这样可以保留现有返回 null 的行为,同时完整保留诊断上下文(调用栈、内部异常等)。
</issue_to_address>

Sourcery 对开源项目免费 —— 如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • In ConfigurationManager.SetConfig the use of AddOrUpdate means oldValue is actually the new stored value, so the equality check will almost always succeed and watchers will never be notified; consider fetching the previous value (e.g., via TryGetValue before updating) so you can correctly detect changes and trigger notifications.
  • ConfigWatcherUnRegister is declared under the GFramework.Core.Abstractions.configuration namespace but implemented in the core project; aligning its namespace (or location) with the assembly it lives in would avoid cross-layer confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In ConfigurationManager.SetConfig the use of AddOrUpdate means `oldValue` is actually the new stored value, so the equality check will almost always succeed and watchers will never be notified; consider fetching the previous value (e.g., via TryGetValue before updating) so you can correctly detect changes and trigger notifications.
- ConfigWatcherUnRegister is declared under the GFramework.Core.Abstractions.configuration namespace but implemented in the core project; aligning its namespace (or location) with the assembly it lives in would avoid cross-layer confusion.

## Individual Comments

### Comment 1
<location path="GFramework.Core/configuration/ConfigurationManager.cs" line_range="90-98" />
<code_context>
+    /// <summary>
+    ///     设置指定键的配置值
+    /// </summary>
+    public void SetConfig<T>(string key, T value)
+    {
+        if (string.IsNullOrWhiteSpace(key))
+            throw new ArgumentException(KeyCannotBeNullOrEmptyMessage, nameof(key));
+
+        var oldValue = _configs.AddOrUpdate(key, value!, (_, _) => value!);
+
+        // 触发监听器
+        if (!EqualityComparer<object>.Default.Equals(oldValue, value))
+        {
+            NotifyWatchers(key, value);
</code_context>
<issue_to_address>
**issue (bug_risk):** SetConfig never notifies watchers because `oldValue` is actually the new value returned by AddOrUpdate.

`ConcurrentDictionary.AddOrUpdate` returns the updated value, so `oldValue` will always be the new value and the comparison will never trigger `NotifyWatchers`. You need access to the pre-update value instead. For example, capture the previous value with `TryGetValue` before calling `AddOrUpdate`, or perform a `TryGetValue` after updating and compare that stored old value against the new one before deciding whether to notify watchers.
</issue_to_address>

### Comment 2
<location path="GFramework.Core/resource/ResourceManager.cs" line_range="72-81" />
<code_context>
+    {
+        var tempFile = Path.GetTempFileName();
+
+        try
+        {
+            _configManager.SetConfig("key1", "value1");
</code_context>
<issue_to_address>
**suggestion:** Logging only the exception message in Load/LoadAsync drops stack traces and makes diagnosing load failures harder.

In `Load` and `LoadAsync`, the catch blocks currently log only `ex.Message` and then return null. Please log the full exception instead (using the appropriate ILogger overload), for example:

```csharp
catch (Exception ex)
{
    _logger.Error(ex, $"[ResourceManager] Failed to load resource '{path}'");
    return null;
}
```

This keeps the null-return behavior but preserves full diagnostic context (stack trace, inner exceptions, etc.).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread GFramework.Core/configuration/ConfigurationManager.cs
Comment thread GFramework.Core/resource/ResourceManager.cs Outdated
- 修改 ConfigurationManager 中的 AddOrUpdate 逻辑,先获取旧值再更新以正确检测变更
- 只有在配置值真正发生变化时才触发监听器回调
- 更新异常日志记录方式,移除冗余的标签前缀
- 将 ConfigWatcherUnRegister 移动到正确的命名空间
- 修复 ResourceManager 中的引用计数逻辑,移除重复的 AddReference 调用
- 优化资源加载和卸载时的异常处理和日志记录
- 更新测试注释以反映正确的引用计数行为
@GeWuYou GeWuYou merged commit 54bed12 into main Mar 5, 2026
7 of 8 checks passed
@GeWuYou GeWuYou deleted the feat/configuration-resource-manager branch March 5, 2026 00:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant