feat: 多文件任务从 json 层面合并后再重新解析 (#6478)

现在如果有 CN 任务
```json
"base": { "text": [ "CN" ], "next": [ "aaa" ] },
"A": { "baseTask": "base", "next": [ "bbb" ] }
"B": { "baseTask": "base", "next": [ "ccc" ] }
```
只需要在 EN 文件中重新定义 base:
```json
"base": { "text": [ "EN" ] }
```
就等价于
```json
"base": { "text": [ "EN" ], "next": [ "aaa" ] },
"A": { "text": [ "EN" ], "next": [ "bbb" ] }
"B": { "text": [ "EN" ], "next": [ "ccc" ] }
```
不必再 copy 重复文件内容。外服适配不再是难题!

close #6313
This commit is contained in:
zzyyyl
2023-09-29 23:13:39 +08:00
committed by GitHub
7 changed files with 132 additions and 218 deletions

View File

@@ -170,10 +170,10 @@ Base task 的逻辑优先于 template task。这意味着 `"B@A": { "baseTask":
#### 多文件任务
- 后加载的任务文件 (例如外服 `tasks.json`; 下称文件二) 中定义的同名任务直接继承先加载的任务文件 (例如国服 `tasks.json`; 下称文件一) 的参数
- 后加载的任务文件 (例如外服 `tasks.json`; 下称文件二) 中定义的同名任务直接继承先加载的任务文件 (例如国服 `tasks.json`; 下称文件一) 的字段
- 如果文件二中定义的任务在文件一中也定义了同名任务,那么:
1. 如果文件二中任务没有 `baseTask` 字段,则直接继承文件一中同名任务的参数
2. 如果文件二中任务有字段 `"baseTask": "#none"`,则不继承文件一中同名任务的参数
1. 如果文件二中任务没有 `baseTask` 字段,则直接继承文件一中同名任务的字段
2. 如果文件二中任务有字段 `"baseTask": "#none"`,则不继承文件一中同名任务的字段
- **若有定义任务 "A",则以 template task 的逻辑处理 "B@A"**
- 若未定义任务 "A",则以普通任务的逻辑处理 "B@A"

View File

@@ -163,10 +163,10 @@ Any parameter that is not explicitly defined uses the value of the `baseTask` pa
#### Multi-File Task
- A task with the same name defined in a later loaded task file (e.g. `tasks.json` for foreign services; hereinafter called File 2) inherits directly from the parameters of the first loaded task file (e.g. `tasks.json` for official services; hereinafter called File 1).
- A task with the same name defined in a later loaded task file (e.g. `tasks.json` for foreign services; hereinafter called File 2) inherits directly from the fields of the first loaded task file (e.g. `tasks.json` for official services; hereinafter called File 1).
- If a task defined in File 2 also has a task of the same name defined in File 1, then.
1. if the task in File 2 does not have a `baseTask` field, then it inherits the parameters of the task with the same name in File 1 directly.
2. If the task in File 2 has the field `"baseTask": "#none"`, then it does not inherit the parameters of the task with the same name in File 1.
1. if the task in File 2 does not have a `baseTask` field, then it inherits the fields of the task with the same name in File 1 directly.
2. If the task in File 2 has the field `"baseTask": "#none"`, then it does not inherit the fields of the task with the same name in File 1.
- **If task "A" is defined, then "B@A" will be treated as template task**
- If task "A" is not defined, then "B@A" will be treated as a normal task

View File

@@ -157,16 +157,18 @@ Base task and template task are collectively called **template task**.
A task with the field `baseTask` is a base task.
Any parameter that is not explicitly defined uses the parameter of the corresponding task if `baseTask` is not explicitly defined, except for `template` which remains `"taskName.png"`. (without prefix)
Base task takes precedence over template task. This means that `"B@A": { "baseTask": "C", ... }` has no relevance to task A.
#### Other related
Any parameter that is not explicitly defined uses the value of the `baseTask` parameter for the corresponding task without a prefix, except for `template` which remains `"taskName.png"` if it is not explicitly defined.
- Tasks with the same name defined in `tasks.json` in foreign service inherit directly from `tasks.json` in national service, equivalent to `"A": { "baseTask": "A in national service" }`
- If task "B@A" is defined in national service `tasks.json` and "B@A" is defined in foreign service `tasks.json`, then
1. if foreign service "B@A" has field `"baseTask": ""`, then it does not inherit from national service "B@A"
- If there is a defined task "A", then "B@A" is handled with the logic of template task
- If there is no defined task "A", then handle "B@A" with the logic of normal task
2. If there is no `baseTask` for foreign "B@A", then inherit the parameters of national "B@A" directly
#### Multi-File Task
- A task with the same name defined in a later loaded task file (e.g. `tasks.json` for foreign services; hereinafter called File 2) inherits directly from the fields of the first loaded task file (e.g. `tasks.json` for official services; hereinafter called File 1).
- If a task defined in File 2 also has a task of the same name defined in File 1, then.
1. if the task in File 2 does not have a `baseTask` field, then it inherits the fields of the task with the same name in File 1 directly.
2. If the task in File 2 has the field `"baseTask": "#none"`, then it does not inherit the fields of the task with the same name in File 1.
- **If task "A" is defined, then "B@A" will be treated as template task**
- If task "A" is not defined, then "B@A" will be treated as a normal task
### Virtual Task
@@ -224,6 +226,19 @@ Task.get_raw("B@Loading")->next = { "B#self", "B#next", "B#back" };
In this case above, `"C@B" -> next` (i.e. `C@A#next`) is `["N1"]` instead of `["C@N0"]`.
## Expression Calculation
| Symbol | Meaning | Example |
|:---------:|:---:|:--------:|
| `@` | Template task | `Fight@ReturnTo` |
| `#` (unary) | Virtual task | `#self` |
| `#` (binary) | Virtual task | `StartUpThemes#next` |
| `*` | Repeat tasks | `(ClickCornerAfterPRTS+ClickCorner)*5` |
| `+` | Task list merge | `A+B` |
| `^` | Task list difference (in the former but not in the latter, the order remains the same) | `(A+A+B+C)^(A+B+D)` (= `C`) |
The operators `@`, `#`, `*`, `+`, `^` have priority: `#` (unary) > `@` = `#` (binary) > `*` > `+` = `^`.
## Schema Check
This project configures a json schema check for `tasks.json`, the schema file is `docs/maa_tasks_schema.json`.

View File

@@ -170,10 +170,10 @@ Base task 的邏輯優先於 template task。這代表 `"B@A": { "baseTask": "C"
#### 多檔任務
- 後載入的任務檔案(例如外服 `tasks.json`,下稱文件二)中定義的同名任務直接繼承先載入的任務檔案(例如國服 `tasks.json`,下稱文件一)的參數
- 後載入的任務檔案(例如外服 `tasks.json`,下稱文件二)中定義的同名任務直接繼承先載入的任務檔案(例如國服 `tasks.json`,下稱文件一)的欄位
- 如果文件二中定義的任務在文件一中也定義了同名任務,那麼:
1. 如果文件二中任務沒有 `baseTask` 欄位,則直接繼承文件一中同名任務的參數
2. 如果文件二中任務有欄位 `"baseTask": "#none"`,則不繼承文件一中同名任務的參數
1. 如果文件二中任務沒有 `baseTask` 欄位,則直接繼承文件一中同名任務的欄位
2. 如果文件二中任務有欄位 `"baseTask": "#none"`,則不繼承文件一中同名任務的欄位
- **若有定義任務 "A",則以 template task 的邏輯處理 "B@A"**
- 若未定義任務 "A",則以普通任務的邏輯處理 "B@A"

View File

@@ -23,12 +23,6 @@
"PRTS"
]
},
"ChapterDifficultyHard": {
"baseTask": "LevelOfDifficulty"
},
"ChapterDifficultyNormal": {
"baseTask": "LevelOfDifficulty"
},
"EnterChapterDifficultyHard": {
"text": [
"Start Operation"
@@ -39,15 +33,6 @@
"Start Operation"
]
},
"ClickedCorrectStageOrSwipe": {
"action": "DoNothing",
"baseTask": "StartButton1",
"next": [
"ClickedCorrectStage",
"FullStageNavigation"
],
"reduceOtherTimes": []
},
"SideStoryReopen": {
"roi": [
0,
@@ -59,22 +44,6 @@
"Rerun"
]
},
"StageQueue@StartButton1": {
"Doc": "StartButton1",
"action": "DoNothing",
"baseTask": "StartButton1",
"exceededNext": [],
"maxTimes": 1,
"next": [
"StageQueue@StartButton2",
"StageQueue@StartButton2Adverse",
"StageQueue@UseMedicine",
"StageQueue@UseStone",
"StageQueue@NoStone",
"StageQueue@OfflineConfirm",
"StageQueue@StartButton1TryAgain"
]
},
"CF-Open": {
"text": [
"Event <A Flurry to the Flame> is now",
@@ -2135,22 +2104,6 @@
"Start"
]
},
"StartButton1TryAgain": {
"baseTask": "StartButton1",
"exceededNext": [],
"next": [
"StartButton1#next"
],
"preDelay": 0,
"rectMove": [
38,
0,
35,
17
],
"rectMoveDoc": "Click on 'Actions' to avoid clicking on StartButton2 the second time.",
"reduceOtherTimes": []
},
"PRTS2": {
"text": [
"Takeover"
@@ -5921,8 +5874,6 @@
]
},
"Reclamation@ManufactureStatus": {
"Doc": "base_task",
"algorithm": "OcrDetect",
"roi": [
1021,
336,
@@ -5932,49 +5883,28 @@
"text": []
},
"Reclamation@ManufactureIncrease": {
"action": "ClickRect",
"baseTask": "Reclamation@ManufactureStatus",
"specificRect": [
1120,
86,
111,
26
],
"text": [
"Craft"
]
},
"Reclamation@ManufactureDecrease": {
"action": "ClickRect",
"baseTask": "Reclamation@ManufactureStatus",
"specificRect": [
1096,
288,
139,
28
],
"text": [
"Insufficient",
"materials"
]
},
"Reclamation@DoManufacture": {
"action": "ClickSelf",
"baseTask": "Reclamation@ManufactureStatus",
"postDelay": 1000,
"text": [
"Craft"
]
},
"Reclamation@ManufactureInsufficientMaterial": {
"baseTask": "Reclamation@ManufactureStatus",
"text": [
"Insufficient",
"materials"
]
},
"Reclamation@ManufactureSufficientMaterial": {
"baseTask": "Reclamation@ManufactureStatus",
"text": [
"Craft"
]
@@ -5992,20 +5922,6 @@
"Skip"
]
},
"StartWithDefaultFormation@Reclamation@ConfirmStart": {
"baseTask": "Reclamation@ConfirmStart",
"next": [],
"next_Old": [
"Stop"
],
"next_Old_Doc": "这里原先有个stop但是会导致StartWithDefaultFormation@Reclamation@Begin中间多出一个Stop姑且当成是不小心加的删了。"
},
"StartActionEnter@Reclamation@StartActionEnter": {
"baseTask": "Reclamation@StartActionEnter",
"next": [
"Stop"
]
},
"GachaLastOnceResult": {
"text": [
"Certificate",

View File

@@ -51,21 +51,12 @@ std::shared_ptr<asst::TaskInfo> asst::TaskData::get(std::string_view name)
return expand_task(name, get_raw(name)).value_or(nullptr);
}
#ifndef ASST_DEBUG
const bool forcedReloadResource = std::ifstream("DEBUG").good() || std::ifstream("DEBUG.txt").good();
#endif // !ASST_DEBUG
bool asst::TaskData::parse(const json::value& json)
{
LogTraceFunction;
#ifndef ASST_DEBUG
if (forcedReloadResource) {
m_all_tasks_info.clear();
}
#endif // !ASST_DEBUG
const auto& json_obj = json.as_object();
m_all_tasks_info.clear();
const auto& json_obj = m_json_all_tasks_info;
{
enum TaskStatus
@@ -76,103 +67,98 @@ bool asst::TaskData::parse(const json::value& json)
NotExists, // 不存在的资源
};
std::unordered_map<std::string_view, TaskStatus> task_status;
for (const std::string& name : json_obj | views::keys) {
task_status[task_name_view(name)] = ToBeGenerate;
for (const auto& [name, task_json] : json.as_object()) {
std::string_view name_view = task_name_view(name);
if (task_json.get("baseTask", "") == "#none") {
// 不继承同名任务参数
m_json_all_tasks_info[name_view] = task_json.as_object();
m_json_all_tasks_info[name_view].erase("baseTask");
continue;
}
if (m_json_all_tasks_info.find(name_view) == m_json_all_tasks_info.cend()) {
m_json_all_tasks_info.emplace(name_view, task_json.as_object());
continue;
}
for (const auto& [key, value] : task_json.as_object()) {
m_json_all_tasks_info[name_view][key] = value;
}
}
auto generate_task_and_its_base = [&](const std::string& name) -> bool {
auto generate_task = [&](const std::string& 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;
for (std::string_view name : m_json_all_tasks_info | views::keys) {
task_status[name] = ToBeGenerate;
}
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;
}
task_status[task_name_view(name)] = NotToBeGenerate;
insert_or_assign_raw_task(name, task_info_ptr);
return true;
};
std::function<bool(std::string_view, bool)> generate_task_and_its_base;
generate_task_and_its_base = [&](std::string_view name, bool must_true) -> bool {
switch (task_status[task_name_view(name)]) {
case NotToBeGenerate:
// 已经显式生成
if (m_raw_all_tasks_info.contains(name)) {
return true;
}
task_status[task_name_view(name)] = NotToBeGenerate;
insert_or_assign_raw_task(name, task_info_ptr);
return true;
};
std::function<bool(const std::string&, bool)> generate_fun;
generate_fun = [&](const std::string& name, bool must_true) -> bool {
switch (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_fun(name.substr(p + 1), must_true);
}
// 隐式生成的资源
if (size_t p = name.find('@'); p != std::string::npos) {
return generate_task_and_its_base(name.substr(p + 1), must_true);
}
task_status[name] = NotExists;
[[fallthrough]];
case NotExists:
if (must_true) {
// 必须有名字为 name 的资源
Log.error("Unknown task:", name);
}
// 不一定必须有名字为 name 的资源,例如 Roguelike@Abandon 不必有 Abandon.
return false;
case ToBeGenerate: {
task_status[name] = Generating;
const json::value& task_json = json_obj.at(name);
task_status[name] = NotExists;
[[fallthrough]];
case NotExists:
if (must_true) {
// 必须有名字为 name 的资源
Log.error("Unknown task:", name);
}
// 不一定必须有名字为 name 的资源,例如 Roguelike@Abandon 不必有 Abandon.
return false;
case ToBeGenerate: {
task_status[name] = Generating;
const json::value& task_json = m_json_all_tasks_info.at(name);
if (auto opt = task_json.find<std::string>("baseTask")) {
// BaseTask
std::string base = opt.value();
if (base == "#none") {
// `"baseTask": "#none"` 表示不使用已生成的同名任务
}
else if (base.empty()) {
Log.warn("Use `\"baseTask\": \"#none\"` instead of `\"baseTask\": \"\"` in Task", name);
}
else {
return generate_fun(base, must_true) && generate_task(name, "", get_raw(base), task_json);
}
}
else if (m_raw_all_tasks_info.contains(name)) {
// 已生成(外服覆写国服资源)
return generate_task(name, "", get_raw(name), task_json);
}
// BaseTask
if (auto opt = task_json.find<std::string>("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 base = name.substr(p + 1); generate_fun(base, false)) {
// 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);
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;
}
};
return generate_fun(name, true);
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;
}
};
for (const std::string& name : json_obj | views::keys) {
generate_task_and_its_base(name);
for (std::string_view name : json_obj | views::keys) {
generate_task_and_its_base(name, true);
}
// 延迟展开,等到一个任务被第一次 get 的时候才展开
// debug 时为了做语法检查,会 *提前* 展开
/*
// 展开 # 型任务
for (const auto& [name, old_task] : m_raw_all_tasks_info) {
expand_task(name, old_task);
}
*/
}
#ifdef ASST_DEBUG
@@ -681,7 +667,7 @@ std::optional<asst::TaskData::taskptr_t> asst::TaskData::expand_task(std::string
}
}
asst::TaskData::taskptr_t asst::TaskData::generate_task_info(const std::string& name, const json::value& task_json,
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) {
@@ -731,7 +717,7 @@ asst::TaskData::taskptr_t asst::TaskData::generate_task_info(const std::string&
return task_info_ptr;
}
asst::TaskData::taskptr_t asst::TaskData::generate_match_task_info(const std::string& name,
asst::TaskData::taskptr_t asst::TaskData::generate_match_task_info(std::string_view name,
const json::value& task_json,
std::shared_ptr<MatchTaskInfo> default_ptr)
{
@@ -740,12 +726,12 @@ asst::TaskData::taskptr_t asst::TaskData::generate_match_task_info(const std::st
}
auto match_task_info_ptr = std::make_shared<MatchTaskInfo>();
#ifdef ASST_DEBUG
if (task_json.get("template", "") == name + ".png") {
if (task_json.get("template", "") == std::string(name) + ".png") {
Log.warn("template name of task", name, "could be omitted.");
}
#endif
// template 留空时不从模板任务继承
match_task_info_ptr->templ_name = task_json.get("template", name + ".png");
match_task_info_ptr->templ_name = task_json.get("template", std::string(name) + ".png");
m_templ_required.emplace(match_task_info_ptr->templ_name);
// 其余若留空则继承模板任务
@@ -761,7 +747,7 @@ asst::TaskData::taskptr_t asst::TaskData::generate_match_task_info(const std::st
return match_task_info_ptr;
}
asst::TaskData::taskptr_t asst::TaskData::generate_ocr_task_info([[maybe_unused]] const std::string& name,
asst::TaskData::taskptr_t asst::TaskData::generate_ocr_task_info([[maybe_unused]] std::string_view name,
const json::value& task_json,
std::shared_ptr<OcrTaskInfo> default_ptr)
{
@@ -792,7 +778,7 @@ asst::TaskData::taskptr_t asst::TaskData::generate_ocr_task_info([[maybe_unused]
return ocr_task_info_ptr;
}
asst::TaskData::taskptr_t asst::TaskData::generate_hash_task_info([[maybe_unused]] const std::string& name,
asst::TaskData::taskptr_t asst::TaskData::generate_hash_task_info([[maybe_unused]] std::string_view name,
const json::value& task_json,
std::shared_ptr<HashTaskInfo> default_ptr)
{
@@ -823,7 +809,7 @@ asst::TaskData::taskptr_t asst::TaskData::generate_hash_task_info([[maybe_unused
return hash_task_info_ptr;
}
bool asst::TaskData::append_base_task_info(taskptr_t task_info_ptr, const std::string& name,
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)
{
@@ -954,7 +940,7 @@ asst::TaskData::taskptr_t asst::TaskData::_default_task_info()
#ifdef ASST_DEBUG
// 为了解决类似 beddc7c828126c678391e0b4da288db6d2c2d58a 导致的问题,加载的时候做一个语法检查
// 主要是处理是否包含未知键值的问题
bool asst::TaskData::syntax_check(const std::string& task_name, const json::value& task_json)
bool asst::TaskData::syntax_check(std::string_view task_name, const json::value& task_json)
{
// clang-format off
// 以下按字典序排序

View File

@@ -32,33 +32,33 @@ namespace asst
const taskptr_t default_task_info_ptr = _default_task_info();
// 这是下面几个函数的封装
taskptr_t generate_task_info(const std::string& name, const json::value&, taskptr_t default_ptr,
taskptr_t generate_task_info(std::string_view name, const json::value&, taskptr_t default_ptr,
std::string_view task_prefix = "");
taskptr_t generate_match_task_info(const std::string& name, const json::value&,
taskptr_t generate_match_task_info(std::string_view name, const json::value&,
std::shared_ptr<MatchTaskInfo> default_ptr);
taskptr_t generate_ocr_task_info(const std::string& name, const json::value&,
taskptr_t generate_ocr_task_info(std::string_view name, const json::value&,
std::shared_ptr<OcrTaskInfo> default_ptr);
taskptr_t generate_hash_task_info(const std::string& name, const json::value&,
taskptr_t generate_hash_task_info(std::string_view name, const json::value&,
std::shared_ptr<HashTaskInfo> default_ptr);
// TaskInfo 的基础成员
bool append_base_task_info(taskptr_t task_info_ptr, const std::string& name, const json::value& task_json,
bool 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 = "");
static std::string append_prefix(const std::string& task_name, std::string_view task_prefix)
static std::string append_prefix(std::string_view task_name, std::string_view task_prefix)
{
if (task_prefix.ends_with('@')) [[unlikely]] {
task_prefix.remove_suffix(1);
}
if (task_prefix.empty()) [[unlikely]] {
return task_name;
return std::string(task_name);
}
if (task_name.empty()) {
return std::string(task_prefix);
}
if (task_name.starts_with('#')) {
return std::string(task_prefix) + task_name;
return std::string(task_prefix) + std::string(task_name);
}
return std::string(task_prefix) + '@' + task_name;
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)
{
@@ -83,11 +83,7 @@ namespace asst
}
l = r + 1;
}
if (has_same_prefix) [[unlikely]] {
task_list.emplace_back(base);
continue;
}
task_list.emplace_back(append_prefix(base, task_prefix));
task_list.emplace_back(has_same_prefix ? base : append_prefix(base, task_prefix));
}
return task_list;
};
@@ -146,7 +142,7 @@ namespace asst
bool& task_changed, bool multi);
std::optional<taskptr_t> expand_task(std::string_view name, taskptr_t old_task);
#ifdef ASST_DEBUG
bool syntax_check(const std::string& task_name, const json::value& task_json);
bool syntax_check(std::string_view task_name, const json::value& task_json);
#endif
std::shared_ptr<TaskInfo> get_raw(std::string_view name) const;
template <typename TargetTaskInfoType>
@@ -176,6 +172,7 @@ namespace asst
std::unordered_set<std::string> m_task_names;
std::unordered_map<std::string_view, taskptr_t> m_raw_all_tasks_info;
std::unordered_map<std::string_view, taskptr_t> m_all_tasks_info;
std::unordered_map<std::string_view, json::object> m_json_all_tasks_info;
std::unordered_set<std::string> m_templ_required;
};