Add `LimboTaskDB` to handle task categories and scanning user tasks

This commit is contained in:
Serhii Snitsaruk 2023-08-25 17:32:46 +02:00
parent 26b9327090
commit 2ffcbf0565
43 changed files with 236 additions and 140 deletions

View File

@ -20,6 +20,7 @@
class BTAwaitAnimation : public BTAction {
GDCLASS(BTAwaitAnimation, BTAction);
TASK_CATEGORY(Actions);
private:
Ref<BBNode> animation_player_param;

View File

@ -18,6 +18,7 @@
class BTCallMethod : public BTAction {
GDCLASS(BTCallMethod, BTAction);
TASK_CATEGORY(Actions);
private:
StringName method_name;

View File

@ -18,6 +18,7 @@
class BTConsolePrint : public BTAction {
GDCLASS(BTConsolePrint, BTAction);
TASK_CATEGORY(Actions);
private:
String text;

View File

@ -16,6 +16,7 @@
class BTFail : public BTAction {
GDCLASS(BTFail, BTAction);
TASK_CATEGORY(Actions);
protected:
virtual int _tick(double p_delta) override;

View File

@ -20,6 +20,7 @@
class BTPauseAnimation : public BTAction {
GDCLASS(BTPauseAnimation, BTAction);
TASK_CATEGORY(Actions);
private:
Ref<BBNode> animation_player_param;

View File

@ -20,6 +20,7 @@
class BTPlayAnimation : public BTAction {
GDCLASS(BTPlayAnimation, BTAction);
TASK_CATEGORY(Actions);
private:
Ref<BBNode> animation_player_param;

View File

@ -16,6 +16,7 @@
class BTRandomWait : public BTAction {
GDCLASS(BTRandomWait, BTAction);
TASK_CATEGORY(Actions);
private:
double min_duration = 1.0;

View File

@ -18,6 +18,7 @@
class BTSetAgentProperty : public BTAction {
GDCLASS(BTSetAgentProperty, BTAction);
TASK_CATEGORY(Actions);
private:
StringName property;

View File

@ -8,7 +8,6 @@
* https://opensource.org/licenses/MIT.
* =============================================================================
*/
/* bt_set_var.h */
#ifndef BT_SET_VAR_H
#define BT_SET_VAR_H
@ -21,6 +20,7 @@
class BTSetVar : public BTAction {
GDCLASS(BTSetVar, BTAction);
TASK_CATEGORY(Actions);
private:
String variable;

View File

@ -20,6 +20,7 @@
class BTStopAnimation : public BTAction {
GDCLASS(BTStopAnimation, BTAction);
TASK_CATEGORY(Actions);
private:
Ref<BBNode> animation_player_param;

View File

@ -16,6 +16,7 @@
class BTWait : public BTAction {
GDCLASS(BTWait, BTAction);
TASK_CATEGORY(Actions);
private:
double duration = 1.0;

View File

@ -16,6 +16,7 @@
class BTWaitTicks : public BTAction {
GDCLASS(BTWaitTicks, BTAction);
TASK_CATEGORY(Actions);
private:
int num_ticks = 1;

View File

@ -8,7 +8,6 @@
* https://opensource.org/licenses/MIT.
* =============================================================================
*/
/* bt_comment.h */
#ifndef BT_COMMENT_H
#define BT_COMMENT_H
@ -18,8 +17,9 @@
class BTComment : public BTTask {
GDCLASS(BTComment, BTTask);
private:
public:
static _FORCE_INLINE_ String get_task_category() { return LimboTaskDB::get_misc_category(); }
virtual Ref<BTTask> clone() const override;
virtual PackedStringArray get_configuration_warnings() const override;
};

View File

@ -13,6 +13,7 @@
#define BTTASK_H
#include "modules/limboai/blackboard/blackboard.h"
#include "modules/limboai/util/limbo_task_db.h"
#include "core/io/resource.h"
#include "core/object/object.h"

View File

@ -16,6 +16,7 @@
class BTDynamicSelector : public BTComposite {
GDCLASS(BTDynamicSelector, BTComposite);
TASK_CATEGORY(Composites);
private:
int last_running_idx = 0;

View File

@ -16,6 +16,7 @@
class BTDynamicSequence : public BTComposite {
GDCLASS(BTDynamicSequence, BTComposite);
TASK_CATEGORY(Composites);
private:
int last_running_idx = 0;

View File

@ -16,6 +16,7 @@
class BTParallel : public BTComposite {
GDCLASS(BTParallel, BTComposite);
TASK_CATEGORY(Composites);
private:
int num_successes_required = 1;

View File

@ -18,6 +18,7 @@
class BTRandomSelector : public BTComposite {
GDCLASS(BTRandomSelector, BTComposite);
TASK_CATEGORY(Composites);
private:
int last_running_idx = 0;

View File

@ -18,6 +18,7 @@
class BTRandomSequence : public BTComposite {
GDCLASS(BTRandomSequence, BTComposite);
TASK_CATEGORY(Composites);
private:
int last_running_idx = 0;

View File

@ -16,6 +16,7 @@
class BTSelector : public BTComposite {
GDCLASS(BTSelector, BTComposite);
TASK_CATEGORY(Composites);
private:
int last_running_idx = 0;

View File

@ -16,6 +16,7 @@
class BTSequence : public BTComposite {
GDCLASS(BTSequence, BTComposite);
TASK_CATEGORY(Composites);
private:
int last_running_idx = 0;

View File

@ -21,6 +21,7 @@
class BTCheckAgentProperty : public BTCondition {
GDCLASS(BTCheckAgentProperty, BTCondition);
TASK_CATEGORY(Conditions);
private:
StringName property;

View File

@ -9,8 +9,6 @@
* =============================================================================
*/
/* bt_check_trigger.h */
#ifndef BT_CHECK_TRIGGER_H
#define BT_CHECK_TRIGGER_H
@ -20,6 +18,7 @@
class BTCheckTrigger : public BTCondition {
GDCLASS(BTCheckTrigger, BTCondition);
TASK_CATEGORY(Conditions);
private:
String variable;

View File

@ -19,6 +19,7 @@
class BTCheckVar : public BTCondition {
GDCLASS(BTCheckVar, BTCondition);
TASK_CATEGORY(Conditions);
private:
String variable;

View File

@ -16,6 +16,7 @@
class BTAlwaysFail : public BTDecorator {
GDCLASS(BTAlwaysFail, BTDecorator);
TASK_CATEGORY(Decorators);
protected:
virtual int _tick(double p_delta) override;

View File

@ -16,6 +16,7 @@
class BTAlwaysSucceed : public BTDecorator {
GDCLASS(BTAlwaysSucceed, BTDecorator);
TASK_CATEGORY(Decorators);
protected:
virtual int _tick(double p_delta) override;

View File

@ -18,6 +18,7 @@
class BTCooldown : public BTDecorator {
GDCLASS(BTCooldown, BTDecorator);
TASK_CATEGORY(Decorators);
private:
double duration = 10.0;

View File

@ -16,6 +16,7 @@
class BTDelay : public BTDecorator {
GDCLASS(BTDelay, BTDecorator);
TASK_CATEGORY(Decorators);
private:
double seconds = 1.0;

View File

@ -16,6 +16,7 @@
class BTForEach : public BTDecorator {
GDCLASS(BTForEach, BTDecorator);
TASK_CATEGORY(Decorators);
private:
String array_var;

View File

@ -16,6 +16,7 @@
class BTInvert : public BTDecorator {
GDCLASS(BTInvert, BTDecorator);
TASK_CATEGORY(Decorators);
protected:
virtual int _tick(double p_delta) override;

View File

@ -16,6 +16,7 @@
class BTNewScope : public BTDecorator {
GDCLASS(BTNewScope, BTDecorator);
TASK_CATEGORY(Actions);
private:
Dictionary blackboard_data;

View File

@ -16,6 +16,7 @@
class BTProbability : public BTDecorator {
GDCLASS(BTProbability, BTDecorator);
TASK_CATEGORY(Decorators);
private:
float run_chance = 0.5;

View File

@ -16,6 +16,7 @@
class BTRepeat : public BTDecorator {
GDCLASS(BTRepeat, BTDecorator);
TASK_CATEGORY(Decorators);
private:
bool forever = false;

View File

@ -16,6 +16,7 @@
class BTRepeatUntilFailure : public BTDecorator {
GDCLASS(BTRepeatUntilFailure, BTDecorator);
TASK_CATEGORY(Decorators);
protected:
virtual int _tick(double p_delta) override;

View File

@ -16,6 +16,7 @@
class BTRepeatUntilSuccess : public BTDecorator {
GDCLASS(BTRepeatUntilSuccess, BTDecorator);
TASK_CATEGORY(Decorators);
protected:
virtual int _tick(double p_delta) override;

View File

@ -16,6 +16,7 @@
class BTRunLimit : public BTDecorator {
GDCLASS(BTRunLimit, BTDecorator);
TASK_CATEGORY(Decorators);
private:
int run_limit = 1;

View File

@ -18,6 +18,7 @@
class BTSubtree : public BTNewScope {
GDCLASS(BTSubtree, BTNewScope);
TASK_CATEGORY(Actions);
private:
Ref<BehaviorTree> subtree;

View File

@ -16,6 +16,7 @@
class BTTimeLimit : public BTDecorator {
GDCLASS(BTTimeLimit, BTDecorator);
TASK_CATEGORY(Decorators);
private:
double time_limit = 5.0;

View File

@ -563,39 +563,12 @@ void TaskPanel::refresh() {
}
}
HashMap<String, List<String>> categorized_tasks;
categorized_tasks["Composites"] = List<String>();
_populate_core_tasks_from_class("BTComposite", &categorized_tasks["Composites"]);
categorized_tasks["Actions"] = List<String>();
_populate_core_tasks_from_class("BTAction", &categorized_tasks["Actions"]);
categorized_tasks["Decorators"] = List<String>();
_populate_core_tasks_from_class("BTDecorator", &categorized_tasks["Decorators"]);
categorized_tasks["Conditions"] = List<String>();
_populate_core_tasks_from_class("BTCondition", &categorized_tasks["Conditions"]);
categorized_tasks["Uncategorized"] = List<String>();
String dir1 = GLOBAL_GET("limbo_ai/behavior_tree/user_task_dir_1");
_populate_from_user_dir(dir1, &categorized_tasks);
String dir2 = GLOBAL_GET("limbo_ai/behavior_tree/user_task_dir_2");
_populate_from_user_dir(dir2, &categorized_tasks);
String dir3 = GLOBAL_GET("limbo_ai/behavior_tree/user_task_dir_3");
_populate_from_user_dir(dir3, &categorized_tasks);
List<String> categories;
for (KeyValue<String, List<String>> &K : categorized_tasks) {
K.value.sort();
categories.push_back(K.key);
}
LimboTaskDB::scan_user_tasks();
List<String> categories = LimboTaskDB::get_categories();
categories.sort();
for (String cat : categories) {
List<String> tasks = categorized_tasks.get(cat);
List<String> tasks = LimboTaskDB::get_tasks_in_category(cat);
if (tasks.size() == 0) {
continue;
@ -642,70 +615,6 @@ void TaskPanel::refresh() {
}
}
void TaskPanel::_populate_core_tasks_from_class(const StringName &p_base_class, List<String> *p_task_classes) {
List<StringName> inheriters;
ClassDB::get_inheriters_from_class(p_base_class, &inheriters);
for (StringName cl : inheriters) {
p_task_classes->push_back(cl);
}
}
void TaskPanel::_populate_from_user_dir(String p_path, HashMap<String, List<String>> *p_categories) {
if (p_path.is_empty()) {
return;
}
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES);
if (dir->change_dir(p_path) == OK) {
dir->list_dir_begin();
String fn = dir->get_next();
while (!fn.is_empty()) {
if (dir->current_is_dir() && fn != "..") {
String full_path;
String category;
if (fn == ".") {
full_path = p_path;
category = "Uncategorized";
} else {
full_path = p_path.path_join(fn);
category = fn.capitalize();
}
if (!p_categories->has(category)) {
p_categories->insert(category, List<String>());
}
_populate_scripted_tasks_from_dir(full_path, &p_categories->get(category));
}
fn = dir->get_next();
}
dir->list_dir_end();
} else {
ERR_FAIL_MSG(vformat("Failed to list \"%s\" directory.", p_path));
}
}
void TaskPanel::_populate_scripted_tasks_from_dir(String p_path, List<String> *p_task_classes) {
if (p_path.is_empty()) {
return;
}
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES);
if (dir->change_dir(p_path) == OK) {
dir->list_dir_begin();
String fn = dir->get_next();
while (!fn.is_empty()) {
if (fn.ends_with(".gd")) {
String full_path = p_path.path_join(fn);
p_task_classes->push_back(full_path);
}
fn = dir->get_next();
}
dir->list_dir_end();
} else {
ERR_FAIL_MSG(vformat("Failed to list \"%s\" directory.", p_path));
}
}
void TaskPanel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_EXIT_TREE: {

View File

@ -129,9 +129,6 @@ private:
String context_task;
void _populate_core_tasks_from_class(const StringName &p_base_class, List<String> *p_task_classes);
void _populate_from_user_dir(String p_path, HashMap<String, List<String>> *p_categories);
void _populate_scripted_tasks_from_dir(String p_path, List<String> *p_task_classes);
void _menu_action_selected(int p_id);
void _on_task_button_pressed(const String &p_task);
void _on_task_button_rmb(const String &p_task);

View File

@ -93,6 +93,7 @@
#include "hsm/limbo_hsm.h"
#include "hsm/limbo_state.h"
#include "util/limbo_string_names.h"
#include "util/limbo_task_db.h"
#include "util/limbo_utility.h"
#ifdef TOOLS_ENABLED
@ -121,51 +122,51 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(BTPlayer);
GDREGISTER_CLASS(BTState);
GDREGISTER_CLASS(BTComment);
LIMBO_REGISTER_TASK(BTComment);
GDREGISTER_CLASS(BTComposite);
GDREGISTER_CLASS(BTSequence);
GDREGISTER_CLASS(BTSelector);
GDREGISTER_CLASS(BTParallel);
GDREGISTER_CLASS(BTDynamicSequence);
GDREGISTER_CLASS(BTDynamicSelector);
GDREGISTER_CLASS(BTRandomSequence);
GDREGISTER_CLASS(BTRandomSelector);
LIMBO_REGISTER_TASK(BTSequence);
LIMBO_REGISTER_TASK(BTSelector);
LIMBO_REGISTER_TASK(BTParallel);
LIMBO_REGISTER_TASK(BTDynamicSequence);
LIMBO_REGISTER_TASK(BTDynamicSelector);
LIMBO_REGISTER_TASK(BTRandomSequence);
LIMBO_REGISTER_TASK(BTRandomSelector);
GDREGISTER_CLASS(BTDecorator);
GDREGISTER_CLASS(BTInvert);
GDREGISTER_CLASS(BTAlwaysFail);
GDREGISTER_CLASS(BTAlwaysSucceed);
GDREGISTER_CLASS(BTDelay);
GDREGISTER_CLASS(BTRepeat);
GDREGISTER_CLASS(BTRepeatUntilFailure);
GDREGISTER_CLASS(BTRepeatUntilSuccess);
GDREGISTER_CLASS(BTRunLimit);
GDREGISTER_CLASS(BTTimeLimit);
GDREGISTER_CLASS(BTCooldown);
GDREGISTER_CLASS(BTProbability);
GDREGISTER_CLASS(BTForEach);
LIMBO_REGISTER_TASK(BTInvert);
LIMBO_REGISTER_TASK(BTAlwaysFail);
LIMBO_REGISTER_TASK(BTAlwaysSucceed);
LIMBO_REGISTER_TASK(BTDelay);
LIMBO_REGISTER_TASK(BTRepeat);
LIMBO_REGISTER_TASK(BTRepeatUntilFailure);
LIMBO_REGISTER_TASK(BTRepeatUntilSuccess);
LIMBO_REGISTER_TASK(BTRunLimit);
LIMBO_REGISTER_TASK(BTTimeLimit);
LIMBO_REGISTER_TASK(BTCooldown);
LIMBO_REGISTER_TASK(BTProbability);
LIMBO_REGISTER_TASK(BTForEach);
GDREGISTER_CLASS(BTAction);
GDREGISTER_CLASS(BTAwaitAnimation);
GDREGISTER_CLASS(BTCallMethod);
GDREGISTER_CLASS(BTConsolePrint);
GDREGISTER_CLASS(BTFail);
GDREGISTER_CLASS(BTNewScope);
GDREGISTER_CLASS(BTPauseAnimation);
GDREGISTER_CLASS(BTPlayAnimation);
GDREGISTER_CLASS(BTRandomWait);
GDREGISTER_CLASS(BTSetAgentProperty);
GDREGISTER_CLASS(BTSetVar);
GDREGISTER_CLASS(BTStopAnimation);
GDREGISTER_CLASS(BTSubtree);
GDREGISTER_CLASS(BTWait);
GDREGISTER_CLASS(BTWaitTicks);
LIMBO_REGISTER_TASK(BTAwaitAnimation);
LIMBO_REGISTER_TASK(BTCallMethod);
LIMBO_REGISTER_TASK(BTConsolePrint);
LIMBO_REGISTER_TASK(BTFail);
LIMBO_REGISTER_TASK(BTNewScope);
LIMBO_REGISTER_TASK(BTPauseAnimation);
LIMBO_REGISTER_TASK(BTPlayAnimation);
LIMBO_REGISTER_TASK(BTRandomWait);
LIMBO_REGISTER_TASK(BTSetAgentProperty);
LIMBO_REGISTER_TASK(BTSetVar);
LIMBO_REGISTER_TASK(BTStopAnimation);
LIMBO_REGISTER_TASK(BTSubtree);
LIMBO_REGISTER_TASK(BTWait);
LIMBO_REGISTER_TASK(BTWaitTicks);
GDREGISTER_CLASS(BTCondition);
GDREGISTER_CLASS(BTCheckAgentProperty);
GDREGISTER_CLASS(BTCheckTrigger);
GDREGISTER_CLASS(BTCheckVar);
LIMBO_REGISTER_TASK(BTCheckAgentProperty);
LIMBO_REGISTER_TASK(BTCheckTrigger);
LIMBO_REGISTER_TASK(BTCheckVar);
GDREGISTER_ABSTRACT_CLASS(BBParam);
GDREGISTER_CLASS(BBInt);

98
util/limbo_task_db.cpp Normal file
View File

@ -0,0 +1,98 @@
/**
* limbo_task_db.cpp
* =============================================================================
* Copyright 2021-2023 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* =============================================================================
*/
#include "limbo_task_db.h"
#include "core/config/project_settings.h"
#include "core/io/dir_access.h"
HashMap<String, List<String>> LimboTaskDB::core_tasks;
HashMap<String, List<String>> LimboTaskDB::tasks_cache;
_FORCE_INLINE_ void _populate_scripted_tasks_from_dir(String p_path, List<String> *p_task_classes) {
if (p_path.is_empty()) {
return;
}
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES);
if (dir->change_dir(p_path) == OK) {
dir->list_dir_begin();
String fn = dir->get_next();
while (!fn.is_empty()) {
if (fn.ends_with(".gd")) {
String full_path = p_path.path_join(fn);
p_task_classes->push_back(full_path);
}
fn = dir->get_next();
}
dir->list_dir_end();
} else {
ERR_FAIL_MSG(vformat("Failed to list \"%s\" directory.", p_path));
}
}
_FORCE_INLINE_ void _populate_from_user_dir(String p_path, HashMap<String, List<String>> *p_categories) {
if (p_path.is_empty()) {
return;
}
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES);
if (dir->change_dir(p_path) == OK) {
dir->list_dir_begin();
String fn = dir->get_next();
while (!fn.is_empty()) {
if (dir->current_is_dir() && fn != "..") {
String full_path;
String category;
if (fn == ".") {
full_path = p_path;
category = LimboTaskDB::get_misc_category();
} else {
full_path = p_path.path_join(fn);
category = fn.capitalize();
}
if (!p_categories->has(category)) {
p_categories->insert(category, List<String>());
}
_populate_scripted_tasks_from_dir(full_path, &p_categories->get(category));
}
fn = dir->get_next();
}
dir->list_dir_end();
} else {
ERR_FAIL_MSG(vformat("Failed to list \"%s\" directory.", p_path));
}
}
void LimboTaskDB::scan_user_tasks() {
tasks_cache = HashMap<String, List<String>>(core_tasks);
if (!tasks_cache.has(LimboTaskDB::get_misc_category())) {
tasks_cache[LimboTaskDB::get_misc_category()] = List<String>();
}
for (int i = 1; i < 4; i++) {
String dir1 = GLOBAL_GET("limbo_ai/behavior_tree/user_task_dir_" + itos(i));
_populate_from_user_dir(dir1, &tasks_cache);
}
}
List<String> LimboTaskDB::get_categories() {
List<String> r_cat;
for (const KeyValue<String, List<String>> &E : tasks_cache) {
r_cat.push_back(E.key);
}
return r_cat;
}
List<String> LimboTaskDB::get_tasks_in_category(const String &p_category) {
return List<String>(tasks_cache[p_category]);
}

57
util/limbo_task_db.h Normal file
View File

@ -0,0 +1,57 @@
/**
* limbo_task_db.h
* =============================================================================
* Copyright 2021-2023 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* =============================================================================
*/
#ifndef LIMBO_TASK_DB_H
#define LIMBO_TASK_DB_H
#include "core/object/class_db.h"
#include "core/templates/hash_map.h"
#include "core/templates/list.h"
class LimboTaskDB {
private:
static HashMap<String, List<String>> core_tasks;
static HashMap<String, List<String>> tasks_cache;
public:
template <class T>
static void register_task() {
GDREGISTER_CLASS(T);
HashMap<String, List<String>>::Iterator E = core_tasks.find(T::get_task_category());
if (E) {
E->value.push_back(T::get_class_static());
} else {
List<String> tasks;
tasks.push_back(T::get_class_static());
core_tasks.insert(T::get_task_category(), tasks);
}
}
static void scan_user_tasks();
static _FORCE_INLINE_ String get_misc_category() { return "Misc"; }
static List<String> get_categories();
static List<String> get_tasks_in_category(const String &p_category);
};
#define LIMBO_REGISTER_TASK(m_class) \
if (m_class::_class_is_enabled) { \
::LimboTaskDB::register_task<m_class>(); \
}
#define TASK_CATEGORY(m_cat) \
public: \
static _FORCE_INLINE_ String get_task_category() { \
return String(#m_cat); \
} \
\
private:
#endif // LIMBO_TASK_DB_H