feat: Display error frame when specific hardware is not available.#51
feat: Display error frame when specific hardware is not available.#51clansty merged 7 commits intoMuNET-OSS:mainfrom
Conversation
…Daemon Error missing issue
审阅者指南此 PR 引入了一个新的 HardwareAlert 模组和一个 ErrorFrame 辅助工具,用于在街机风格的错误窗口中拦截和显示自定义启动错误。HardwareAlert 可通过 YAML 条目完全配置,它通过 Harmony 后缀钩入启动过程,以检测特定的硬件故障(触摸传感器、LED、摄像头),并通过调用 ErrorFrame 停止初始化。ErrorFrame 调度一个 ErrorProcess 并修补内置的 ErrorMonitor 以渲染自定义代码和消息。 硬件错误拦截和错误帧显示的序列图sequenceDiagram
participant StartupProcess
participant HardwareAlert
participant ErrorFrame
participant ErrorMonitor
participant ErrorProcess
participant GameManager
StartupProcess->>HardwareAlert: OnUpdate()
HardwareAlert-->>ErrorFrame: Show(process, errCode, errMsg)
ErrorFrame->>ErrorFrame: Store custom error info
ErrorFrame->>ErrorFrame: Show(process)
ErrorFrame->>ErrorProcess: Add ErrorProcess to manager
ErrorFrame->>GameManager: Set IsErrorMode = true
ErrorMonitor->>ErrorFrame: Initialize(errCode, isCustom)
ErrorFrame-->>ErrorMonitor: Patch to display custom error info
文件级别变更
提示和命令与 Sourcery 交互
自定义您的体验访问您的仪表板以:
获取帮助Original review guide in EnglishReviewer's GuideThis PR adds a new HardwareAlert mod and an ErrorFrame helper to intercept and display custom startup errors in the arcade-style error window. HardwareAlert is fully configurable via YAML entries, hooks into the startup process with Harmony postfixes to detect specific hardware failures (touch sensors, LEDs, cameras) and halts initialization by invoking ErrorFrame. ErrorFrame schedules an ErrorProcess and patches the built-in ErrorMonitor to render custom codes and messages. Sequence diagram for hardware error interception and error frame displaysequenceDiagram
participant StartupProcess
participant HardwareAlert
participant ErrorFrame
participant ErrorMonitor
participant ErrorProcess
participant GameManager
StartupProcess->>HardwareAlert: OnUpdate()
HardwareAlert-->>ErrorFrame: Show(process, errCode, errMsg)
ErrorFrame->>ErrorFrame: Store custom error info
ErrorFrame->>ErrorFrame: Show(process)
ErrorFrame->>ErrorProcess: Add ErrorProcess to manager
ErrorFrame->>GameManager: Set IsErrorMode = true
ErrorMonitor->>ErrorFrame: Initialize(errCode, isCustom)
ErrorFrame-->>ErrorMonitor: Patch to display custom error info
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
你好 - 我已审阅了你的更改 - 以下是一些反馈:
- OnPostUpdate 中的硬件检查逻辑是重复的;考虑重构为数据驱动结构,将每个配置条目映射到其错误代码/消息,以减少样板代码。
- 验证 CameraTypeList 和 _cameraIndex 的集合初始化器是否为有效的 C# 语法(例如,使用 new List { ... } 和 new SortedDictionary<,>()),因为当前的字面量可能无法编译。
- 检查 CodeReader_1P 和 CodeReader_2P 的错误代码分配(都使用 3101),以确保如果需要,它们是不同的,或者更新注释以反映共享代码。
给 AI 代理的提示
请处理此代码审查中的注释:
## 总体注释
- OnPostUpdate 中的硬件检查逻辑是重复的;考虑重构为数据驱动结构,将每个配置条目映射到其错误代码/消息,以减少样板代码。
- 验证 CameraTypeList 和 _cameraIndex 的集合初始化器是否为有效的 C# 语法(例如,使用 new List<string> { ... } 和 new SortedDictionary<,>()),因为当前的字面量可能无法编译。
- 检查 CodeReader_1P 和 CodeReader_2P 的错误代码分配(都使用 3101),以确保如果需要,它们是不同的,或者更新注释以反映共享代码。
## 单独注释
### 注释 1
<location> `AquaMai.Mods/UX/HardwareAlert.cs:111` </location>
<code_context>
+ // get current startup state
+ var tv = Traverse.Create(__instance);
+ // var state = tv.Field("_state").GetValue<byte>();
+ var statusSubMsg = tv.Field("_statusSubMsg").GetValue<string[]>();
+
+ // Do another version check, since the AMDaemon error code gets disappeared sometimes...
</code_context>
<issue_to_address>
如果 statusSubMsg 为 null 或元素数量少于预期,则存在潜在风险。
在访问 statusSubMsg 的项目之前,添加检查以确保 statusSubMsg 不为 null 且至少包含四个元素,以防止运行时异常。
</issue_to_address>
### 注释 2
<location> `AquaMai.Mods/UX/HardwareAlert.cs:140` </location>
<code_context>
+ if (PlayerCamera && !CameraManager.IsAvailableCameras[_cameraIndex[CameraTypeEnumInner.Photo]])
</code_context>
<issue_to_address>
如果 _cameraIndex 不包含预期的键,可能会发生 KeyNotFoundException。
如果 _cameraIndex 缺少 CameraTypeEnumInner 值,这将抛出异常。在访问之前使用 TryGetValue 或检查键是否存在。
</issue_to_address>
### 注释 3
<location> `AquaMai.Core/Helpers/ErrorFrame.cs:48` </location>
<code_context>
+ return;
+ }
+ MelonLogger.Msg($"Displaying error frame with custom code {_customErrCode}: {_customErrMsg}");
+ tv.Field("ErrorID").GetValue<TextMeshProUGUI>().text = _customErrCode.ToString().PadLeft(4, '0');
+ tv.Field("ErrorMessage").GetValue<TextMeshProUGUI>().text = _customErrMsg;
+ tv.Field("ErrorDate").GetValue<TextMeshProUGUI>().text = _customErrDate.ToString();
+ }
+
</code_context>
<issue_to_address>
ErrorDate 格式可能与用户预期不一致。
在将 _customErrDate 转换为字符串时,指定格式字符串或使用不变区域性,以确保日期/时间显示一致。
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
tv.Field("ErrorDate").GetValue<TextMeshProUGUI>().text = _customErrDate.ToString();
=======
tv.Field("ErrorDate").GetValue<TextMeshProUGUI>().text = _customErrDate.ToString("yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
>>>>>>> REPLACE
</suggested_fix>
### 注释 4
<location> `AquaMai.Mods/UX/HardwareAlert.cs:93` </location>
<code_context>
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(StartupProcess), "OnUpdate")]
+ public static void OnPostUpdate(StartupProcess __instance)
+ {
+ if (AMDaemon.Error.Number > 0)
</code_context>
<issue_to_address>
考虑将重复的硬件检查块重构为使用描述符对象的数据驱动循环。
这是将所有重复的 `if (…) ErrorFrame.Show(…) return;` 块折叠成单个数据驱动循环的一种方法。您可以保留所有标志、代码和消息,但将它们打包成一个小的“条目”类型,然后只需迭代。
```csharp
// 1) Define a small descriptor for each check
private class HardwareCheck
{
public Func<bool> IsEnabled;
public Func<StartupProcess, string[], bool> Triggers;
public int ErrorCode;
public IReadOnlyDictionary<string,string> Messages;
}
// 2) Build your list of checks once
private static readonly HardwareCheck[] _checks = new[]
{
// AMDaemon error fallback for 1P touch
new HardwareCheck {
IsEnabled = () => TouchSensor_1P,
Triggers = (proc,msg) =>
AMDaemon.Error.Number > 0 &&
(AMDaemon.Error.Number == 3300 || AMDaemon.Error.Number == 3301),
ErrorCode = 3300,
Messages = FaultTouchSensor1P
},
// statusSubMsg sensor checks
new HardwareCheck {
IsEnabled = () => TouchSensor_1P,
Triggers = (proc,msg) => msg[0] == ConstParameter.TestString_Bad,
ErrorCode = 3300,
Messages = FaultTouchSensor1P
},
// ... repeat for TouchSensor_2P, LED_1P, LED_2P
};
// Camera‐based checks (after CameraManager.IsReady)
private static readonly HardwareCheck[] _cameraChecks = new[]
{
new HardwareCheck {
IsEnabled = () => PlayerCamera,
Triggers = (proc,msg) =>
!CameraManager.IsAvailableCameras[_cameraIndex[CameraTypeEnumInner.Photo]],
ErrorCode = 3102,
Messages = FaultPlayerCamera
},
// ... QR1P, QR2P, Chime
};
```
然后,您的整个 `OnPostUpdate` 简化为:
```csharp
[HarmonyPostfix]
[HarmonyPatch(typeof(StartupProcess), "OnUpdate")]
public static void OnPostUpdate(StartupProcess __instance)
{
var tv = Traverse.Create(__instance);
var statusSubMsg = tv.Field("_statusSubMsg").GetValue<string[]>();
// Try all raw‐sensor & statusSubMsg entries
foreach (var chk in _checks)
if (chk.IsEnabled() && chk.Triggers(__instance, statusSubMsg))
{
ErrorFrame.Show(__instance, chk.ErrorCode, chk.Messages[GetLocale()]);
return;
}
// Then camera checks
if (CameraManager.IsReady)
foreach (var chk in _cameraChecks)
if (chk.IsEnabled() && chk.Triggers(__instance, statusSubMsg))
{
ErrorFrame.Show(__instance, chk.ErrorCode, chk.Messages[GetLocale()]);
return;
}
}
```
优点:
- 所有“样板” `if (…) { Show; return }` 都位于单个循环中。
- 添加新的传感器或相机只是数组中的另一个条目。
- 消除了大型单一方法、重度嵌套以及字符串到枚举的转换。
</issue_to_address>帮助我更有用!请点击每个评论上的 👍 或 👎,我将使用反馈来改进您的评论。
Original comment in English
Hey there - I've reviewed your changes - here's some feedback:
- The hardware check logic in OnPostUpdate is repetitive; consider refactoring into a data-driven structure mapping each config entry to its error code/message to reduce boilerplate.
- Verify the collection initializers for CameraTypeList and _cameraIndex are valid C# syntax (e.g., use new List { ... } and new SortedDictionary<,>()), as the current literals might not compile.
- Review the error code assignments for CodeReader_1P and CodeReader_2P (both using 3101) to ensure they are distinct if intended or update comments to reflect the shared code.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The hardware check logic in OnPostUpdate is repetitive; consider refactoring into a data-driven structure mapping each config entry to its error code/message to reduce boilerplate.
- Verify the collection initializers for CameraTypeList and _cameraIndex are valid C# syntax (e.g., use new List<string> { ... } and new SortedDictionary<,>()), as the current literals might not compile.
- Review the error code assignments for CodeReader_1P and CodeReader_2P (both using 3101) to ensure they are distinct if intended or update comments to reflect the shared code.
## Individual Comments
### Comment 1
<location> `AquaMai.Mods/UX/HardwareAlert.cs:111` </location>
<code_context>
+ // get current startup state
+ var tv = Traverse.Create(__instance);
+ // var state = tv.Field("_state").GetValue<byte>();
+ var statusSubMsg = tv.Field("_statusSubMsg").GetValue<string[]>();
+
+ // Do another version check, since the AMDaemon error code gets disappeared sometimes...
</code_context>
<issue_to_address>
Potential risk if statusSubMsg is null or has fewer than expected elements.
Add a check to ensure statusSubMsg is not null and contains at least four elements before accessing its items to prevent runtime exceptions.
</issue_to_address>
### Comment 2
<location> `AquaMai.Mods/UX/HardwareAlert.cs:140` </location>
<code_context>
+ if (PlayerCamera && !CameraManager.IsAvailableCameras[_cameraIndex[CameraTypeEnumInner.Photo]])
</code_context>
<issue_to_address>
Possible KeyNotFoundException if _cameraIndex does not contain expected keys.
If _cameraIndex lacks a CameraTypeEnumInner value, this will throw. Use TryGetValue or check for key existence before accessing.
</issue_to_address>
### Comment 3
<location> `AquaMai.Core/Helpers/ErrorFrame.cs:48` </location>
<code_context>
+ return;
+ }
+ MelonLogger.Msg($"Displaying error frame with custom code {_customErrCode}: {_customErrMsg}");
+ tv.Field("ErrorID").GetValue<TextMeshProUGUI>().text = _customErrCode.ToString().PadLeft(4, '0');
+ tv.Field("ErrorMessage").GetValue<TextMeshProUGUI>().text = _customErrMsg;
+ tv.Field("ErrorDate").GetValue<TextMeshProUGUI>().text = _customErrDate.ToString();
+ }
+
</code_context>
<issue_to_address>
ErrorDate formatting may be inconsistent with user expectations.
Specify a format string or use invariant culture when converting _customErrDate to a string to ensure consistent date/time display.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
tv.Field("ErrorDate").GetValue<TextMeshProUGUI>().text = _customErrDate.ToString();
=======
tv.Field("ErrorDate").GetValue<TextMeshProUGUI>().text = _customErrDate.ToString("yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
>>>>>>> REPLACE
</suggested_fix>
### Comment 4
<location> `AquaMai.Mods/UX/HardwareAlert.cs:93` </location>
<code_context>
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(StartupProcess), "OnUpdate")]
+ public static void OnPostUpdate(StartupProcess __instance)
+ {
+ if (AMDaemon.Error.Number > 0)
</code_context>
<issue_to_address>
Consider refactoring the repeated hardware check blocks into a data-driven loop using descriptor objects for each check.
Here’s one way to collapse all of those repeated `if (…) ErrorFrame.Show(…) return;` blocks into a single data‐driven loop. You keep all your flags, codes and messages, but package them into a small “entry” type and then just iterate.
```csharp
// 1) Define a small descriptor for each check
private class HardwareCheck
{
public Func<bool> IsEnabled;
public Func<StartupProcess, string[], bool> Triggers;
public int ErrorCode;
public IReadOnlyDictionary<string,string> Messages;
}
// 2) Build your list of checks once
private static readonly HardwareCheck[] _checks = new[]
{
// AMDaemon error fallback for 1P touch
new HardwareCheck {
IsEnabled = () => TouchSensor_1P,
Triggers = (proc,msg) =>
AMDaemon.Error.Number > 0 &&
(AMDaemon.Error.Number == 3300 || AMDaemon.Error.Number == 3301),
ErrorCode = 3300,
Messages = FaultTouchSensor1P
},
// statusSubMsg sensor checks
new HardwareCheck {
IsEnabled = () => TouchSensor_1P,
Triggers = (proc,msg) => msg[0] == ConstParameter.TestString_Bad,
ErrorCode = 3300,
Messages = FaultTouchSensor1P
},
// ... repeat for TouchSensor_2P, LED_1P, LED_2P
};
// Camera‐based checks (after CameraManager.IsReady)
private static readonly HardwareCheck[] _cameraChecks = new[]
{
new HardwareCheck {
IsEnabled = () => PlayerCamera,
Triggers = (proc,msg) =>
!CameraManager.IsAvailableCameras[_cameraIndex[CameraTypeEnumInner.Photo]],
ErrorCode = 3102,
Messages = FaultPlayerCamera
},
// ... QR1P, QR2P, Chime
};
```
Then your entire `OnPostUpdate` collapses to:
```csharp
[HarmonyPostfix]
[HarmonyPatch(typeof(StartupProcess), "OnUpdate")]
public static void OnPostUpdate(StartupProcess __instance)
{
var tv = Traverse.Create(__instance);
var statusSubMsg = tv.Field("_statusSubMsg").GetValue<string[]>();
// Try all raw‐sensor & statusSubMsg entries
foreach (var chk in _checks)
if (chk.IsEnabled() && chk.Triggers(__instance, statusSubMsg))
{
ErrorFrame.Show(__instance, chk.ErrorCode, chk.Messages[GetLocale()]);
return;
}
// Then camera checks
if (CameraManager.IsReady)
foreach (var chk in _cameraChecks)
if (chk.IsEnabled() && chk.Triggers(__instance, statusSubMsg))
{
ErrorFrame.Show(__instance, chk.ErrorCode, chk.Messages[GetLocale()]);
return;
}
}
```
Benefits:
- All your “boilerplate” `if (…) { Show; return }` lives in a single loop.
- Adding a new sensor or camera is just another entry in the array.
- Removes the large monolithic method, heavy nesting, and string‐to‐enum plumbing.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| CollectWantedPatches(wantedPatches, typeof(KeyListener)); | ||
| CollectWantedPatches(wantedPatches, typeof(Shim)); | ||
| CollectWantedPatches(wantedPatches, typeof(NetPacketHook)); | ||
| CollectWantedPatches(wantedPatches, typeof(ErrorFrame)); |
There was a problem hiding this comment.
是不是应该在启用之后才 patch 这个,这个也没有被别的地方调用过
There was a problem hiding this comment.
这个是调用的它hdd里内置的error frame,感觉是个比较通用的功能,是不是放在这里当通用组件会好一些?这样比如后面要实现CPU温度监控之类的功能,也可以用这个frame还原街机的效果
Before
Currently, when any hardware is unavailable, the system only displays a "BAD" status in the list, and the message quickly disappears.
After (with this MR)
This merge request introduces a custom hardware check that halts the startup process if a hardware error is detected.
Users can configure which hardware components should be monitored, ensuring the system does not proceed until the specified devices are available and functioning correctly.
Additionally, the ErrorFrame implementation adopts the built-in startup error window style of the system, closely replicating the way arcade machines display errors during the boot process.
Sourcery 总结
引入一个
HardwareAlert模块和辅助的ErrorFrame助手,用于在启动时监控指定的硬件组件,在出现故障时阻止初始化,并显示一个带有自定义代码和消息的样式化错误窗口。新功能:
改进:
ErrorFrame,并与ErrorMonitor集成以覆盖错误显示CameraManager以动态检测和索引摄像头类型进行监控Original summary in English
Summary by Sourcery
Introduce a HardwareAlert module and supporting ErrorFrame helper to monitor specified hardware components during startup, block initialization on failures, and present a styled error window with custom codes and messages.
New Features:
Enhancements: