Skip to content

update:为应对新版ts,改为延迟分批清扫 - #1176

Closed
AX-17 wants to merge 3 commits into
UnrealMultiple:masterfrom
AX-17:master
Closed

update:为应对新版ts,改为延迟分批清扫#1176
AX-17 wants to merge 3 commits into
UnrealMultiple:masterfrom
AX-17:master

Conversation

@AX-17

@AX-17 AX-17 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

update:为应对新版ts,改为延迟分批清扫

Summary by Sourcery

引入一个全新的 AutoClear 插件,以渐进节奏、主线程安全的方式清理世界物品,并与 TShock 及可选的 Starver 保护进行集成。

新功能:

  • 添加 AutoClearService,用于将世界物品加入队列并批量移除,从而安全地进行手动和自动清理。
  • 添加 AutoClearPlugin,将 TShock 命令、钩子以及服务器更新循环接入新的清理服务。
  • 添加 AutoClearScheduler,用于检测世界物品堆积情况,并在满足可配置条件和消息提示的前提下,触发延时的自动清扫。
  • 添加 AutoClearConfiguration 系统,支持 JSON 持久化和旧版配置导入,用于控制清扫行为、阈值以及排除物品列表。
  • 添加物品命令解析和分类规则,以支持基于半径的清理以及按类别划分的自动清扫。

改进:

  • 使用精简的、以配置为驱动的设计替换旧的 AutoClear 实现及本地化资源,重点支持延时、批量清理,并与较新的 TShock/TS 版本保持兼容。
Original summary in English

Summary by Sourcery

Introduce a new AutoClear plugin that performs paced, main-thread-safe world item cleanup and integrates with TShock and optional Starver protection.

New Features:

  • Add AutoClearService to queue and batch-despawn world items safely for manual and automatic cleanup.
  • Add AutoClearPlugin that wires TShock commands, hooks, and server update loop into the new cleanup service.
  • Add AutoClearScheduler to detect world item accumulation and trigger delayed automatic sweeps with configurable conditions and messaging.
  • Add AutoClearConfiguration system with JSON persistence and legacy config import to control sweep behavior, thresholds, and excluded items.
  • Add item command parsing and classification rules to support radius-based clearing and category-specific automatic sweeps.

Enhancements:

  • Replace the previous AutoClear implementation and localization assets with a streamlined, configuration-driven design focused on delayed, batched cleanup compatible with newer TShock/TS versions.

@AX-17
AX-17 requested a review from a team as a code owner July 15, 2026 19:00

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

  • AutoClearService.ValidateStartState 中的主线程检测依赖于在 Update() 中初始化的 mainThreadId,因此在主线程第一次执行 Update() 之前发生的任何 TryStart/TryStartAutomatic 调用都会错误地返回 MainThreadRequired;建议尽早初始化 mainThreadId(例如在插件的 Initialize 或第一次 TryStart 中),或者把 -1 视为未初始化,并将第一个调用者视为主线程。
  • Starver 集成(HasStarverCleanupProtectionIsStarverCleanupRunning)在每次调用时都会对所有插件做基于反射的扫描;如果在大型插件集上这条路径变成热点,建议在发现协调器 Type 或插件容器之后对其进行缓存,以避免重复的反射和枚举。
给 AI 代理的提示
Please address the comments from this code review:

## Overall Comments
- `AutoClearService.ValidateStartState` 中的主线程检测依赖于在 `Update()` 中初始化的 `mainThreadId`,因此在主线程第一次执行 `Update()` 之前发生的任何 `TryStart`/`TryStartAutomatic` 调用都会错误地返回 `MainThreadRequired`;建议尽早初始化 `mainThreadId`(例如在插件的 `Initialize` 或第一次 `TryStart` 中),或者把 `-1` 视为未初始化,并将第一个调用者视为主线程。
- Starver 集成(`HasStarverCleanupProtection``IsStarverCleanupRunning`)在每次调用时都会对所有插件做基于反射的扫描;如果在大型插件集上这条路径变成热点,建议在发现协调器 `Type` 或插件容器之后对其进行缓存,以避免重复的反射和枚举。

## Individual Comments

### Comment 1
<location path="src/AutoClear/AutoClearPlugin.cs" line_range="107-116" />
<code_context>
+                && string.Equals(command.CommandDelegate.Method.Name, "Clear", StringComparison.Ordinal));
+        }
+
+        private void OnPrePlayerCommand(PrePlayerCommandEventArgs args)
+        {
+            if (HasStarverCleanupProtection()
+                || !ReferenceEquals(args.Command, tshockClearCommand)
+                || !AutoClearCommandRules.TryParseItemClearParameters(
+                    args.Arguments.Parameters,
+                    out int radiusTiles))
+            {
+                return;
+            }
+
+            args.Handled = true;
+            StartCleanup(args.Arguments.Player, radiusTiles, args.Arguments.Silent);
+        }
+
</code_context>
<issue_to_address>
**suggestion (performance):** 可以对每次命令执行时重复进行的基于反射的 Starver 检测进行缓存。

