Skip to content

feat: Win32 截图方式测速#1183

Merged
MistEO merged 2 commits intomainfrom
feat/win32_test
Mar 5, 2026
Merged

feat: Win32 截图方式测速#1183
MistEO merged 2 commits intomainfrom
feat/win32_test

Conversation

@MistEO
Copy link
Member

@MistEO MistEO commented Mar 5, 2026

Summary by Sourcery

为 Win32 屏幕捕获方法添加支持,使其可作为按位标志组合使用,并在运行时自动选择可用的最快方法。

新功能:

  • 允许将 Win32 屏幕捕获方法指定为可组合的按位标志,并在 C、Python、NodeJS 和 C++ 模块绑定中暴露预定义的前景/背景/全部组合。
  • Win32ControlUnitMgr 中引入运行时基准测试,用于测试已配置的屏幕捕获方法,并为目标窗口选择最快且可用的实现。

增强:

  • 重构 Win32ControlUnitMgr,以封装屏幕捕获初始化、复用共享的输入创建逻辑,并确保在重连时重建屏幕捕获对象。

文档:

  • 更新英文和中文的控制方法文档,以描述新的屏幕捕获标志组合、预定义宏以及伪最小化行为。
Original summary in English

Summary by Sourcery

Add support for combining multiple Win32 screencap methods as bitwise flags and automatically selecting the fastest available method at runtime.

New Features:

  • Allow Win32 screencap methods to be specified as combinable bitwise flags with predefined foreground/background/all combinations exposed in C, Python, NodeJS, and C++ module bindings.
  • Introduce runtime benchmarking in Win32ControlUnitMgr to test the configured screencap methods and pick the fastest working implementation for the target window.

Enhancements:

  • Refactor Win32ControlUnitMgr to encapsulate screencap initialization, reuse shared input creation logic, and ensure screencap objects are rebuilt on reconnect.

Documentation:

  • Update English and Chinese control method documentation to describe the new screencap flag combinations, predefined macros, and pseudo-minimize behavior.

Copilot AI review requested due to automatic review settings March 5, 2026 13:24
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 - 我发现了两个问题,并给出了一些整体性的反馈:

  • 记录 ScreencapMethod 日志(例如 LogInfo << "Testing" << methodVAR(method))时,需要为该枚举提供 operator<< 重载,或者显式转换为整型;否则这些日志调用可能无法编译,或者会产生难以阅读的输出。
  • screencap_method_ == MaaWin32ScreencapMethod_None 时,init_screencap() 现在会返回 true 并让 screencap_ 保持为空指针,这改变了之前“缺少 method 视为错误”的行为;如果调用方在 connect() 之后假定 screencap_ 一定是有效的,建议要么保持原来的失败语义,要么显式文档化并在代码中防范 nullptr 情况。
  • 当前的 speed_test 对每种 screencap method 只调用一次 screencap() 来测量;为了降低方差(例如首次调用的初始化开销)并提高选择结果的稳定性,建议对每种 method 运行多次并忽略明显的离群值,或者增加一个短暂的预热阶段。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- Logging `ScreencapMethod` (e.g., `LogInfo << "Testing" << method` and `VAR(method)`) will require an `operator<<` overload for the enum or an explicit cast to an integral type; otherwise these log calls may not compile or will produce unreadable output.
- When `screencap_method_ == MaaWin32ScreencapMethod_None`, `init_screencap()` now returns `true` and leaves `screencap_` null, changing the previous behavior (which treated missing method as error); if callers assume a valid `screencap_` after `connect()`, consider either keeping the failure semantics or explicitly documenting and guarding against the `nullptr` case.
- The `speed_test` currently measures each screencap method with a single `screencap()` call; to reduce variance (e.g., first-call initialization costs) and improve selection stability, consider running each method multiple times and ignoring obvious outliers or adding a short warm-up phase.

## Individual Comments

