Merge pull request #2381 from MaaAssistantArknights/feat/sharp_task
feat: 增加 `#` 型任务 和 `baseTask` 字段
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
1241
resource/tasks.json
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
@@ -78,11 +78,65 @@ ProcessTask& asst::ProcessTask::set_rear_delay(std::string name, int delay)
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool ProcessTask::generate_tasks(const std::vector<std::string>& raw_tasks)
|
||||
{
|
||||
for (const std::string& task : raw_tasks) {
|
||||
size_t pos = task.find('#');
|
||||
if (pos == std::string_view::npos) {
|
||||
m_cur_tasks_name.emplace_back(task);
|
||||
continue;
|
||||
}
|
||||
std::string other_task_name = task.substr(0, pos);
|
||||
std::string type = task.substr(pos + 1);
|
||||
auto other_tasklist_ref = Task.get(other_task_name);
|
||||
if (other_tasklist_ref == nullptr) {
|
||||
Log.error(task, "not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
#define ASST_PROCESSTASK_GENERATE_TASKS(t) \
|
||||
else if (type == #t) \
|
||||
{ \
|
||||
if (!generate_tasks(other_tasklist_ref->t)) { \
|
||||
return false; \
|
||||
} \
|
||||
}
|
||||
if constexpr (false) {}
|
||||
ASST_PROCESSTASK_GENERATE_TASKS(next)
|
||||
ASST_PROCESSTASK_GENERATE_TASKS(sub)
|
||||
ASST_PROCESSTASK_GENERATE_TASKS(on_error_next)
|
||||
ASST_PROCESSTASK_GENERATE_TASKS(exceeded_next)
|
||||
ASST_PROCESSTASK_GENERATE_TASKS(reduce_other_times)
|
||||
else {
|
||||
Log.error("Unknown type", type);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#undef ASST_PROCESSTASK_GENERATE_TASKS
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcessTask::generate_tasks()
|
||||
{
|
||||
// std::move 后 m_cur_tasks_name 已经为空
|
||||
std::vector<std::string> cur_tasks_name = std::move(m_cur_tasks_name);
|
||||
if (!generate_tasks(cur_tasks_name)) [[unlikely]] {
|
||||
Log.error("Generate task failed.");
|
||||
m_cur_tasks_name.clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProcessTask::_run()
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
while (!m_cur_tasks_name.empty()) {
|
||||
if (!generate_tasks()) {
|
||||
return false;
|
||||
}
|
||||
if (need_exit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace asst
|
||||
virtual json::value basic_info() const override;
|
||||
|
||||
std::pair<int, TimesLimitType> calc_time_limit() const;
|
||||
bool generate_tasks(const std::vector<std::string>& raw_tasks);
|
||||
bool generate_tasks();
|
||||
void exec_click_task(const Rect& matched_rect);
|
||||
void exec_swipe_task(ProcessTaskAction action);
|
||||
void exec_slowly_swipe_task(ProcessTaskAction action);
|
||||
|
||||
@@ -19,34 +19,65 @@ bool asst::TaskData::parse(const json::value& json)
|
||||
{
|
||||
LogTraceFunction;
|
||||
|
||||
// <任务名, 模板任务标志位置, 任务参数ref>
|
||||
std::vector<std::tuple<std::string, size_t, std::reference_wrapper<const json::value>>> template_task_info {};
|
||||
const auto& json_obj = json.as_object();
|
||||
|
||||
for (const auto& [name, task_json] : json.as_object()) {
|
||||
// 模板任务特化需要等模板任务生成后才能生成
|
||||
if (size_t p = name.find('@'); p != std::string::npos) {
|
||||
template_task_info.emplace_back(name, p, task_json);
|
||||
continue;
|
||||
{
|
||||
std::unordered_map<std::string, bool> to_be_generated;
|
||||
for (const std::string& name : json_obj | views::keys) {
|
||||
to_be_generated[name] = true;
|
||||
}
|
||||
|
||||
std::shared_ptr<TaskInfo> task_info_ptr = generate_task_info(name, task_json, nullptr);
|
||||
if (task_info_ptr == nullptr) {
|
||||
return false;
|
||||
}
|
||||
m_all_tasks_info[name] = task_info_ptr;
|
||||
}
|
||||
auto generate_task_and_its_base = [&](const std::string& name) -> bool {
|
||||
auto generate_task = [&](const std::string& name, std::string_view prefix,
|
||||
std::shared_ptr<TaskInfo> 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;
|
||||
}
|
||||
to_be_generated[name] = false;
|
||||
m_all_tasks_info[name] = task_info_ptr;
|
||||
return true;
|
||||
};
|
||||
std::function<bool(const std::string&, bool)> generate_fun;
|
||||
// must_true 若为真,那么 return false 了就是炸了。
|
||||
// 否则可能只是某个 B@A 的任务没定义 A(这不是少见现象,例如 Roguelike@Abandon)
|
||||
generate_fun = [&](const std::string& name, bool must_true) -> bool {
|
||||
if (!to_be_generated[name]) {
|
||||
// 已生成(它是之前加载过的某个资源的 base)
|
||||
if (m_all_tasks_info.contains(name)) {
|
||||
return true;
|
||||
}
|
||||
// 不在 json 内且未生成(例如生成 C@B@A 时没有定义 B@A,而是定义了 A)
|
||||
if (size_t p = name.find('@'); p != std::string::npos) {
|
||||
return generate_fun(name.substr(p + 1), must_true);
|
||||
}
|
||||
// 例如 Roguelike@Abandon 在这里如果 must_true = true,会报 `Unknown task: Abandon`
|
||||
if (must_true) {
|
||||
Log.error("Unknown task:", name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const json::value& task_json = json_obj.at(name);
|
||||
if (auto opt = task_json.find<std::string>("baseTask")) {
|
||||
std::string base = opt.value();
|
||||
return generate_fun(base, must_true) && generate_task(name, "", get(base, false), task_json);
|
||||
}
|
||||
|
||||
// 短任务在前,因为模板任务特化一定比模板任务长
|
||||
ranges::sort(template_task_info,
|
||||
[](const auto& l, const auto& r) { return std::get<0>(l).length() < std::get<0>(r).length(); });
|
||||
if (size_t p = name.find('@'); p != std::string::npos) {
|
||||
if (std::string base = name.substr(p + 1); generate_fun(base, false)) {
|
||||
return generate_task(name, name.substr(0, p), get(base, false), task_json);
|
||||
}
|
||||
// 这类任务有点多,非必要不输出(
|
||||
// Log.debug("Task", name, "based on unknown task, just use task_json.");
|
||||
}
|
||||
return generate_task(name, "", nullptr, task_json);
|
||||
};
|
||||
return generate_fun(name, true);
|
||||
};
|
||||
|
||||
for (const auto& [name, pos, task_json] : template_task_info) {
|
||||
// 可能某个名字里带@的任务不是模板任务特化,只是普通的基类任务,这里最后一个参数可能为 nullptr
|
||||
auto task_info_ptr = generate_task_info(name, task_json, get(name.substr(pos + 1)), name.substr(0, pos));
|
||||
if (task_info_ptr == nullptr) {
|
||||
return false;
|
||||
for (const std::string& name : json_obj | views::keys) {
|
||||
generate_task_and_its_base(name);
|
||||
}
|
||||
m_all_tasks_info[name] = task_info_ptr;
|
||||
}
|
||||
|
||||
#ifdef ASST_DEBUG
|
||||
@@ -54,18 +85,78 @@ bool asst::TaskData::parse(const json::value& json)
|
||||
bool validity = true;
|
||||
|
||||
// 语法检查
|
||||
for (const auto& [name, task_json] : json.as_object()) {
|
||||
for (const auto& [name, task_json] : json_obj) {
|
||||
validity &= syntax_check(name, task_json);
|
||||
}
|
||||
|
||||
// next 存在性检查
|
||||
// TODO: 这块感觉可以合并到 syntax_check 里
|
||||
std::unordered_map<std::string, std::vector<std::string>> dependency_graph; // "#" 型任务依赖关系 (有向图邻接表)
|
||||
std::unordered_map<std::string, int> checked; // 拓扑排序相关的标志
|
||||
static const std::unordered_set<std::string> accepted_type = {
|
||||
"next", "sub", "on_error_next", "exceeded_next", "reduce_other_times",
|
||||
};
|
||||
|
||||
for (const auto& [name, task] : m_all_tasks_info) {
|
||||
for (const auto& next : task->next) {
|
||||
if (get(next, false) == nullptr) {
|
||||
Log.error(name, "'s next", next, "is null");
|
||||
validity = false;
|
||||
auto check_and_link = [&](const std::vector<std::string>& task_list, std::string node_name) {
|
||||
for (const auto& task_name : task_list) {
|
||||
size_t pos = task_name.find('#');
|
||||
// next、sub 等的存在性检查
|
||||
if (pos == std::string::npos) {
|
||||
if (get(task_name, false) == nullptr) {
|
||||
Log.error(node_name, task_name, "is null");
|
||||
validity = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string other_task_name = task_name.substr(0, pos);
|
||||
if (get(other_task_name, false) == nullptr) {
|
||||
Log.error(node_name, task_name, "is null");
|
||||
validity = false;
|
||||
}
|
||||
std::string type = task_name.substr(pos + 1);
|
||||
if (!accepted_type.contains(type)) {
|
||||
Log.error(node_name, task_name, "has unknown type:", type);
|
||||
validity = false;
|
||||
}
|
||||
else {
|
||||
// 建立一条依赖关系 (有向边)
|
||||
dependency_graph[node_name].emplace_back(task_name);
|
||||
}
|
||||
}
|
||||
};
|
||||
check_and_link(task->next, name + "#next");
|
||||
check_and_link(task->sub, name + "#sub");
|
||||
check_and_link(task->exceeded_next, name + "#exceeded_next");
|
||||
check_and_link(task->on_error_next, name + "#on_error_next");
|
||||
check_and_link(task->reduce_other_times, name + "#reduce_other_times");
|
||||
}
|
||||
|
||||
// dfs 检查 "#" 型任务是否循环依赖 (有向无环图)
|
||||
auto check_circle = [&](const std::string& x) {
|
||||
std::function<bool(const std::string&)> dfs;
|
||||
dfs = [&](const std::string& x) {
|
||||
checked[x] = -1;
|
||||
for (const auto& y : dependency_graph[x]) {
|
||||
if (checked[y] == 0) {
|
||||
if (!dfs(y)) [[unlikely]] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (checked[y] < 0) [[unlikely]] {
|
||||
Log.error("Task", y, "has circular dependency.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
checked[x] = 1;
|
||||
return true;
|
||||
};
|
||||
return dfs(x);
|
||||
};
|
||||
|
||||
for (const auto& name : dependency_graph | views::keys) {
|
||||
if (!checked[name] && !check_circle(name)) {
|
||||
validity = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,10 +169,11 @@ bool asst::TaskData::parse(const json::value& json)
|
||||
std::shared_ptr<asst::TaskInfo> asst::TaskData::generate_task_info(const std::string& name,
|
||||
const json::value& task_json,
|
||||
std::shared_ptr<TaskInfo> default_ptr,
|
||||
const std::string& task_prefix)
|
||||
std::string_view task_prefix)
|
||||
{
|
||||
if (default_ptr == nullptr) {
|
||||
default_ptr = default_task_info_ptr;
|
||||
task_prefix = "";
|
||||
}
|
||||
|
||||
// 获取 algorithm 并按照 algorithm 生成 TaskInfo
|
||||
@@ -227,7 +319,7 @@ std::shared_ptr<asst::TaskInfo> asst::TaskData::generate_hash_task_info([[maybe_
|
||||
|
||||
bool asst::TaskData::append_base_task_info(std::shared_ptr<TaskInfo> task_info_ptr, const std::string& name,
|
||||
const json::value& task_json, std::shared_ptr<TaskInfo> default_ptr,
|
||||
std::string task_prefix)
|
||||
std::string_view task_prefix)
|
||||
{
|
||||
if (default_ptr == nullptr) {
|
||||
default_ptr = default_task_info_ptr;
|
||||
@@ -330,6 +422,50 @@ bool asst::TaskData::append_base_task_info(std::shared_ptr<TaskInfo> task_info_p
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<asst::MatchTaskInfo> asst::TaskData::_default_match_task_info()
|
||||
{
|
||||
auto match_task_info_ptr = std::make_shared<MatchTaskInfo>();
|
||||
match_task_info_ptr->templ_name = "__INVALID__";
|
||||
match_task_info_ptr->templ_threshold = TemplThresholdDefault;
|
||||
match_task_info_ptr->special_threshold = 0;
|
||||
|
||||
return match_task_info_ptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<asst::OcrTaskInfo> asst::TaskData::_default_ocr_task_info()
|
||||
{
|
||||
auto ocr_task_info_ptr = std::make_shared<OcrTaskInfo>();
|
||||
ocr_task_info_ptr->full_match = false;
|
||||
|
||||
return ocr_task_info_ptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<asst::HashTaskInfo> asst::TaskData::_default_hash_task_info()
|
||||
{
|
||||
auto hash_task_info_ptr = std::make_shared<HashTaskInfo>();
|
||||
hash_task_info_ptr->dist_threshold = 0;
|
||||
hash_task_info_ptr->bound = true;
|
||||
|
||||
return hash_task_info_ptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<asst::TaskInfo> asst::TaskData::_default_task_info()
|
||||
{
|
||||
auto task_info_ptr = std::make_shared<TaskInfo>();
|
||||
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->rear_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 导致的问题,加载的时候做一个语法检查
|
||||
// 主要是处理是否包含未知键值的问题
|
||||
@@ -339,6 +475,7 @@ bool asst::TaskData::syntax_check(const std::string& task_name, const json::valu
|
||||
{ AlgorithmType::Invalid,
|
||||
{
|
||||
"algorithm",
|
||||
"baseTask",
|
||||
"template",
|
||||
"text",
|
||||
"action",
|
||||
@@ -365,6 +502,7 @@ bool asst::TaskData::syntax_check(const std::string& task_name, const json::valu
|
||||
{ AlgorithmType::MatchTemplate,
|
||||
{
|
||||
"algorithm",
|
||||
"baseTask",
|
||||
"template",
|
||||
"action",
|
||||
"sub",
|
||||
@@ -385,6 +523,7 @@ bool asst::TaskData::syntax_check(const std::string& task_name, const json::valu
|
||||
{ AlgorithmType::OcrDetect,
|
||||
{
|
||||
"algorithm",
|
||||
"baseTask",
|
||||
"text",
|
||||
"action",
|
||||
"sub",
|
||||
@@ -405,6 +544,7 @@ bool asst::TaskData::syntax_check(const std::string& task_name, const json::valu
|
||||
{ AlgorithmType::JustReturn,
|
||||
{
|
||||
"algorithm",
|
||||
"baseTask",
|
||||
"action",
|
||||
"sub",
|
||||
"subErrorIgnored",
|
||||
@@ -419,6 +559,7 @@ bool asst::TaskData::syntax_check(const std::string& task_name, const json::valu
|
||||
{ AlgorithmType::Hash,
|
||||
{
|
||||
"algorithm",
|
||||
"baseTask",
|
||||
"action",
|
||||
"sub",
|
||||
"subErrorIgnored",
|
||||
@@ -486,8 +627,7 @@ bool asst::TaskData::syntax_check(const std::string& task_name, const json::valu
|
||||
allowed_key.merge(tmp);
|
||||
}
|
||||
|
||||
// TODO: 之后也许还要对 key-value 联合检查,json 先留着
|
||||
for (const auto& [name, json] : task_json.as_object()) {
|
||||
for (const auto& name : task_json.as_object() | views::keys) {
|
||||
if (!allowed_key.contains(name) && !is_doc(name) && !has_doc(name)) {
|
||||
Log.error(task_name, "has unknown key:", name);
|
||||
validity = false;
|
||||
|
||||
@@ -18,49 +18,10 @@ namespace asst
|
||||
class TaskData final : public SingletonHolder<TaskData>, public AbstractConfigerWithTempl
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<MatchTaskInfo> _default_match_task_info()
|
||||
{
|
||||
auto match_task_info_ptr = std::make_shared<MatchTaskInfo>();
|
||||
match_task_info_ptr->templ_name = "__INVALID__";
|
||||
match_task_info_ptr->templ_threshold = TemplThresholdDefault;
|
||||
match_task_info_ptr->special_threshold = 0;
|
||||
|
||||
return match_task_info_ptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<OcrTaskInfo> _default_ocr_task_info()
|
||||
{
|
||||
auto ocr_task_info_ptr = std::make_shared<OcrTaskInfo>();
|
||||
ocr_task_info_ptr->full_match = false;
|
||||
|
||||
return ocr_task_info_ptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<HashTaskInfo> _default_hash_task_info()
|
||||
{
|
||||
auto hash_task_info_ptr = std::make_shared<HashTaskInfo>();
|
||||
hash_task_info_ptr->dist_threshold = 0;
|
||||
hash_task_info_ptr->bound = true;
|
||||
|
||||
return hash_task_info_ptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<TaskInfo> _default_task_info()
|
||||
{
|
||||
auto task_info_ptr = std::make_shared<TaskInfo>();
|
||||
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->rear_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;
|
||||
}
|
||||
std::shared_ptr<MatchTaskInfo> _default_match_task_info();
|
||||
std::shared_ptr<OcrTaskInfo> _default_ocr_task_info();
|
||||
std::shared_ptr<HashTaskInfo> _default_hash_task_info();
|
||||
std::shared_ptr<TaskInfo> _default_task_info();
|
||||
|
||||
// 从模板任务生成
|
||||
const std::shared_ptr<MatchTaskInfo> default_match_task_info_ptr = _default_match_task_info();
|
||||
@@ -71,7 +32,7 @@ namespace asst
|
||||
// 这是下面几个函数的封装
|
||||
std::shared_ptr<TaskInfo> generate_task_info(const std::string& name, const json::value&,
|
||||
std::shared_ptr<TaskInfo> default_ptr,
|
||||
const std::string& task_prefix = "");
|
||||
std::string_view task_prefix = "");
|
||||
|
||||
std::shared_ptr<TaskInfo> generate_match_task_info(const std::string& name, const json::value&,
|
||||
std::shared_ptr<MatchTaskInfo> default_ptr);
|
||||
@@ -82,10 +43,10 @@ namespace asst
|
||||
// TaskInfo 的基础成员
|
||||
bool append_base_task_info(std::shared_ptr<TaskInfo> task_info_ptr, const std::string& name,
|
||||
const json::value& task_json, std::shared_ptr<TaskInfo> default_ptr,
|
||||
std::string task_prefix = "");
|
||||
std::string_view task_prefix = "");
|
||||
|
||||
static std::vector<std::string> append_prefix(const std::vector<std::string>& base_task_list,
|
||||
const std::string& task_prefix)
|
||||
std::string_view task_prefix)
|
||||
{
|
||||
if (task_prefix.empty()) {
|
||||
return base_task_list;
|
||||
@@ -108,7 +69,7 @@ namespace asst
|
||||
task_list.emplace_back(base);
|
||||
}
|
||||
else {
|
||||
task_list.emplace_back(task_prefix + "@" + base);
|
||||
task_list.emplace_back(std::string(task_prefix) + "@" + base);
|
||||
}
|
||||
}
|
||||
return task_list;
|
||||
|
||||