Compare commits

..

10 Commits

Author SHA1 Message Date
Wilson E. Alvarez e12612fc31
Merge c42d9ebedf into 5e961ec6fa 2024-05-04 11:06:58 +02:00
Serhii Snitsaruk 5e961ec6fa
Merge pull request #103 from limbonaut/pre-commit
Add pre-commit configuration
2024-05-03 01:31:25 +02:00
Serhii Snitsaruk bc5d5d8610
Add pre-commit configuration
To install git hook scripts:
  pip install pre-commit
  pre-commit install
2024-05-03 00:21:51 +02:00
Serhii Snitsaruk 75e8e68da4
Merge pull request #100 from TranquilMarmot/patch-1
Add note about how to run demo/tutorial
2024-05-01 21:23:18 +02:00
Nate Moore 4fe4049c3a
game -> showcase 2024-05-01 10:01:44 -07:00
Serhii Snitsaruk 69e921be31
Merge pull request #101 from limbonaut/unit-tests
Add tests for nested HSM flow, tests for dispatch() and get_root(), and fix BBParam saved_value initializing to null
2024-05-01 18:43:13 +02:00
Serhii Snitsaruk c6bb5bad74
Fix: BBParam saved_value defaults to null 2024-05-01 18:09:06 +02:00
Serhii Snitsaruk 2c2f2dd4be
Test: BBParam default values 2024-05-01 18:08:59 +02:00
Serhii Snitsaruk dedffc4f22
Test: Nested HSM flow, dispatch() and get_root() 2024-05-01 18:08:40 +02:00
Nate Moore 4491a23d52
Add note about how to run demo/tutorial
Closes https://github.com/limbonaut/limboai/issues/99
2024-04-30 23:26:00 -07:00
9 changed files with 215 additions and 44 deletions

24
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,24 @@
repos:
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v17.0.6
hooks:
- id: clang-format
files: \.(c|h|cpp|hpp|cc|cxx|m|mm|inc|java|glsl)$
types_or: [text]
exclude: |
(?x)^(
tests/python_build.*|
.*thirdparty.*|
.*platform/android/java/lib/src/com.*|
.*-so_wrap.*
)
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.2.0
hooks:
- id: black
files: (\.py$|SConstruct|SCsub)
types_or: [text]
exclude: .*thirdparty.*
args:
- --line-length=120

View File