### Comment 1
<location path="source/MaaWin32ControlUnit/Manager/Win32ControlUnitMgr.cpp" line_range="171-172" />
<code_context>
+    }
+}
+
+std::shared_ptr<ScreencapBase> Win32ControlUnitMgr::speed_test(
+    std::unordered_map<ScreencapMethod, std::shared_ptr<ScreencapBase>>& units) const
+{
+    LogFunc;
</code_context>
<issue_to_address>
**suggestion:** Use a const reference for the `units` parameter in `speed_test` to better express intent.

Since `speed_test` doesn’t modify `units`, the parameter can be `const std::unordered_map<ScreencapMethod, std::shared_ptr<ScreencapBase>>&`. This improves const-correctness and allows calling it with const maps.

```suggestion
std::shared_ptr<ScreencapBase> Win32ControlUnitMgr::speed_test(
    const std::unordered_map<ScreencapMethod, std::shared_ptr<ScreencapBase>>& units) const
```
</issue_to_address>

### Comment 2
<location path="source/MaaWin32ControlUnit/Manager/Win32ControlUnitMgr.cpp" line_range="188-195" />
<code_context>
+        LogInfo << VAR(method) << VAR(duration);
+    };
+
+    for (auto& [method, unit] : units) {
+        LogInfo << "Testing" << method;
+        auto now = std::chrono::steady_clock::now();
+        if (!unit->screencap()) {
+            LogWarn << "failed to test" << method;
+            continue;
+        }
+        check(method, now);
+    }
+
</code_context>
<issue_to_address>
**suggestion (performance):** Consider multiple samples or a warmup for each screencap method to avoid one-off timing artifacts.

Using only one timing per method means any one-time initialization (e.g., GPU/driver setup, first capture overhead) can dominate the measurement and bias the selection toward a method that’s not actually best in steady state. To reduce this, consider adding a warmup call and/or measuring min/average over several `screencap()` calls per method.

Suggested implementation:

```cpp
    auto check = [&fastest, &cost](ScreencapMethod method, std::chrono::steady_clock::duration duration) {
        if (duration < cost) {
            fastest = method;
            cost = duration;
        }
        LogInfo << VAR(method) << VAR(duration);
    };

```

```cpp
    constexpr int warmup_runs = 1;
    constexpr int measure_runs = 5;

    for (auto& [method, unit] : units) {
        LogInfo << "Testing" << method;

        bool warmup_ok = true;
        for (int i = 0; i < warmup_runs; ++i) {
            if (!unit->screencap()) {
                LogWarn << "failed to warmup" << method;
                warmup_ok = false;
                break;
            }
        }
        if (!warmup_ok) {
            continue;
        }

        std::chrono::steady_clock::duration best_duration = std::chrono::steady_clock::duration::max();
        bool any_success = false;

        for (int i = 0; i < measure_runs; ++i) {
            auto start = std::chrono::steady_clock::now();
            if (!unit->screencap()) {
                LogWarn << "failed to test" << method;
                continue;
            }
            auto duration = duration_since(start);
            any_success = true;
            if (duration < best_duration) {
                best_duration = duration;
            }
        }

        if (!any_success) {
            continue;
        }

        check(method, best_duration);
    }

```
</issue_to_address>

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

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

  • Logging ScreencapMethod (e.g., LogInfo << "Testing" << method and VAR(method)) will require an operator<< overload for the enum or an explicit cast to an integral type; otherwise these log calls may not compile or will produce unreadable output.
  • When screencap_method_ == MaaWin32ScreencapMethod_None, init_screencap() now returns true and leaves screencap_ null, changing the previous behavior (which treated missing method as error); if callers assume a valid screencap_ after connect(), consider either keeping the failure semantics or explicitly documenting and guarding against the nullptr case.
  • The speed_test currently measures each screencap method with a single screencap() call; to reduce variance (e.g., first-call initialization costs) and improve selection stability, consider running each method multiple times and ignoring obvious outliers or adding a short warm-up phase.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Logging `ScreencapMethod` (e.g., `LogInfo << "Testing" << method` and `VAR(method)`) will require an `operator<<` overload for the enum or an explicit cast to an integral type; otherwise these log calls may not compile or will produce unreadable output.
- When `screencap_method_ == MaaWin32ScreencapMethod_None`, `init_screencap()` now returns `true` and leaves `screencap_` null, changing the previous behavior (which treated missing method as error); if callers assume a valid `screencap_` after `connect()`, consider either keeping the failure semantics or explicitly documenting and guarding against the `nullptr` case.
- The `speed_test` currently measures each screencap method with a single `screencap()` call; to reduce variance (e.g., first-call initialization costs) and improve selection stability, consider running each method multiple times and ignoring obvious outliers or adding a short warm-up phase.

## Individual Comments