`OnPrePlayerCommand``OnSafeClearItems` 都会调用 `HasStarverCleanupProtection()`,每次都会遍历 `ServerApi.Plugins` 并使用反射。相反,可以依赖已有的 `starverProtectionActive` 字段,只在重载/插件初始化(或插件发生变化)时重新计算,这样在命令处理的热点路径上就能避免重复的反射。

建议实现如下:

```csharp
        private void OnPrePlayerCommand(PrePlayerCommandEventArgs args)
        {
            if (starverProtectionActive
                || !ReferenceEquals(args.Command, tshockClearCommand)
                || !AutoClearCommandRules.TryParseItemClearParameters(
                    args.Arguments.Parameters,
                    out int radiusTiles))
            {
                return;
            }

            args.Handled = true;
            StartCleanup(args.Arguments.Player, radiusTiles, args.Arguments.Silent);
        }

```

1. 更新其它热点调用方(例如 `OnSafeClearItems`),改为使用缓存标志而不是反射:
   - 在条件中将 `HasStarverCleanupProtection()` 替换为 `starverProtectionActive`2.`HasStarverCleanupProtection()` 改造成*重新计算*例程,用来设置 `starverProtectionActive`(如果仍然需要返回值,可以在设置之后返回该值),而不是在每次命令时遍历 `ServerApi.Plugins`- 将现有的反射逻辑抽取到一个类似 `RecalculateStarverCleanupProtection()` 的方法中,由它来设置 `starverProtectionActive`- 在插件初始化(例如 `Initialize`/构造函数)、配置重载以及你可能拥有的插件加载/卸载事件钩子中调用该方法。
3. 如果 `starverProtectionActive` 可能在非主线程中被更新,请确保将其声明为 `private volatile bool`(或以其他方式安全发布),以便命令处理程序读取到一致的值。
</issue_to_address>

### Comment 2
<location path="src/AutoClear/AutoClearConfiguration.cs" line_range="67-76" />
<code_context>
+        internal static AutoClearConfiguration Load()
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 始终通过 `JObject``FromLegacyObject` 加载会丢失未知或未来的配置字段。

由于 `Load` 总是将 `AutoClear.json` 解析为 `JObject`,然后使用 `FromLegacyObject`,任何未被 `FromLegacyObject` 明确处理的字段(包括未来新增的配置属性)都会丢失。为避免静默数据丢失并保持配置的前向兼容性,建议优先使用 `JsonConvert.DeserializeObject<AutoClearConfiguration>`,只有在反序列化失败或检测到旧版格式时,才回退到 `JObject` + `FromLegacyObject`。

建议实现如下:

```csharp
        internal static AutoClearConfiguration Load()
        {
            try
            {
                AutoClearConfiguration configuration;
                if (File.Exists(ConfigPath))
                {
                    var json = File.ReadAllText(ConfigPath, Encoding.UTF8);
                    try
                    {
                        // Prefer direct deserialization to preserve unknown/future fields
                        configuration = JsonConvert.DeserializeObject<AutoClearConfiguration>(json)
                                        ?? new AutoClearConfiguration();
                    }
                    catch (JsonException)
                    {
                        // Fallback: legacy format, parse as JObject and convert
                        JObject root = JObject.Parse(json);
                        configuration = FromLegacyObject(root);
                    }
                }
                else if (!TryImportLegacy(out configuration))

```

如果 `JsonConvert` 或 `JsonException` 尚未在作用域内,请确保在 `AutoClearConfiguration.cs` 文件顶部添加:
1. `using Newtonsoft.Json;`
`JObject` 已经在使用,因此 `using Newtonsoft.Json.Linq;` 应该已经存在。
</issue_to_address>

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

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

  • The main-thread detection in AutoClearService.ValidateStartState relies on mainThreadId being initialized in Update(), so any TryStart/TryStartAutomatic calls that occur before the first Update() on the main thread will incorrectly return MainThreadRequired; consider initializing mainThreadId eagerly (e.g., in plugin Initialize or first TryStart) or falling back to treating -1 as uninitialized and accepting the first caller as the main thread.
  • The Starver integration (HasStarverCleanupProtection and IsStarverCleanupRunning) performs reflection-based scans over all plugins on each call; if this becomes a hot path on large plugin sets, consider caching the coordinator Type or the plugin container once discovered to avoid repeated reflection and enumeration.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The main-thread detection in `AutoClearService.ValidateStartState` relies on `mainThreadId` being initialized in `Update()`, so any `TryStart`/`TryStartAutomatic` calls that occur before the first `Update()` on the main thread will incorrectly return `MainThreadRequired`; consider initializing `mainThreadId` eagerly (e.g., in plugin `Initialize` or first `TryStart`) or falling back to treating `-1` as uninitialized and accepting the first caller as the main thread.
