Merge pull request #2381 from MaaAssistantArknights/feat/sharp_task

feat: 增加 `#` 型任务 和 `baseTask` 字段
This commit is contained in:
MistEO
2022-10-24 21:58:17 +08:00
committed by GitHub
14 changed files with 508 additions and 1050 deletions

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;