From 158f98a4654d13dfe088fe4a115cb9c0364b14be Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 11 May 2026 10:33:52 +0800
Subject: [PATCH] =?UTF-8?q?fix(input):=20=E4=BF=AE=E5=A4=8D=E8=BE=93?=
=?UTF-8?q?=E5=85=A5=E7=BB=91=E5=AE=9A=E9=87=8D=E7=BD=AE=E4=B8=8E=E5=AE=A1?=
=?UTF-8?q?=E6=9F=A5=E9=81=97=E7=95=99=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 InputBindingStore 的线程安全使用说明并消除 GodotInputBindingCodec 的重复键码计算\n- 修复 GodotInputMapBackend 在全量重置时未移除运行时新增动作的语义偏差并补回归测试\n- 更新 input-system-godot-integration 的 tracking 与 trace,补录 PR review follow-up 验证结果
---
GFramework.Game/Input/InputBindingStore.cs | 2 +
.../Input/GodotInputBindingStoreTests.cs | 59 +++++++++++++++++++
.../Input/GodotInputBindingCodec.cs | 5 +-
.../Input/GodotInputMapBackend.cs | 4 +-
...input-system-godot-integration-tracking.md | 14 +++--
.../input-system-godot-integration-trace.md | 21 +++++++
6 files changed, 97 insertions(+), 8 deletions(-)
diff --git a/GFramework.Game/Input/InputBindingStore.cs b/GFramework.Game/Input/InputBindingStore.cs
index 8cad92a2..a75929d6 100644
--- a/GFramework.Game/Input/InputBindingStore.cs
+++ b/GFramework.Game/Input/InputBindingStore.cs
@@ -11,6 +11,8 @@ namespace GFramework.Game.Input;
///
/// 该实现聚焦于框架级动作绑定管理语义:默认值恢复、主绑定替换、冲突交换与快照导入导出。
/// 它不依赖具体宿主输入事件,适合作为 `Game` 层默认运行时与单元测试基线。
+/// 该类型内部使用普通 `Dictionary` / `List` 保存可变状态,不提供额外同步原语。
+/// 宿主应在同一输入线程或受控的串行配置阶段访问它;如果存在跨线程读写需求,应由外层协调同步。
///
public sealed class InputBindingStore : IInputBindingStore
{
diff --git a/GFramework.Godot.Tests/Input/GodotInputBindingStoreTests.cs b/GFramework.Godot.Tests/Input/GodotInputBindingStoreTests.cs
index 731d112c..0f992040 100644
--- a/GFramework.Godot.Tests/Input/GodotInputBindingStoreTests.cs
+++ b/GFramework.Godot.Tests/Input/GodotInputBindingStoreTests.cs
@@ -190,6 +190,65 @@ public sealed class GodotInputBindingStoreTests
});
}
+ ///
+ /// 验证重置全部绑定时,会移除运行时新增且默认快照中不存在的动作。
+ ///
+ [Test]
+ public void ResetAll_WhenRuntimeActionIsNotInDefaults_Should_RemoveAction()
+ {
+ var backend = new FakeInputMapBackend(
+ new InputBindingSnapshot(
+ [
+ new InputActionBinding(
+ "ui_accept",
+ [
+ new InputBindingDescriptor(
+ InputDeviceKind.KeyboardMouse,
+ InputBindingKind.Key,
+ "key:13",
+ "Enter")
+ ])
+ ]));
+
+ var store = new GodotInputBindingStore(backend);
+ store.ImportSnapshot(
+ new InputBindingSnapshot(
+ [
+ new InputActionBinding(
+ "ui_accept",
+ [
+ new InputBindingDescriptor(
+ InputDeviceKind.KeyboardMouse,
+ InputBindingKind.Key,
+ "key:13",
+ "Enter")
+ ]),
+ new InputActionBinding(
+ "debug_toggle",
+ [
+ new InputBindingDescriptor(
+ InputDeviceKind.KeyboardMouse,
+ InputBindingKind.Key,
+ "key:192",
+ "QuoteLeft")
+ ])
+ ]));
+
+ store.ResetAll();
+
+ var snapshot = store.ExportSnapshot();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(
+ snapshot.Actions.Any(action => string.Equals(action.ActionName, "ui_accept", StringComparison.Ordinal)),
+ Is.True);
+ Assert.That(
+ snapshot.Actions.Any(action => string.Equals(action.ActionName, "debug_toggle", StringComparison.Ordinal)),
+ Is.False);
+ });
+ }
+
///
/// 测试用的纯托管 InputMap 后端。
///
diff --git a/GFramework.Godot/Input/GodotInputBindingCodec.cs b/GFramework.Godot/Input/GodotInputBindingCodec.cs
index 6932072f..15432dd7 100644
--- a/GFramework.Godot/Input/GodotInputBindingCodec.cs
+++ b/GFramework.Godot/Input/GodotInputBindingCodec.cs
@@ -24,11 +24,12 @@ internal static class GodotInputBindingCodec
switch (inputEvent)
{
case InputEventKey keyEvent:
+ var keyCode = GetKeyCode(keyEvent);
binding = new InputBindingDescriptor(
InputDeviceKind.KeyboardMouse,
InputBindingKind.Key,
- FormattableString.Invariant($"key:{(int)GetKeyCode(keyEvent)}"),
- GetKeyCode(keyEvent).ToString());
+ FormattableString.Invariant($"key:{(int)keyCode}"),
+ keyCode.ToString());
return true;
case InputEventMouseButton mouseButtonEvent:
binding = new InputBindingDescriptor(
diff --git a/GFramework.Godot/Input/GodotInputMapBackend.cs b/GFramework.Godot/Input/GodotInputMapBackend.cs
index 49c7c84c..89bcb8e7 100644
--- a/GFramework.Godot/Input/GodotInputMapBackend.cs
+++ b/GFramework.Godot/Input/GodotInputMapBackend.cs
@@ -82,7 +82,9 @@ internal sealed class GodotInputMapBackend : IGodotInputMapBackend
if (InputMap.HasAction(actionName))
{
- InputMap.ActionEraseEvents(actionName);
+ // Actions absent from the captured default snapshot should disappear after reset
+ // so the live InputMap matches the original project defaults exactly.
+ InputMap.EraseAction(actionName);
}
}
diff --git a/ai-plan/public/input-system-godot-integration/todos/input-system-godot-integration-tracking.md b/ai-plan/public/input-system-godot-integration/todos/input-system-godot-integration-tracking.md
index a69cc020..d39cefbd 100644
--- a/ai-plan/public/input-system-godot-integration/todos/input-system-godot-integration-tracking.md
+++ b/ai-plan/public/input-system-godot-integration/todos/input-system-godot-integration-tracking.md
@@ -22,6 +22,7 @@ Godot `InputMap` 适配,优先服务 UI 语义动作桥接和绑定重映射
- `GodotInputBindingStore` 当前把 `InputMap` 默认绑定和主绑定替换接到框架抽象,允许导出 / 导入 `InputBindingSnapshot`
- `InputBindingStore.GetBindings(...)` 已改为纯读取语义,不再因查询缺失动作而把空条目带进导出快照
- `GodotInputBindingStore.ImportSnapshot(...)` 已改为快照级覆盖语义,会清空快照中未出现动作的后端绑定
+- `GodotInputMapBackend.ResetAction(...)` / `ResetAll()` 已对齐默认快照替换语义,运行时新增动作在全量重置后不会残留在 `InputMap`
- `project.godot -> InputActions` 生成器链路保持不变,新的输入系统直接复用动作名常量,而不是替代它
## 当前风险
@@ -44,11 +45,14 @@ Godot `InputMap` 适配,优先服务 UI 语义动作桥接和绑定重映射
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~GodotInputBindingStoreTests" -m:1 -p:RestoreFallbackFolders= -nodeReuse:false`
- 结果:通过
- `python3 scripts/license-header.py --check --paths GFramework.Game.Abstractions/README.md GFramework.Game.Tests/Input/InputBindingStoreTests.cs GFramework.Game/Input/InputBindingStore.cs GFramework.Game/Input/InputDeviceTracker.cs GFramework.Game/Input/UiInputDispatcher.cs GFramework.Game/README.md GFramework.Godot.Tests/Input/GodotInputBindingStoreTests.cs GFramework.Godot/Input/GodotInputBindingStore.cs GFramework.Godot/Input/GodotInputMapBackend.cs GFramework.Godot/Input/IGodotInputMapBackend.cs ai-plan/public/input-system-godot-integration/traces/input-system-godot-integration-trace.md`
- - 结果:待本轮验证补录
+ - 结果:通过(All supported files include an Apache-2.0 license header.)
+- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release -m:1 -nodeReuse:false`
+ - 结果:通过(0 warning, 0 error)
+- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release -m:1 -nodeReuse:false`
+ - 结果:通过(0 warning, 0 error)
## 下一步
-1. 若继续扩展输入系统,优先补更多逻辑动作与 gameplay 输入场景,而不是先扩面到品牌图标、震动预设或平台文案
-2. 若要增强 Godot 宿主覆盖,优先补真实 `InputMap` / `InputEvent` 集成测试宿主,而不是把更多原生对象直接放进普通 `dotnet test`
-3. 若要开放给消费者使用,继续完善 `README.md`、模块 README 与教程中的采用路径示例
-4. 若继续处理 PR review,可再评估值对象改成 `record` 的收益与兼容性,而不是把该风格建议与行为修复混在同一波提交
+1. 若继续处理 PR review,可再单独评估值对象切换到 `record` 是否值得进入同一个 PR
+2. 若继续扩展输入系统,优先补更多逻辑动作与 gameplay 输入场景,而不是先扩面到品牌图标、震动预设或平台文案
+3. 若要增强 Godot 宿主覆盖,优先补真实 `InputMap` / `InputEvent` 集成测试宿主,而不是把更多原生对象直接放进普通 `dotnet test`
diff --git a/ai-plan/public/input-system-godot-integration/traces/input-system-godot-integration-trace.md b/ai-plan/public/input-system-godot-integration/traces/input-system-godot-integration-trace.md
index 58d5b82b..16fb9bf8 100644
--- a/ai-plan/public/input-system-godot-integration/traces/input-system-godot-integration-trace.md
+++ b/ai-plan/public/input-system-godot-integration/traces/input-system-godot-integration-trace.md
@@ -68,6 +68,27 @@
- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~InputBindingStoreTests|FullyQualifiedName~UiInputDispatcherTests"` 通过
- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~GodotInputBindingStoreTests"` 通过
+### 阶段:PR #346 review 二次 follow-up(RP-001)
+
+- 再次抓取当前分支 PR `#346` 的 latest-head review threads,区分已在本地修复但 GitHub 线程仍未折叠的问题与仍然有效的问题
+- 确认以下 review 点已在本地代码中成立并继续处理:
+ - `InputBindingStore` 缺少共享可变状态的线程安全使用约束说明
+ - `GodotInputBindingCodec.TryCreateBinding(...)` 在键盘事件分支重复计算 `GetKeyCode(...)`
+ - `GodotInputMapBackend.ResetAll()` 对运行时新增动作只清空事件、不移除动作本身,和默认快照替换语义不一致
+- 新增回归测试:
+ - `GodotInputBindingStoreTests.ResetAll_WhenRuntimeActionIsNotInDefaults_Should_RemoveAction`
+- 验证结果:
+ - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~GodotInputBindingStoreTests"` 通过(5/5)
+ - `python3 scripts/license-header.py --check --paths GFramework.Game.Abstractions/README.md GFramework.Game.Tests/Input/InputBindingStoreTests.cs GFramework.Game/Input/InputBindingStore.cs GFramework.Game/Input/InputDeviceTracker.cs GFramework.Game/Input/UiInputDispatcher.cs GFramework.Game/README.md GFramework.Godot.Tests/Input/GodotInputBindingStoreTests.cs GFramework.Godot/Input/GodotInputBindingStore.cs GFramework.Godot/Input/GodotInputMapBackend.cs GFramework.Godot/Input/IGodotInputMapBackend.cs ai-plan/public/input-system-godot-integration/traces/input-system-godot-integration-trace.md` 通过
+ - `dotnet build GFramework.Game/GFramework.Game.csproj -c Release -m:1 -nodeReuse:false` 通过(0 warning, 0 error)
+ - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release -m:1 -nodeReuse:false` 通过(0 warning, 0 error)
+ - `git ... diff --check` 通过
+
+### 下一步
+
+1. 如需继续消化 open review threads,可再评估值对象切换到 `record` 的收益与兼容性
+2. 若需要更高置信度的宿主验证,再补真实 Godot `InputMap` 集成测试宿主
+
### 下一步
1. 运行针对本次改动文件的 license-header 检查并补录结果