From dfca198657116d3a03628defce0fe761421e7c01 Mon Sep 17 00:00:00 2001 From: zzyyyl <74587068+zzyyyl@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:23:44 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20ProcessTask=20?= =?UTF-8?q?(#10001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MaaCore/Task/ProcessTask.cpp | 557 +++++++++++++++++-------------- src/MaaCore/Task/ProcessTask.h | 133 +++++--- 2 files changed, 389 insertions(+), 301 deletions(-) diff --git a/src/MaaCore/Task/ProcessTask.cpp b/src/MaaCore/Task/ProcessTask.cpp index aa30faac39..94c0a6e27c 100644 --- a/src/MaaCore/Task/ProcessTask.cpp +++ b/src/MaaCore/Task/ProcessTask.cpp @@ -15,337 +15,402 @@ using namespace asst; -asst::ProcessTask::ProcessTask(const AbstractTask& abs, std::vector tasks_name) - : AbstractTask(abs), m_raw_task_name_list(std::move(tasks_name)) +ProcessTask::ProcessTask(const AbstractTask& abs, std::vector tasks_name) : + AbstractTask(abs), + m_begin_task_list(std::move(tasks_name)) { m_task_delay = Config.get_options().task_delay; m_basic_info_cache = json::value(); } -asst::ProcessTask::ProcessTask(AbstractTask&& abs, std::vector tasks_name) noexcept - : AbstractTask(std::move(abs)), m_raw_task_name_list(std::move(tasks_name)) +ProcessTask::ProcessTask(AbstractTask&& abs, std::vector tasks_name) noexcept : + AbstractTask(std::move(abs)), + m_begin_task_list(std::move(tasks_name)) { m_task_delay = Config.get_options().task_delay; m_basic_info_cache = json::value(); } -bool asst::ProcessTask::run() -{ - if (!m_enable) { - Log.info("task disabled, pass", basic_info().to_string()); - return true; - } - if (m_task_delay == TaskDelayUnsetted) { - m_task_delay = Config.get_options().task_delay; - } - - m_cur_task_name_list = m_raw_task_name_list; - for (m_cur_retry = 0; m_cur_retry <= m_retry_times; ++m_cur_retry) { - if (_run()) { - return true; - } - if (need_exit()) { - return false; - } - sleep(m_task_delay); - } - callback(AsstMsg::SubTaskError, basic_info()); - return on_run_fails(); -} - -ProcessTask& asst::ProcessTask::set_task_delay(int delay) noexcept +ProcessTask& ProcessTask::set_task_delay(int delay) noexcept { m_task_delay = delay; return *this; } -asst::ProcessTask& asst::ProcessTask::set_tasks(std::vector tasks_name) noexcept +ProcessTask& ProcessTask::set_tasks(std::vector tasks_name) noexcept { - m_cur_retry = 0; - m_raw_task_name_list = std::move(tasks_name); + m_begin_task_list = std::move(tasks_name); m_pre_task_name.clear(); + m_last_task_name.clear(); return *this; } -ProcessTask& asst::ProcessTask::set_times_limit(std::string name, int limit, TimesLimitType type) +ProcessTask& ProcessTask::set_times_limit(std::string name, int limit, TimesLimitType type) { m_times_limit[std::move(name)] = TimesLimitData { limit, type }; return *this; } -ProcessTask& asst::ProcessTask::set_post_delay(std::string name, int delay) +ProcessTask& ProcessTask::set_post_delay(std::string name, int delay) { m_post_delay[std::move(name)] = delay; return *this; } -ProcessTask& asst::ProcessTask::set_reusable_image(const cv::Mat& reusable) +ProcessTask& ProcessTask::set_reusable_image(const cv::Mat& reusable) { m_reusable = reusable; return *this; } -bool ProcessTask::_run() +bool ProcessTask::run() { LogTraceFunction; - while (!m_cur_task_name_list.empty()) { - if (need_exit()) { - return false; - } - if (!m_enable) { - Log.info("task disabled, pass", basic_info().to_string()); - return true; - } - if (m_cur_task_ptr && m_pre_task_name != m_cur_task_ptr->name) { - m_pre_task_name = m_cur_task_ptr->name; - } + if (!m_enable) { + Log.info("task disabled, pass", basic_info().to_string()); + return true; + } - json::value info = basic_info(); - info["details"] = json::object { - { "to_be_recognized", json::array(m_cur_task_name_list) }, - { "cur_retry", m_cur_retry }, - { "retry_times", m_retry_times }, - }; - Log.info(info.to_string()); + if (m_begin_task_list.empty()) { + Log.warn("task list is empty, pass", basic_info().to_string()); + return true; + } - auto front_task_ptr = Task.get(m_cur_task_name_list.front()); - // 可能有配置错误,导致不存在对应的任务 - if (front_task_ptr == nullptr) { - Log.error("Invalid task", m_cur_task_name_list.front()); - return false; - } + if (m_task_delay == TaskDelayUnsetted) { + m_task_delay = Config.get_options().task_delay; + } - Rect rect; - json::value result; - - // 如果第一个任务是JustReturn的,那就没必要再截图并计算了 - if (front_task_ptr->algorithm == AlgorithmType::JustReturn) { - m_cur_task_ptr = front_task_ptr; - } - else { - cv::Mat image = m_reusable.empty() ? ctrler()->get_image() : m_reusable; - m_reusable = cv::Mat(); - PipelineAnalyzer analyzer(image, Rect(), m_inst); - analyzer.set_tasks(m_cur_task_name_list); - - auto res_opt = analyzer.analyze(); - if (!res_opt) { + TaskConstPtr cur_task_ptr = nullptr; // 当前任务,仅用于计算 on_error_next + TaskList to_be_recognized = m_begin_task_list; // 待匹配任务列表 + while (true) { + auto [status /*识别成功与否*/, next_task_ptr /*匹配到的任务*/] = find_and_run_task(to_be_recognized); + switch (status) { + case NodeStatus::RetryFailed: + // retry 次数达到上限,下一个匹配列表是 on_error_next,若没有则回调 SubTaskError + if (cur_task_ptr == nullptr || cur_task_ptr->on_error_next.empty()) { + callback(AsstMsg::SubTaskError, basic_info()); return false; } - m_cur_task_ptr = res_opt->task_ptr; - if (m_cur_task_ptr->algorithm == AlgorithmType::MatchTemplate) { - auto& raw_result = std::get<0>(res_opt->result); - result = json::object { { "score", raw_result.score } }; - } - else if (m_cur_task_ptr->algorithm == AlgorithmType::OcrDetect) { - auto& raw_result = std::get<1>(res_opt->result); - result = json::object { { "score", raw_result.score }, { "text", raw_result.text } }; - } - rect = res_opt->rect; - } - if (need_exit()) { - return false; - } - m_last_task_name = m_cur_task_ptr->name; - - int& exec_times = m_exec_times[m_last_task_name]; - - auto [max_times, limit_type] = calc_time_limit(); - - if (limit_type == TimesLimitType::Pre && exec_times >= max_times) { - info["what"] = "ExceededLimit"; - info["details"] = json::object { - { "task", m_last_task_name }, - { "exec_times", exec_times }, - { "max_times", max_times }, - { "limit_type", "pre" }, - }; - Log.info("exec times exceeded the limit", info.to_string()); - callback(AsstMsg::SubTaskExtraInfo, info); - m_cur_task_name_list = m_cur_task_ptr->exceeded_next; - sleep(m_task_delay); - continue; - } - - m_cur_retry = 0; - ++exec_times; - - info["details"] = json::object { - { "task", m_last_task_name }, - { "action", enum_to_string(m_cur_task_ptr->action) }, - { "exec_times", exec_times }, - { "max_times", max_times }, - { "algorithm", enum_to_string(m_cur_task_ptr->algorithm) }, - { "result", result }, - }; - - callback(AsstMsg::SubTaskStart, info); - // 允许插件停用ProcessTask - if (!m_enable) { - Log.info("task disabled after SubTaskStart callback, pass", basic_info().to_string()); + to_be_recognized = cur_task_ptr->on_error_next; + break; + case NodeStatus::Runout: + // 成功匹配但执行次数达到上限,下一个匹配列表是 exceeded_next + to_be_recognized = next_task_ptr->exceeded_next; + break; + case NodeStatus::Success: + // 成功匹配且执行成功,下一个匹配列表是 next + to_be_recognized = next_task_ptr->next; + break; + case NodeStatus::Interrupted: + // need_exit() or Stop action return true; - } - - // 前置固定延时 - if (!sleep(m_cur_task_ptr->pre_delay)) { + case NodeStatus::InternalError: + callback(AsstMsg::SubTaskError, basic_info()); return false; - } - - bool need_stop = false; - switch (m_cur_task_ptr->action) { - case ProcessTaskAction::ClickRect: - rect = m_cur_task_ptr->specific_rect; - exec_click_task(rect); - break; - case ProcessTaskAction::ClickSelf: - if (const auto& res_move = m_cur_task_ptr->rect_move; !res_move.empty()) { - rect = rect.move(res_move); - } - exec_click_task(rect); - break; - case ProcessTaskAction::Swipe: - exec_swipe_task(m_cur_task_ptr->specific_rect, m_cur_task_ptr->rect_move, - m_cur_task_ptr->special_params.empty() ? 0 : m_cur_task_ptr->special_params.at(0), - (m_cur_task_ptr->special_params.size() < 2) ? false : m_cur_task_ptr->special_params.at(1), - (m_cur_task_ptr->special_params.size() < 3) ? 1 : m_cur_task_ptr->special_params.at(2), - (m_cur_task_ptr->special_params.size() < 4) ? 1 : m_cur_task_ptr->special_params.at(3)); - break; - case ProcessTaskAction::DoNothing: - break; - case ProcessTaskAction::Stop: - Log.info("stop action", info.to_string()); - need_stop = true; - break; default: - break; - } - - status()->set_number(Status::ProcessTaskLastTimePrefix + m_last_task_name, time(nullptr)); - - // 减少其他任务的执行次数 - // 例如,进入吃理智药的界面了,相当于上一次点蓝色开始行动没生效 - // 所以要给蓝色开始行动的次数减一 - for (const std::string& reduce : m_cur_task_ptr->reduce_other_times) { - auto& v = m_exec_times[reduce]; - if (v > 0) { - --v; - Log.trace("Task `", m_cur_task_ptr->name, "` reduce `", reduce, "` times to ", v); - } - else { - Log.trace("Task `", m_cur_task_ptr->name, "` attempt to reduce `", reduce, - "` times, but it is already 0"); - } - } - - // 后置固定延时 - if (!sleep(calc_post_delay())) { + Log.error(__FUNCTION__, "| Unknown status", static_cast(status)); return false; } - for (const std::string& sub : m_cur_task_ptr->sub) { - LogTraceScope("Sub: " + sub); - bool sub_ret = ProcessTask(*this, { sub }).run(); - if (!sub_ret && !m_cur_task_ptr->sub_error_ignored) { - Log.error("Sub error and not ignored", sub); - // 子任务如果失败了,一定已经经历过子任务自己的 m_retry_times 次重试了 - // 这时候即使再重试父任务也没有意义,直接把父任务也跟着报错出去 - m_cur_retry = m_retry_times; - return false; - } - } - - callback(AsstMsg::SubTaskCompleted, info); - // 允许插件停用ProcessTask - if (!m_enable) { - Log.info("task disabled after SubTaskCompleted callback, pass", basic_info().to_string()); + if (to_be_recognized.empty()) { // Finished, skip delay return true; } - if (limit_type == TimesLimitType::Post && exec_times >= max_times) { - info["what"] = "ExceededLimit"; - info["details"] = json::object { - { "task", m_last_task_name }, - { "exec_times", exec_times }, - { "max_times", max_times }, - { "limit_type", "post" }, - }; - Log.info("exec times exceeded the limit", info.to_string()); - callback(AsstMsg::SubTaskExtraInfo, info); - m_cur_task_name_list = m_cur_task_ptr->exceeded_next; - sleep(m_task_delay); - continue; - } - - if (m_cur_task_ptr->next.empty()) { - need_stop = true; - } - - if (need_stop) { + if (!sleep(m_task_delay)) { // Interrupted return true; } - m_cur_task_name_list = m_cur_task_ptr->next; - sleep(m_task_delay); + + cur_task_ptr = next_task_ptr; } return true; } -bool asst::ProcessTask::on_run_fails() +bool ProcessTask::_run() { - LogTraceFunction; - - if (!m_cur_task_ptr || m_cur_task_ptr->on_error_next.empty()) { - return false; - } - - set_tasks(m_cur_task_ptr->on_error_next); - return run(); + Log.error(__FUNCTION__, "should not be called"); +#ifdef ASST_DEBUG + throw std::runtime_error("ProcessTask::_run() should not be called"); +#else + return false; +#endif } -std::pair asst::ProcessTask::calc_time_limit() const +ProcessTask::HitDetail ProcessTask::find_first(const TaskList& list) /* const, except m_reusable */ { - // eg. "C@B@A" 的 max_times 取 "C@B@A", "B@A", "A" 中有 max_times 定义的最靠前者 - for (std::string cur_base_name = m_cur_task_ptr->name;;) { - if (auto iter = m_times_limit.find(cur_base_name); iter != m_times_limit.cend()) { - return { iter->second.times, iter->second.type }; + if (list.empty()) [[unlikely]] { + Log.warn(__FUNCTION__, "| empty task list"); + return { .task_ptr = nullptr }; + } + + // 如果第一个任务是JustReturn的,那就没必要再截图并计算了 + TaskConstPtr task_ptr = Task.get(list.front()); + if (task_ptr != nullptr && task_ptr->algorithm == AlgorithmType::JustReturn) { + return { .task_ptr = std::move(task_ptr) }; + } + + cv::Mat image = m_reusable.empty() ? ctrler()->get_image() : m_reusable; + m_reusable = cv::Mat(); + PipelineAnalyzer analyzer(image, Rect(), m_inst); + analyzer.set_tasks(list); + + auto res_opt = analyzer.analyze(); + if (!res_opt) { + return { .task_ptr = nullptr }; + } + + task_ptr = std::move(res_opt->task_ptr); + + if (task_ptr->algorithm == AlgorithmType::MatchTemplate) { + auto& raw_result = std::get<0>(res_opt->result); + return { .rect = res_opt->rect, + .reco_detail = json::object { { "score", raw_result.score } }, + .task_ptr = task_ptr }; + } + + if (task_ptr->algorithm == AlgorithmType::OcrDetect) { + auto& raw_result = std::get<1>(res_opt->result); + return { .rect = res_opt->rect, + .reco_detail = json::object { { "score", raw_result.score }, { "text", raw_result.text } }, + .task_ptr = task_ptr }; + } + + return { .rect = res_opt->rect, .task_ptr = task_ptr }; +} + +// action 为 Stop 时返回 Interrupted, 其它返回 Success +ProcessTask::NodeStatus ProcessTask::run_action(const HitDetail& hits) const +{ + const auto& task = hits.task_ptr; + switch (task->action) { + case ProcessTaskAction::ClickRect: + exec_click_task(task->specific_rect); + return NodeStatus::Success; + case ProcessTaskAction::ClickSelf: { + Rect rect = hits.rect; + if (const auto& res_move = task->rect_move; !res_move.empty()) { + rect = rect.move(res_move); } - size_t at_pos = cur_base_name.find('@'); + exec_click_task(rect); + return NodeStatus::Success; + } + case ProcessTaskAction::Swipe: { + size_t param_size = task->special_params.size(); + // Warning: 这里的后两个参数 slope_in 和 slope_out 是 double 类型,但是在 json 中是 int 类型 + exec_swipe_task( + task->specific_rect, + task->rect_move, + (param_size > 0) ? task->special_params.at(0) : 0, + (param_size > 1) ? task->special_params.at(1) : false, + (param_size > 2) ? task->special_params.at(2) : 1, + (param_size > 3) ? task->special_params.at(3) : 1); + return NodeStatus::Success; + } + case ProcessTaskAction::DoNothing: + return NodeStatus::Success; + case ProcessTaskAction::Stop: + Log.info("Action: Stop"); + return NodeStatus::Interrupted; + default: + return NodeStatus::Success; + } +}; + +ProcessTask::NodeStatus ProcessTask::run_task(const HitDetail& hits) +{ + const auto& task = hits.task_ptr; + const auto& task_name = task->name; + int& exec_times = m_exec_times[task_name]; + auto [max_times, limit_type] = calc_time_limit(task); + json::value info = basic_info(); + + if (exec_times >= max_times && limit_type == TimesLimitType::Pre) { + info["what"] = "ExceededLimit"; + info["details"] = json::object { + { "task", task_name }, + { "exec_times", exec_times }, + { "max_times", max_times }, + }; + Log.info("exec times exceeded the limit", info.to_string()); + callback(AsstMsg::SubTaskExtraInfo, info); + return NodeStatus::Runout; + } + + ++exec_times; + + info["details"] = json::object { + { "task", task_name }, + { "exec_times", exec_times }, + { "max_times", max_times }, + { "action", enum_to_string(task->action) }, + { "algorithm", enum_to_string(task->algorithm) }, + { "result", hits.reco_detail }, + }; + + callback(AsstMsg::SubTaskStart, info); + // 允许插件停用ProcessTask + if (!m_enable) { + Log.info("task disabled after SubTaskStart callback, pass", basic_info().to_string()); + return NodeStatus::Interrupted; + } + + // 前置固定延时 + if (!sleep(task->pre_delay)) { + return NodeStatus::Interrupted; + } + + // 根据任务的 action 执行任务 + if (auto result = run_action(hits); result != NodeStatus::Success) { + return result; + } + + status()->set_number(Status::ProcessTaskLastTimePrefix + task_name, time(nullptr)); + + // 减少其他任务的执行次数 + // 例如,进入吃理智药的界面了,相当于上一次点蓝色开始行动没生效 + // 所以要给蓝色开始行动的次数减一 + for (const std::string& other_task : task->reduce_other_times) { + if (int& v = m_exec_times[other_task]; v > 0) { + --v; + Log.trace("task `", task_name, "` reduce `", other_task, "` exec times to", v); + } + } + + // 后置固定延时 + if (!sleep(calc_post_delay(task))) { + return NodeStatus::Interrupted; + } + + for (const std::string& sub : task->sub) { + LogTraceScope("Sub: " + sub); + bool sub_ret = ProcessTask(*this, { sub }).run(); + if (!sub_ret && !task->sub_error_ignored) { + Log.error("Sub error and not ignored", sub); + // 感觉应该把 run 改成 NodeStatus 类型,这样可以知道 sub 的具体执行结果 + return NodeStatus::InternalError; + } + if (need_exit()) { + return NodeStatus::Interrupted; + } + } + + callback(AsstMsg::SubTaskCompleted, info); + // 允许插件停用ProcessTask + if (!m_enable) { + Log.info("task disabled after SubTaskCompleted callback, pass", basic_info().to_string()); + return NodeStatus::Interrupted; + } + + if (exec_times >= max_times && limit_type == TimesLimitType::Post) { + info["what"] = "ExceededLimit"; + info["details"] = json::object { + { "task", task_name }, + { "exec_times", exec_times }, + { "max_times", max_times }, + }; + Log.info("exec times exceeded the limit", info.to_string()); + callback(AsstMsg::SubTaskExtraInfo, info); + return NodeStatus::Runout; + } + + return NodeStatus::Success; +} + +// 保证 first 为 Success 或 Runout 时 second 不为 nullptr +std::pair ProcessTask::find_and_run_task(const TaskList& list) +{ + if (need_exit()) { + return { NodeStatus::Interrupted, nullptr }; + } + + if (!m_enable) { + Log.info("task disabled, pass", basic_info().to_string()); + return { NodeStatus::Interrupted, nullptr }; + } + + m_pre_task_name = std::move(m_last_task_name); + + HitDetail hits; + for (int cur_retry = 0; cur_retry <= m_retry_times; ++cur_retry) { + json::value info = basic_info(); + info["details"] = json::object { + { "to_be_recognized", json::array(list) }, + { "cur_retry", cur_retry }, + { "retry_times", m_retry_times }, + }; + Log.info(info.to_string()); + + if (cur_retry != 0 && !sleep(m_task_delay)) { + return { NodeStatus::Interrupted, nullptr }; + } + if (hits = find_first(list); hits.task_ptr != nullptr) { + m_last_task_name = hits.task_ptr->name; + break; + } + } + + if (hits.task_ptr == nullptr) { + return { NodeStatus::RetryFailed, nullptr }; + } + + if (need_exit()) { + return { NodeStatus::Interrupted, nullptr }; + } + + return { run_task(hits), hits.task_ptr }; +} + +ProcessTask::TimesLimitData ProcessTask::calc_time_limit(TaskConstPtr task) const +{ + std::string task_name = task->name; + // eg. "C@B@A" 的 times_limit 取 "C@B@A", "B@A", "A" 中有设置 m_times_limit 的最靠前者,否则取 task 的值 + while (true) { + if (auto iter = m_times_limit.find(task_name); iter != m_times_limit.cend()) { + return { .times = iter->second.times, .type = iter->second.type }; + } + size_t at_pos = task_name.find('@'); if (at_pos == std::string::npos) { - return { m_cur_task_ptr->max_times, TimesLimitType::Pre }; + return { .times = task->max_times, .type = TimesLimitType::Pre }; } - cur_base_name = cur_base_name.substr(at_pos + 1); + task_name = task_name.substr(at_pos + 1); } } -int asst::ProcessTask::calc_post_delay() const +int ProcessTask::calc_post_delay(TaskConstPtr task) const { - // eg. "C@B@A" 的 max_times 取 "C@B@A", "B@A", "A" 中有 max_times 定义的最靠前者 - for (std::string cur_base_name = m_cur_task_ptr->name;;) { - if (auto iter = m_post_delay.find(cur_base_name); iter != m_post_delay.cend()) { + std::string task_name = task->name; + // eg. "C@B@A" 的 post_delay 取 "C@B@A", "B@A", "A" 中有设置 m_post_delay 的最靠前者,否则取 task 的值 + while (true) { + if (auto iter = m_post_delay.find(task_name); iter != m_post_delay.cend()) { return iter->second; } - size_t at_pos = cur_base_name.find('@'); + size_t at_pos = task_name.find('@'); if (at_pos == std::string::npos) { - return m_cur_task_ptr->post_delay; + return task->post_delay; } - cur_base_name = cur_base_name.substr(at_pos + 1); + task_name = task_name.substr(at_pos + 1); } } -json::value asst::ProcessTask::basic_info() const +json::value ProcessTask::basic_info() const { return AbstractTask::basic_info() | - json::object { { "first", json::array(m_raw_task_name_list) }, { "pre_task", m_pre_task_name } }; + json::object { { "first", json::array(m_begin_task_list) }, { "pre_task", m_pre_task_name } }; } -void ProcessTask::exec_click_task(const Rect& matched_rect) +void ProcessTask::exec_click_task(const Rect& matched_rect) const { ctrler()->click(matched_rect); } -void asst::ProcessTask::exec_swipe_task(const Rect& r1, const Rect& r2, int duration, bool extra_swipe, double slope_in, - double slope_out) +void ProcessTask::exec_swipe_task( + const Rect& r1, + const Rect& r2, + int duration, + bool extra_swipe, + double slope_in, + double slope_out) const { ctrler()->swipe(r1, r2, duration, extra_swipe, slope_in, slope_out); } diff --git a/src/MaaCore/Task/ProcessTask.h b/src/MaaCore/Task/ProcessTask.h index 440723aa56..c37e2cb1b0 100644 --- a/src/MaaCore/Task/ProcessTask.h +++ b/src/MaaCore/Task/ProcessTask.h @@ -6,61 +6,84 @@ namespace asst { - // 流程任务,按照配置文件里的设置的流程运行 - class ProcessTask final : public AbstractTask +// 流程任务,按照配置文件里的设置的流程运行 +class ProcessTask final : public AbstractTask +{ +public: + enum class TimesLimitType { - public: - enum class TimesLimitType - { - Pre, - Post, - }; - - struct TimesLimitData - { - int times = 0; - TimesLimitType type = TimesLimitType::Pre; - }; - - public: - using AbstractTask::AbstractTask; - ProcessTask(const AbstractTask& abs, std::vector tasks_name); - ProcessTask(AbstractTask&& abs, std::vector tasks_name) noexcept; - - virtual ~ProcessTask() override = default; - - virtual bool run() override; - - ProcessTask& set_task_delay(int delay) noexcept; - ProcessTask& set_tasks(std::vector tasks_name) noexcept; - ProcessTask& set_times_limit(std::string name, int limit, TimesLimitType type = TimesLimitType::Pre); - ProcessTask& set_post_delay(std::string name, int delay); - ProcessTask& set_reusable_image(const cv::Mat& reusable); - - const std::string& get_last_task_name() const noexcept { return m_last_task_name; } - - protected: - virtual bool _run() override; - virtual bool on_run_fails() override; - virtual json::value basic_info() const override; - - std::pair calc_time_limit() const; - int calc_post_delay() const; - - void exec_click_task(const Rect& matched_rect); - void exec_swipe_task(const Rect& r1, const Rect& r2, int duration, bool extra_swipe, double slope_in, - double slope_out); - - std::shared_ptr m_cur_task_ptr = nullptr; - std::vector m_raw_task_name_list; - std::vector m_cur_task_name_list; - std::string m_pre_task_name; - std::string m_last_task_name; - std::unordered_map m_post_delay; - std::unordered_map m_times_limit; - std::unordered_map m_exec_times; - static constexpr int TaskDelayUnsetted = -1; - int m_task_delay = TaskDelayUnsetted; - cv::Mat m_reusable; + Pre, + Post, }; + + struct TimesLimitData + { + int times = 0; + TimesLimitType type = TimesLimitType::Pre; + }; + +public: + using AbstractTask::AbstractTask; + ProcessTask(const AbstractTask& abs, std::vector tasks_name); + ProcessTask(AbstractTask&& abs, std::vector tasks_name) noexcept; + + virtual ~ProcessTask() override = default; + + virtual bool run() override; + + ProcessTask& set_task_delay(int delay) noexcept; + ProcessTask& set_tasks(std::vector tasks_name) noexcept; + ProcessTask& set_times_limit(std::string name, int limit, TimesLimitType type = TimesLimitType::Pre); + ProcessTask& set_post_delay(std::string name, int delay); + ProcessTask& set_reusable_image(const cv::Mat& reusable); + + const std::string& get_last_task_name() const noexcept { return m_last_task_name; } + +protected: + enum class NodeStatus + { + Success, + RetryFailed, // 重试次数达到上限,对应 on_error_next + Runout, // 执行次数达到上限,对应 exceeded_next + Interrupted, + InternalError, + }; + + struct HitDetail + { + Rect rect; + json::value reco_detail; + TaskConstPtr task_ptr = nullptr; + }; + + virtual bool _run() override; + virtual json::value basic_info() const override; + + HitDetail find_first(const TaskList& list); + NodeStatus run_action(const HitDetail& hits) const; + NodeStatus run_task(const HitDetail& hits); + std::pair find_and_run_task(const TaskList& list); + + TimesLimitData calc_time_limit(TaskConstPtr task) const; + int calc_post_delay(TaskConstPtr task) const; + + void exec_click_task(const Rect& matched_rect) const; + void exec_swipe_task( + const Rect& r1, + const Rect& r2, + int duration, + bool extra_swipe, + double slope_in, + double slope_out) const; + + TaskList m_begin_task_list; + std::string m_pre_task_name; + std::string m_last_task_name; + std::unordered_map m_post_delay; + std::unordered_map m_times_limit; + std::unordered_map m_exec_times; + static constexpr int TaskDelayUnsetted = -1; + int m_task_delay = TaskDelayUnsetted; + cv::Mat m_reusable; +}; }