diff --git a/src/MaaCore/Config/TaskData.cpp b/src/MaaCore/Config/TaskData.cpp index fb78a14bb7..2fe1372fb8 100644 --- a/src/MaaCore/Config/TaskData.cpp +++ b/src/MaaCore/Config/TaskData.cpp @@ -28,6 +28,24 @@ namespace asst return false; } + bool parse_json_as(const json::value& input, AlgorithmType& output) + { + if (input.is_string()) { + output = get_algorithm_type(input.as_string()); + return output != AlgorithmType::Invalid; + } + return false; + } + + bool parse_json_as(const json::value& input, ProcessTaskAction& output) + { + if (input.is_string()) { + output = get_action_type(input.as_string()); + return output != ProcessTaskAction::Invalid; + } + return false; + } + // std::pair <- [first, second] template requires(requires(const json::value& input, FirstT x, SecondT y) { @@ -95,7 +113,8 @@ namespace asst template requires(std::constructible_from || std::constructible_from>) - bool get_and_check_value(const json::value& input, const std::string& key, OutT& output, DefaultT&& default_val) + bool get_value_or(std::string_view repr, const json::value& input, const std::string& key, OutT& output, + DefaultT&& default_val) { auto opt = input.find(key); if (!opt) { @@ -108,9 +127,22 @@ namespace asst return true; } if (parse_json_as(*opt, output)) { +#ifdef ASST_DEBUG + // 如果有默认值,检查是否与默认值相同 + if constexpr (std::constructible_from) { + if (output == default_val) { + Log.warn("Value of", key, "in", repr, "is same as default value"); + } + } + else { + if (output == default_val()) { + Log.warn("Value of", key, "in", repr, "is same as default value"); + } + } +#endif return true; } - Log.error("Invalid type of", key, "in", input.to_string()); + Log.error("Invalid type of", key, "in", repr); return false; } } // namespace asst @@ -119,40 +151,67 @@ const std::unordered_set& asst::TaskData::get_templ_required() cons { return m_templ_required; } -std::shared_ptr asst::TaskData::get_raw(std::string_view name) +asst::TaskData::taskptr_t asst::TaskData::get_raw(std::string_view name) { - if (!generate_task_and_its_base(name, true)) { + if (!generate_task_and_its_base(name, true)) [[unlikely]] { return nullptr; } - if (auto it = m_raw_all_tasks_info.find(name); it != m_raw_all_tasks_info.cend()) [[likely]] { + if (auto it = m_raw_all_tasks_info.find(name); it != m_raw_all_tasks_info.cend()) { return it->second; } - size_t at_pos = name.find('@'); - if (at_pos == std::string_view::npos) [[unlikely]] { - return nullptr; - } - // `@` 前面的字符长度 - size_t name_len = at_pos; - auto base_task_iter = get_raw(name.substr(name_len + 1)); - if (base_task_iter == nullptr) [[unlikely]] { + size_t name_len = name.find('@'); + if (name_len == std::string_view::npos) [[unlikely]] { return nullptr; } - std::string_view derived_task_name = name.substr(0, name_len); - return _generate_task_info(base_task_iter, derived_task_name); + // 隐式 TemplateTask + if (auto base_task_iter = get_raw(name.substr(name_len + 1))) { + return _generate_task_info(base_task_iter, name.substr(0, name_len)); + } + + return nullptr; } -std::shared_ptr asst::TaskData::get(std::string_view name) +asst::TaskData::taskptr_t asst::TaskData::get(std::string_view name) { - // 普通 task 或已经生成过的 `@` 型 task + // 普通任务 或 已经生成过的高级任务 if (auto it = m_all_tasks_info.find(name); it != m_all_tasks_info.cend()) [[likely]] { return it->second; } - return expand_task(name, get_raw(name)).value_or(nullptr); + auto raw_task = get_raw(name); + auto task = _generate_task_info(raw_task); + if (!task) [[unlikely]] { + return nullptr; + } + bool changed = false; // 任务列表是否发生变化(是否包含虚任务) +#define ASST_TASKDATA_GET_IF_BRANCH(list, m) \ + task->list.clear(); \ + if (!compile_tasklist(task->list, raw_task->list, name, changed, m)) [[unlikely]] { \ + Log.error("Generate task_list", std::string(name) + "->" #list, "failed."); \ + return nullptr; \ + } + // 展开五个任务列表中的虚任务 + ASST_TASKDATA_GET_IF_BRANCH(next, false); + ASST_TASKDATA_GET_IF_BRANCH(sub, true); + ASST_TASKDATA_GET_IF_BRANCH(exceeded_next, false); + ASST_TASKDATA_GET_IF_BRANCH(on_error_next, false); + ASST_TASKDATA_GET_IF_BRANCH(reduce_other_times, true); +#undef ASST_TASKDATA_GET_IF_BRANCH + + constexpr size_t MAX_TASKS_SIZE = 65535; + if (m_all_tasks_info.size() < MAX_TASKS_SIZE) [[likely]] { + // 保存最终生成的任务,下次查询时可以直接返回 + return insert_or_assign_task(name, changed ? task : raw_task).first->second; + } + else { + // 个数超过上限时不保存,直接返回,防止内存占用过大 + Log.warn("Task count has exceeded the upper limit:", MAX_TASKS_SIZE, "current task:", name); + return changed ? task : raw_task; + } } bool asst::TaskData::lazy_parse(const json::value& json) @@ -300,13 +359,308 @@ void asst::TaskData::set_task_base(const std::string_view task_name, std::string clear_tasks(); } +bool asst::TaskData::generate_task_and_its_base(std::string_view name, bool must_true) +{ + auto generate_task = [&](std::string_view name, std::string_view prefix, taskptr_t base_ptr, + const json::value& task_json) { + auto task_info_ptr = generate_task_info(name, task_json, base_ptr, prefix); + if (task_info_ptr == nullptr) { + return false; + } + m_task_status[task_name_view(name)] = NotToBeGenerate; + // 保存虚任务未展开的任务 + insert_or_assign_raw_task(name, task_info_ptr); + return true; + }; + switch (m_task_status[task_name_view(name)]) { + case NotToBeGenerate: + // 已经显式生成 + if (m_raw_all_tasks_info.contains(name)) { + return true; + } + + // 隐式生成的资源 + if (size_t p = name.find('@'); p != std::string::npos) { + return generate_task_and_its_base(name.substr(p + 1), must_true); + } + + m_task_status[name] = NotExists; + [[fallthrough]]; + case NotExists: + if (must_true) { + // 必须有名字为 name 的资源 + Log.error("Unknown task:", name); + } + // 不一定必须有名字为 name 的资源,例如 Roguelike@Abandon 不必有 Abandon. + return false; + case ToBeGenerate: { + if (!m_json_all_tasks_info.contains(name)) [[unlikely]] { + // 这段正常情况来说是不可能的,除非有 string_view 引用失效 + Log.error("Unexcepted ToBeGenerate task:", name); + return false; + } + + m_task_status[name] = Generating; + + const json::value& task_json = m_json_all_tasks_info.at(name); + + // BaseTask + if (auto opt = task_json.find("baseTask")) { + std::string base = opt.value(); + return generate_task_and_its_base(base, must_true) && generate_task(name, "", get_raw(base), task_json); + } + + // TemplateTask + if (size_t p = name.find('@'); p != std::string::npos) { + if (std::string_view base = name.substr(p + 1); generate_task_and_its_base(base, false)) { +#ifdef ASST_DEBUG + if (task_json.as_object().empty() && get_raw(base)->algorithm != AlgorithmType::MatchTemplate) { + // 多余的空任务 + Log.warn("Task", name, "is a redundant empty task because it is not MatchTemplate"); + } +#endif + return generate_task(name, name.substr(0, p), get_raw(base), task_json); + } + } + return generate_task(name, "", nullptr, task_json); + } + [[unlikely]] case Generating: + Log.error("Task", name, "is generated cyclically"); + return false; + [[unlikely]] default: + Log.error("Task", name, "has unknown status"); + return false; + } +} + +asst::TaskData::taskptr_t asst::TaskData::generate_task_info(std::string_view name, const json::value& task_json, + taskptr_t default_ptr, std::string_view task_prefix) +{ + if (default_ptr == nullptr) { + default_ptr = default_task_info_ptr; + task_prefix = ""; + } + + // 获取 algorithm 并按照 algorithm 生成 TaskInfo + AlgorithmType algorithm; + get_value_or(name, task_json, "algorithm", algorithm, default_ptr->algorithm); + + taskptr_t task_ptr = nullptr; + switch (algorithm) { + case AlgorithmType::MatchTemplate: + task_ptr = generate_match_task_info(name, task_json, std::dynamic_pointer_cast(default_ptr)); + break; + case AlgorithmType::OcrDetect: + task_ptr = generate_ocr_task_info(name, task_json, std::dynamic_pointer_cast(default_ptr)); + break; + case AlgorithmType::Hash: + task_ptr = generate_hash_task_info(name, task_json, std::dynamic_pointer_cast(default_ptr)); + break; + case AlgorithmType::JustReturn: + task_ptr = std::make_shared(); + break; + default: + Log.error("Unknown algorithm in task", name); + return nullptr; + } + + if (task_ptr == nullptr) { + return nullptr; + } + + // 不管什么algorithm,都有基础成员(next, roi, 等等) + if (!append_base_task_info(task_ptr, name, task_json, default_ptr, task_prefix)) { + return nullptr; + } + task_ptr->algorithm = algorithm; + task_ptr->name = name; + return task_ptr; +} + +asst::TaskData::taskptr_t asst::TaskData::generate_match_task_info(std::string_view name, const json::value& task_json, + std::shared_ptr default_ptr) +{ + if (default_ptr == nullptr) { + default_ptr = default_match_task_info_ptr; + } + auto match_task_info_ptr = std::make_shared(); + if (!get_value_or(name, task_json, "template", match_task_info_ptr->templ_names, + [&]() { return std::vector { std::string(name) + ".png" }; })) { + return nullptr; + } + + m_templ_required.insert(match_task_info_ptr->templ_names.begin(), match_task_info_ptr->templ_names.end()); + + // 其余若留空则继承模板任务 + + auto threshold_opt = task_json.find("templThreshold"); + if (!threshold_opt) { + match_task_info_ptr->templ_thresholds = default_ptr->templ_thresholds; + match_task_info_ptr->templ_thresholds.resize(match_task_info_ptr->templ_names.size(), + default_ptr->templ_thresholds.back()); + } + else if (threshold_opt->is_number()) { + // 单个数值时,所有模板都使用这个阈值 + match_task_info_ptr->templ_thresholds.resize(match_task_info_ptr->templ_names.size(), + threshold_opt->as_double()); + } + else if (threshold_opt->is_array()) { + ranges::copy(threshold_opt->as_array() | views::transform(&ranges::range_value_t::as_double), + std::back_inserter(match_task_info_ptr->templ_thresholds)); + } + else { + Log.error("Invalid templThreshold type in task", name); + return nullptr; + } + + if (match_task_info_ptr->templ_names.size() != match_task_info_ptr->templ_thresholds.size()) { + Log.error("Template count and templThreshold count not match in task", name); + return nullptr; + } + + if (match_task_info_ptr->templ_names.size() == 0 || match_task_info_ptr->templ_thresholds.size() == 0) { + Log.error("Template or templThreshold is empty in task", name); + return nullptr; + } + + get_value_or(name, task_json, "maskRange", match_task_info_ptr->mask_range, default_ptr->mask_range); + return match_task_info_ptr; +} + +asst::TaskData::taskptr_t asst::TaskData::generate_ocr_task_info(std::string_view name, const json::value& task_json, + std::shared_ptr default_ptr) +{ + if (default_ptr == nullptr) { + default_ptr = default_ocr_task_info_ptr; + } + auto ocr_task_info_ptr = std::make_shared(); + + // text 不允许为字符串,必须是字符串数组,不能用 get_value_or + auto array_opt = task_json.find("text"); + ocr_task_info_ptr->text = array_opt ? to_string_list(array_opt.value()) : default_ptr->text; +#ifdef ASST_DEBUG + if (!array_opt && default_ptr->text.empty()) { + Log.warn("Ocr task", name, "has implicit empty text."); + } +#endif + get_value_or(name, task_json, "fullMatch", ocr_task_info_ptr->full_match, default_ptr->full_match); + get_value_or(name, task_json, "isAscii", ocr_task_info_ptr->is_ascii, default_ptr->is_ascii); + get_value_or(name, task_json, "withoutDet", ocr_task_info_ptr->without_det, default_ptr->without_det); + get_value_or(name, task_json, "replaceFull", ocr_task_info_ptr->replace_full, default_ptr->replace_full); + get_value_or(name, task_json, "ocrReplace", ocr_task_info_ptr->replace_map, default_ptr->replace_map); + return ocr_task_info_ptr; +} + +asst::TaskData::taskptr_t asst::TaskData::generate_hash_task_info(std::string_view name, const json::value& task_json, + std::shared_ptr default_ptr) +{ + if (default_ptr == nullptr) { + default_ptr = default_hash_task_info_ptr; + } + auto hash_task_info_ptr = std::make_shared(); + // hash 不允许为字符串,必须是字符串数组,不能用 get_value_or + auto array_opt = task_json.find("hash"); + hash_task_info_ptr->hashes = array_opt ? to_string_list(array_opt.value()) : default_ptr->hashes; +#ifdef ASST_DEBUG + if (!array_opt && default_ptr->hashes.empty()) { + Log.warn("Hash task", name, "has implicit empty hashes."); + } +#endif + get_value_or(name, task_json, "threshold", hash_task_info_ptr->dist_threshold, default_ptr->dist_threshold); + get_value_or(name, task_json, "maskRange", hash_task_info_ptr->mask_range, default_ptr->mask_range); + get_value_or(name, task_json, "bound", hash_task_info_ptr->bound, default_ptr->bound); + return hash_task_info_ptr; +} + +bool asst::TaskData::append_base_task_info(taskptr_t task_info_ptr, std::string_view name, const json::value& task_json, + taskptr_t default_ptr, std::string_view task_prefix) +{ + if (default_ptr == nullptr) { + default_ptr = default_task_info_ptr; + } + + get_value_or(name, task_json, "action", task_info_ptr->action, default_ptr->action); + get_value_or(name, task_json, "cache", task_info_ptr->cache, default_ptr->cache); + get_value_or(name, task_json, "maxTimes", task_info_ptr->max_times, default_ptr->max_times); + get_value_or(name, task_json, "exceededNext", task_info_ptr->exceeded_next, + [&]() { return append_prefix(default_ptr->exceeded_next, task_prefix); }); + get_value_or(name, task_json, "onErrorNext", task_info_ptr->on_error_next, + [&]() { return append_prefix(default_ptr->on_error_next, task_prefix); }); + get_value_or(name, task_json, "preDelay", task_info_ptr->pre_delay, default_ptr->pre_delay); + get_value_or(name, task_json, "postDelay", task_info_ptr->post_delay, default_ptr->post_delay); + get_value_or(name, task_json, "reduceOtherTimes", task_info_ptr->reduce_other_times, + [&]() { return append_prefix(default_ptr->reduce_other_times, task_prefix); }); + get_value_or(name, task_json, "roi", task_info_ptr->roi, default_ptr->roi); + get_value_or(name, task_json, "sub", task_info_ptr->sub, + [&]() { return append_prefix(default_ptr->sub, task_prefix); }); + get_value_or(name, task_json, "subErrorIgnored", task_info_ptr->sub_error_ignored, default_ptr->sub_error_ignored); + get_value_or(name, task_json, "next", task_info_ptr->next, + [&]() { return append_prefix(default_ptr->next, task_prefix); }); + get_value_or(name, task_json, "rectMove", task_info_ptr->rect_move, default_ptr->rect_move); + get_value_or(name, task_json, "specificRect", task_info_ptr->specific_rect, default_ptr->specific_rect); + get_value_or(name, task_json, "specialParams", task_info_ptr->special_params, default_ptr->special_params); +#ifdef ASST_DEBUG + // Debug 模式下检查 roi 是否超出边界 + if (auto [x, y, w, h] = task_info_ptr->roi; x + w > WindowWidthDefault || y + h > WindowHeightDefault) { + Log.warn(name, "roi is out of bounds"); + } +#endif + return true; +} + +std::shared_ptr asst::TaskData::_default_match_task_info() +{ + auto match_task_info_ptr = std::make_shared(); + match_task_info_ptr->templ_names = { "__INVALID__" }; + match_task_info_ptr->templ_thresholds = { TemplThresholdDefault }; + + return match_task_info_ptr; +} + +std::shared_ptr asst::TaskData::_default_ocr_task_info() +{ + auto ocr_task_info_ptr = std::make_shared(); + ocr_task_info_ptr->full_match = false; + ocr_task_info_ptr->is_ascii = false; + ocr_task_info_ptr->without_det = false; + ocr_task_info_ptr->replace_full = false; + + return ocr_task_info_ptr; +} + +std::shared_ptr asst::TaskData::_default_hash_task_info() +{ + auto hash_task_info_ptr = std::make_shared(); + hash_task_info_ptr->dist_threshold = 0; + hash_task_info_ptr->bound = true; + + return hash_task_info_ptr; +} + +asst::TaskData::taskptr_t asst::TaskData::_default_task_info() +{ + auto task_info_ptr = std::make_shared(); + task_info_ptr->algorithm = AlgorithmType::MatchTemplate; + task_info_ptr->action = ProcessTaskAction::DoNothing; + task_info_ptr->cache = true; + task_info_ptr->max_times = INT_MAX; + task_info_ptr->pre_delay = 0; + task_info_ptr->post_delay = 0; + task_info_ptr->roi = Rect(); + task_info_ptr->sub_error_ignored = false; + task_info_ptr->rect_move = Rect(); + task_info_ptr->specific_rect = Rect(); + + return task_info_ptr; +} + // new_tasks 是目的任务列表 // raw_tasks 是源任务表达式列表 // task_name 是任务名 // task_changed 记录 raw_tasks 在转换为 new_tasks 时是否发生变化 -// multi 表示是否允许重复 -bool asst::TaskData::explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_tasks, std::string_view task_name, - bool& task_changed, bool multi) +// allow_duplicate 是否允许重复 +bool asst::TaskData::compile_tasklist(tasklist_t& new_tasks, const tasklist_t& raw_tasks, std::string_view task_name, + bool& task_changed, bool allow_duplicate) { std::unordered_set tasks_set; // 记录任务列表中已有的任务(内容元素与 new_tasks 基本一致) @@ -324,7 +678,6 @@ bool asst::TaskData::explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_ [[maybe_unused]] constexpr symbl_t symbl_sub = 10; [[maybe_unused]] constexpr symbl_t symbl_name_start = 11; - [[maybe_unused]] constexpr symbl_t symbl_name_sub = 11; [[maybe_unused]] constexpr symbl_t symbl_name_next = 12; [[maybe_unused]] constexpr symbl_t symbl_name_on_error_next = 13; @@ -356,7 +709,7 @@ bool asst::TaskData::explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_ "none", // 18 }; - [[maybe_unused]] auto is_symbl_name = [&](symbl_t x) { return x >= symbl_name_start; }; + auto is_symbl_name = [&](symbl_t x) { return x >= symbl_name_start; }; auto is_symbl_subtask_type = [&](symbl_t x) { switch (x) { case symbl_name_sub: @@ -464,8 +817,8 @@ bool asst::TaskData::explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_ else if (type == #t) \ { \ bool task_changed = false; \ - if (!explain_tasks(*ret, other_task_info_ptr->t, task_name, task_changed, m)) { \ - Log.error("Failed to explain task", task + "->" #t, "while perform op", symbl_table[op], "in", task_expr, \ + if (!compile_tasklist(*ret, other_task_info_ptr->t, task_name, task_changed, m)) { \ + Log.error("Failed to compile task", task + "->" #t, "while perform op", symbl_table[op], "in", task_expr, \ "of task:", task_name); \ return std::nullopt; \ } \ @@ -500,7 +853,7 @@ bool asst::TaskData::explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_ }; for (std::string_view task_expr : raw_tasks) { - if (task_expr.empty() || (!multi && tasks_set.contains(task_expr))) { + if (task_expr.empty() || (!allow_duplicate && tasks_set.contains(task_expr))) { task_changed = true; continue; } @@ -581,12 +934,12 @@ bool asst::TaskData::explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_ $subtask = $subtask_type ( $tasks ) - $lambda_task = { $subtask , ... } // 推迟实现 - | { $tasks } // 推迟实现;不写 subtask_type 默认为 sub + $lambda_task = { $subtask , ... } // 暂未实现 + | { $tasks } // 暂未实现;不写 subtask_type 默认为 sub $parens = # $sharp_type | $name - | $name $lambda_task // 推迟实现 + | $name $lambda_task // 暂未实现 | ( $tasks ) $top_tasks = $top_tasks @ $parens @@ -605,7 +958,7 @@ bool asst::TaskData::explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_ using decode_func_t = std::function()>; decode_func_t decode_name, decode_tasks, decode_multasks, decode_vtasks, decode_parens; decode_name = [&]() -> std::optional { - if (*cur >= symbl_name_start) { + if (is_symbl_name(*cur)) { return std::make_shared(tasklist_t { symbols_table[*(cur++)] }); } return std::nullopt; @@ -665,7 +1018,7 @@ bool asst::TaskData::explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_ if (!r) return std::nullopt; return perform_op(task_expr, symbl_sharp, nullptr, *r); } - if (*cur >= symbl_name_start) { + if (is_symbl_name(*cur)) { return decode_name(); } Log.error("Invalid symbol", *cur, "at", cur - symbol_stream.cbegin(), "in", symbol_stream); @@ -677,7 +1030,7 @@ bool asst::TaskData::explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_ } for (const auto& task : **opt) { - if (task.empty() || (!multi && tasks_set.contains(task))) { + if (task.empty() || (!allow_duplicate && tasks_set.contains(task))) { task_changed = true; continue; } @@ -691,353 +1044,6 @@ bool asst::TaskData::explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_ return true; } -bool asst::TaskData::generate_task_and_its_base(std::string_view name, bool must_true) -{ - auto generate_task = [&](std::string_view name, std::string_view prefix, taskptr_t base_ptr, - const json::value& task_json) { - auto task_info_ptr = generate_task_info(name, task_json, base_ptr, prefix); - if (task_info_ptr == nullptr) { - return false; - } - m_task_status[task_name_view(name)] = NotToBeGenerate; - insert_or_assign_raw_task(name, task_info_ptr); - return true; - }; - switch (m_task_status[task_name_view(name)]) { - case NotToBeGenerate: - // 已经显式生成 - if (m_raw_all_tasks_info.contains(name)) { - return true; - } - - // 隐式生成的资源 - if (size_t p = name.find('@'); p != std::string::npos) { - return generate_task_and_its_base(name.substr(p + 1), must_true); - } - - m_task_status[name] = NotExists; - [[fallthrough]]; - case NotExists: - if (must_true) { - // 必须有名字为 name 的资源 - Log.error("Unknown task:", name); - } - // 不一定必须有名字为 name 的资源,例如 Roguelike@Abandon 不必有 Abandon. - return false; - case ToBeGenerate: { - if (!m_json_all_tasks_info.contains(name)) [[unlikely]] { - // 这段正常情况来说是不可能的,除非有 string_view 引用失效 - Log.error("Unexcepted ToBeGenerate task:", name); - return false; - } - - m_task_status[name] = Generating; - - const json::value& task_json = m_json_all_tasks_info.at(name); - - // BaseTask - if (auto opt = task_json.find("baseTask")) { - std::string base = opt.value(); - return generate_task_and_its_base(base, must_true) && generate_task(name, "", get_raw(base), task_json); - } - - // TemplateTask - if (size_t p = name.find('@'); p != std::string::npos) { - if (std::string_view base = name.substr(p + 1); generate_task_and_its_base(base, false)) { -#ifdef ASST_DEBUG - if (task_json.as_object().empty() && get_raw(base)->algorithm != AlgorithmType::MatchTemplate) { - // 多余的空任务 - Log.warn("Task", name, "is a redundant empty task because it is not MatchTemplate"); - } -#endif - return generate_task(name, name.substr(0, p), get_raw(base), task_json); - } - } - return generate_task(name, "", nullptr, task_json); - } - [[unlikely]] case Generating: - Log.error("Task", name, "is generated cyclically"); - return false; - [[unlikely]] default: - Log.error("Task", name, "has unknown status"); - return false; - } -} - -std::optional asst::TaskData::expand_task(std::string_view name, taskptr_t old_task) -{ - if (old_task == nullptr) [[unlikely]] { - return std::nullopt; - } - auto task_info = _generate_task_info(old_task); // 复制一份 - bool task_changed = false; // 任务列表是否发生变化 -#define ASST_TASKDATA_EXPEND_TASK_IF_BRANCH(type, m) \ - task_info->type.clear(); \ - if (!explain_tasks(task_info->type, old_task->type, name, task_changed, m)) [[unlikely]] { \ - Log.error("Generate task_list", std::string(name) + "->" #type, "failed."); \ - return std::nullopt; \ - } - ASST_TASKDATA_EXPEND_TASK_IF_BRANCH(next, false); - ASST_TASKDATA_EXPEND_TASK_IF_BRANCH(sub, true); - ASST_TASKDATA_EXPEND_TASK_IF_BRANCH(exceeded_next, false); - ASST_TASKDATA_EXPEND_TASK_IF_BRANCH(on_error_next, false); - ASST_TASKDATA_EXPEND_TASK_IF_BRANCH(reduce_other_times, true); -#undef ASST_TASKDATA_EXPEND_TASK_IF_BRANCH - - // tasks 个数超过上限时不再 emplace,返回临时值 - constexpr size_t MAX_TASKS_SIZE = 65535; - if (m_all_tasks_info.size() < MAX_TASKS_SIZE) [[likely]] { - return insert_or_assign_task(name, task_changed ? task_info : old_task).first->second; - } - else { -#ifdef ASST_DEBUG - Log.debug("Task count has exceeded the upper limit:", MAX_TASKS_SIZE, "current task:", name); -#endif // ASST_DEBUG - return task_changed ? task_info : old_task; - } -} - -asst::TaskData::taskptr_t asst::TaskData::generate_task_info(std::string_view name, const json::value& task_json, - taskptr_t default_ptr, std::string_view task_prefix) -{ - if (default_ptr == nullptr) { - default_ptr = default_task_info_ptr; - task_prefix = ""; - } - - // 获取 algorithm 并按照 algorithm 生成 TaskInfo - auto algorithm = default_ptr->algorithm; - taskptr_t default_derived_ptr = default_ptr; - if (auto opt = task_json.find("algorithm")) { - std::string algorithm_str = opt.value(); - algorithm = get_algorithm_type(algorithm_str); - if (default_ptr->algorithm != algorithm) { - // 相同 algorithm 时才继承派生类成员 - default_derived_ptr = nullptr; - } - } - taskptr_t task_info_ptr = nullptr; - switch (algorithm) { - case AlgorithmType::MatchTemplate: - task_info_ptr = - generate_match_task_info(name, task_json, std::dynamic_pointer_cast(default_derived_ptr)); - break; - case AlgorithmType::OcrDetect: - task_info_ptr = - generate_ocr_task_info(name, task_json, std::dynamic_pointer_cast(default_derived_ptr)); - break; - case AlgorithmType::Hash: - task_info_ptr = - generate_hash_task_info(name, task_json, std::dynamic_pointer_cast(default_derived_ptr)); - break; - case AlgorithmType::JustReturn: - task_info_ptr = std::make_shared(); - break; - default: - Log.error("Unknown algorithm in task", name); - return nullptr; - } - - if (task_info_ptr == nullptr) { - return nullptr; - } - - // 不管什么algorithm,都有基础成员(next, roi, 等等) - if (!append_base_task_info(task_info_ptr, name, task_json, default_ptr, task_prefix)) { - return nullptr; - } - task_info_ptr->algorithm = algorithm; - task_info_ptr->name = name; - return task_info_ptr; -} - -asst::TaskData::taskptr_t asst::TaskData::generate_match_task_info(std::string_view name, const json::value& task_json, - std::shared_ptr default_ptr) -{ - if (default_ptr == nullptr) { - default_ptr = default_match_task_info_ptr; - } - auto match_task_info_ptr = std::make_shared(); - if (!get_and_check_value(task_json, "template", match_task_info_ptr->templ_names, - [&]() { return std::vector { std::string(name) + ".png" }; })) { - return nullptr; - } - - m_templ_required.insert(match_task_info_ptr->templ_names.begin(), match_task_info_ptr->templ_names.end()); - - // 其余若留空则继承模板任务 - - auto threshold_opt = task_json.find("templThreshold"); - if (!threshold_opt) { - match_task_info_ptr->templ_thresholds = default_ptr->templ_thresholds; - match_task_info_ptr->templ_thresholds.resize(match_task_info_ptr->templ_names.size(), - default_ptr->templ_thresholds.back()); - } - else if (threshold_opt->is_number()) { - // 单个数值时,所有模板都使用这个阈值 - match_task_info_ptr->templ_thresholds.resize(match_task_info_ptr->templ_names.size(), - threshold_opt->as_double()); - } - else if (threshold_opt->is_array()) { - ranges::copy(threshold_opt->as_array() | views::transform(&ranges::range_value_t::as_double), - std::back_inserter(match_task_info_ptr->templ_thresholds)); - } - else { - Log.error("Invalid templThreshold type in task", name); - return nullptr; - } - - if (match_task_info_ptr->templ_names.size() != match_task_info_ptr->templ_thresholds.size()) { - Log.error("Template count and templThreshold count not match in task", name); - return nullptr; - } - - if (match_task_info_ptr->templ_names.size() == 0 || match_task_info_ptr->templ_thresholds.size() == 0) { - Log.error("Template or templThreshold is empty in task", name); - return nullptr; - } - - get_and_check_value(task_json, "maskRange", match_task_info_ptr->mask_range, default_ptr->mask_range); - return match_task_info_ptr; -} - -asst::TaskData::taskptr_t asst::TaskData::generate_ocr_task_info([[maybe_unused]] std::string_view name, - const json::value& task_json, - std::shared_ptr default_ptr) -{ - if (default_ptr == nullptr) { - default_ptr = default_ocr_task_info_ptr; - } - auto ocr_task_info_ptr = std::make_shared(); - - // text 不允许为字符串,必须是字符串数组,不能用 get_and_check_value - auto array_opt = task_json.find("text"); - ocr_task_info_ptr->text = array_opt ? to_string_list(array_opt.value()) : default_ptr->text; -#ifdef ASST_DEBUG - if (!array_opt && default_ptr->text.empty()) { - Log.warn("Ocr task", name, "has implicit empty text."); - } -#endif - get_and_check_value(task_json, "fullMatch", ocr_task_info_ptr->full_match, default_ptr->full_match); - get_and_check_value(task_json, "isAscii", ocr_task_info_ptr->is_ascii, default_ptr->is_ascii); - get_and_check_value(task_json, "withoutDet", ocr_task_info_ptr->without_det, default_ptr->without_det); - get_and_check_value(task_json, "replaceFull", ocr_task_info_ptr->replace_full, default_ptr->replace_full); - get_and_check_value(task_json, "ocrReplace", ocr_task_info_ptr->replace_map, default_ptr->replace_map); - return ocr_task_info_ptr; -} - -asst::TaskData::taskptr_t asst::TaskData::generate_hash_task_info([[maybe_unused]] std::string_view name, - const json::value& task_json, - std::shared_ptr default_ptr) -{ - if (default_ptr == nullptr) { - default_ptr = default_hash_task_info_ptr; - } - auto hash_task_info_ptr = std::make_shared(); - // hash 不允许为字符串,必须是字符串数组,不能用 get_and_check_value - auto array_opt = task_json.find("hash"); - hash_task_info_ptr->hashes = array_opt ? to_string_list(array_opt.value()) : default_ptr->hashes; -#ifdef ASST_DEBUG - if (!array_opt && default_ptr->hashes.empty()) { - Log.warn("Hash task", name, "has implicit empty hashes."); - } -#endif - get_and_check_value(task_json, "threshold", hash_task_info_ptr->dist_threshold, default_ptr->dist_threshold); - get_and_check_value(task_json, "maskRange", hash_task_info_ptr->mask_range, default_ptr->mask_range); - get_and_check_value(task_json, "bound", hash_task_info_ptr->bound, default_ptr->bound); - return hash_task_info_ptr; -} - -bool asst::TaskData::append_base_task_info(taskptr_t task_info_ptr, std::string_view name, const json::value& task_json, - taskptr_t default_ptr, std::string_view task_prefix) -{ - if (default_ptr == nullptr) { - default_ptr = default_task_info_ptr; - } - if (auto opt = task_json.find("action")) { - std::string action = opt.value(); - task_info_ptr->action = get_action_type(action); - if (task_info_ptr->action == ProcessTaskAction::Invalid) [[unlikely]] { - Log.error("Unknown action:", action, ", Task:", name); - return false; - } - } - else { - task_info_ptr->action = default_ptr->action; - } - get_and_check_value(task_json, "cache", task_info_ptr->cache, default_ptr->cache); - get_and_check_value(task_json, "maxTimes", task_info_ptr->max_times, default_ptr->max_times); - get_and_check_value(task_json, "exceededNext", task_info_ptr->exceeded_next, - [&]() { return append_prefix(default_ptr->exceeded_next, task_prefix); }); - get_and_check_value(task_json, "onErrorNext", task_info_ptr->on_error_next, - [&]() { return append_prefix(default_ptr->on_error_next, task_prefix); }); - get_and_check_value(task_json, "preDelay", task_info_ptr->pre_delay, default_ptr->pre_delay); - get_and_check_value(task_json, "postDelay", task_info_ptr->post_delay, default_ptr->post_delay); - get_and_check_value(task_json, "reduceOtherTimes", task_info_ptr->reduce_other_times, - [&]() { return append_prefix(default_ptr->reduce_other_times, task_prefix); }); - get_and_check_value(task_json, "roi", task_info_ptr->roi, default_ptr->roi); -#ifdef ASST_DEBUG - if (auto [x, y, w, h] = task_info_ptr->roi; x + w > WindowWidthDefault || y + h > WindowHeightDefault) { - Log.warn(name, "roi is out of bounds"); - } -#endif - get_and_check_value(task_json, "sub", task_info_ptr->sub, - [&]() { return append_prefix(default_ptr->sub, task_prefix); }); - get_and_check_value(task_json, "subErrorIgnored", task_info_ptr->sub_error_ignored, default_ptr->sub_error_ignored); - get_and_check_value(task_json, "next", task_info_ptr->next, - [&]() { return append_prefix(default_ptr->next, task_prefix); }); - get_and_check_value(task_json, "rectMove", task_info_ptr->rect_move, default_ptr->rect_move); - get_and_check_value(task_json, "specificRect", task_info_ptr->specific_rect, default_ptr->specific_rect); - get_and_check_value(task_json, "specialParams", task_info_ptr->special_params, default_ptr->special_params); - return true; -} - -std::shared_ptr asst::TaskData::_default_match_task_info() -{ - auto match_task_info_ptr = std::make_shared(); - match_task_info_ptr->templ_names = { "__INVALID__" }; - match_task_info_ptr->templ_thresholds = { TemplThresholdDefault }; - - return match_task_info_ptr; -} - -std::shared_ptr asst::TaskData::_default_ocr_task_info() -{ - auto ocr_task_info_ptr = std::make_shared(); - ocr_task_info_ptr->full_match = false; - ocr_task_info_ptr->is_ascii = false; - ocr_task_info_ptr->without_det = false; - ocr_task_info_ptr->replace_full = false; - - return ocr_task_info_ptr; -} - -std::shared_ptr asst::TaskData::_default_hash_task_info() -{ - auto hash_task_info_ptr = std::make_shared(); - hash_task_info_ptr->dist_threshold = 0; - hash_task_info_ptr->bound = true; - - return hash_task_info_ptr; -} - -asst::TaskData::taskptr_t asst::TaskData::_default_task_info() -{ - auto task_info_ptr = std::make_shared(); - task_info_ptr->algorithm = AlgorithmType::MatchTemplate; - task_info_ptr->action = ProcessTaskAction::DoNothing; - task_info_ptr->cache = true; - task_info_ptr->max_times = INT_MAX; - task_info_ptr->pre_delay = 0; - task_info_ptr->post_delay = 0; - task_info_ptr->roi = Rect(); - task_info_ptr->sub_error_ignored = false; - task_info_ptr->rect_move = Rect(); - task_info_ptr->specific_rect = Rect(); - - return task_info_ptr; -} - #ifdef ASST_DEBUG // 为了解决类似 beddc7c828126c678391e0b4da288db6d2c2d58a 导致的问题,加载的时候做一个语法检查 // 主要是处理是否包含未知键值的问题 @@ -1048,26 +1054,25 @@ bool asst::TaskData::syntax_check(std::string_view task_name, const json::value& static const std::unordered_map> allowed_key_under_algorithm = { { AlgorithmType::Invalid, { - "action", "algorithm", "baseTask", "cache", "exceededNext", "fullMatch", - "hash", "isAscii", "maskRange", "maxTimes", "next", "ocrReplace", - "onErrorNext", "postDelay", "preDelay", "rectMove", "reduceOtherTimes", "replaceFull", "roi", - "specialParams", "sub", "subErrorIgnored", "templThreshold", "template", "text", - "threshold", "withoutDet", + "action", "algorithm", "baseTask", "cache", "exceededNext", "fullMatch", + "hash", "isAscii", "maskRange", "maxTimes", "next", "ocrReplace", + "onErrorNext", "postDelay", "preDelay", "rectMove", "reduceOtherTimes", "replaceFull", + "roi", "specialParams", "sub", "subErrorIgnored", "templThreshold", "template", + "text", "threshold", "withoutDet", } }, { AlgorithmType::MatchTemplate, { "action", "algorithm", "baseTask", "cache", "exceededNext", "maskRange", "maxTimes", "next", "onErrorNext", "postDelay", "preDelay", "rectMove", "reduceOtherTimes", "roi", "sub", "subErrorIgnored", "templThreshold", "template", - "specialParams" + "specialParams", } }, { AlgorithmType::OcrDetect, { - "action", "algorithm", "baseTask", "cache", "exceededNext", - "fullMatch", "isAscii", "maxTimes", "next", "ocrReplace", - "onErrorNext", "postDelay", "preDelay", "rectMove", "reduceOtherTimes", "replaceFull", - "roi", "sub", "subErrorIgnored", "text", "withoutDet", - "specialParams" + "action", "algorithm", "baseTask", "cache", "exceededNext", "fullMatch", + "isAscii", "maxTimes", "next", "ocrReplace", "onErrorNext", "postDelay", + "preDelay", "rectMove", "reduceOtherTimes", "replaceFull", "roi", "sub", + "subErrorIgnored", "text", "withoutDet", "specialParams", } }, { AlgorithmType::JustReturn, { @@ -1086,10 +1091,7 @@ bool asst::TaskData::syntax_check(std::string_view task_name, const json::value& // clang-format on static const std::unordered_map> allowed_key_under_action = { - { ProcessTaskAction::ClickRect, - { - "specificRect", - } }, + { ProcessTaskAction::ClickRect, { "specificRect" } }, { ProcessTaskAction::Swipe, { "specificRect", "rectMove" } }, }; diff --git a/src/MaaCore/Config/TaskData.h b/src/MaaCore/Config/TaskData.h index 850368bbc1..79c02c2d49 100644 --- a/src/MaaCore/Config/TaskData.h +++ b/src/MaaCore/Config/TaskData.h @@ -60,15 +60,14 @@ namespace asst } return std::string(task_prefix) + '@' + std::string(task_name); } - static tasklist_t append_prefix(const tasklist_t& base_task_list, std::string_view task_prefix_view) + static tasklist_t append_prefix(const tasklist_t& base_task_list, std::string_view task_prefix) { - if (task_prefix_view.ends_with('@')) [[unlikely]] { - task_prefix_view.remove_suffix(1); + if (task_prefix.ends_with('@')) [[unlikely]] { + task_prefix.remove_suffix(1); } - if (task_prefix_view.empty()) { + if (task_prefix.empty()) { return base_task_list; } - std::string task_prefix(task_prefix_view); tasklist_t task_list = {}; for (const std::string& base : base_task_list) { std::string_view base_view = base; @@ -77,35 +76,37 @@ namespace asst // base 任务里已经存在相同前缀,则不加前缀 // https://github.com/MaaAssistantArknights/MaaAssistantArknights/pull/2116#issuecomment-1270115238 for (size_t r = base_view.find('@'); r != std::string_view::npos; r = base_view.find('@', l)) { - if (task_prefix_view == base_view.substr(l, r - l)) [[unlikely]] { + if (task_prefix == base_view.substr(l, r - l)) [[unlikely]] { has_same_prefix = true; break; } l = r + 1; } - task_list.emplace_back(has_same_prefix ? base : append_prefix(base, task_prefix)); + task_list.emplace_back(has_same_prefix ? base : append_prefix(base_view, task_prefix)); } return task_list; }; + // 运行时动态生成任务 - static taskptr_t _generate_task_info(const taskptr_t& base_ptr, std::string_view task_prefix = "") + static taskptr_t _generate_task_info(const taskptr_t& base_ptr) { - taskptr_t task_info_ptr; + if (!base_ptr) [[unlikely]] { + return nullptr; + } switch (base_ptr->algorithm) { case AlgorithmType::MatchTemplate: - task_info_ptr = std::make_shared(*std::dynamic_pointer_cast(base_ptr)); - break; + return std::make_shared(*std::dynamic_pointer_cast(base_ptr)); case AlgorithmType::OcrDetect: - task_info_ptr = std::make_shared(*std::dynamic_pointer_cast(base_ptr)); - break; + return std::make_shared(*std::dynamic_pointer_cast(base_ptr)); case AlgorithmType::Hash: - task_info_ptr = std::make_shared(*std::dynamic_pointer_cast(base_ptr)); - break; + return std::make_shared(*std::dynamic_pointer_cast(base_ptr)); default: - task_info_ptr = std::make_shared(*base_ptr); - break; + return std::make_shared(*base_ptr); } - + } + static taskptr_t _generate_task_info(const taskptr_t& base_ptr, std::string_view task_prefix) + { + taskptr_t task_info_ptr = _generate_task_info(base_ptr); if (!task_prefix.empty()) { task_info_ptr->name = append_prefix(base_ptr->name, task_prefix); task_info_ptr->sub = append_prefix(base_ptr->sub, task_prefix); @@ -114,7 +115,6 @@ namespace asst task_info_ptr->on_error_next = append_prefix(base_ptr->on_error_next, task_prefix); task_info_ptr->reduce_other_times = append_prefix(base_ptr->reduce_other_times, task_prefix); } - return task_info_ptr; } template @@ -138,14 +138,13 @@ namespace asst { return m_all_tasks_info.insert_or_assign(task_name_view(task_name), task_info_ptr); } - bool explain_tasks(tasklist_t& new_tasks, const tasklist_t& raw_tasks, std::string_view name, - bool& task_changed, bool multi); + bool compile_tasklist(tasklist_t& new_tasks, const tasklist_t& raw_tasks, std::string_view name, + bool& task_changed, bool multi); bool generate_task_and_its_base(std::string_view name, bool must_true); - std::optional expand_task(std::string_view name, taskptr_t old_task); #ifdef ASST_DEBUG bool syntax_check(std::string_view task_name, const json::value& task_json); #endif - std::shared_ptr get_raw(std::string_view name); + taskptr_t get_raw(std::string_view name); template requires(std::derived_from && !std::same_as) // Parameter must be a TaskInfo @@ -161,7 +160,7 @@ namespace asst void set_task_base(const std::string_view task_name, std::string base_task_name); bool lazy_parse(const json::value& json); - std::shared_ptr get(std::string_view name); + taskptr_t get(std::string_view name); template requires(std::derived_from && !std::same_as) // Parameter must be a TaskInfo @@ -180,20 +179,20 @@ namespace asst protected: enum TaskStatus { - NotToBeGenerate = 0, // 已经显式生成 或 不是待显式生成 的资源 - ToBeGenerate, // 待生成 的资源 - Generating, // 正在生成 的资源 - NotExists, // 不存在的资源 + NotToBeGenerate = 0, // 已经显式生成 或 不是待显式生成 的任务 + ToBeGenerate, // 待生成 的任务 + Generating, // 正在生成 的任务 + NotExists, // 不存在的任务 }; virtual bool parse(const json::value& json) override; std::unordered_set m_task_names; - std::unordered_map m_raw_all_tasks_info; - std::unordered_map m_all_tasks_info; - std::unordered_map m_json_all_tasks_info; std::unordered_set m_templ_required; std::unordered_map m_task_status; + std::unordered_map m_json_all_tasks_info; // 原始的 json 信息 + std::unordered_map m_raw_all_tasks_info; // 未展开虚任务的任务信息 + std::unordered_map m_all_tasks_info; // 已展开虚任务的任务信息 }; inline static auto& Task = TaskData::get_instance();