Skip to content

Comments

feat(context): 为 MaaContext 添加 wait_freezes 接口支持#1095

Merged
MistEO merged 7 commits intomainfrom
feat/wait_freezes
Jan 30, 2026
Merged

feat(context): 为 MaaContext 添加 wait_freezes 接口支持#1095
MistEO merged 7 commits intomainfrom
feat/wait_freezes

Conversation

@MistEO
Copy link
Member

@MistEO MistEO commented Jan 29, 2026

Summary by Sourcery

在上下文级别新增 wait_freezes 接口,用于在可配置的时间与 ROI 下等待画面稳定,并通过核心层、各语言绑定以及远程代理层对外提供该能力。

New Features:

  • 引入 MaaContext::wait_freezes 方法,通过时间与 ROI 或流水线参数来等待屏幕指定区域稳定。
  • 通过 C 框架 API、Python 绑定,以及代理端客户端/服务端消息机制,在远程上下文中暴露 wait_freezes 能力。

Enhancements:

  • 将等待冻结的逻辑与目标 ROI 计算重构为可复用的 ActionHelper 组件,并更新 Actuator 以使用该组件。
  • 扩展 PipelineParser,支持从数值型或对象型 JSON 中解析 wait_freezes 参数,以实现更灵活的配置方式。

Documentation:

  • 在英文版集成接口总览文档中补充 MaaContextWaitFreezes 接口及其参数说明。

Tests:

  • 增加 Python 绑定和代理集成测试,用于验证在两个时间参数均为 0 时对 wait_freezes 参数的校验行为。
Original summary in English

Summary by Sourcery

Add a context-level wait_freezes API to wait for screen stabilization with configurable timing and ROI, and expose it through core, bindings, and remote agent layers.

New Features:

  • Introduce a MaaContext::wait_freezes method to wait for a stable screen region using time and ROI or pipeline parameters.
  • Expose the wait_freezes capability via the C framework API, Python bindings, and agent client/server messaging for remote contexts.

Enhancements:

  • Refactor wait-freeze waiting logic and target ROI calculation into a reusable ActionHelper component and update Actuator to use it.
  • Extend PipelineParser to parse wait_freezes parameters from either numeric or object JSON values for more flexible configuration.

Documentation:

  • Document the MaaContextWaitFreezes interface and parameters in the English integrated interface overview.

Tests:

  • Add Python binding and agent integration tests to validate wait_freezes parameter validation when both time values are zero.

新功能:

  • 引入上下文级别的 wait_freezes API,用于在屏幕稳定后继续执行,并支持可配置的时间与 ROI(感兴趣区域)参数。
  • 通过 C API、Python 和 NodeJS 绑定,以及 agent 客户端/服务端消息机制,在远程上下文中暴露 wait_freezes 功能。

增强:

  • 将等待冻结和目标 ROI 计算逻辑重构到可复用的 ActionHelper 组件中,并更新 Actuator 以使用该组件。
  • 扩展 PipelineParser,使其支持从数值型或对象型 JSON 格式中解析 wait_freezes 参数。

文档:

  • 在英文版集成接口概览中记录 MaaContextWaitFreezes 接口及其参数。

测试:

  • 增加 Python 和 agent 集成测试,用于验证在两个时间参数均为 0 时 wait_freezes 参数校验的行为。
Original summary in English

Summary by Sourcery

在上下文级别新增 wait_freezes 接口,用于在可配置的时间与 ROI 下等待画面稳定,并通过核心层、各语言绑定以及远程代理层对外提供该能力。

New Features:

  • 引入 MaaContext::wait_freezes 方法,通过时间与 ROI 或流水线参数来等待屏幕指定区域稳定。
  • 通过 C 框架 API、Python 绑定,以及代理端客户端/服务端消息机制,在远程上下文中暴露 wait_freezes 能力。

Enhancements:

  • 将等待冻结的逻辑与目标 ROI 计算重构为可复用的 ActionHelper 组件,并更新 Actuator 以使用该组件。
  • 扩展 PipelineParser,支持从数值型或对象型 JSON 中解析 wait_freezes 参数,以实现更灵活的配置方式。

Documentation:

  • 在英文版集成接口总览文档中补充 MaaContextWaitFreezes 接口及其参数说明。