@ -34,6 +34,7 @@ Behavior Trees are powerful hierarchical structures used to model and control th
[![Demonstration](https://img.youtube.com/vi/NWaMArUg7mY/0.jpg)](https://www.youtube.com/watch?v=NWaMArUg7mY)
>**🛈 Demo project** lives in the `demo` folder and is available separately in [**Releases**](https://github.com/limbonaut/limboai/releases).
> Run `demo/scenes/showcase.tscn` to get started.
> It also contains a tutorial that introduces behavior trees using examples.
## Features

2
SCsub
View File

@ -5,7 +5,7 @@ Import("env_modules")
module_env = env.Clone()
module_env.Append(CPPDEFINES = ['LIMBOAI_MODULE'])
module_env.Append(CPPDEFINES=["LIMBOAI_MODULE"])
module_env.add_source_files(env.modules_sources, "*.cpp")
module_env.add_source_files(env.modules_sources, "blackboard/*.cpp")

View File

@ -79,6 +79,9 @@ Variant BBParam::get_value(Object *p_agent, const Ref<Blackboard> &p_blackboard,
ERR_FAIL_COND_V(!p_blackboard.is_valid(), p_default);
if (value_source == SAVED_VALUE) {
if (saved_value == Variant()) {
_assign_default_value();
}
return saved_value;
} else {
ERR_FAIL_COND_V_MSG(!p_blackboard->has_var(variable), p_default, vformat("BBParam: Blackboard variable \"%s\" doesn't exist.", variable));
@ -114,6 +117,4 @@ void BBParam::_bind_methods() {
BBParam::BBParam() {
value_source = SAVED_VALUE;
_assign_default_value();
}

View File

@ -6,19 +6,19 @@
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'LimboAI'
copyright = '2021-present Serhii Snitsaruk and the LimboAI contributors'
author = 'Serhii Snitsaruk and the LimboAI contributors'
release = '1.0'
project = "LimboAI"
copyright = "2021-present Serhii Snitsaruk and the LimboAI contributors"
author = "Serhii Snitsaruk and the LimboAI contributors"
release = "1.0"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = ['sphinx_rtd_dark_mode', 'sphinx_copybutton']
extensions = ["sphinx_rtd_dark_mode", "sphinx_copybutton"]
master_doc = 'index'
templates_path = ['_templates']
exclude_patterns = ['_build']
master_doc = "index"
templates_path = ["_templates"]
exclude_patterns = ["_build"]
# -- Markdown configuration (sphinx_markdown_builder).
# markdown_anchor_sections = True
@ -33,12 +33,11 @@ default_dark_mode = False
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
html_logo = "logo.png"
html_theme_options = {
'logo_only': True,
'display_version': True,
"logo_only": True,
"display_version": True,
"collapse_navigation": True,
}

View File

@ -14,22 +14,22 @@ env = SConscript("godot-cpp/SConstruct")
# tweak this if you want to use different folders, or more folders, to store your source code in.
env.Append(CPPPATH=["limboai/"])
env.Append(CPPDEFINES = ['LIMBOAI_GDEXTENSION'])
env.Append(CPPDEFINES=["LIMBOAI_GDEXTENSION"])
sources = Glob("limboai/*.cpp")
sources += (Glob("limboai/blackboard/*.cpp"))
sources += (Glob("limboai/blackboard/bb_param/*.cpp"))
sources += (Glob("limboai/bt/*.cpp"))
sources += (Glob("limboai/bt/tasks/*.cpp"))
sources += (Glob("limboai/bt/tasks/blackboard/*.cpp"))
sources += (Glob("limboai/bt/tasks/composites/*.cpp"))
sources += (Glob("limboai/bt/tasks/decorators/*.cpp"))
sources += (Glob("limboai/bt/tasks/scene/*.cpp"))
sources += (Glob("limboai/bt/tasks/utility/*.cpp"))
sources += (Glob("limboai/gdextension/*.cpp"))
sources += (Glob("limboai/editor/debugger/*.cpp"))
sources += (Glob("limboai/editor/*.cpp"))
sources += (Glob("limboai/hsm/*.cpp"))
sources += (Glob("limboai/util/*.cpp"))
sources += Glob("limboai/blackboard/*.cpp")
sources += Glob("limboai/blackboard/bb_param/*.cpp")
sources += Glob("limboai/bt/*.cpp")
sources += Glob("limboai/bt/tasks/*.cpp")
sources += Glob("limboai/bt/tasks/blackboard/*.cpp")
sources += Glob("limboai/bt/tasks/composites/*.cpp")
sources += Glob("limboai/bt/tasks/decorators/*.cpp")
sources += Glob("limboai/bt/tasks/scene/*.cpp")
sources += Glob("limboai/bt/tasks/utility/*.cpp")
sources += Glob("limboai/gdextension/*.cpp")
sources += Glob("limboai/editor/debugger/*.cpp")
sources += Glob("limboai/editor/*.cpp")
sources += Glob("limboai/hsm/*.cpp")
sources += Glob("limboai/util/*.cpp")
if env["platform"] == "macos":

View File

@ -31,18 +31,23 @@ def get_script_dir():
def main():
silent = False
try:
opts, args = getopt.getopt(sys.argv[1:],
"s", ["silent"])
opts, args = getopt.getopt(sys.argv[1:], "s", ["silent"])
except getopt.GetoptError as e:
print('%s: %s!\n' % (os.path.basename(__file__), e.msg,))
print(
"%s: %s!\n"
% (
os.path.basename(__file__),
e.msg,
)
)
usage()
sys.exit(2)
for opt, arg in opts:
if opt in ('-h', '--help'):
if opt in ("-h", "--help"):
usage()
sys.exit(0)
elif opt in ('-s','--silent'):
elif opt in ("-s", "--silent"):
silent = True
config_dir = get_script_dir()
@ -66,7 +71,7 @@ def main():
icon_files.sort()
for icon_file in icon_files:
content += os.path.splitext(icon_file)[0] + " = \"res://addons/limboai/icons/" + icon_file + "\"\n"
content += os.path.splitext(icon_file)[0] + ' = "res://addons/limboai/icons/' + icon_file + '"\n'
f = open(config_path, "w")
f.write(content)

View File

@ -17,8 +17,14 @@
#include "core/object/ref_counted.h"
#include "core/string/node_path.h"
#include "core/variant/variant.h"
#include "modules/limboai/blackboard/bb_param/bb_bool.h"
#include "modules/limboai/blackboard/bb_param/bb_float.h"
#include "modules/limboai/blackboard/bb_param/bb_int.h"
#include "modules/limboai/blackboard/bb_param/bb_node.h"
#include "modules/limboai/blackboard/bb_param/bb_param.h"
#include "modules/limboai/blackboard/bb_param/bb_string.h"
#include "modules/limboai/blackboard/bb_param/bb_variant.h"
#include "modules/limboai/blackboard/bb_param/bb_vector2.h"
#include "modules/limboai/blackboard/blackboard.h"
#include "modules/limboai/bt/tasks/bt_task.h"
#include "tests/test_macros.h"
@ -122,6 +128,68 @@ TEST_CASE("[Modules][LimboAI] BBNode") {
memdelete(dummy);
}
TEST_CASE("[Modules][LimboAI] BBParam default values") {
Node *dummy = memnew(Node);
Ref<Blackboard> bb = memnew(Blackboard);
SUBCASE("Test default value for BBBool") {
Ref<BBBool> param = memnew(BBBool);
param->set_value_source(BBParam::SAVED_VALUE);
CHECK_EQ(param->get_value(dummy, bb), Variant(false));
CHECK_NE(param->get_value(dummy, bb), Variant());
}
SUBCASE("Test default value for BBInt") {
Ref<BBInt> param = memnew(BBInt);
param->set_value_source(BBParam::SAVED_VALUE);
CHECK_EQ(param->get_value(dummy, bb), Variant(0));
CHECK_NE(param->get_value(dummy, bb), Variant());
}
SUBCASE("Test default value for BBFloat") {
Ref<BBFloat> param = memnew(BBFloat);
param->set_value_source(BBParam::SAVED_VALUE);
CHECK_EQ(param->get_value(dummy, bb), Variant(0.0));
CHECK_NE(param->get_value(dummy, bb), Variant());
}
SUBCASE("Test default value for BBString") {
Ref<BBString> param = memnew(BBString);
param->set_value_source(BBParam::SAVED_VALUE);
CHECK_EQ(param->get_value(dummy, bb), Variant(""));
CHECK_NE(param->get_value(dummy, bb), Variant());
}
SUBCASE("Test default value for BBVector2") {
Ref<BBVector2> param = memnew(BBVector2);
param->set_value_source(BBParam::SAVED_VALUE);
CHECK_EQ(param->get_value(dummy, bb), Variant(Vector2()));
CHECK_NE(param->get_value(dummy, bb), Variant());
}
SUBCASE("Test default value for BBVariant") {
Ref<BBVariant> param = memnew(BBVariant);
CHECK_EQ(param->get_value(dummy, bb), Variant());
param->set_value_source(BBParam::SAVED_VALUE);
CHECK_EQ(param->get_value(dummy, bb), Variant());
param->set_type(Variant::BOOL);
CHECK_EQ(param->get_value(dummy, bb), Variant(false));
CHECK_NE(param->get_value(dummy, bb), Variant());
param->set_type(Variant::INT);
CHECK_EQ(param->get_value(dummy, bb), Variant(0));
CHECK_NE(param->get_value(dummy, bb), Variant());
param->set_type(Variant::FLOAT);
CHECK_EQ(param->get_value(dummy, bb), Variant(0.0));
CHECK_NE(param->get_value(dummy, bb), Variant());
param->set_type(Variant::STRING);
CHECK_EQ(param->get_value(dummy, bb), Variant(""));
CHECK_NE(param->get_value(dummy, bb), Variant());
param->set_type(Variant::VECTOR2);
CHECK_EQ(param->get_value(dummy, bb), Variant(Vector2()));
CHECK_NE(param->get_value(dummy, bb), Variant());
param->set_type(Variant::NODE_PATH);
CHECK_EQ(param->get_value(dummy, bb), Variant(NodePath()));
CHECK_NE(param->get_value(dummy, bb), Variant());
}
memdelete(dummy);
}
} //namespace TestBBParam
#endif // TEST_BB_PARAM_H

View File

@ -24,6 +24,12 @@
namespace TestHSM {
inline void wire_callbacks(LimboState *p_state, Ref<CallbackCounter> p_entries_counter, Ref<CallbackCounter> p_updates_counter, Ref<CallbackCounter> p_exits_counter) {
p_state->call_on_enter(callable_mp(p_entries_counter.ptr(), &CallbackCounter::callback));
p_state->call_on_update(callable_mp(p_updates_counter.ptr(), &CallbackCounter::callback_delta));
p_state->call_on_exit(callable_mp(p_exits_counter.ptr(), &CallbackCounter::callback));
}
class TestGuard : public RefCounted {
GDCLASS(TestGuard, RefCounted);
@ -42,22 +48,38 @@ TEST_CASE("[Modules][LimboAI] HSM") {
Ref<CallbackCounter> beta_entries = memnew(CallbackCounter);
Ref<CallbackCounter> beta_exits = memnew(CallbackCounter);
Ref<CallbackCounter> beta_updates = memnew(CallbackCounter);
Ref<CallbackCounter> nested_entries = memnew(CallbackCounter);
Ref<CallbackCounter> nested_exits = memnew(CallbackCounter);
Ref<CallbackCounter> nested_updates = memnew(CallbackCounter);
Ref<CallbackCounter> gamma_entries = memnew(CallbackCounter);
Ref<CallbackCounter> gamma_exits = memnew(CallbackCounter);
Ref<CallbackCounter> gamma_updates = memnew(CallbackCounter);
Ref<CallbackCounter> delta_entries = memnew(CallbackCounter);
Ref<CallbackCounter> delta_exits = memnew(CallbackCounter);
Ref<CallbackCounter> delta_updates = memnew(CallbackCounter);
LimboState *state_alpha = memnew(LimboState);
state_alpha->call_on_enter(callable_mp(alpha_entries.ptr(), &CallbackCounter::callback));
state_alpha->call_on_update(callable_mp(alpha_updates.ptr(), &CallbackCounter::callback_delta));
state_alpha->call_on_exit(callable_mp(alpha_exits.ptr(), &CallbackCounter::callback));
wire_callbacks(state_alpha, alpha_entries, alpha_updates, alpha_exits);
LimboState *state_beta = memnew(LimboState);
state_beta->call_on_enter(callable_mp(beta_entries.ptr(), &CallbackCounter::callback));
state_beta->call_on_update(callable_mp(beta_updates.ptr(), &CallbackCounter::callback_delta));
state_beta->call_on_exit(callable_mp(beta_exits.ptr(), &CallbackCounter::callback));
wire_callbacks(state_beta, beta_entries, beta_updates, beta_exits);
LimboHSM *nested_hsm = memnew(LimboHSM);
wire_callbacks(nested_hsm, nested_entries, nested_updates, nested_exits);
LimboState *state_gamma = memnew(LimboState);
wire_callbacks(state_gamma, gamma_entries, gamma_updates, gamma_exits);
LimboState *state_delta = memnew(LimboState);
wire_callbacks(state_delta, delta_entries, delta_updates, delta_exits);
hsm->add_child(state_alpha);
hsm->add_child(state_beta);
hsm->add_child(nested_hsm);
nested_hsm->add_child(state_gamma);
nested_hsm->add_child(state_delta);
hsm->add_transition(state_alpha, state_beta, "event_one");
hsm->add_transition(state_beta, state_alpha, "event_two");
hsm->add_transition(hsm->anystate(), nested_hsm, "goto_nested");
nested_hsm->add_transition(state_gamma, state_delta, "goto_delta");
nested_hsm->add_transition(state_delta, state_gamma, "goto_gamma");
hsm->set_initial_state(state_alpha);
Ref<Blackboard> parent_scope = memnew(Blackboard);
@ -179,6 +201,57 @@ TEST_CASE("[Modules][LimboAI] HSM") {
CHECK(state_beta->get_blackboard()->get_parent() == parent_scope);
CHECK(state_alpha->get_blackboard()->get_var("parent_var", Variant()) == Variant(100));
}
SUBCASE("Test flow with a nested HSM, and test dispatch() from nested states") {
state_gamma->dispatch("goto_nested");
CHECK(hsm->get_leaf_state() == state_gamma);
CHECK(nested_entries->num_callbacks == 1);
CHECK(nested_updates->num_callbacks == 0);
CHECK(nested_exits->num_callbacks == 0);
CHECK(gamma_entries->num_callbacks == 1);
CHECK(gamma_updates->num_callbacks == 0);
CHECK(gamma_exits->num_callbacks == 0);
hsm->update(0.01666);
CHECK(nested_entries->num_callbacks == 1);
CHECK(nested_updates->num_callbacks == 1);
CHECK(nested_exits->num_callbacks == 0);
CHECK(gamma_entries->num_callbacks == 1);
CHECK(gamma_updates->num_callbacks == 1);
CHECK(gamma_exits->num_callbacks == 0);
state_gamma->dispatch("goto_delta");
CHECK(hsm->get_leaf_state() == state_delta);
CHECK(nested_entries->num_callbacks == 1);
CHECK(nested_updates->num_callbacks == 1);
CHECK(nested_exits->num_callbacks == 0);
CHECK(gamma_entries->num_callbacks == 1);
CHECK(gamma_updates->num_callbacks == 1);
CHECK(gamma_exits->num_callbacks == 1);
CHECK(delta_entries->num_callbacks == 1);
CHECK(delta_updates->num_callbacks == 0);
CHECK(delta_exits->num_callbacks == 0);
state_delta->dispatch(hsm->event_finished());
CHECK(nested_entries->num_callbacks == 1);
CHECK(nested_updates->num_callbacks == 1);
CHECK(nested_exits->num_callbacks == 1);
CHECK(gamma_entries->num_callbacks == 1);
CHECK(gamma_updates->num_callbacks == 1);
CHECK(gamma_exits->num_callbacks == 1);
CHECK(delta_entries->num_callbacks == 1);
CHECK(delta_updates->num_callbacks == 0);
CHECK(delta_exits->num_callbacks == 1);
CHECK(hsm->is_active() == false);
CHECK(hsm->get_leaf_state() == hsm);
}
SUBCASE("Test get_root()") {
CHECK(hsm->get_root() == hsm);
CHECK(state_alpha->get_root() == hsm);
CHECK(state_beta->get_root() == hsm);
CHECK(nested_hsm->get_root() == hsm);
CHECK(state_delta->get_root() == hsm);
CHECK(state_gamma->get_root() == hsm);
}
memdelete(agent);
memdelete(hsm);