### Comment 1
<location path="source/MaaWin32ControlUnit/Manager/Win32ControlUnitMgr.cpp" line_range="171-172" />
<code_context>
+    }
+}
+
+std::shared_ptr<ScreencapBase> Win32ControlUnitMgr::speed_test(
+    std::unordered_map<ScreencapMethod, std::shared_ptr<ScreencapBase>>& units) const
+{
+    LogFunc;
</code_context>
<issue_to_address>
**suggestion:** Use a const reference for the `units` parameter in `speed_test` to better express intent.

Since `speed_test` doesn’t modify `units`, the parameter can be `const std::unordered_map<ScreencapMethod, std::shared_ptr<ScreencapBase>>&`. This improves const-correctness and allows calling it with const maps.

```suggestion
std::shared_ptr<ScreencapBase> Win32ControlUnitMgr::speed_test(
    const std::unordered_map<ScreencapMethod, std::shared_ptr<ScreencapBase>>& units) const
```
</issue_to_address>

### Comment 2
<location path="source/MaaWin32ControlUnit/Manager/Win32ControlUnitMgr.cpp" line_range="188-195" />
<code_context>
+        LogInfo << VAR(method) << VAR(duration);
+    };
+
+    for (auto& [method, unit] : units) {
+        LogInfo << "Testing" << method;
+        auto now = std::chrono::steady_clock::now();
+        if (!unit->screencap()) {
+            LogWarn << "failed to test" << method;
+            continue;
+        }
+        check(method, now);
+    }
+
</code_context>
<issue_to_address>
**suggestion (performance):** Consider multiple samples or a warmup for each screencap method to avoid one-off timing artifacts.

Using only one timing per method means any one-time initialization (e.g., GPU/driver setup, first capture overhead) can dominate the measurement and bias the selection toward a method that’s not actually best in steady state. To reduce this, consider adding a warmup call and/or measuring min/average over several `screencap()` calls per method.

Suggested implementation:

```cpp
    auto check = [&fastest, &cost](ScreencapMethod method, std::chrono::steady_clock::duration duration) {
        if (duration < cost) {
            fastest = method;
            cost = duration;
        }
        LogInfo << VAR(method) << VAR(duration);
    };

```

```cpp
    constexpr int warmup_runs = 1;
    constexpr int measure_runs = 5;

    for (auto& [method, unit] : units) {
        LogInfo << "Testing" << method;

        bool warmup_ok = true;
        for (int i = 0; i < warmup_runs; ++i) {
            if (!unit->screencap()) {
                LogWarn << "failed to warmup" << method;
                warmup_ok = false;
                break;
            }
        }
        if (!warmup_ok) {
            continue;
        }

        std::chrono::steady_clock::duration best_duration = std::chrono::steady_clock::duration::max();
        bool any_success = false;

        for (int i = 0; i < measure_runs; ++i) {
            auto start = std::chrono::steady_clock::now();
            if (!unit->screencap()) {
                LogWarn << "failed to test" << method;
                continue;
            }
            auto duration = duration_since(start);
            any_success = true;
            if (duration < best_duration) {
                best_duration = duration;
            }
        }

        if (!any_success) {
            continue;
        }

        check(method, best_duration);
    }

```
</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 updates the Win32 controller to treat screencap methods as bitmask flags and introduces a startup speed test to automatically select the fastest working method among the provided options, with corresponding updates to C/C++ exports, Python/NodeJS bindings, and English documentation.

Changes:

  • Add Win32 screencap method flag combinations (All, Foreground, Background) across C API, C++ module exports, and language bindings.
  • Refactor Win32 controller connection to build candidate screencap units and pick the fastest via runtime testing.
  • Update NodeJS/Python docs/types and Win32 screencap documentation to describe the new flag-based selection model.

Reviewed changes

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

Show a summary per file
File Description
source/modules/MaaFramework.cppm Exports new Win32 screencap method flag constants to the module interface.
include/MaaFramework/MaaDef.h Defines new Win32 screencap combination macros and updates API doc comments to reflect flag semantics.
source/MaaWin32ControlUnit/Manager/Win32ControlUnitMgr.h Adds internal screencap selection helpers and data structures to support speed testing.
source/MaaWin32ControlUnit/Manager/Win32ControlUnitMgr.cpp Implements flag-based unit construction and a runtime speed test to select the fastest screencap method.
source/binding/Python/maa/define.py Updates Win32 screencap enum to flags and adds All/Foreground/Background.
source/binding/Python/maa/controller.py Changes Win32Controller default screencap selection to Background.
source/binding/NodeJS/src/apis/constant.d.ts Updates Win32 screencap method documentation and adds new keys to the typed constant map.
source/binding/NodeJS/src/apis/constant.cpp Exposes new Win32 screencap flag constants to JS.
docs/en_us/2.4-ControlMethods.md Updates Win32 screencap documentation to describe OR-combination and predefined combos.

@MistEO MistEO merged commit d068b13 into main Mar 5, 2026
12 of 14 checks passed
@MistEO MistEO deleted the feat/win32_test branch March 5, 2026 13:41
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