Tests:

  • 增加 Python 绑定和代理集成测试,用于验证在两个时间参数均为 0 时对 wait_freezes 参数的校验行为。
Original summary in English

Summary by Sourcery

Add a context-level wait_freezes API to wait for screen stabilization with configurable timing and ROI, and expose it through core, bindings, and remote agent layers.

New Features:

  • Introduce a MaaContext::wait_freezes method to wait for a stable screen region using time and ROI or pipeline parameters.
  • Expose the wait_freezes capability via the C framework API, Python bindings, and agent client/server messaging for remote contexts.

Enhancements:

  • Refactor wait-freeze waiting logic and target ROI calculation into a reusable ActionHelper component and update Actuator to use it.
  • Extend PipelineParser to parse wait_freezes parameters from either numeric or object JSON values for more flexible configuration.

Documentation:

  • Document the MaaContextWaitFreezes interface and parameters in the English integrated interface overview.

Tests:

  • Add Python binding and agent integration tests to validate wait_freezes parameter validation when both time values are zero.

@MistEO MistEO marked this pull request as ready for review January 30, 2026 01:56
Copilot AI review requested due to automatic review settings January 30, 2026 01:56
Copy link
Contributor

@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 - 我发现了 1 个问题,并给出了一些整体性的反馈:

  • 新的 Context::wait_freezes 路径会直接使用可能为 null 的 json::value 调用 PipelineParser::parse_wait_freezes_value(例如 MaaContextWaitFreezeswait_freezes_param == nullptr 时被调用),但目前 parse_wait_freezes_value 会把非数字/非对象视为错误,而不是回退到提供的 default_value;这会导致简单的 C API 形式在没有 JSON 对象时无法使用,应当要么对 null 做特殊处理,将其视为“使用 default_value”,要么走类似 parse_wait_freezes_param 的逻辑。
  • Context::wait_freezes 中显式 time 参数与解析出的 param.time 之间的互斥检查在默认值场景下会产生不良交互:当调用方传入非零的 time 但在 wait_freezes_param 中省略 time 时,parse_wait_freezes_value 通常会从默认 pipeline(非零)中填充 param.time,从而触发“二者都非零”的错误;可以考虑要么让显式 time 无条件覆盖 param.time 且不视为错误,要么调整解析/合并逻辑,使在外部提供了 time 参数时不应用默认的 time
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new Context::wait_freezes path calls PipelineParser::parse_wait_freezes_value directly with a json::value that may be null (e.g., MaaContextWaitFreezes called with wait_freezes_param == nullptr), but parse_wait_freezes_value currently treats non-number/non-object as an error instead of falling back to the provided default_value; this makes the simple C API form unusable without a JSON object and should either special-case null to mean 'use default_value' or route through parse_wait_freezes_param-like logic.
- The mutual-exclusion check between the explicit time argument and parsed param.time in Context::wait_freezes can interact badly with defaults: when a caller passes a non-zero time but omits time in wait_freezes_param, parse_wait_freezes_value will usually populate param.time from the default pipeline (non-zero), causing the 'both non-zero' error; consider either letting the explicit time unconditionally override param.time without treating this as an error, or adjusting the parser/merge logic so that default time is not applied when an external time argument is provided.

## Individual Comments

### Comment 1
<location> `source/Common/MaaContext.cpp:201-210` </location>
<code_context>
     return context->run_action_direct(action_type, *param_opt, cv_box, reco_detail);
 }

+MaaBool MaaContextWaitFreezes(MaaContext* context, MaaSize time, const MaaRect* roi, const char* wait_freezes_param)
+{
+    LogFunc << VAR_VOIDP(context) << VAR(time) << VAR(wait_freezes_param);
+
+    if (!context) {
+        LogError << "handle is null";
+        return false;
+    }
+
+    json::value param_json;
+    if (wait_freezes_param) {
+        auto param_opt = json::parse(wait_freezes_param);
+        if (!param_opt) {
+            LogError << "failed to parse wait_freezes_param" << VAR(wait_freezes_param);
+            return false;
+        }
+        param_json = std::move(*param_opt);
+    }
+
+    cv::Rect cv_roi {};
+    if (roi) {
+        cv_roi.x = roi->x;
+        cv_roi.y = roi->y;
+        cv_roi.width = roi->width;
+        cv_roi.height = roi->height;
+    }
+
+    return context->wait_freezes(std::chrono::milliseconds(time), cv_roi, param_json);
+}
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Calling MaaContextWaitFreezes without wait_freezes_param will always fail due to null JSON not being accepted by parse_wait_freezes_value.

