From 2d78b4a6ddaaaee805dde66d86e53e7700e831ee Mon Sep 17 00:00:00 2001 From: "K. S. Ernest (iFire) Lee" Date: Tue, 21 Jun 2022 14:43:33 -0700 Subject: [PATCH] Add AnimationTree Advance Expressions * Allows specifying an expression as a condition for state machine transitions. * For this to work safely (user not call queue_free or something in the expression), a const call mode was added to Object and Variant (and optionally Script). * This mode ensures only const functions can be called, making it safe to use from the editor. * Bonus: Fixed an animation import bug in Collada. This gives much greater flexibility for creating complex state machines. By directly interfacing with the script code, it is possible to create complex animation advance condition for switching between states. Ensure assigning AnimationTreeStateMachineTransition base expression node in editor is relative to current AnimationTree node. Fix advance expression edge stripping. Allow setting an expression base node on the AnimationTree itself. --- core/core_constants.cpp | 1 + core/extension/gdnative_interface.h | 2 +- core/math/expression.cpp | 38 +- core/math/expression.h | 4 +- core/object/object.cpp | 49 +++ core/object/object.h | 382 ++++++++++-------- core/object/script_language.cpp | 5 + core/object/script_language.h | 9 +- core/variant/callable.h | 1 + core/variant/variant.cpp | 2 + core/variant/variant.h | 1 + core/variant/variant_call.cpp | 37 ++ doc/classes/@GlobalScope.xml | 55 +-- .../AnimationNodeStateMachineTransition.xml | 11 + doc/classes/AnimationTree.xml | 3 + doc/classes/Expression.xml | 1 + editor/editor_properties.cpp | 14 +- editor/editor_properties.h | 3 +- editor/editor_spin_slider.cpp | 2 +- editor/plugins/script_text_editor.cpp | 2 +- editor/property_editor.cpp | 4 +- modules/gdscript/gdscript_analyzer.cpp | 3 + modules/gdscript/gdscript_vm.cpp | 2 + modules/visual_script/visual_script.cpp | 2 + .../animation_node_state_machine.cpp | 80 ++++ .../animation/animation_node_state_machine.h | 14 + scene/animation/animation_tree.cpp | 18 + scene/animation/animation_tree.h | 5 + scene/gui/spin_box.cpp | 2 +- 29 files changed, 531 insertions(+), 221 deletions(-) diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 67aa2bbbfbe2..24d8b0af6e64 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -588,6 +588,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_GLOBAL_DIR); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_RESOURCE_TYPE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_MULTILINE_TEXT); + BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_EXPRESSION); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_PLACEHOLDER_TEXT); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_COLOR_NO_ALPHA); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_IMAGE_COMPRESS_LOSSY); diff --git a/core/extension/gdnative_interface.h b/core/extension/gdnative_interface.h index 095c7983ee1e..ccd6fb0f7e16 100644 --- a/core/extension/gdnative_interface.h +++ b/core/extension/gdnative_interface.h @@ -153,7 +153,7 @@ typedef enum { GDNATIVE_CALL_ERROR_TOO_MANY_ARGUMENTS, /* expected is number of arguments */ GDNATIVE_CALL_ERROR_TOO_FEW_ARGUMENTS, /* expected is number of arguments */ GDNATIVE_CALL_ERROR_INSTANCE_IS_NULL, - + GDNATIVE_CALL_ERROR_METHOD_NOT_CONST, /* used for const call */ } GDNativeCallErrorType; typedef struct { diff --git a/core/math/expression.cpp b/core/math/expression.cpp index 5a90f68b66c8..419056d7d6ec 100644 --- a/core/math/expression.cpp +++ b/core/math/expression.cpp @@ -1240,7 +1240,7 @@ bool Expression::_compile_expression() { return false; } -bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression::ENode *p_node, Variant &r_ret, String &r_error_str) { +bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression::ENode *p_node, Variant &r_ret, bool p_const_calls_only, String &r_error_str) { switch (p_node->type) { case Expression::ENode::TYPE_INPUT: { const Expression::InputNode *in = static_cast(p_node); @@ -1266,7 +1266,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: const Expression::OperatorNode *op = static_cast(p_node); Variant a; - bool ret = _execute(p_inputs, p_instance, op->nodes[0], a, r_error_str); + bool ret = _execute(p_inputs, p_instance, op->nodes[0], a, p_const_calls_only, r_error_str); if (ret) { return true; } @@ -1274,7 +1274,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: Variant b; if (op->nodes[1]) { - ret = _execute(p_inputs, p_instance, op->nodes[1], b, r_error_str); + ret = _execute(p_inputs, p_instance, op->nodes[1], b, p_const_calls_only, r_error_str); if (ret) { return true; } @@ -1292,14 +1292,14 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: const Expression::IndexNode *index = static_cast(p_node); Variant base; - bool ret = _execute(p_inputs, p_instance, index->base, base, r_error_str); + bool ret = _execute(p_inputs, p_instance, index->base, base, p_const_calls_only, r_error_str); if (ret) { return true; } Variant idx; - ret = _execute(p_inputs, p_instance, index->index, idx, r_error_str); + ret = _execute(p_inputs, p_instance, index->index, idx, p_const_calls_only, r_error_str); if (ret) { return true; } @@ -1316,7 +1316,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: const Expression::NamedIndexNode *index = static_cast(p_node); Variant base; - bool ret = _execute(p_inputs, p_instance, index->base, base, r_error_str); + bool ret = _execute(p_inputs, p_instance, index->base, base, p_const_calls_only, r_error_str); if (ret) { return true; } @@ -1336,7 +1336,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: arr.resize(array->array.size()); for (int i = 0; i < array->array.size(); i++) { Variant value; - bool ret = _execute(p_inputs, p_instance, array->array[i], value, r_error_str); + bool ret = _execute(p_inputs, p_instance, array->array[i], value, p_const_calls_only, r_error_str); if (ret) { return true; @@ -1353,14 +1353,14 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: Dictionary d; for (int i = 0; i < dictionary->dict.size(); i += 2) { Variant key; - bool ret = _execute(p_inputs, p_instance, dictionary->dict[i + 0], key, r_error_str); + bool ret = _execute(p_inputs, p_instance, dictionary->dict[i + 0], key, p_const_calls_only, r_error_str); if (ret) { return true; } Variant value; - ret = _execute(p_inputs, p_instance, dictionary->dict[i + 1], value, r_error_str); + ret = _execute(p_inputs, p_instance, dictionary->dict[i + 1], value, p_const_calls_only, r_error_str); if (ret) { return true; } @@ -1380,7 +1380,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: for (int i = 0; i < constructor->arguments.size(); i++) { Variant value; - bool ret = _execute(p_inputs, p_instance, constructor->arguments[i], value, r_error_str); + bool ret = _execute(p_inputs, p_instance, constructor->arguments[i], value, p_const_calls_only, r_error_str); if (ret) { return true; @@ -1408,7 +1408,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: for (int i = 0; i < bifunc->arguments.size(); i++) { Variant value; - bool ret = _execute(p_inputs, p_instance, bifunc->arguments[i], value, r_error_str); + bool ret = _execute(p_inputs, p_instance, bifunc->arguments[i], value, p_const_calls_only, r_error_str); if (ret) { return true; } @@ -1429,7 +1429,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: const Expression::CallNode *call = static_cast(p_node); Variant base; - bool ret = _execute(p_inputs, p_instance, call->base, base, r_error_str); + bool ret = _execute(p_inputs, p_instance, call->base, base, p_const_calls_only, r_error_str); if (ret) { return true; @@ -1442,7 +1442,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: for (int i = 0; i < call->arguments.size(); i++) { Variant value; - ret = _execute(p_inputs, p_instance, call->arguments[i], value, r_error_str); + ret = _execute(p_inputs, p_instance, call->arguments[i], value, p_const_calls_only, r_error_str); if (ret) { return true; @@ -1452,7 +1452,11 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression: } Callable::CallError ce; - base.callp(call->method, (const Variant **)argp.ptr(), argp.size(), r_ret, ce); + if (p_const_calls_only) { + base.call_const(call->method, (const Variant **)argp.ptr(), argp.size(), r_ret, ce); + } else { + base.callp(call->method, (const Variant **)argp.ptr(), argp.size(), r_ret, ce); + } if (ce.error != Callable::CallError::CALL_OK) { r_error_str = vformat(RTR("On call to '%s':"), String(call->method)); @@ -1491,13 +1495,13 @@ Error Expression::parse(const String &p_expression, const Vector &p_inpu return OK; } -Variant Expression::execute(Array p_inputs, Object *p_base, bool p_show_error) { +Variant Expression::execute(Array p_inputs, Object *p_base, bool p_show_error, bool p_const_calls_only) { ERR_FAIL_COND_V_MSG(error_set, Variant(), "There was previously a parse error: " + error_str + "."); execution_error = false; Variant output; String error_txt; - bool err = _execute(p_inputs, p_base, root, output, error_txt); + bool err = _execute(p_inputs, p_base, root, output, p_const_calls_only, error_txt); if (err) { execution_error = true; error_str = error_txt; @@ -1517,7 +1521,7 @@ String Expression::get_error_text() const { void Expression::_bind_methods() { ClassDB::bind_method(D_METHOD("parse", "expression", "input_names"), &Expression::parse, DEFVAL(Vector())); - ClassDB::bind_method(D_METHOD("execute", "inputs", "base_instance", "show_error"), &Expression::execute, DEFVAL(Array()), DEFVAL(Variant()), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("execute", "inputs", "base_instance", "show_error", "const_calls_only"), &Expression::execute, DEFVAL(Array()), DEFVAL(Variant()), DEFVAL(true), DEFVAL(false)); ClassDB::bind_method(D_METHOD("has_execute_failed"), &Expression::has_execute_failed); ClassDB::bind_method(D_METHOD("get_error_text"), &Expression::get_error_text); } diff --git a/core/math/expression.h b/core/math/expression.h index 6ea3c1611f1c..2d5891599678 100644 --- a/core/math/expression.h +++ b/core/math/expression.h @@ -257,14 +257,14 @@ class Expression : public RefCounted { Vector input_names; bool execution_error = false; - bool _execute(const Array &p_inputs, Object *p_instance, Expression::ENode *p_node, Variant &r_ret, String &r_error_str); + bool _execute(const Array &p_inputs, Object *p_instance, Expression::ENode *p_node, Variant &r_ret, bool p_const_calls_only, String &r_error_str); protected: static void _bind_methods(); public: Error parse(const String &p_expression, const Vector &p_input_names = Vector()); - Variant execute(Array p_inputs = Array(), Object *p_base = nullptr, bool p_show_error = true); + Variant execute(Array p_inputs = Array(), Object *p_base = nullptr, bool p_show_error = true, bool p_const_calls_only = false); bool has_execute_failed() const; String get_error_text() const; diff --git a/core/object/object.cpp b/core/object/object.cpp index 6585c3fa7938..440da00c178f 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -669,6 +669,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_ case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: return ret; case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: { } @@ -688,6 +689,54 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_ return ret; } +Variant Object::call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + r_error.error = Callable::CallError::CALL_OK; + + if (p_method == CoreStringNames::get_singleton()->_free) { + // Free is not const, so fail. + r_error.error = Callable::CallError::CALL_ERROR_METHOD_NOT_CONST; + return Variant(); + } + + Variant ret; + OBJ_DEBUG_LOCK + + if (script_instance) { + ret = script_instance->call_const(p_method, p_args, p_argcount, r_error); + //force jumptable + switch (r_error.error) { + case Callable::CallError::CALL_OK: + return ret; + case Callable::CallError::CALL_ERROR_INVALID_METHOD: + break; + case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: + break; + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + return ret; + case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: { + } + } + } + + //extension does not need this, because all methods are registered in MethodBind + + MethodBind *method = ClassDB::get_method(get_class_name(), p_method); + + if (method) { + if (!method->is_const()) { + r_error.error = Callable::CallError::CALL_ERROR_METHOD_NOT_CONST; + return ret; + } + ret = method->call(this, p_args, p_argcount, r_error); + } else { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + } + + return ret; +} + void Object::notification(int p_notification, bool p_reversed) { _notificationv(p_notification, p_reversed); diff --git a/core/object/object.h b/core/object/object.h index 7dac96bc2bcc..7ad539968071 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -67,6 +67,7 @@ enum PropertyHint { PROPERTY_HINT_GLOBAL_DIR, ///< a directory path must be passed PROPERTY_HINT_RESOURCE_TYPE, ///< a resource object type PROPERTY_HINT_MULTILINE_TEXT, ///< used for string properties that can contain multiple lines + PROPERTY_HINT_EXPRESSION, ///< used for string properties that can contain multiple lines PROPERTY_HINT_PLACEHOLDER_TEXT, ///< used to set a placeholder text for string properties PROPERTY_HINT_COLOR_NO_ALPHA, ///< used for ignoring alpha component when editing a color PROPERTY_HINT_IMAGE_COMPRESS_LOSSY, @@ -336,158 +337,166 @@ struct ObjectNativeExtension { * much alone defines the object model. */ -#define REVERSE_GET_PROPERTY_LIST \ -public: \ - _FORCE_INLINE_ bool _is_gpl_reversed() const { return true; }; \ - \ +#define REVERSE_GET_PROPERTY_LIST \ +public: \ + _FORCE_INLINE_ bool _is_gpl_reversed() const { \ + return true; \ + }; \ + \ private: -#define UNREVERSE_GET_PROPERTY_LIST \ -public: \ - _FORCE_INLINE_ bool _is_gpl_reversed() const { return false; }; \ - \ +#define UNREVERSE_GET_PROPERTY_LIST \ +public: \ + _FORCE_INLINE_ bool _is_gpl_reversed() const { \ + return false; \ + }; \ + \ private: -#define GDCLASS(m_class, m_inherits) \ -private: \ - void operator=(const m_class &p_rval) {} \ - mutable StringName _class_name; \ - friend class ::ClassDB; \ - \ -public: \ - virtual String get_class() const override { \ - if (_get_extension()) { \ - return _get_extension()->class_name.operator String(); \ - } \ - return String(#m_class); \ - } \ - virtual const StringName *_get_class_namev() const override { \ - if (_get_extension()) { \ - return &_get_extension()->class_name; \ - } \ - if (!_class_name) { \ - _class_name = get_class_static(); \ - } \ - return &_class_name; \ - } \ - static _FORCE_INLINE_ void *get_class_ptr_static() { \ - static int ptr; \ - return &ptr; \ - } \ - static _FORCE_INLINE_ String get_class_static() { \ - return String(#m_class); \ - } \ - static _FORCE_INLINE_ String get_parent_class_static() { \ - return m_inherits::get_class_static(); \ - } \ - static void get_inheritance_list_static(List *p_inheritance_list) { \ - m_inherits::get_inheritance_list_static(p_inheritance_list); \ - p_inheritance_list->push_back(String(#m_class)); \ - } \ - virtual bool is_class(const String &p_class) const override { \ - if (_get_extension() && _get_extension()->is_class(p_class)) { \ - return true; \ - } \ - return (p_class == (#m_class)) ? true : m_inherits::is_class(p_class); \ - } \ - virtual bool is_class_ptr(void *p_ptr) const override { return (p_ptr == get_class_ptr_static()) ? true : m_inherits::is_class_ptr(p_ptr); } \ - \ - static void get_valid_parents_static(List *p_parents) { \ - if (m_class::_get_valid_parents_static != m_inherits::_get_valid_parents_static) { \ - m_class::_get_valid_parents_static(p_parents); \ - } \ - \ - m_inherits::get_valid_parents_static(p_parents); \ - } \ - \ -protected: \ - _FORCE_INLINE_ static void (*_get_bind_methods())() { \ - return &m_class::_bind_methods; \ - } \ - \ -public: \ - static void initialize_class() { \ - static bool initialized = false; \ - if (initialized) { \ - return; \ - } \ - m_inherits::initialize_class(); \ - ::ClassDB::_add_class(); \ - if (m_class::_get_bind_methods() != m_inherits::_get_bind_methods()) { \ - _bind_methods(); \ - } \ - initialized = true; \ - } \ - \ -protected: \ - virtual void _initialize_classv() override { \ - initialize_class(); \ - } \ - _FORCE_INLINE_ bool (Object::*_get_get() const)(const StringName &p_name, Variant &) const { \ - return (bool(Object::*)(const StringName &, Variant &) const) & m_class::_get; \ - } \ - virtual bool _getv(const StringName &p_name, Variant &r_ret) const override { \ - if (m_class::_get_get() != m_inherits::_get_get()) { \ - if (_get(p_name, r_ret)) { \ - return true; \ - } \ - } \ - return m_inherits::_getv(p_name, r_ret); \ - } \ - _FORCE_INLINE_ bool (Object::*_get_set() const)(const StringName &p_name, const Variant &p_property) { \ - return (bool(Object::*)(const StringName &, const Variant &)) & m_class::_set; \ - } \ - virtual bool _setv(const StringName &p_name, const Variant &p_property) override { \ - if (m_inherits::_setv(p_name, p_property)) { \ - return true; \ - } \ - if (m_class::_get_set() != m_inherits::_get_set()) { \ - return _set(p_name, p_property); \ - } \ - return false; \ - } \ - _FORCE_INLINE_ void (Object::*_get_get_property_list() const)(List * p_list) const { \ - return (void(Object::*)(List *) const) & m_class::_get_property_list; \ - } \ - virtual void _get_property_listv(List *p_list, bool p_reversed) const override { \ - if (!p_reversed) { \ - m_inherits::_get_property_listv(p_list, p_reversed); \ - } \ - p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); \ - if (!_is_gpl_reversed()) { \ - ::ClassDB::get_property_list(#m_class, p_list, true, this); \ - } \ - if (m_class::_get_get_property_list() != m_inherits::_get_get_property_list()) { \ - _get_property_list(p_list); \ - } \ - if (_is_gpl_reversed()) { \ - ::ClassDB::get_property_list(#m_class, p_list, true, this); \ - } \ - if (p_reversed) { \ - m_inherits::_get_property_listv(p_list, p_reversed); \ - } \ - } \ - _FORCE_INLINE_ void (Object::*_get_notification() const)(int) { \ - return (void(Object::*)(int)) & m_class::_notification; \ - } \ - virtual void _notificationv(int p_notification, bool p_reversed) override { \ - if (!p_reversed) { \ - m_inherits::_notificationv(p_notification, p_reversed); \ - } \ - if (m_class::_get_notification() != m_inherits::_get_notification()) { \ - _notification(p_notification); \ - } \ - if (p_reversed) { \ - m_inherits::_notificationv(p_notification, p_reversed); \ - } \ - } \ - \ +#define GDCLASS(m_class, m_inherits) \ +private: \ + void operator=(const m_class &p_rval) {} \ + mutable StringName _class_name; \ + friend class ::ClassDB; \ + \ +public: \ + virtual String get_class() const override { \ + if (_get_extension()) { \ + return _get_extension()->class_name.operator String(); \ + } \ + return String(#m_class); \ + } \ + virtual const StringName *_get_class_namev() const override { \ + if (_get_extension()) { \ + return &_get_extension()->class_name; \ + } \ + if (!_class_name) { \ + _class_name = get_class_static(); \ + } \ + return &_class_name; \ + } \ + static _FORCE_INLINE_ void *get_class_ptr_static() { \ + static int ptr; \ + return &ptr; \ + } \ + static _FORCE_INLINE_ String get_class_static() { \ + return String(#m_class); \ + } \ + static _FORCE_INLINE_ String get_parent_class_static() { \ + return m_inherits::get_class_static(); \ + } \ + static void get_inheritance_list_static(List *p_inheritance_list) { \ + m_inherits::get_inheritance_list_static(p_inheritance_list); \ + p_inheritance_list->push_back(String(#m_class)); \ + } \ + virtual bool is_class(const String &p_class) const override { \ + if (_get_extension() && _get_extension()->is_class(p_class)) { \ + return true; \ + } \ + return (p_class == (#m_class)) ? true : m_inherits::is_class(p_class); \ + } \ + virtual bool is_class_ptr(void *p_ptr) const override { \ + return (p_ptr == get_class_ptr_static()) ? true : m_inherits::is_class_ptr(p_ptr); \ + } \ + \ + static void get_valid_parents_static(List *p_parents) { \ + if (m_class::_get_valid_parents_static != m_inherits::_get_valid_parents_static) { \ + m_class::_get_valid_parents_static(p_parents); \ + } \ + \ + m_inherits::get_valid_parents_static(p_parents); \ + } \ + \ +protected: \ + _FORCE_INLINE_ static void (*_get_bind_methods())() { \ + return &m_class::_bind_methods; \ + } \ + \ +public: \ + static void initialize_class() { \ + static bool initialized = false; \ + if (initialized) { \ + return; \ + } \ + m_inherits::initialize_class(); \ + ::ClassDB::_add_class(); \ + if (m_class::_get_bind_methods() != m_inherits::_get_bind_methods()) { \ + _bind_methods(); \ + } \ + initialized = true; \ + } \ + \ +protected: \ + virtual void _initialize_classv() override { \ + initialize_class(); \ + } \ + _FORCE_INLINE_ bool (Object::*_get_get() const)(const StringName &p_name, Variant &) const { \ + return (bool(Object::*)(const StringName &, Variant &) const) & m_class::_get; \ + } \ + virtual bool _getv(const StringName &p_name, Variant &r_ret) const override { \ + if (m_class::_get_get() != m_inherits::_get_get()) { \ + if (_get(p_name, r_ret)) { \ + return true; \ + } \ + } \ + return m_inherits::_getv(p_name, r_ret); \ + } \ + _FORCE_INLINE_ bool (Object::*_get_set() const)(const StringName &p_name, const Variant &p_property) { \ + return (bool(Object::*)(const StringName &, const Variant &)) & m_class::_set; \ + } \ + virtual bool _setv(const StringName &p_name, const Variant &p_property) override { \ + if (m_inherits::_setv(p_name, p_property)) { \ + return true; \ + } \ + if (m_class::_get_set() != m_inherits::_get_set()) { \ + return _set(p_name, p_property); \ + } \ + return false; \ + } \ + _FORCE_INLINE_ void (Object::*_get_get_property_list() const)(List * p_list) const { \ + return (void(Object::*)(List *) const) & m_class::_get_property_list; \ + } \ + virtual void _get_property_listv(List *p_list, bool p_reversed) const override { \ + if (!p_reversed) { \ + m_inherits::_get_property_listv(p_list, p_reversed); \ + } \ + p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); \ + if (!_is_gpl_reversed()) { \ + ::ClassDB::get_property_list(#m_class, p_list, true, this); \ + } \ + if (m_class::_get_get_property_list() != m_inherits::_get_get_property_list()) { \ + _get_property_list(p_list); \ + } \ + if (_is_gpl_reversed()) { \ + ::ClassDB::get_property_list(#m_class, p_list, true, this); \ + } \ + if (p_reversed) { \ + m_inherits::_get_property_listv(p_list, p_reversed); \ + } \ + } \ + _FORCE_INLINE_ void (Object::*_get_notification() const)(int) { \ + return (void(Object::*)(int)) & m_class::_notification; \ + } \ + virtual void _notificationv(int p_notification, bool p_reversed) override { \ + if (!p_reversed) { \ + m_inherits::_notificationv(p_notification, p_reversed); \ + } \ + if (m_class::_get_notification() != m_inherits::_get_notification()) { \ + _notification(p_notification); \ + } \ + if (p_reversed) { \ + m_inherits::_notificationv(p_notification, p_reversed); \ + } \ + } \ + \ private: -#define OBJ_SAVE_TYPE(m_class) \ -public: \ - virtual String get_save_class() const override { return #m_class; } \ - \ +#define OBJ_SAVE_TYPE(m_class) \ +public: \ + virtual String get_save_class() const override { \ + return #m_class; \ + } \ + \ private: class ScriptInstance; @@ -606,17 +615,31 @@ class Object { } friend class NativeExtensionMethodBind; - _ALWAYS_INLINE_ const ObjectNativeExtension *_get_extension() const { return _extension; } - _ALWAYS_INLINE_ GDExtensionClassInstancePtr _get_extension_instance() const { return _extension_instance; } - virtual void _initialize_classv() { initialize_class(); } - virtual bool _setv(const StringName &p_name, const Variant &p_property) { return false; }; - virtual bool _getv(const StringName &p_name, Variant &r_property) const { return false; }; + _ALWAYS_INLINE_ const ObjectNativeExtension *_get_extension() const { + return _extension; + } + _ALWAYS_INLINE_ GDExtensionClassInstancePtr _get_extension_instance() const { + return _extension_instance; + } + virtual void _initialize_classv() { + initialize_class(); + } + virtual bool _setv(const StringName &p_name, const Variant &p_property) { + return false; + }; + virtual bool _getv(const StringName &p_name, Variant &r_property) const { + return false; + }; virtual void _get_property_listv(List *p_list, bool p_reversed) const {}; virtual void _notificationv(int p_notification, bool p_reversed) {} static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_property) { return false; }; - bool _get(const StringName &p_name, Variant &r_property) const { return false; }; + bool _set(const StringName &p_name, const Variant &p_property) { + return false; + }; + bool _get(const StringName &p_name, Variant &r_property) const { + return false; + }; void _get_property_list(List *p_list) const {}; void _notification(int p_notification) {} @@ -671,10 +694,14 @@ class Object { return &ptr; } - bool _is_gpl_reversed() const { return false; } + bool _is_gpl_reversed() const { + return false; + } void detach_from_objectdb(); - _FORCE_INLINE_ ObjectID get_instance_id() const { return _instance_id; } + _FORCE_INLINE_ ObjectID get_instance_id() const { + return _instance_id; + } template static T *cast_to(Object *p_object) { @@ -714,10 +741,16 @@ class Object { }; /* TYPE API */ - static void get_inheritance_list_static(List *p_inheritance_list) { p_inheritance_list->push_back("Object"); } + static void get_inheritance_list_static(List *p_inheritance_list) { + p_inheritance_list->push_back("Object"); + } - static String get_class_static() { return "Object"; } - static String get_parent_class_static() { return String(); } + static String get_class_static() { + return "Object"; + } + static String get_parent_class_static() { + return String(); + } virtual String get_class() const { if (_extension) { @@ -725,7 +758,9 @@ class Object { } return "Object"; } - virtual String get_save_class() const { return get_class(); } //class stored when saving + virtual String get_save_class() const { + return get_class(); + } //class stored when saving virtual bool is_class(const String &p_class) const { if (_extension && _extension->is_class(p_class)) { @@ -733,7 +768,9 @@ class Object { } return (p_class == "Object"); } - virtual bool is_class_ptr(void *p_ptr) const { return get_class_ptr_static() == p_ptr; } + virtual bool is_class_ptr(void *p_ptr) const { + return get_class_ptr_static() == p_ptr; + } _FORCE_INLINE_ const StringName &get_class_name() const { if (_extension) { @@ -759,6 +796,7 @@ class Object { void get_method_list(List *p_list) const; Variant callv(const StringName &p_method, const Array &p_args); virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); + virtual Variant call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); template Variant call(const StringName &p_method, VarArgs... p_args) { @@ -797,7 +835,9 @@ class Object { #endif void set_script_instance(ScriptInstance *p_instance); - _FORCE_INLINE_ ScriptInstance *get_script_instance() const { return script_instance; } + _FORCE_INLINE_ ScriptInstance *get_script_instance() const { + return script_instance; + } // Some script languages can't control instance creation, so this function eases the process. void set_script_and_instance(const Variant &p_script, ScriptInstance *p_instance); @@ -850,14 +890,22 @@ class Object { bool _is_queued_for_deletion = false; // Set to true by SceneTree::queue_delete(). bool is_queued_for_deletion() const; - _FORCE_INLINE_ void set_message_translation(bool p_enable) { _can_translate = p_enable; } - _FORCE_INLINE_ bool can_translate_messages() const { return _can_translate; } + _FORCE_INLINE_ void set_message_translation(bool p_enable) { + _can_translate = p_enable; + } + _FORCE_INLINE_ bool can_translate_messages() const { + return _can_translate; + } #ifdef TOOLS_ENABLED void editor_set_section_unfold(const String &p_section, bool p_unfolded); bool editor_is_section_unfolded(const String &p_section); - const HashSet &editor_get_section_folding() const { return editor_section_folding; } - void editor_clear_section_folding() { editor_section_folding.clear(); } + const HashSet &editor_get_section_folding() const { + return editor_section_folding; + } + void editor_clear_section_folding() { + editor_section_folding.clear(); + } #endif @@ -869,7 +917,9 @@ class Object { void clear_internal_resource_paths(); - _ALWAYS_INLINE_ bool is_ref_counted() const { return type_is_reference; } + _ALWAYS_INLINE_ bool is_ref_counted() const { + return type_is_reference; + } Object(); virtual ~Object(); diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index 66c9a80193c5..4623d0e52539 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -295,6 +295,11 @@ void ScriptServer::save_global_classes() { } //////////////////// + +Variant ScriptInstance::call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + return callp(p_method, p_args, p_argcount, r_error); +} + void ScriptInstance::get_property_state(List> &state) { List pinfo; get_property_list(&pinfo); diff --git a/core/object/script_language.h b/core/object/script_language.h index 0a8e79a24e62..17e22b2f6d8e 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -152,12 +152,16 @@ class Script : public Resource { virtual void get_script_method_list(List *p_list) const = 0; virtual void get_script_property_list(List *p_list) const = 0; - virtual int get_member_line(const StringName &p_member) const { return -1; } + virtual int get_member_line(const StringName &p_member) const { + return -1; + } virtual void get_constants(HashMap *p_constants) {} virtual void get_members(HashSet *p_constants) {} - virtual bool is_placeholder_fallback_enabled() const { return false; } + virtual bool is_placeholder_fallback_enabled() const { + return false; + } virtual const Vector get_rpc_methods() const = 0; @@ -190,6 +194,7 @@ class ScriptInstance { return callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr); } + virtual Variant call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); // implement if language supports const functions virtual void notification(int p_notification) = 0; virtual String to_string(bool *r_valid) { if (r_valid) { diff --git a/core/variant/callable.h b/core/variant/callable.h index 6a760958d6e0..bbcf5427badd 100644 --- a/core/variant/callable.h +++ b/core/variant/callable.h @@ -61,6 +61,7 @@ class Callable { CALL_ERROR_TOO_MANY_ARGUMENTS, // expected is number of arguments CALL_ERROR_TOO_FEW_ARGUMENTS, // expected is number of arguments CALL_ERROR_INSTANCE_IS_NULL, + CALL_ERROR_METHOD_NOT_CONST, }; Error error = Error::CALL_OK; int argument = 0; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 6007268e214a..75aec9a619de 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -3395,6 +3395,8 @@ String Variant::get_call_error_text(Object *p_base, const StringName &p_method, err_text = "Method not found."; } else if (ce.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { err_text = "Instance is null"; + } else if (ce.error == Callable::CallError::CALL_ERROR_METHOD_NOT_CONST) { + err_text = "Method not const in const instance"; } else if (ce.error == Callable::CallError::CALL_OK) { return "Call OK"; } diff --git a/core/variant/variant.h b/core/variant/variant.h index 992d9cad4010..83d244145b31 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -555,6 +555,7 @@ class Variant { return ret; } + void call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error); static void call_static(Variant::Type p_type, const StringName &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error); static String get_call_error_text(const StringName &p_method, const Variant **p_argptrs, int p_argcount, const Callable::CallError &ce); diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 8e420ecf0460..f2c7dbb32e1c 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1046,6 +1046,43 @@ void Variant::callp(const StringName &p_method, const Variant **p_args, int p_ar } } +void Variant::call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) { + if (type == Variant::OBJECT) { + //call object + Object *obj = _get_obj().obj; + if (!obj) { + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; + } +#ifdef DEBUG_ENABLED + if (EngineDebugger::is_active() && !_get_obj().id.is_ref_counted() && ObjectDB::get_instance(_get_obj().id) == nullptr) { + r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; + } + +#endif + r_ret = _get_obj().obj->call_const(p_method, p_args, p_argcount, r_error); + + //else if (type==Variant::METHOD) { + } else { + r_error.error = Callable::CallError::CALL_OK; + + const VariantBuiltInMethodInfo *imf = builtin_method_info[type].lookup_ptr(p_method); + + if (!imf) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + return; + } + + if (!imf->is_const) { + r_error.error = Callable::CallError::CALL_ERROR_METHOD_NOT_CONST; + return; + } + + imf->call(this, p_args, p_argcount, r_ret, imf->default_arguments, r_error); + } +} + void Variant::call_static(Variant::Type p_type, const StringName &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) { r_error.error = Callable::CallError::CALL_OK; diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 863ea899ee62..ebda0794ce01 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2521,21 +2521,24 @@ Hints that a string property is text with line breaks. Editing it will show a text input field where line breaks can be typed. - + + Hints that a string property is an [Expression]. + + Hints that a string property should have a placeholder text visible on its input field, whenever the property is empty. The hint string is the placeholder text to use. - + Hints that a color property should be edited without changing its alpha component, i.e. only R, G and B channels are edited. - + Hints that an image is compressed using lossy compression. - + Hints that an image is compressed using lossless compression. - + - + Hint that a property represents a particular type. If a property is [constant TYPE_STRING], allows to set a type from the create dialog. If you need to create an [Array] to contain elements of a specific type, the [code]hint_string[/code] must encode nested types using [code]":"[/code] and [code]"/"[/code] for specifying [Resource] types. For instance: [codeblock] hint_string = "%s:" % [TYPE_INT] # Array of inteters. @@ -2545,47 +2548,47 @@ [/codeblock] [b]Note:[/b] The final colon is required to specify for properly detecting built-in types. - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + Hints that a string property is a locale code. Editing it will show a locale dialog for picking language and country. - + Hints that a dictionary property is string translation map. Dictionary keys are locale codes and, values are translated strings. - + - + diff --git a/doc/classes/AnimationNodeStateMachineTransition.xml b/doc/classes/AnimationNodeStateMachineTransition.xml index 94e6a2f23d96..e5f6d6331aea 100644 --- a/doc/classes/AnimationNodeStateMachineTransition.xml +++ b/doc/classes/AnimationNodeStateMachineTransition.xml @@ -7,6 +7,13 @@ $DOCS_URL/tutorials/animation/animation_tree.html + + + + + + + Turn on auto advance when this condition is set. The provided name will become a boolean parameter on the [AnimationTree] that can be controlled from code (see [url=$DOCS_URL/tutorials/animation/animation_tree.html#controlling-from-code][/url]). For example, if [member AnimationTree.tree_root] is an [AnimationNodeStateMachine] and [member advance_condition] is set to [code]"idle"[/code]: @@ -19,6 +26,10 @@ [/csharp] [/codeblocks] + + + + Turn on the transition automatically when this state is reached. This works best with [constant SWITCH_MODE_AT_END]. diff --git a/doc/classes/AnimationTree.xml b/doc/classes/AnimationTree.xml index 67e64c6beed6..a18f5a1c9410 100644 --- a/doc/classes/AnimationTree.xml +++ b/doc/classes/AnimationTree.xml @@ -37,6 +37,9 @@ If [code]true[/code], the [AnimationTree] will be processing. + + The path to the [Node] used to evaluate AnimationNode [Expression]s if one is not explictly specified internally. + The path to the [AnimationPlayer] used for animating. diff --git a/doc/classes/Expression.xml b/doc/classes/Expression.xml index b37de09e04d3..50979c9b6841 100644 --- a/doc/classes/Expression.xml +++ b/doc/classes/Expression.xml @@ -56,6 +56,7 @@ + Executes the expression that was previously parsed by [method parse] and returns the result. Before you use the returned object, you should check if the method failed by calling [method has_execute_failed]. If you defined input variables in [method parse], you can specify their values in the inputs array, in the same order. diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 0e6c9162cea5..e5105fdedcec 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -165,6 +165,9 @@ void EditorPropertyMultilineText::_notification(int p_what) { Ref font = get_theme_font(SNAME("font"), SNAME("Label")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label")); text->set_custom_minimum_size(Vector2(0, font->get_height(font_size) * 6)); + text->add_theme_font_override("font", get_theme_font("expression", "EditorFonts")); + text->add_theme_font_size_override("font_size", get_theme_font_size("expression_size", "EditorFonts")); + } break; } } @@ -172,7 +175,7 @@ void EditorPropertyMultilineText::_notification(int p_what) { void EditorPropertyMultilineText::_bind_methods() { } -EditorPropertyMultilineText::EditorPropertyMultilineText() { +EditorPropertyMultilineText::EditorPropertyMultilineText(bool p_expression) { HBoxContainer *hb = memnew(HBoxContainer); hb->add_theme_constant_override("separation", 0); add_child(hb); @@ -189,6 +192,12 @@ EditorPropertyMultilineText::EditorPropertyMultilineText() { hb->add_child(open_big_text); big_text_dialog = nullptr; big_text = nullptr; + if (p_expression) { + expression = true; + Ref highlighter; + highlighter.instantiate(); + text->set_syntax_highlighter(highlighter); + } } ///////////////////// TEXT ENUM ///////////////////////// @@ -3771,6 +3780,9 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ } else if (p_hint == PROPERTY_HINT_MULTILINE_TEXT) { EditorPropertyMultilineText *editor = memnew(EditorPropertyMultilineText); return editor; + } else if (p_hint == PROPERTY_HINT_EXPRESSION) { + EditorPropertyMultilineText *editor = memnew(EditorPropertyMultilineText(true)); + return editor; } else if (p_hint == PROPERTY_HINT_TYPE_STRING) { EditorPropertyClassName *editor = memnew(EditorPropertyClassName); editor->setup("Object", p_hint_text); diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 72b2b0b283f5..7cd6ea4f6b55 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -81,6 +81,7 @@ class EditorPropertyMultilineText : public EditorProperty { void _big_text_changed(); void _text_changed(); void _open_big_text(); + bool expression = false; protected: virtual void _set_read_only(bool p_read_only) override; @@ -89,7 +90,7 @@ class EditorPropertyMultilineText : public EditorProperty { public: virtual void update_property() override; - EditorPropertyMultilineText(); + EditorPropertyMultilineText(bool p_expression = false); }; class EditorPropertyTextEnum : public EditorProperty { diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index a0c818ba8431..f23f0cf7583d 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -530,7 +530,7 @@ void EditorSpinSlider::_evaluate_input_text() { return; } - Variant v = expr->execute(Array(), nullptr, false); + Variant v = expr->execute(Array(), nullptr, false, true); if (v.get_type() == Variant::NIL) { return; } diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 05c707c065a6..7d4ffd1a2526 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1196,7 +1196,7 @@ void ScriptTextEditor::_edit_option(int p_op) { String whitespace = line.substr(0, line.size() - line.strip_edges(true, false).size()); //extract the whitespace at the beginning if (expression.parse(line) == OK) { - Variant result = expression.execute(Array(), Variant(), false); + Variant result = expression.execute(Array(), Variant(), false, true); if (expression.get_error_text().is_empty()) { results.push_back(whitespace + result.get_construct_string()); } else { diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp index 771d34d84143..d936e821df63 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -1474,7 +1474,7 @@ void CustomPropertyEditor::_modified(String p_string) { v = value_editor[0]->get_text().to_int(); return; } else { - v = expr->execute(Array(), nullptr, false); + v = expr->execute(Array(), nullptr, false, false); } if (v != prev_v) { @@ -1650,7 +1650,7 @@ real_t CustomPropertyEditor::_parse_real_expression(String text) { if (err != OK) { out = value_editor[0]->get_text().to_float(); } else { - out = expr->execute(Array(), nullptr, false); + out = expr->execute(Array(), nullptr, false, true); } return out; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 42b02ce3b9da..ea994654bf76 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -2278,6 +2278,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(Too few arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); break; case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: + case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: break; // Can't happen in a builtin constructor. case Callable::CallError::CALL_OK: p_call->is_constant = true; @@ -2380,6 +2381,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call); break; + case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: break; // Can't happen in a builtin constructor. case Callable::CallError::CALL_OK: @@ -2422,6 +2424,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", function_name, err.expected, p_call->arguments.size()), p_call); break; + case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: break; // Can't happen in a builtin constructor. case Callable::CallError::CALL_OK: diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 20b8d29ec328..3f25c2fa432d 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -177,6 +177,8 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const err_text = "Invalid call. Nonexistent " + p_where + "."; } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { err_text = "Attempt to call " + p_where + " on a null instance."; + } else if (p_err.error == Callable::CallError::CALL_ERROR_METHOD_NOT_CONST) { + err_text = "Attempt to call " + p_where + " on a const instance."; } else { err_text = "Bug, call error: #" + itos(p_err.error); } diff --git a/modules/visual_script/visual_script.cpp b/modules/visual_script/visual_script.cpp index c4fafb66761b..c5bcf23c8e6b 100644 --- a/modules/visual_script/visual_script.cpp +++ b/modules/visual_script/visual_script.cpp @@ -1655,6 +1655,8 @@ Variant VisualScriptInstance::_call_internal(const StringName &p_method, void *p error_str += "Expected " + itos(r_error.argument) + " arguments."; } else if (r_error.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { error_str += "Invalid Call."; + } else if (r_error.error == Callable::CallError::CALL_ERROR_METHOD_NOT_CONST) { + error_str += "Method not const in a const instance."; } else if (r_error.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { error_str += "Base Instance is null"; } diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp index 8dcf538b8f83..03dfef59b838 100644 --- a/scene/animation/animation_node_state_machine.cpp +++ b/scene/animation/animation_node_state_machine.cpp @@ -30,8 +30,22 @@ #include "animation_node_state_machine.h" +#ifdef TOOLS_ENABLED +#include "editor/plugins/animation_tree_editor_plugin.h" +#endif + ///////////////////////////////////////////////// +Node *AnimationNodeStateMachineTransition::get_root_path() { +#ifdef TOOLS_ENABLED + AnimationTreeEditor *editor = AnimationTreeEditor::get_singleton(); + if (editor) { + return editor->get_tree(); + } +#endif + return nullptr; +} + void AnimationNodeStateMachineTransition::set_switch_mode(SwitchMode p_mode) { switch_mode = p_mode; } @@ -68,6 +82,34 @@ StringName AnimationNodeStateMachineTransition::get_advance_condition_name() con return advance_condition_name; } +void AnimationNodeStateMachineTransition::set_advance_expression(const String &p_expression) { + advance_expression = p_expression; + + String advance_expression_stripped = advance_expression.strip_edges(); + if (advance_expression_stripped == String()) { + expression.unref(); + return; + } + + if (expression.is_null()) { + expression.instantiate(); + } + + expression->parse(advance_expression_stripped); +} + +String AnimationNodeStateMachineTransition::get_advance_expression() const { + return advance_expression; +} + +void AnimationNodeStateMachineTransition::set_advance_expression_base_node(const NodePath &p_expression_base_node) { + advance_expression_base_node = p_expression_base_node; +} + +NodePath AnimationNodeStateMachineTransition::get_advance_expression_base_node() const { + return advance_expression_base_node; +} + void AnimationNodeStateMachineTransition::set_xfade_time(float p_xfade) { ERR_FAIL_COND(p_xfade < 0); xfade = p_xfade; @@ -97,6 +139,8 @@ int AnimationNodeStateMachineTransition::get_priority() const { } void AnimationNodeStateMachineTransition::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationNodeStateMachineTransition::get_root_path); + ClassDB::bind_method(D_METHOD("set_switch_mode", "mode"), &AnimationNodeStateMachineTransition::set_switch_mode); ClassDB::bind_method(D_METHOD("get_switch_mode"), &AnimationNodeStateMachineTransition::get_switch_mode); @@ -115,11 +159,24 @@ void AnimationNodeStateMachineTransition::_bind_methods() { ClassDB::bind_method(D_METHOD("set_priority", "priority"), &AnimationNodeStateMachineTransition::set_priority); ClassDB::bind_method(D_METHOD("get_priority"), &AnimationNodeStateMachineTransition::get_priority); + ClassDB::bind_method(D_METHOD("set_advance_expression", "text"), &AnimationNodeStateMachineTransition::set_advance_expression); + ClassDB::bind_method(D_METHOD("get_advance_expression"), &AnimationNodeStateMachineTransition::get_advance_expression); + + ClassDB::bind_method(D_METHOD("set_advance_expression_base_node", "path"), &AnimationNodeStateMachineTransition::set_advance_expression_base_node); + ClassDB::bind_method(D_METHOD("get_advance_expression_base_node"), &AnimationNodeStateMachineTransition::get_advance_expression_base_node); + + ADD_GROUP("Switch", ""); ADD_PROPERTY(PropertyInfo(Variant::INT, "switch_mode", PROPERTY_HINT_ENUM, "Immediate,Sync,At End"), "set_switch_mode", "get_switch_mode"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01"), "set_xfade_time", "get_xfade_time"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_advance"), "set_auto_advance", "has_auto_advance"); + ADD_GROUP("Advance", "advance_"); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "advance_condition"), "set_advance_condition", "get_advance_condition"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,240,0.01,suffix:s"), "set_xfade_time", "get_xfade_time"); ADD_PROPERTY(PropertyInfo(Variant::INT, "priority", PROPERTY_HINT_RANGE, "0,32,1"), "set_priority", "get_priority"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "advance_expression", PROPERTY_HINT_EXPRESSION, ""), "set_advance_expression", "get_advance_expression"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "advance_expression_base_node"), "set_advance_expression_base_node", "get_advance_expression_base_node"); + ADD_GROUP("Disabling", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled"); BIND_ENUM_CONSTANT(SWITCH_MODE_IMMEDIATE); @@ -577,6 +634,29 @@ bool AnimationNodeStateMachinePlayback::_check_advance_condition(const Refexpression.is_valid()) { + AnimationTree *tree_base = state_machine->get_animation_tree(); + ERR_FAIL_COND_V(tree_base == nullptr, false); + + NodePath advance_expression_base_node_path; + if (!transition->advance_expression_base_node.is_empty()) { + advance_expression_base_node_path = transition->advance_expression_base_node; + } else { + advance_expression_base_node_path = tree_base->get_advance_expression_base_node(); + } + + Node *expression_base = tree_base->get_node_or_null(advance_expression_base_node_path); + if (expression_base) { + Ref exp = transition->expression; + bool ret = exp->execute(Array(), tree_base, false, Engine::get_singleton()->is_editor_hint()); // Avoid user from crashing the system with an expression by only allowing const calls when editor runs + if (!exp->has_execute_failed()) { + if (ret) { + return true; + } + } + } + } + return false; } diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h index 20f2d6f858a4..a59c4e894d3a 100644 --- a/scene/animation/animation_node_state_machine.h +++ b/scene/animation/animation_node_state_machine.h @@ -31,6 +31,7 @@ #ifndef ANIMATION_NODE_STATE_MACHINE_H #define ANIMATION_NODE_STATE_MACHINE_H +#include "core/math/expression.h" #include "scene/animation/animation_tree.h" class AnimationNodeStateMachineTransition : public Resource { @@ -51,11 +52,18 @@ class AnimationNodeStateMachineTransition : public Resource { float xfade = 0.0; bool disabled = false; int priority = 1; + String advance_expression; + NodePath advance_expression_base_node = NodePath(); + + friend class AnimationNodeStateMachinePlayback; + Ref expression; protected: static void _bind_methods(); public: + Node *get_root_path(); + void set_switch_mode(SwitchMode p_mode); SwitchMode get_switch_mode() const; @@ -67,6 +75,12 @@ class AnimationNodeStateMachineTransition : public Resource { StringName get_advance_condition_name() const; + void set_advance_expression(const String &p_expression); + String get_advance_expression() const; + + void set_advance_expression_base_node(const NodePath &p_expression_base_node); + NodePath get_advance_expression_base_node() const; + void set_xfade_time(float p_xfade); float get_xfade_time() const; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index bcd49d75faa5..8c8822ac3fa7 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -136,6 +136,11 @@ double AnimationNode::_pre_process(const StringName &p_base_path, AnimationNode return t; } +AnimationTree *AnimationNode::get_animation_tree() const { + ERR_FAIL_COND_V(!state, nullptr); + return state->tree; +} + void AnimationNode::make_invalid(const String &p_reason) { ERR_FAIL_COND(!state); state->valid = false; @@ -1704,6 +1709,14 @@ NodePath AnimationTree::get_animation_player() const { return animation_player; } +void AnimationTree::set_advance_expression_base_node(const NodePath &p_advance_expression_base_node) { + advance_expression_base_node = p_advance_expression_base_node; +} + +NodePath AnimationTree::get_advance_expression_base_node() const { + return advance_expression_base_node; +} + bool AnimationTree::is_state_invalid() const { return !state.valid; } @@ -1899,6 +1912,9 @@ void AnimationTree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_animation_player", "root"), &AnimationTree::set_animation_player); ClassDB::bind_method(D_METHOD("get_animation_player"), &AnimationTree::get_animation_player); + ClassDB::bind_method(D_METHOD("set_advance_expression_base_node", "node"), &AnimationTree::set_advance_expression_base_node); + ClassDB::bind_method(D_METHOD("get_advance_expression_base_node"), &AnimationTree::get_advance_expression_base_node); + ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track); ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track); @@ -1912,6 +1928,8 @@ void AnimationTree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "AnimationRootNode"), "set_tree_root", "get_tree_root"); ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "anim_player", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationPlayer"), "set_animation_player", "get_animation_player"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "advance_expression_base_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node"), "set_advance_expression_base_node", "get_advance_expression_base_node"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback"); ADD_GROUP("Root Motion", "root_motion_"); diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index b646efede421..0bfe615c9bad 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -107,6 +107,7 @@ class AnimationNode : public Resource { double blend_input(int p_input, double p_time, bool p_seek, bool p_seek_root, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true); void make_invalid(const String &p_reason); + AnimationTree *get_animation_tree() const; static void _bind_methods(); @@ -270,6 +271,7 @@ class AnimationTree : public Node { HashSet playing_caches; Ref root; + NodePath advance_expression_base_node = NodePath(String(".")); AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE; bool active = false; @@ -332,6 +334,9 @@ class AnimationTree : public Node { void set_animation_player(const NodePath &p_player); NodePath get_animation_player() const; + void set_advance_expression_base_node(const NodePath &p_advance_expression_base_node); + NodePath get_advance_expression_base_node() const; + TypedArray get_configuration_warnings() const override; bool is_state_invalid() const; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index e50d7e765cca..890e349afb4d 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -62,7 +62,7 @@ void SpinBox::_text_submitted(const String &p_string) { return; } - Variant value = expr->execute(Array(), nullptr, false); + Variant value = expr->execute(Array(), nullptr, false, true); if (value.get_type() != Variant::NIL) { set_value(value); }