- The Starver integration (`HasStarverCleanupProtection` and `IsStarverCleanupRunning`) performs reflection-based scans over all plugins on each call; if this becomes a hot path on large plugin sets, consider caching the coordinator `Type` or the plugin container once discovered to avoid repeated reflection and enumeration.

## Individual Comments

### Comment 1
<location path="src/AutoClear/AutoClearPlugin.cs" line_range="107-116" />
<code_context>
+                && string.Equals(command.CommandDelegate.Method.Name, "Clear", StringComparison.Ordinal));
+        }
+
+        private void OnPrePlayerCommand(PrePlayerCommandEventArgs args)
+        {
+            if (HasStarverCleanupProtection()
+                || !ReferenceEquals(args.Command, tshockClearCommand)
+                || !AutoClearCommandRules.TryParseItemClearParameters(
+                    args.Arguments.Parameters,
+                    out int radiusTiles))
+            {
+                return;
+            }
+
+            args.Handled = true;
+            StartCleanup(args.Arguments.Player, radiusTiles, args.Arguments.Silent);
+        }
+
</code_context>
<issue_to_address>
**suggestion (performance):** Repeated reflection-based Starver detection on every command could be cached.

`OnPrePlayerCommand` and `OnSafeClearItems` both call `HasStarverCleanupProtection()`, which walks `ServerApi.Plugins` and uses reflection each time. Instead, rely on the existing `starverProtectionActive` field and only recompute it on reload/plugin init (or when plugins change), so command handling avoids repeated reflection on this hot path.

Suggested implementation:

```csharp
        private void OnPrePlayerCommand(PrePlayerCommandEventArgs args)
        {
            if (starverProtectionActive
                || !ReferenceEquals(args.Command, tshockClearCommand)
                || !AutoClearCommandRules.TryParseItemClearParameters(
                    args.Arguments.Parameters,
                    out int radiusTiles))
            {
                return;
            }

            args.Handled = true;
            StartCleanup(args.Arguments.Player, radiusTiles, args.Arguments.Silent);
        }

```

1. Update any other hot-path callers (e.g. `OnSafeClearItems`) to use the cached flag instead of reflection:
   - Replace `HasStarverCleanupProtection()` with `starverProtectionActive` in their conditionals.
2. Change `HasStarverCleanupProtection()` to become the *recalculation* routine that sets `starverProtectionActive` (and returns it if you still need a return value), rather than walking `ServerApi.Plugins` on every command:
   - Extract the existing reflection logic into a method like `RecalculateStarverCleanupProtection()` that sets `starverProtectionActive`.
   - Call this method from plugin initialization (e.g. `Initialize`/constructor), configuration reload, and any hooks you may have for plugin load/unload events.
3. Ensure `starverProtectionActive` is a `private volatile bool` (or otherwise safely published) if it can be updated from non-main threads, so command handlers read a consistent value.
</issue_to_address>

### Comment 2
<location path="src/AutoClear/AutoClearConfiguration.cs" line_range="67-76" />
<code_context>
+        internal static AutoClearConfiguration Load()
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Loading always via `JObject` and `FromLegacyObject` can drop unknown or future config fields.

Because `Load` always parses `AutoClear.json` into a `JObject` and then uses `FromLegacyObject`, any fields not explicitly handled by `FromLegacyObject` (including future config properties) are lost. To avoid silent data loss and keep configs forward‑compatible, prefer `JsonConvert.DeserializeObject<AutoClearConfiguration>` first, and only fall back to `JObject` + `FromLegacyObject` when deserialization fails or you detect a legacy format.

Suggested implementation:

```csharp
        internal static AutoClearConfiguration Load()
        {
            try
            {
                AutoClearConfiguration configuration;
                if (File.Exists(ConfigPath))
                {
                    var json = File.ReadAllText(ConfigPath, Encoding.UTF8);
                    try
                    {
                        // Prefer direct deserialization to preserve unknown/future fields
                        configuration = JsonConvert.DeserializeObject<AutoClearConfiguration>(json)
                                        ?? new AutoClearConfiguration();
                    }
                    catch (JsonException)
                    {
                        // Fallback: legacy format, parse as JObject and convert
                        JObject root = JObject.Parse(json);
                        configuration = FromLegacyObject(root);
                    }
                }
                else if (!TryImportLegacy(out configuration))

```