Because `param_json` stays default-constructed when `wait_freezes_param` is `nullptr`, `Context::wait_freezes` ends up calling `parse_wait_freezes_value` with a `null` JSON value. The new parser rejects non-number, non-object inputs, logs `"invalid wait_freezes_param"`, and returns `false`, so `MaaContextWaitFreezes(..., nullptr)` will always fail even when defaults are available, despite the C API implying the parameter is optional.

To preserve the existing API behavior, either:
- Treat a `null`/empty `json::value` as "use defaults" inside `Context::wait_freezes`, or
- Initialize `param_json` to an empty object when `wait_freezes_param` is null, so the parser can fall back to defaults.
</issue_to_address>

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

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

  • The new Context::wait_freezes path calls PipelineParser::parse_wait_freezes_value directly with a json::value that may be null (e.g., MaaContextWaitFreezes called with wait_freezes_param == nullptr), but parse_wait_freezes_value currently treats non-number/non-object as an error instead of falling back to the provided default_value; this makes the simple C API form unusable without a JSON object and should either special-case null to mean 'use default_value' or route through parse_wait_freezes_param-like logic.
  • The mutual-exclusion check between the explicit time argument and parsed param.time in Context::wait_freezes can interact badly with defaults: when a caller passes a non-zero time but omits time in wait_freezes_param, parse_wait_freezes_value will usually populate param.time from the default pipeline (non-zero), causing the 'both non-zero' error; consider either letting the explicit time unconditionally override param.time without treating this as an error, or adjusting the parser/merge logic so that default time is not applied when an external time argument is provided.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new Context::wait_freezes path calls PipelineParser::parse_wait_freezes_value directly with a json::value that may be null (e.g., MaaContextWaitFreezes called with wait_freezes_param == nullptr), but parse_wait_freezes_value currently treats non-number/non-object as an error instead of falling back to the provided default_value; this makes the simple C API form unusable without a JSON object and should either special-case null to mean 'use default_value' or route through parse_wait_freezes_param-like logic.
- The mutual-exclusion check between the explicit time argument and parsed param.time in Context::wait_freezes can interact badly with defaults: when a caller passes a non-zero time but omits time in wait_freezes_param, parse_wait_freezes_value will usually populate param.time from the default pipeline (non-zero), causing the 'both non-zero' error; consider either letting the explicit time unconditionally override param.time without treating this as an error, or adjusting the parser/merge logic so that default time is not applied when an external time argument is provided.

## Individual Comments

### Comment 1
<location> `source/Common/MaaContext.cpp:201-210` </location>
<code_context>
     return context->run_action_direct(action_type, *param_opt, cv_box, reco_detail);
 }

+MaaBool MaaContextWaitFreezes(MaaContext* context, MaaSize time, const MaaRect* roi, const char* wait_freezes_param)
+{
+    LogFunc << VAR_VOIDP(context) << VAR(time) << VAR(wait_freezes_param);
+
+    if (!context) {
+        LogError << "handle is null";
+        return false;
+    }
+
+    json::value param_json;
+    if (wait_freezes_param) {
+        auto param_opt = json::parse(wait_freezes_param);
+        if (!param_opt) {
+            LogError << "failed to parse wait_freezes_param" << VAR(wait_freezes_param);
+            return false;
+        }
+        param_json = std::move(*param_opt);
+    }
+
+    cv::Rect cv_roi {};
+    if (roi) {
+        cv_roi.x = roi->x;
+        cv_roi.y = roi->y;
+        cv_roi.width = roi->width;
+        cv_roi.height = roi->height;
+    }
+
+    return context->wait_freezes(std::chrono::milliseconds(time), cv_roi, param_json);
+}
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Calling MaaContextWaitFreezes without wait_freezes_param will always fail due to null JSON not being accepted by parse_wait_freezes_value.

