Skip to content

feat(godot): 添加资源仓储功能支持#46

Merged
GeWuYou merged 2 commits into
mainfrom
feature/godot-resource-repository
Feb 23, 2026
Merged

feat(godot): 添加资源仓储功能支持#46
GeWuYou merged 2 commits into
mainfrom
feature/godot-resource-repository

Conversation

@GeWuYou

@GeWuYou GeWuYou commented Feb 23, 2026

Copy link
Copy Markdown
Owner
  • 新增 IHasKey 接口定义键值访问契约
  • 新增 IRepository 接口提供通用数据仓储功能
  • 实现 GodotResourceRepository 类支持资源的存储和加载
  • 添加 IResourceRepository 接口扩展通用仓储功能
  • 实现从路径批量加载 Godot 资源的功能
  • 支持递归加载子目录中的资源文件
  • 提供 .tres 和 .res 文件的自动识别和加载

Summary by Sourcery

Introduce a generic repository abstraction and a Godot-specific resource repository supporting keyed access and bulk loading from filesystem paths.

New Features:

  • Add IHasKey interface to represent objects with a key property.
  • Add generic IRepository<TKey, TValue> interface for basic keyed data storage operations.
  • Add IResourceRepository<TKey, TResource> interface extending the generic repository with resource-loading capabilities for Godot resources.
  • Implement GodotResourceRepository<TKey, TResource> to store keyed Godot resources and load them from .tres and .res files with optional recursive directory traversal.

Enhancements:

  • Simplify ECS advanced tests by removing an experimental attribute and unused using directive.

- 新增 IHasKey 接口定义键值访问契约
- 新增 IRepository 接口提供通用数据仓储功能
- 实现 GodotResourceRepository 类支持资源的存储和加载
- 添加 IResourceRepository 接口扩展通用仓储功能
- 实现从路径批量加载 Godot 资源的功能
- 支持递归加载子目录中的资源文件
- 提供 .tres 和 .res 文件的自动识别和加载
@sourcery-ai

sourcery-ai Bot commented Feb 23, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces a generic repository abstraction and Godot-specific resource repository implementation based on keyed resources, including recursive loading from filesystem paths, along with a small cleanup in ECS tests.

Sequence diagram for GodotResourceRepository LoadFromPath recursive resource loading

sequenceDiagram
    actor GameSystem
    participant Repo as GodotResourceRepository
    participant Dir as DirAccess
    participant GD as GD

    GameSystem->>Repo: LoadFromPath(paths, recursive)
    loop for each path in paths
        Repo->>Repo: LoadSinglePath(path, recursive)
        Repo->>Dir: Open(path)
        alt dir is null
            Dir-->>Repo: null
            Repo->>GD: PushWarning("Path not found")
        else dir is valid
            Dir-->>Repo: dir
            Repo->>Dir: ListDirBegin()
            loop entries
                Repo->>Dir: GetNext()
                Dir-->>Repo: entry
                alt entry is empty
                    Repo->>Dir: ListDirEnd()
                    break
                end
                else entry is directory
                    Repo->>Dir: CurrentIsDir()
                    Dir-->>Repo: true
                    alt recursive and not . or ..
                        Repo->>Repo: LoadSinglePath(subPath, true)
                    end
                else entry is file
                    Repo->>Dir: CurrentIsDir()
                    Dir-->>Repo: false
                    alt endsWith .tres or .res
                        Repo->>GD: Load~TResource~(fullPath)
                        GD-->>Repo: resource
                        alt resource is null
                            Repo->>GD: PushWarning("Failed to load resource")
                        else resource valid
                            Repo->>Repo: Add(resource.Key, resource)
                        end
                    else other extension
                        Repo-->>Repo: skip file
                    end
                end
            end
        end
    end
Loading

Class diagram for new repository and Godot resource repository abstractions

classDiagram
    direction LR

    class IHasKey~TKey~ {
        <<interface>>
        +TKey Key
    }

    class IRepository~TKey, TValue~ {
        <<interface>>
        +void Add(TKey key, TValue value)
        +TValue Get(TKey key)
        +bool TryGet(TKey key, out TValue value)
        +IReadOnlyCollection~TValue~ GetAll()
        +bool Contains(TKey key)
        +void Remove(TKey key)
        +void Clear()
    }

    class IResourceRepository~TKey, TResource~ {
        <<interface>>
        +void LoadFromPath(IEnumerable~string~ paths, bool recursive)
        +void LoadFromPath(bool recursive, string[] paths)
    }

    class GodotResourceRepository~TKey, TResource~ {
        -Dictionary~TKey, TResource~ _storage
        +void Add(TKey key, TResource value)
        +TResource Get(TKey key)
        +bool TryGet(TKey key, out TResource value)
        +IReadOnlyCollection~TResource~ GetAll()
        +bool Contains(TKey key)
        +void Remove(TKey key)
        +void Clear()
        +void LoadFromPath(IEnumerable~string~ paths, bool recursive)
        +void LoadFromPath(bool recursive, string[] paths)
        -void LoadSinglePath(string path, bool recursive)
    }

    class Resource {
    }

    IResourceRepository~TKey, TResource~ --|> IRepository~TKey, TResource~
    GodotResourceRepository~TKey, TResource~ --|> IResourceRepository~TKey, TResource~
    TResource --|> Resource
    TResource ..|> IHasKey~TKey~
Loading

File-Level Changes

Change Details Files
Introduce a generic repository contract for keyed data access.
  • Add IRepository<TKey,TValue> interface defining basic CRUD-like operations over keyed collections, including Add, Get, TryGet, GetAll, Contains, Remove, and Clear.
GFramework.Game.Abstractions/data/IRepository.cs
Define a key-bearing contract to unify how resources expose their keys.
  • Add IHasKey interface with a Key getter to be implemented by types that can be used as keyed entities/resources.
GFramework.Core.Abstractions/bases/IHasKey.cs
Add a Godot-specific resource repository abstraction that extends the generic repository with path-based loading.
  • Create IResourceRepository<TKey,TResource> extending IRepository<TKey,TResource> for Godot.Resource types.
  • Define LoadFromPath overloads to load resources from path collections or params string arrays, with optional recursive directory traversal.
GFramework.Godot/data/IResourceRepository.cs
Implement a concrete Godot resource repository with directory traversal and automatic .tres/.res loading.
  • Implement GodotResourceRepository<TKey,TResource> backed by an internal Dictionary for keyed storage, enforcing non-null TKey and TResource : Resource, IHasKey.
  • Provide repository methods (Add/Get/TryGet/GetAll/Contains/Remove/Clear) with exception behavior for duplicate/missing keys.
  • Implement LoadFromPath overloads delegating to a private LoadSinglePath that uses Godot.DirAccess to traverse directories, optionally recursively, filtering .tres/.res files and loading them via GD.Load.
  • On successful load, insert resources into the dictionary using resource.Key; on failure or missing path, log warnings via GD.PushWarning.
GFramework.Godot/data/GodotResourceRepository.cs
Minor cleanup in ECS test attributes and usings.
  • Remove unused System.Diagnostics.CodeAnalysis using.
  • Drop the [Experimental("GFrameworkECS")] attribute from EcsAdvancedTests test fixture.