If `JsonConvert` or `JsonException` are not yet in scope, ensure the file has:
1. `using Newtonsoft.Json;` at the top of `AutoClearConfiguration.cs`.
`JObject` is already used, so `using Newtonsoft.Json.Linq;` should already be present.
</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 on lines +107 to +116
private void OnPrePlayerCommand(PrePlayerCommandEventArgs args)
{
if (HasStarverCleanupProtection()
|| !ReferenceEquals(args.Command, tshockClearCommand)
|| !AutoClearCommandRules.TryParseItemClearParameters(
args.Arguments.Parameters,
out int radiusTiles))
{
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (performance): 可以对每次命令执行时重复进行的基于反射的 Starver 检测进行缓存。

OnPrePlayerCommandOnSafeClearItems 都会调用 HasStarverCleanupProtection(),每次都会遍历 ServerApi.Plugins 并使用反射。相反,可以依赖已有的 starverProtectionActive 字段,只在重载/插件初始化(或插件发生变化)时重新计算,这样在命令处理的热点路径上就能避免重复的反射。

建议实现如下:

        private void OnPrePlayerCommand(PrePlayerCommandEventArgs args)
        {
            if (starverProtectionActive
                || !ReferenceEquals(args.Command, tshockClearCommand)
                || !AutoClearCommandRules.TryParseItemClearParameters(
                    args.Arguments.Parameters,
                    out int radiusTiles))
            {
                return;
            }

            args.Handled = true;
            StartCleanup(args.Arguments.Player, radiusTiles, args.Arguments.Silent);
        }
  1. 更新其它热点调用方(例如 OnSafeClearItems),改为使用缓存标志而不是反射:
    • 在条件中将 HasStarverCleanupProtection() 替换为 starverProtectionActive
  2. HasStarverCleanupProtection() 改造成重新计算例程,用来设置 starverProtectionActive(如果仍然需要返回值,可以在设置之后返回该值),而不是在每次命令时遍历 ServerApi.Plugins
    • 将现有的反射逻辑抽取到一个类似 RecalculateStarverCleanupProtection() 的方法中,由它来设置 starverProtectionActive
    • 在插件初始化(例如 Initialize/构造函数)、配置重载以及你可能拥有的插件加载/卸载事件钩子中调用该方法。
  3. 如果 starverProtectionActive 可能在非主线程中被更新,请确保将其声明为 private volatile bool(或以其他方式安全发布),以便命令处理程序读取到一致的值。
Original comment in English

suggestion (performance): Repeated reflection-based Starver detection on every command could be cached.

OnPrePlayerCommand and OnSafeClearItems both call HasStarverCleanupProtection(), which walks ServerApi.Plugins and uses reflection each time. Instead, rely on the existing starverProtectionActive field and only recompute it on reload/plugin init (or when plugins change), so command handling avoids repeated reflection on this hot path.

Suggested implementation:

        private void OnPrePlayerCommand(PrePlayerCommandEventArgs args)
        {
            if (starverProtectionActive
                || !ReferenceEquals(args.Command, tshockClearCommand)
                || !AutoClearCommandRules.TryParseItemClearParameters(
                    args.Arguments.Parameters,
                    out int radiusTiles))
            {
                return;
            }

            args.Handled = true;
            StartCleanup(args.Arguments.Player, radiusTiles, args.Arguments.Silent);
        }
  1. Update any other hot-path callers (e.g. OnSafeClearItems) to use the cached flag instead of reflection:
    • Replace HasStarverCleanupProtection() with starverProtectionActive in their conditionals.
  2. Change HasStarverCleanupProtection() to become the recalculation routine that sets starverProtectionActive (and returns it if you still need a return value), rather than walking ServerApi.Plugins on every command:
    • Extract the existing reflection logic into a method like RecalculateStarverCleanupProtection() that sets starverProtectionActive.
    • Call this method from plugin initialization (e.g. Initialize/constructor), configuration reload, and any hooks you may have for plugin load/unload events.
  3. Ensure starverProtectionActive is a private volatile bool (or otherwise safely published) if it can be updated from non-main threads, so command handlers read a consistent value.

Comment thread src/AutoClear/AutoClearConfiguration.cs
@Controllerdestiny

Controllerdestiny commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

纯ai改的改完还没有检查,多个相关文件被删除,i18n相关全部被移除。

@AX-17 AX-17 closed this by deleting the head repository Jul 22, 2026
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.

2 participants