Because `param_json` stays default-constructed when `wait_freezes_param` is `nullptr`, `Context::wait_freezes` ends up calling `parse_wait_freezes_value` with a `null` JSON value. The new parser rejects non-number, non-object inputs, logs `"invalid wait_freezes_param"`, and returns `false`, so `MaaContextWaitFreezes(..., nullptr)` will always fail even when defaults are available, despite the C API implying the parameter is optional.

To preserve the existing API behavior, either:
- Treat a `null`/empty `json::value` as "use defaults" inside `Context::wait_freezes`, or
- Initialize `param_json` to an empty object when `wait_freezes_param` is null, so the parser can fall back to defaults.
</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.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new wait_freezes method to the MaaContext API, allowing users to wait for screen stabilization at the context level. The implementation includes comprehensive cross-platform support through C API, Python bindings, and agent client/server protocol. The PR also refactors existing freeze-waiting logic into a reusable ActionHelper component and extends the PipelineParser to handle both numeric and object-based wait_freezes parameters.

Changes:

  • Added Context-level wait_freezes API with validation for mutually exclusive time and ROI parameters
  • Refactored freeze-waiting and target ROI calculation logic from Actuator into a new reusable ActionHelper component
  • Extended PipelineParser with parse_wait_freezes_value to support both simple numeric and advanced object parameter formats

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/python/binding_test.py Adds test validating parameter validation when both time values are zero
test/agent/agent_child_test.py Adds identical agent test for wait_freezes parameter validation
source/modules/MaaFramework.cppm Exports MaaContextWaitFreezes for C++ module consumers
source/include/MaaAgent/Message.hpp Defines ContextWaitFreezesReverseRequest/Response message structures
source/include/Common/MaaTypes.h Adds wait_freezes virtual method to MaaContext interface
source/binding/Python/maa/context.py Implements Python binding for wait_freezes with JWaitFreezes dataclass support
source/MaaFramework/Task/Context.h Declares wait_freezes method and reorganizes method declarations
source/MaaFramework/Task/Context.cpp Implements wait_freezes with comprehensive parameter validation and merging logic
source/MaaFramework/Task/Component/Actuator.h Changes get_target_rect signature to accept const reference
source/MaaFramework/Task/Component/Actuator.cpp Refactors to delegate to ActionHelper for freeze-waiting and target rect calculations
source/MaaFramework/Task/Component/ActionHelper.h Defines new ActionHelper class with wait_freezes and get_target_rect methods
source/MaaFramework/Task/Component/ActionHelper.cpp Implements ActionHelper with logic extracted from Actuator, returning bool for success/failure
source/MaaFramework/Resource/PipelineParser.h Declares parse_wait_freezes_value for parsing wait_freezes from JSON
source/MaaFramework/Resource/PipelineParser.cpp Refactors parsing logic into parse_wait_freezes_value to handle number and object formats
source/MaaAgentServer/RemoteInstance/RemoteContext.h Declares wait_freezes override for remote context
source/MaaAgentServer/RemoteInstance/RemoteContext.cpp Implements wait_freezes by sending request through agent protocol
source/MaaAgentClient/Client/AgentClient.h Declares handle_context_wait_freezes handler
source/MaaAgentClient/Client/AgentClient.cpp Implements handler to process wait_freezes requests from agent server
source/Common/MaaContext.cpp Implements C API wrapper MaaContextWaitFreezes with JSON parsing
include/MaaFramework/Instance/MaaContext.h Declares C API function MaaContextWaitFreezes
docs/en_us/2.2-IntegratedInterfaceOverview.md Documents MaaContextWaitFreezes parameters and usage constraints

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
ActionHelper helper(tasker_);
cv::Rect roi = helper.get_target_rect(param.target, box);
helper.wait_freezes(param, roi);
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The ActionHelper::wait_freezes method now returns bool indicating success/failure, which is a behavior change from the original Actuator::wait_freezes that returned void. This is an improvement, but the Actuator::wait_freezes method that wraps it ignores the return value, potentially silencing errors. Consider handling the return value appropriately, such as logging a warning when wait_freezes fails.

Suggested change
helper.wait_freezes(param, roi);
bool ret = helper.wait_freezes(param, roi);
if (!ret) {
LogWarn << "wait_freezes failed" << VAR(roi);
}