GFramework.Core.Tests/ecs/EcsAdvancedTests.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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The GetAll() methods in IRepository/IResourceRepository and GodotResourceRepository currently return IReadOnlyCollection<T> but _storage.Values is a Dictionary<TKey,TValue>.ValueCollection which does not implement IReadOnlyCollection<T>; consider either changing the return type to ICollection<T> or materializing a collection (e.g. ToArray() or ToList()) to match the signature.
  • The two LoadFromPath overloads use different parameter orders (IEnumerable<string> paths, bool recursive = false vs bool recursive = false, params string[] paths), which can be confusing at call sites; consider aligning their parameter order (e.g. always paths, recursive) or removing the default on recursive for the params overload for clearer usage.
  • In LoadSinglePath, Add(resource.Key, resource) will throw on duplicate keys and abort the whole loading process; if duplicate keys are expected or possible in content, consider handling this more gracefully (e.g. skip and log a warning, or allow overwrite) instead of throwing.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `GetAll()` methods in `IRepository`/`IResourceRepository` and `GodotResourceRepository` currently return `IReadOnlyCollection<T>` but `_storage.Values` is a `Dictionary<TKey,TValue>.ValueCollection` which does not implement `IReadOnlyCollection<T>`; consider either changing the return type to `ICollection<T>` or materializing a collection (e.g. `ToArray()` or `ToList()`) to match the signature.
- The two `LoadFromPath` overloads use different parameter orders (`IEnumerable<string> paths, bool recursive = false` vs `bool recursive = false, params string[] paths`), which can be confusing at call sites; consider aligning their parameter order (e.g. always `paths, recursive`) or removing the default on `recursive` for the params overload for clearer usage.
- In `LoadSinglePath`, `Add(resource.Key, resource)` will throw on duplicate keys and abort the whole loading process; if duplicate keys are expected or possible in content, consider handling this more gracefully (e.g. skip and log a warning, or allow overwrite) instead of throwing.

## Individual Comments

### Comment 1
<location> `GFramework.Godot/data/GodotResourceRepository.cs:121-123` </location>
<code_context>
+    /// </summary>
+    /// <param name="recursive">是否递归加载子目录中的资源</param>
+    /// <param name="paths">资源文件路径的参数数组</param>
+    public void LoadFromPath(bool recursive = false, params string[] paths)
+    {
+        LoadFromPath(paths, recursive);
+    }
+
</code_context>

<issue_to_address>
**suggestion:** The overload with a defaulted `recursive` parameter may be confusing at call sites and could be simplified.

Because `recursive` precedes the `params` array, callers can’t actually omit it positionally despite the default value; they must pass a `bool` or use a named argument. This can make call sites harder to read. Consider either requiring `recursive` to be passed explicitly on this overload, or splitting into clearer methods (e.g. `LoadFromPath(params string[] paths)` and `LoadFromPathRecursive(params string[] paths)`) so the API intent is unambiguous.

Suggested implementation:

```csharp
    /// <summary>
    /// 从指定路径数组加载资源到仓储中(非递归)。
    /// </summary>
    /// <param name="paths">资源文件路径的参数数组</param>
    public void LoadFromPath(params string[] paths)
    {
        LoadFromPath(paths, recursive: false);
    }

    /// <summary>
    /// 从指定路径数组递归加载资源到仓储中。
    /// </summary>
    /// <param name="paths">资源文件路径的参数数组</param>
    public void LoadFromPathRecursive(params string[] paths)
    {
        LoadFromPath(paths, recursive: true);
    }


```

1. Ensure there is an existing overload `LoadFromPath(IEnumerable<string> paths, bool recursive)` or `LoadFromPath(string[] paths, bool recursive)` that the new methods can delegate to. If its signature differs, adjust the delegation calls accordingly.
2. Update any existing call sites that relied on the removed `LoadFromPath(bool recursive = false, params string[] paths)` overload:
   - Non-recursive calls should now use `LoadFromPath("path1", "path2", ...)`.
   - Recursive calls should now use `LoadFromPathRecursive("path1", "path2", ...)`.
</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.Godot/data/GodotResourceRepository.cs Outdated
- 添加ILogger用于日志记录替换GD.PushWarning
- 修改GetAll方法返回ToArray()副本而非直接Values引用
- 分离路径加载方法为非递归和递归两个独立接口
- 新增LoadFromPath和LoadFromPathRecursive的重载方法
- 提取内部处理逻辑到ProcessEntry私有方法
- 优化目录遍历逻辑并改进错误处理机制
- 添加重复键检测和资源加载失败的日志记录
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