From b7ea9145df5183a3c801ae44684c1ad4f1ec5950 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Thu, 29 Sep 2022 12:54:07 +0200 Subject: [PATCH] Implement LimboHSM --- limbo_hsm.cpp | 209 +++++++++++++++++++++++++++++++++++++++++ limbo_hsm.h | 60 ++++++++++++ limbo_state.cpp | 10 +- limbo_state.h | 6 +- limbo_string_names.cpp | 1 + limbo_string_names.h | 1 + register_types.cpp | 2 + 7 files changed, 282 insertions(+), 7 deletions(-) create mode 100644 limbo_hsm.cpp create mode 100644 limbo_hsm.h diff --git a/limbo_hsm.cpp b/limbo_hsm.cpp new file mode 100644 index 0000000..491a6ff --- /dev/null +++ b/limbo_hsm.cpp @@ -0,0 +1,209 @@ +/* limbo_hsm.cpp */ + +#include "limbo_hsm.h" + +#include "core/engine.h" +#include "core/error_macros.h" +#include "core/object.h" +#include "core/typedefs.h" +#include "core/variant.h" +#include "modules/limboai/blackboard.h" +#include "modules/limboai/limbo_state.h" +#include "modules/limboai/limbo_string_names.h" + +VARIANT_ENUM_CAST(LimboHSM::UpdateMode); + +void LimboHSM::set_active(bool p_active) { + ERR_FAIL_COND_MSG(agent == nullptr, "LimboHSM is not initialized."); + ERR_FAIL_COND_MSG(p_active && initial_state == nullptr, "LimboHSM has no initial substate candidate."); + + if (active == p_active) { + return; + } + + active = p_active; + switch (update_mode) { + case UpdateMode::IDLE: { + set_process(p_active); + set_physics_process(false); + } break; + case UpdateMode::PHYSICS: { + set_process(false); + set_physics_process(p_active); + } break; + case UpdateMode::MANUAL: { + set_process(false); + set_physics_process(false); + } break; + } + set_process_input(p_active); + + if (active) { + _enter(); + } else { + _exit(); + } +} + +void LimboHSM::_change_state(LimboState *p_state) { + ERR_FAIL_COND(p_state == nullptr); + ERR_FAIL_COND(p_state->get_parent() != this); + + if (active_state) { + active_state->_exit(); + } + + active_state = p_state; + active_state->_enter(); + emit_signal(LimboStringNames::get_singleton()->state_changed, active_state); +} + +void LimboHSM::_enter() { + ERR_FAIL_COND_MSG(get_child_count() == 0, "LimboHSM has no candidate for initial substate."); + ERR_FAIL_COND(active_state != nullptr); + + LimboState::_enter(); + + if (initial_state == nullptr) { + initial_state = Object::cast_to(get_child(0)); + } + if (initial_state) { + _change_state(initial_state); + } +} + +void LimboHSM::_exit() { + ERR_FAIL_COND(active_state == nullptr); + active_state->_exit(); + active_state = nullptr; + LimboState::_exit(); +} + +void LimboHSM::_update(float p_delta) { + if (active) { + ERR_FAIL_COND(active_state == nullptr); + LimboState::_update(p_delta); + active_state->_update(p_delta); + } +} + +void LimboHSM::add_transition(Node *p_from_state, Node *p_to_state, const String &p_event) { + ERR_FAIL_COND(p_from_state == nullptr); + ERR_FAIL_COND(p_from_state->get_parent() != this); + ERR_FAIL_COND(!p_from_state->is_class("LimboState")); + ERR_FAIL_COND(p_to_state == nullptr); + ERR_FAIL_COND(p_to_state->get_parent() != this); + ERR_FAIL_COND(!p_to_state->is_class("LimboState")); + ERR_FAIL_COND(p_event.empty()); + + uint64_t key = _get_transition_key(p_from_state, p_event); + transitions[key] = Object::cast_to(p_to_state); +} + +LimboState *LimboHSM::get_leaf_state() const { + LimboHSM *hsm = const_cast(this); + while (hsm->active_state != nullptr && hsm->active_state->is_class("LimboHSM")) { + hsm = Object::cast_to(hsm->active_state); + } + if (hsm->active_state) { + return hsm->active_state; + } else { + return hsm; + } +} + +bool LimboHSM::dispatch(const String &p_event, const Variant &p_cargo) { + ERR_FAIL_COND_V(p_event.empty(), false); + + bool event_consumed = false; + + if (active_state) { + event_consumed = active_state->dispatch(p_event, p_cargo); + } + + if (!event_consumed) { + event_consumed = LimboState::dispatch(p_event, p_cargo); + } + + if (!event_consumed && active_state) { + uint64_t key = _get_transition_key(active_state, p_event); + if (transitions.has(key)) { + LimboState *to_state = transitions[key]; + _change_state(to_state); + event_consumed = true; + } + } + + if (!event_consumed && p_event == EVENT_FINISHED && !(get_parent()->is_class("LimboState"))) { + _exit(); + } + + return event_consumed; +} + +void LimboHSM::initialize(Object *p_agent, const Ref &p_blackboard) { + ERR_FAIL_COND(p_agent == nullptr); + ERR_FAIL_COND(!p_blackboard.is_valid()); + ERR_FAIL_COND_MSG(get_child_count() == 0, "Cannot initialize LimboHSM: no candidate for initial substate."); + + if (initial_state == nullptr) { + initial_state = Object::cast_to(get_child(0)); + ERR_FAIL_COND_MSG(initial_state == nullptr, "LimboHSM: Child at index 0 is not a LimboState."); + } + + LimboState::initialize(p_agent, p_blackboard); + + for (int i = 0; i < get_child_count(); i++) { + LimboState *c = Object::cast_to(get_child(i)); + if (unlikely(c == nullptr)) { + ERR_PRINT(vformat("LimboHSM: Child at index %d is not a LimboState.", i)); + } else { + c->initialize(p_agent, p_blackboard); + } + } +} + +void LimboHSM::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POST_ENTER_TREE: { + } break; + case NOTIFICATION_PROCESS: { + if (!Engine::get_singleton()->is_editor_hint()) { + if (update_mode == UpdateMode::IDLE) { + _update(get_process_delta_time()); + } + } + } break; + case NOTIFICATION_PHYSICS_PROCESS: { + if (!Engine::get_singleton()->is_editor_hint()) { + if (update_mode == UpdateMode::PHYSICS) { + _update(get_physics_process_delta_time()); + } + } + } break; + } +} + +void LimboHSM::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_update_mode", "p_mode"), &LimboHSM::set_update_mode); + ClassDB::bind_method(D_METHOD("get_update_mode"), &LimboHSM::get_update_mode); + ClassDB::bind_method(D_METHOD("get_active_state"), &LimboHSM::get_active_state); + ClassDB::bind_method(D_METHOD("get_leaf_state"), &LimboHSM::get_leaf_state); + ClassDB::bind_method(D_METHOD("set_active", "p_active"), &LimboHSM::set_active); + ClassDB::bind_method(D_METHOD("update", "p_delta"), &LimboHSM::update); + ClassDB::bind_method(D_METHOD("add_transition", "p_from_state", "p_to_state", "p_event"), &LimboHSM::add_transition); + + BIND_ENUM_CONSTANT(IDLE); + BIND_ENUM_CONSTANT(PHYSICS); + BIND_ENUM_CONSTANT(MANUAL); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Idle, Physics, Manual"), "set_update_mode", "get_update_mode"); + + ADD_SIGNAL(MethodInfo("state_changed", PropertyInfo(Variant::OBJECT, "p_state", PROPERTY_HINT_NONE, "", 0, "LimboState"))); +} + +LimboHSM::LimboHSM() { + update_mode = UpdateMode::IDLE; + active_state = nullptr; + initial_state = nullptr; +} diff --git a/limbo_hsm.h b/limbo_hsm.h new file mode 100644 index 0000000..22002e9 --- /dev/null +++ b/limbo_hsm.h @@ -0,0 +1,60 @@ +/* limbo_hsm.h */ + +#ifndef LIMBO_HSM_H +#define LIMBO_HSM_H + +#include "core/object.h" +#include "limbo_state.h" + +class LimboHSM : public LimboState { + GDCLASS(LimboHSM, LimboState); + +public: + enum UpdateMode : unsigned int { + IDLE, // automatically call update() during NOTIFICATION_PROCESS + PHYSICS, // automatically call update() during NOTIFICATION_PHYSICS + MANUAL, // manually update state machine: user must call update(delta) + }; + +private: + UpdateMode update_mode; + LimboState *initial_state; + LimboState *active_state; + Map transitions; + + _FORCE_INLINE_ uint64_t _get_transition_key(Node *p_from_state, const String &p_event) { + uint64_t key = hash_djb2_one_64(Variant::OBJECT); + key = hash_djb2_one_64(Variant(p_from_state).hash(), key); + key = hash_djb2_one_64(p_event.hash(), key); + return key; + } + +protected: + static void _bind_methods(); + + void _notification(int p_what); + + virtual void _enter(); + virtual void _exit(); + virtual void _update(float p_delta); + + void _change_state(LimboState *p_state); + +public: + void set_update_mode(UpdateMode p_mode) { update_mode = p_mode; } + UpdateMode get_update_mode() const { return update_mode; } + + LimboState *get_active_state() const { return active_state; }; + LimboState *get_leaf_state() const; + void set_active(bool p_active); + + virtual void initialize(Object *p_agent, const Ref &p_blackboard); + virtual bool dispatch(const String &p_event, const Variant &p_cargo); + + void update(float p_delta) { _update(p_delta); } + void add_transition(Node *p_from_state, Node *p_to_state, const String &p_event); + + LimboHSM(); +}; + +#endif // LIMBO_HSM_H \ No newline at end of file diff --git a/limbo_state.cpp b/limbo_state.cpp index 400a5ec..4eb854a 100644 --- a/limbo_state.cpp +++ b/limbo_state.cpp @@ -5,6 +5,7 @@ #include "core/class_db.h" #include "core/error_macros.h" #include "core/object.h" +#include "core/typedefs.h" #include "core/variant.h" #include "limbo_string_names.h" @@ -41,6 +42,9 @@ void LimboState::_enter() { }; void LimboState::_exit() { + if (!active) { + return; + } if (get_script_instance() && get_script_instance()->has_method(LimboStringNames::get_singleton()->_exit)) { get_script_instance()->call(LimboStringNames::get_singleton()->_exit); @@ -62,12 +66,6 @@ void LimboState::initialize(Object *p_agent, const Ref &p_blackboard ERR_FAIL_COND(!p_blackboard.is_valid()); agent = p_agent; blackboard = p_blackboard; - for (int i = 0; i < get_child_count(); i++) { - LimboState *c = Object::cast_to(get_child(i)); - if (c) { - c->initialize(p_agent, p_blackboard); - } - } _setup(); }; diff --git a/limbo_state.h b/limbo_state.h index 891ad40..fa9dee9 100644 --- a/limbo_state.h +++ b/limbo_state.h @@ -10,6 +10,7 @@ #include "scene/main/node.h" // TODO Implement guards +class LimboHSM; class LimboState : public Node { GDCLASS(LimboState, Node); @@ -18,10 +19,12 @@ private: Object *agent; Ref blackboard; Map handlers; - bool active; // Guard *guard; protected: + friend LimboHSM; + bool active; + static void _bind_methods(); void _notification(int p_what); @@ -48,6 +51,7 @@ public: String event_finished() const { return EVENT_FINISHED; } LimboState *get_root() const; bool is_active() const { return active; } + uint32_t hash() const; LimboState(); }; diff --git a/limbo_string_names.cpp b/limbo_string_names.cpp index 26417f0..e743cca 100644 --- a/limbo_string_names.cpp +++ b/limbo_string_names.cpp @@ -17,4 +17,5 @@ LimboStringNames::LimboStringNames() { exited = StaticCString::create("exited"); updated = StaticCString::create("updated"); _update = StaticCString::create("_update"); + state_changed = StaticCString::create("state_changed"); } \ No newline at end of file diff --git a/limbo_string_names.h b/limbo_string_names.h index f938116..6e8bc05 100644 --- a/limbo_string_names.h +++ b/limbo_string_names.h @@ -34,6 +34,7 @@ public: StringName exited; StringName updated; StringName _update; + StringName state_changed; }; #endif // LIMBO_STRING_NAMES_H \ No newline at end of file diff --git a/register_types.cpp b/register_types.cpp index ca09bdd..cfb5f7b 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -39,6 +39,7 @@ #include "bt/decorators/bt_subtree.h" #include "bt/decorators/bt_time_limit.h" #include "core/os/memory.h" +#include "limbo_hsm.h" #include "limbo_state.h" #include "limbo_string_names.h" #include "limbo_utility.h" @@ -53,6 +54,7 @@ void register_limboai_types() { ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class();