Fix and improve LimboHSM/State

This commit is contained in:
Serhii Snitsaruk 2022-12-16 11:56:41 +01:00
parent 5de3bf344e
commit 6ae7ea8e40
4 changed files with 99 additions and 105 deletions

View File

@ -7,6 +7,7 @@
#include "core/object/class_db.h" #include "core/object/class_db.h"
#include "core/object/object.h" #include "core/object/object.h"
#include "core/typedefs.h" #include "core/typedefs.h"
#include "core/variant/callable.h"
#include "core/variant/variant.h" #include "core/variant/variant.h"
#include "modules/limboai/blackboard.h" #include "modules/limboai/blackboard.h"
#include "modules/limboai/limbo_state.h" #include "modules/limboai/limbo_state.h"
@ -56,6 +57,7 @@ void LimboHSM::_change_state(LimboState *p_state) {
active_state = p_state; active_state = p_state;
active_state->_enter(); active_state->_enter();
emit_signal(LimboStringNames::get_singleton()->state_changed, active_state); emit_signal(LimboStringNames::get_singleton()->state_changed, active_state);
} }
@ -68,10 +70,11 @@ void LimboHSM::_enter() {
if (initial_state == nullptr) { if (initial_state == nullptr) {
initial_state = Object::cast_to<LimboState>(get_child(0)); initial_state = Object::cast_to<LimboState>(get_child(0));
} }
if (initial_state) {
ERR_FAIL_COND_MSG(initial_state == nullptr, "LimboHSM: Failed to acquire initial state.");
_change_state(initial_state); _change_state(initial_state);
} }
}
void LimboHSM::_exit() { void LimboHSM::_exit() {
ERR_FAIL_COND(active_state == nullptr); ERR_FAIL_COND(active_state == nullptr);
@ -146,12 +149,17 @@ bool LimboHSM::dispatch(const String &p_event, const Variant &p_cargo) {
} }
if (to_state != nullptr) { if (to_state != nullptr) {
bool permitted = true; bool permitted = true;
if (to_state->guard.obj != nullptr) { if (to_state->guard_callable.is_valid()) {
Variant result = to_state->guard.obj->callv(to_state->guard.func, to_state->guard.binds); Callable::CallError ce;
if (unlikely(result.get_type() != Variant::BOOL)) { Variant ret;
ERR_PRINT_ONCE(vformat("State guard func \"%s()\" returned non-boolean value (%s).", to_state->guard.func, to_state)); to_state->guard_callable.callp(nullptr, 0, ret, ce);
if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
ERR_PRINT_ONCE("LimboHSM: Error calling substate's guard callable: " + Variant::get_callable_error_text(to_state->guard_callable, nullptr, 0, ce));
}
if (unlikely(ret.get_type() != Variant::BOOL)) {
ERR_PRINT_ONCE(vformat("State guard callable %s returned non-boolean value (%s).", to_state->guard_callable, to_state));
} else { } else {
permitted = bool(result); permitted = bool(ret);
} }
} }
if (permitted) { if (permitted) {
@ -168,7 +176,7 @@ bool LimboHSM::dispatch(const String &p_event, const Variant &p_cargo) {
return event_consumed; return event_consumed;
} }
void LimboHSM::initialize(Object *p_agent, const Ref<Blackboard> &p_parent_scope) { void LimboHSM::initialize(Node *p_agent, const Ref<Blackboard> &p_parent_scope) {
ERR_FAIL_COND(p_agent == nullptr); ERR_FAIL_COND(p_agent == nullptr);
if (!p_parent_scope.is_null()) { if (!p_parent_scope.is_null()) {
blackboard->set_parent_scope(p_parent_scope); blackboard->set_parent_scope(p_parent_scope);
@ -176,7 +184,7 @@ void LimboHSM::initialize(Object *p_agent, const Ref<Blackboard> &p_parent_scope
_initialize(p_agent, nullptr); _initialize(p_agent, nullptr);
} }
void LimboHSM::_initialize(Object *p_agent, const Ref<Blackboard> &p_blackboard) { void LimboHSM::_initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard) {
ERR_FAIL_COND(p_agent == nullptr); ERR_FAIL_COND(p_agent == nullptr);
ERR_FAIL_COND_MSG(agent != nullptr, "LimboAI: HSM already initialized."); ERR_FAIL_COND_MSG(agent != nullptr, "LimboAI: HSM already initialized.");
ERR_FAIL_COND_MSG(get_child_count() == 0, "Cannot initialize LimboHSM: no candidate for initial substate."); ERR_FAIL_COND_MSG(get_child_count() == 0, "Cannot initialize LimboHSM: no candidate for initial substate.");
@ -203,18 +211,18 @@ void LimboHSM::_notification(int p_what) {
case NOTIFICATION_POST_ENTER_TREE: { case NOTIFICATION_POST_ENTER_TREE: {
} break; } break;
case NOTIFICATION_PROCESS: { case NOTIFICATION_PROCESS: {
if (!Engine::get_singleton()->is_editor_hint()) { // if (!Engine::get_singleton()->is_editor_hint()) {
if (update_mode == UpdateMode::IDLE) { // if (update_mode == UpdateMode::IDLE) {
_update(get_process_delta_time()); _update(get_process_delta_time());
} // }
} // }
} break; } break;
case NOTIFICATION_PHYSICS_PROCESS: { case NOTIFICATION_PHYSICS_PROCESS: {
if (!Engine::get_singleton()->is_editor_hint()) { // if (!Engine::get_singleton()->is_editor_hint()) {
if (update_mode == UpdateMode::PHYSICS) { // if (update_mode == UpdateMode::PHYSICS) {
_update(get_physics_process_delta_time()); _update(get_physics_process_delta_time());
} // }
} // }
} break; } break;
} }
} }
@ -240,14 +248,14 @@ void LimboHSM::_bind_methods() {
BIND_ENUM_CONSTANT(MANUAL); BIND_ENUM_CONSTANT(MANUAL);
ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Idle, Physics, Manual"), "set_update_mode", "get_update_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Idle, Physics, Manual"), "set_update_mode", "get_update_mode");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ANYSTATE", PROPERTY_HINT_NONE, "", 0), "", "anystate"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ANYSTATE", PROPERTY_HINT_RESOURCE_TYPE, "LimboState", 0), "", "anystate");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "initial_state", PROPERTY_HINT_RESOURCE_TYPE, "LimboState", 0), "set_initial_state", "get_initial_state"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "initial_state", PROPERTY_HINT_RESOURCE_TYPE, "LimboState", 0), "set_initial_state", "get_initial_state");
ADD_SIGNAL(MethodInfo("state_changed", PropertyInfo(Variant::OBJECT, "p_state", PROPERTY_HINT_NONE, "", 0, "LimboState"))); ADD_SIGNAL(MethodInfo("state_changed", PropertyInfo(Variant::OBJECT, "p_state", PROPERTY_HINT_RESOURCE_TYPE, "LimboState", 0)));
} }
LimboHSM::LimboHSM() { LimboHSM::LimboHSM() {
update_mode = UpdateMode::IDLE; update_mode = UpdateMode::PHYSICS;
active_state = nullptr; active_state = nullptr;
initial_state = nullptr; initial_state = nullptr;
} }

View File

@ -35,7 +35,7 @@ protected:
void _notification(int p_what); void _notification(int p_what);
virtual void _initialize(Object *p_agent, const Ref<Blackboard> &p_blackboard) override; virtual void _initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard) override;
virtual void _enter() override; virtual void _enter() override;
virtual void _exit() override; virtual void _exit() override;
virtual void _update(float p_delta) override; virtual void _update(float p_delta) override;
@ -53,11 +53,12 @@ public:
void set_initial_state(Node *p_state); void set_initial_state(Node *p_state);
LimboState *get_initial_state() const { return initial_state; } LimboState *get_initial_state() const { return initial_state; }
virtual void initialize(Object *p_agent, const Ref<Blackboard> &p_parent_scope = nullptr); virtual void initialize(Node *p_agent, const Ref<Blackboard> &p_parent_scope = nullptr);
virtual bool dispatch(const String &p_event, const Variant &p_cargo) override; virtual bool dispatch(const String &p_event, const Variant &p_cargo) override;
void update(float p_delta) { _update(p_delta); } void update(float p_delta) { _update(p_delta); }
void add_transition(Node *p_from_state, Node *p_to_state, const String &p_event); void add_transition(Node *p_from_state, Node *p_to_state, const String &p_event);
// void add_transition_from_any_state(Node *p_to_state, const String &p_event);
LimboState *anystate() const { return nullptr; }; LimboState *anystate() const { return nullptr; };
LimboHSM(); LimboHSM();

View File

@ -6,6 +6,7 @@
#include "core/object/object.h" #include "core/object/object.h"
#include "core/typedefs.h" #include "core/typedefs.h"
#include "core/variant/array.h" #include "core/variant/array.h"
#include "core/variant/callable.h"
#include "core/variant/variant.h" #include "core/variant/variant.h"
#include "limbo_string_names.h" #include "limbo_string_names.h"
@ -25,19 +26,13 @@ LimboState *LimboState::named(String p_name) {
}; };
void LimboState::_setup() { void LimboState::_setup() {
if (get_script_instance() && GDVIRTUAL_CALL(_setup);
get_script_instance()->has_method(LimboStringNames::get_singleton()->_setup)) {
get_script_instance()->call(LimboStringNames::get_singleton()->_setup);
}
emit_signal(LimboStringNames::get_singleton()->setup); emit_signal(LimboStringNames::get_singleton()->setup);
}; };
void LimboState::_enter() { void LimboState::_enter() {
active = true; active = true;
if (get_script_instance() && GDVIRTUAL_CALL(_enter);
get_script_instance()->has_method(LimboStringNames::get_singleton()->_enter)) {
get_script_instance()->call(LimboStringNames::get_singleton()->_enter);
}
emit_signal(LimboStringNames::get_singleton()->entered); emit_signal(LimboStringNames::get_singleton()->entered);
}; };
@ -45,23 +40,17 @@ void LimboState::_exit() {
if (!active) { if (!active) {
return; return;
} }
if (get_script_instance() && GDVIRTUAL_CALL(_exit);
get_script_instance()->has_method(LimboStringNames::get_singleton()->_exit)) {
get_script_instance()->call(LimboStringNames::get_singleton()->_exit);
}
emit_signal(LimboStringNames::get_singleton()->exited); emit_signal(LimboStringNames::get_singleton()->exited);
active = false; active = false;
}; };
void LimboState::_update(float p_delta) { void LimboState::_update(float p_delta) {
if (get_script_instance() && GDVIRTUAL_CALL(_update, p_delta);
get_script_instance()->has_method(LimboStringNames::get_singleton()->_update)) {
get_script_instance()->call(LimboStringNames::get_singleton()->_update, p_delta);
}
emit_signal(LimboStringNames::get_singleton()->updated, p_delta); emit_signal(LimboStringNames::get_singleton()->updated, p_delta);
}; };
void LimboState::_initialize(Object *p_agent, const Ref<Blackboard> &p_blackboard) { void LimboState::_initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard) {
ERR_FAIL_COND(p_agent == nullptr); ERR_FAIL_COND(p_agent == nullptr);
agent = p_agent; agent = p_agent;
@ -79,60 +68,66 @@ void LimboState::_initialize(Object *p_agent, const Ref<Blackboard> &p_blackboar
bool LimboState::dispatch(const String &p_event, const Variant &p_cargo) { bool LimboState::dispatch(const String &p_event, const Variant &p_cargo) {
ERR_FAIL_COND_V(p_event.is_empty(), false); ERR_FAIL_COND_V(p_event.is_empty(), false);
bool result = false;
if (handlers.size() > 0 && handlers.has(p_event)) { if (handlers.size() > 0 && handlers.has(p_event)) {
Callable::CallError ce;
Variant ret;
if (p_cargo.get_type() == Variant::NIL) { if (p_cargo.get_type() == Variant::NIL) {
result = call(handlers[p_event]); handlers[p_event].callp(nullptr, 0, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_PRINT("Error calling event handler " + Variant::get_callable_error_text(handlers[p_event], nullptr, 0, ce));
}
} else { } else {
result = call(handlers[p_event], p_cargo); const Variant *argptrs[1];
argptrs[0] = &p_cargo;
handlers[p_event].callp(argptrs, 1, ret, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_PRINT("Error calling event handler " + Variant::get_callable_error_text(handlers[p_event], argptrs, 1, ce));
} }
} }
return result;
}
void LimboState::add_event_handler(const String &p_event, const StringName &p_method) { if (unlikely(ret.get_type() != Variant::BOOL)) {
ERR_PRINT("Event handler returned unexpected type: " + Variant::get_type_name(ret.get_type()));
} else {
return ret;
}
}
return false;
}
void LimboState::add_event_handler(const String &p_event, const Callable &p_handler) {
ERR_FAIL_COND(p_event.is_empty()); ERR_FAIL_COND(p_event.is_empty());
ERR_FAIL_COND(!has_method(p_method)); ERR_FAIL_COND(!p_handler.is_valid());
handlers.insert(p_event, p_method); handlers.insert(p_event, p_handler);
} }
LimboState *LimboState::call_on_enter(Object *p_object, const StringName &p_method) { LimboState *LimboState::call_on_enter(Object *p_object, const Callable &p_callable) {
ERR_FAIL_COND_V(p_object == nullptr, this); ERR_FAIL_COND_V(p_object == nullptr, this);
ERR_FAIL_COND_V(!p_object->has_method(p_method), this); ERR_FAIL_COND_V(!p_callable.is_valid(), this);
ERR_PRINT("NOT IMPLEMENTED"); connect(LimboStringNames::get_singleton()->entered, p_callable);
// connect(LimboStringNames::get_singleton()->entered, p_object, p_method);
return this; return this;
} }
LimboState *LimboState::call_on_exit(Object *p_object, const StringName &p_method) { LimboState *LimboState::call_on_exit(Object *p_object, const Callable &p_callable) {
ERR_FAIL_COND_V(p_object == nullptr, this); ERR_FAIL_COND_V(p_object == nullptr, this);
ERR_FAIL_COND_V(!p_object->has_method(p_method), this); ERR_FAIL_COND_V(!p_callable.is_valid(), this);
ERR_PRINT("NOT IMPLEMENTED"); connect(LimboStringNames::get_singleton()->exited, p_callable);
// connect(LimboStringNames::get_singleton()->exited, p_object, p_method);
return this; return this;
} }
LimboState *LimboState::call_on_update(Object *p_object, const StringName &p_method) { LimboState *LimboState::call_on_update(Object *p_object, const Callable &p_callable) {
ERR_FAIL_COND_V(p_object == nullptr, this); ERR_FAIL_COND_V(p_object == nullptr, this);
ERR_FAIL_COND_V(!p_object->has_method(p_method), this); ERR_FAIL_COND_V(!p_callable.is_valid(), this);
ERR_PRINT("NOT IMPLEMENTED"); connect(LimboStringNames::get_singleton()->updated, p_callable);
// connect(LimboStringNames::get_singleton()->updated, p_object, p_method);
return this; return this;
} }
void LimboState::set_guard_func(Object *p_object, const StringName &p_func, const Array &p_binds) { void LimboState::set_guard(const Callable &p_guard_callable) {
ERR_FAIL_COND(p_object == nullptr); ERR_FAIL_COND(!p_guard_callable.is_valid());
ERR_FAIL_COND(!p_object->has_method(p_func)); guard_callable = p_guard_callable;
guard.obj = p_object;
guard.func = p_func;
guard.binds = p_binds;
} }
void LimboState::clear_guard_func() { void LimboState::clear_guard() {
guard.obj = nullptr; guard_callable = Callable();
guard.func = "";
guard.binds.clear();
} }
void LimboState::_notification(int p_what) { void LimboState::_notification(int p_what) {
@ -158,29 +153,24 @@ void LimboState::_bind_methods() {
ClassDB::bind_method(D_METHOD("_initialize", "p_agent", "p_blackboard"), &LimboState::_initialize); ClassDB::bind_method(D_METHOD("_initialize", "p_agent", "p_blackboard"), &LimboState::_initialize);
ClassDB::bind_method(D_METHOD("dispatch", "p_event", "p_cargo"), &LimboState::dispatch, Variant()); ClassDB::bind_method(D_METHOD("dispatch", "p_event", "p_cargo"), &LimboState::dispatch, Variant());
ClassDB::bind_method(D_METHOD("named", "p_name"), &LimboState::named); ClassDB::bind_method(D_METHOD("named", "p_name"), &LimboState::named);
ClassDB::bind_method(D_METHOD("add_event_handler", "p_event", "p_method"), &LimboState::add_event_handler); ClassDB::bind_method(D_METHOD("add_event_handler", "p_event", "p_handler"), &LimboState::add_event_handler);
ClassDB::bind_method(D_METHOD("call_on_enter", "p_object", "p_method"), &LimboState::call_on_enter); ClassDB::bind_method(D_METHOD("call_on_enter", "p_callable"), &LimboState::call_on_enter);
ClassDB::bind_method(D_METHOD("call_on_exit", "p_object", "p_method"), &LimboState::call_on_exit); ClassDB::bind_method(D_METHOD("call_on_exit", "p_callable"), &LimboState::call_on_exit);
ClassDB::bind_method(D_METHOD("call_on_update", "p_object", "p_method"), &LimboState::call_on_update); ClassDB::bind_method(D_METHOD("call_on_update", "p_callable"), &LimboState::call_on_update);
ClassDB::bind_method(D_METHOD("set_guard_func", "p_object", "p_func", "p_binds"), &LimboState::set_guard_func, Array()); ClassDB::bind_method(D_METHOD("set_guard", "p_guard_callable"), &LimboState::set_guard);
ClassDB::bind_method(D_METHOD("clear_guard_func"), &LimboState::clear_guard_func); ClassDB::bind_method(D_METHOD("clear_guard"), &LimboState::clear_guard);
ClassDB::bind_method(D_METHOD("get_blackboard"), &LimboState::get_blackboard); ClassDB::bind_method(D_METHOD("get_blackboard"), &LimboState::get_blackboard);
ClassDB::bind_method(D_METHOD("_set_blackboard_data", "p_blackboard"), &LimboState::_set_blackboard_data); ClassDB::bind_method(D_METHOD("_set_blackboard_data", "p_blackboard"), &LimboState::_set_blackboard_data);
ClassDB::bind_method(D_METHOD("_get_blackboard_data"), &LimboState::_get_blackboard_data); ClassDB::bind_method(D_METHOD("_get_blackboard_data"), &LimboState::_get_blackboard_data);
// BIND_VMETHOD(MethodInfo("_setup"));
// BIND_VMETHOD(MethodInfo("_enter"));
// BIND_VMETHOD(MethodInfo("_exit"));
// BIND_VMETHOD(MethodInfo("_update", PropertyInfo(Variant::REAL, "p_delta")));
GDVIRTUAL_BIND(_setup); GDVIRTUAL_BIND(_setup);
GDVIRTUAL_BIND(_enter); GDVIRTUAL_BIND(_enter);
GDVIRTUAL_BIND(_exit); GDVIRTUAL_BIND(_exit);
GDVIRTUAL_BIND(_update, "p_delta"); GDVIRTUAL_BIND(_update, "p_delta");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "EVENT_FINISHED", PROPERTY_HINT_NONE, "", 0), "", "event_finished"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "EVENT_FINISHED", PROPERTY_HINT_NONE, "", 0), "", "event_finished");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "agent", PROPERTY_HINT_RESOURCE_TYPE, "Object", 0), "set_agent", "get_agent"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "agent", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_agent", "get_agent");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard", PROPERTY_HINT_RESOURCE_TYPE, "Blackboard", 0), "", "get_blackboard"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard", PROPERTY_HINT_RESOURCE_TYPE, "Blackboard", 0), "", "get_blackboard");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_blackboard_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_blackboard_data", "_get_blackboard_data"); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_blackboard_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_blackboard_data", "_get_blackboard_data");
@ -195,7 +185,7 @@ LimboState::LimboState() {
active = false; active = false;
blackboard = Ref<Blackboard>(memnew(Blackboard)); blackboard = Ref<Blackboard>(memnew(Blackboard));
guard.obj = nullptr; guard_callable = Callable();
set_process(false); set_process(false);
set_physics_process(false); set_physics_process(false);

View File

@ -10,6 +10,7 @@
#include "core/string/string_name.h" #include "core/string/string_name.h"
#include "core/string/ustring.h" #include "core/string/ustring.h"
#include "core/templates/hash_map.h" #include "core/templates/hash_map.h"
#include "core/variant/callable.h"
#include "core/variant/variant.h" #include "core/variant/variant.h"
#include "scene/main/node.h" #include "scene/main/node.h"
@ -19,19 +20,14 @@ class LimboState : public Node {
GDCLASS(LimboState, Node); GDCLASS(LimboState, Node);
private: private:
struct GuardCallback { Node *agent;
Object *obj = nullptr;
StringName func;
Array binds;
};
Object *agent;
Ref<Blackboard> blackboard; Ref<Blackboard> blackboard;
HashMap<String, StringName> handlers; HashMap<String, Callable> handlers;
GuardCallback guard; Callable guard_callable;
protected: protected:
friend LimboHSM; friend LimboHSM;
bool active; bool active;
static void _bind_methods(); static void _bind_methods();
@ -41,7 +37,7 @@ protected:
void _set_blackboard_data(Dictionary p_value) { blackboard->set_data(p_value.duplicate()); } void _set_blackboard_data(Dictionary p_value) { blackboard->set_data(p_value.duplicate()); }
Dictionary _get_blackboard_data() const { return blackboard->get_data(); } Dictionary _get_blackboard_data() const { return blackboard->get_data(); }
virtual void _initialize(Object *p_agent, const Ref<Blackboard> &p_blackboard); virtual void _initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard);
virtual void _setup(); virtual void _setup();
virtual void _enter(); virtual void _enter();
@ -53,30 +49,29 @@ protected:
GDVIRTUAL0(_exit); GDVIRTUAL0(_exit);
GDVIRTUAL1(_update, float); GDVIRTUAL1(_update, float);
void add_event_handler(const String &p_event, const StringName &p_method); void add_event_handler(const String &p_event, const Callable &p_handler);
public: public:
static const String EVENT_FINISHED; static const String EVENT_FINISHED;
Ref<Blackboard> get_blackboard() const { return blackboard; } Ref<Blackboard> get_blackboard() const { return blackboard; }
Object *get_agent() const { return agent; } Node *get_agent() const { return agent; }
void set_agent(Object *p_agent) { agent = p_agent; } void set_agent(Node *p_agent) { agent = p_agent; }
virtual bool dispatch(const String &p_event, const Variant &p_cargo); virtual bool dispatch(const String &p_event, const Variant &p_cargo);
LimboState *named(String p_name); LimboState *named(String p_name);
// LimboState *call_on_setup(Object *p_object, const StringName &p_method) {} LimboState *call_on_enter(Object *p_object, const Callable &p_callable);
LimboState *call_on_enter(Object *p_object, const StringName &p_method); LimboState *call_on_exit(Object *p_object, const Callable &p_callable);
LimboState *call_on_exit(Object *p_object, const StringName &p_method); LimboState *call_on_update(Object *p_object, const Callable &p_callable);
LimboState *call_on_update(Object *p_object, const StringName &p_method);
String event_finished() const { return EVENT_FINISHED; } _FORCE_INLINE_ String event_finished() const { return EVENT_FINISHED; }
LimboState *get_root() const; LimboState *get_root() const;
bool is_active() const { return active; } bool is_active() const { return active; }
void set_guard_func(Object *p_object, const StringName &p_func, const Array &p_binds = Array()); void set_guard(const Callable &p_guard_callable);
void clear_guard_func(); void clear_guard();
LimboState(); LimboState();
}; };