Copilot uses AI. Check for mistakes.
Comment on lines +846 to +847
Wait for screen to stabilize. `time` and `wait_freezes_param.time` are mutually exclusive: both cannot be non-zero, and both cannot be zero. `roi` and `wait_freezes_param.target` are mutually exclusive: cannot specify both.

Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The documentation states "both cannot be non-zero, and both cannot be zero" but doesn't clearly explain what happens when one is zero and the other is non-zero, or what the expected behavior is. Consider adding an example showing the correct usage patterns, such as "Pass time=1000 with wait_freezes_param=None for simple usage" or "Pass time=0 with wait_freezes_param=JWaitFreezes(time=1000) for advanced configuration".

Suggested change
Wait for screen to stabilize. `time` and `wait_freezes_param.time` are mutually exclusive: both cannot be non-zero, and both cannot be zero. `roi` and `wait_freezes_param.target` are mutually exclusive: cannot specify both.
Wait for screen to stabilize.
`time` and `wait_freezes_param.time` are mutually exclusive: exactly one of them must be non-zero.
- Simple usage: pass a non-zero `time` (for example, `time = 1000`) and omit `wait_freezes_param` or set it to `NULL`/`None`. The whole screen (or the specified `roi`) is watched for about 1 second of stability.
- Advanced usage: pass `time = 0` and provide `wait_freezes_param` with a non-zero `time` (for example, `wait_freezes_param = { "time": 1000, ... }`) to further configure `target`, `threshold`, and other options.
If both `time` and `wait_freezes_param.time` are non-zero, or if both are zero, the call is invalid and the behavior is undefined (it may be rejected by the implementation).
`roi` and `wait_freezes_param.target` are also mutually exclusive: specify at most one of them.

Copilot uses AI. Check for mistakes.
@@ -56,6 +56,8 @@ extern "C"
const MaaRect* box,
const char* reco_detail);

Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The MaaContextWaitFreezes function declaration lacks documentation comments, while other similar functions in this file (like MaaContextRunRecognitionDirect and MaaContextRunActionDirect) have detailed documentation comments. Consider adding a documentation comment describing the function's purpose, parameters, and return value for consistency.

Suggested change
/**
* @brief Wait until the content within a region of interest appears frozen or until a timeout.
*
* This function monitors the current context and waits for the screen (or image source)
* to become stable, optionally within a specified region of interest (ROI). The waiting
* behavior can be further configured through the @p wait_freezes_param argument.
*
* @param context Context in which the wait operation is performed.
* @param time Maximum time to wait before giving up (timeout).
* @param roi Optional region of interest; if null, the entire area is used.
* @param wait_freezes_param Parameters for the wait operation, typically in JSON or configuration string form.
*
* @return MaaBool indicating whether the wait operation succeeded according to the configured criteria.
*/

Copilot uses AI. Check for mistakes.
Comment on lines +154 to +160
# 测试 wait_freezes API(参数校验:time 和 wait_freezes_param.time 同时为零应返回 false)
from maa.pipeline import JWaitFreezes
wait_result = new_ctx.wait_freezes(time=0, wait_freezes_param=JWaitFreezes(time=0))
print(f" wait_freezes (both zero): {wait_result}")
assert (
not wait_result
), "wait_freezes should return false when both time are zero"
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

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

The test code is identical in both test files (Python binding test and agent test). While this ensures consistency, if the test logic needs to be updated, both files will need to be changed. Consider extracting the test into a shared test helper function or a common test module to reduce duplication and maintenance burden.

Copilot uses AI. Check for mistakes.
@MistEO MistEO merged commit 7a3922f into main Jan 30, 2026
14 of 28 checks passed
@MistEO MistEO deleted the feat/wait_freezes branch January 30, 2026 09:36
moomiji added a commit to MaaXYZ/MaaFramework.Binding.CSharp that referenced this pull request Feb 14, 2026
moomiji added a commit to MaaXYZ/MaaFramework.Binding.CSharp that referenced this pull request Feb 14, 2026
moomiji added a commit to MaaXYZ/MaaFramework.Binding.CSharp that referenced this pull request Feb 14, 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.

1 participant