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.
This commit is contained in:
reduz 2021-10-27 19:26:50 -03:00
parent d3547be9ae
commit a6e72a971c
29 changed files with 297 additions and 52 deletions

View file

@ -539,6 +539,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);

View file

@ -152,7 +152,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 {

View file

@ -1157,7 +1157,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<const Expression::InputNode *>(p_node);
@ -1183,7 +1183,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression:
const Expression::OperatorNode *op = static_cast<const Expression::OperatorNode *>(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;
}
@ -1191,7 +1191,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;
}
@ -1209,14 +1209,14 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression:
const Expression::IndexNode *index = static_cast<const Expression::IndexNode *>(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;
}
@ -1233,7 +1233,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression:
const Expression::NamedIndexNode *index = static_cast<const Expression::NamedIndexNode *>(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;
}
@ -1253,7 +1253,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;
@ -1270,14 +1270,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;
}
@ -1297,7 +1297,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;
@ -1325,7 +1325,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;
}
@ -1346,7 +1346,7 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression:
const Expression::CallNode *call = static_cast<const Expression::CallNode *>(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;
@ -1359,7 +1359,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;
@ -1369,7 +1369,11 @@ bool Expression::_execute(const Array &p_inputs, Object *p_instance, Expression:
}
Callable::CallError ce;
base.call(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.call(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));
@ -1408,13 +1412,13 @@ Error Expression::parse(const String &p_expression, const Vector<String> &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;
@ -1434,7 +1438,7 @@ String Expression::get_error_text() const {
void Expression::_bind_methods() {
ClassDB::bind_method(D_METHOD("parse", "expression", "input_names"), &Expression::parse, DEFVAL(Vector<String>()));
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);
}

View file

@ -256,14 +256,14 @@ private:
Vector<String> 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<String> &p_input_names = Vector<String>());
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;

View file

@ -818,6 +818,7 @@ Variant Object::call(const StringName &p_method, const Variant **p_args, int p_a
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: {
}
@ -837,6 +838,54 @@ Variant Object::call(const StringName &p_method, const Variant **p_args, int p_a
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);

View file

@ -77,6 +77,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,
@ -748,6 +749,7 @@ public:
Variant callv(const StringName &p_method, const Array &p_args);
virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Variant call(const StringName &p_name, VARIANT_ARG_LIST); // C++ helper
virtual Variant call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
void notification(int p_notification, bool p_reversed = false);
virtual String to_string();

View file

@ -294,6 +294,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 call(p_method, p_args, p_argcount, r_error);
}
void ScriptInstance::get_property_state(List<Pair<StringName, Variant>> &state) {
List<PropertyInfo> pinfo;
get_property_list(&pinfo);

View file

@ -178,6 +178,7 @@ public:
virtual bool has_method(const StringName &p_method) const = 0;
virtual Variant call(const StringName &p_method, VARIANT_ARG_LIST);
virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) = 0;
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) {

View file

@ -61,6 +61,7 @@ public:
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;

View file

@ -3360,6 +3360,46 @@ Variant Variant::call(const StringName &p_method, VARIANT_ARG_DECLARE) {
return ret;
}
Variant Variant::call_const(const StringName &p_method, VARIANT_ARG_DECLARE) {
VARIANT_ARGPTRS;
int argc = 0;
for (int i = 0; i < VARIANT_ARG_MAX; i++) {
if (argptr[i]->get_type() == Variant::NIL) {
break;
}
argc++;
}
Callable::CallError error;
Variant ret;
call_const(p_method, argptr, argc, ret, error);
switch (error.error) {
case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
String err = "Invalid type for argument #" + itos(error.argument) + ", expected '" + Variant::get_type_name(Variant::Type(error.expected)) + "'.";
ERR_PRINT(err.utf8().get_data());
} break;
case Callable::CallError::CALL_ERROR_INVALID_METHOD: {
String err = "Invalid method '" + p_method + "' for type '" + Variant::get_type_name(type) + "'.";
ERR_PRINT(err.utf8().get_data());
} break;
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: {
String err = "Too many arguments for method '" + p_method + "'";
ERR_PRINT(err.utf8().get_data());
} break;
case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: {
String err = "Method is not const '" + p_method + "' and call_const used";
ERR_PRINT(err.utf8().get_data());
} break;
default: {
}
}
return ret;
}
void Variant::construct_from_string(const String &p_string, Variant &r_value, ObjectConstruct p_obj_construct, void *p_construct_ud) {
r_value = Variant();
}
@ -3389,6 +3429,8 @@ String Variant::get_call_error_text(const StringName &p_method, const Variant **
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";
}
@ -3413,6 +3455,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";
}
@ -3443,6 +3487,8 @@ String Variant::get_callable_error_text(const Callable &p_callable, const Varian
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";
}

View file

@ -511,6 +511,9 @@ public:
void call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error);
Variant call(const StringName &p_method, const Variant &p_arg1 = Variant(), const Variant &p_arg2 = Variant(), const Variant &p_arg3 = Variant(), const Variant &p_arg4 = Variant(), const Variant &p_arg5 = Variant(), const Variant &p_arg6 = Variant(), const Variant &p_arg7 = Variant(), const Variant &p_arg8 = Variant());
void call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error);
Variant call_const(const StringName &p_method, const Variant &p_arg1 = Variant(), const Variant &p_arg2 = Variant(), const Variant &p_arg3 = Variant(), const Variant &p_arg4 = Variant(), const Variant &p_arg5 = Variant(), const Variant &p_arg6 = Variant(), const Variant &p_arg7 = Variant(), const Variant &p_arg8 = Variant());
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);

View file

@ -1031,6 +1031,43 @@ void Variant::call(const StringName &p_method, const Variant **p_args, int p_arg
}
}
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;

View file

@ -2318,21 +2318,23 @@
<constant name="PROPERTY_HINT_MULTILINE_TEXT" value="19" enum="PropertyHint">
Hints that a string property is text with line breaks. Editing it will show a text input field where line breaks can be typed.
</constant>
<constant name="PROPERTY_HINT_PLACEHOLDER_TEXT" value="20" enum="PropertyHint">
<constant name="PROPERTY_HINT_EXPRESSION" value="20" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_PLACEHOLDER_TEXT" value="21" enum="PropertyHint">
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.
</constant>
<constant name="PROPERTY_HINT_COLOR_NO_ALPHA" value="21" enum="PropertyHint">
<constant name="PROPERTY_HINT_COLOR_NO_ALPHA" value="22" enum="PropertyHint">
Hints that a color property should be edited without changing its alpha component, i.e. only R, G and B channels are edited.
</constant>
<constant name="PROPERTY_HINT_IMAGE_COMPRESS_LOSSY" value="22" enum="PropertyHint">
<constant name="PROPERTY_HINT_IMAGE_COMPRESS_LOSSY" value="23" enum="PropertyHint">
Hints that an image is compressed using lossy compression.
</constant>
<constant name="PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS" value="23" enum="PropertyHint">
<constant name="PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS" value="24" enum="PropertyHint">
Hints that an image is compressed using lossless compression.
</constant>
<constant name="PROPERTY_HINT_OBJECT_ID" value="24" enum="PropertyHint">
<constant name="PROPERTY_HINT_OBJECT_ID" value="25" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_TYPE_STRING" value="25" enum="PropertyHint">
<constant name="PROPERTY_HINT_TYPE_STRING" value="26" enum="PropertyHint">
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.
@ -2342,37 +2344,37 @@
[/codeblock]
[b]Note:[/b] The final colon is required to specify for properly detecting built-in types.
</constant>
<constant name="PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE" value="26" enum="PropertyHint">
<constant name="PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE" value="27" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_METHOD_OF_VARIANT_TYPE" value="27" enum="PropertyHint">
<constant name="PROPERTY_HINT_METHOD_OF_VARIANT_TYPE" value="28" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_METHOD_OF_BASE_TYPE" value="28" enum="PropertyHint">
<constant name="PROPERTY_HINT_METHOD_OF_BASE_TYPE" value="29" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_METHOD_OF_INSTANCE" value="29" enum="PropertyHint">
<constant name="PROPERTY_HINT_METHOD_OF_INSTANCE" value="30" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_METHOD_OF_SCRIPT" value="30" enum="PropertyHint">
<constant name="PROPERTY_HINT_METHOD_OF_SCRIPT" value="31" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE" value="31" enum="PropertyHint">
<constant name="PROPERTY_HINT_PROPERTY_OF_VARIANT_TYPE" value="32" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_PROPERTY_OF_BASE_TYPE" value="32" enum="PropertyHint">
<constant name="PROPERTY_HINT_PROPERTY_OF_BASE_TYPE" value="33" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_PROPERTY_OF_INSTANCE" value="33" enum="PropertyHint">
<constant name="PROPERTY_HINT_PROPERTY_OF_INSTANCE" value="34" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_PROPERTY_OF_SCRIPT" value="34" enum="PropertyHint">
<constant name="PROPERTY_HINT_PROPERTY_OF_SCRIPT" value="35" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_OBJECT_TOO_BIG" value="35" enum="PropertyHint">
<constant name="PROPERTY_HINT_OBJECT_TOO_BIG" value="36" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_NODE_PATH_VALID_TYPES" value="36" enum="PropertyHint">
<constant name="PROPERTY_HINT_NODE_PATH_VALID_TYPES" value="37" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_SAVE_FILE" value="37" enum="PropertyHint">
<constant name="PROPERTY_HINT_SAVE_FILE" value="38" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_INT_IS_OBJECTID" value="38" enum="PropertyHint">
<constant name="PROPERTY_HINT_INT_IS_OBJECTID" value="39" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_INT_IS_POINTER" value="40" enum="PropertyHint">
<constant name="PROPERTY_HINT_INT_IS_POINTER" value="41" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_ARRAY_TYPE" value="39" enum="PropertyHint">
<constant name="PROPERTY_HINT_ARRAY_TYPE" value="40" enum="PropertyHint">
</constant>
<constant name="PROPERTY_HINT_MAX" value="41" enum="PropertyHint">
<constant name="PROPERTY_HINT_MAX" value="42" enum="PropertyHint">
</constant>
<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags">
</constant>

View file

@ -19,6 +19,10 @@
[/csharp]
[/codeblocks]
</member>
<member name="advance_expression" type="String" setter="set_advance_expression" getter="get_advance_expression" default="&quot;&quot;">
</member>
<member name="advance_expression_base_node" type="NodePath" setter="set_advance_expression_base_node" getter="get_advance_expression_base_node" default="NodePath(&quot;..&quot;)">
</member>
<member name="auto_advance" type="bool" setter="set_auto_advance" getter="has_auto_advance" default="false">
Turn on the transition automatically when this state is reached. This works best with [constant SWITCH_MODE_AT_END].
</member>

View file

@ -56,6 +56,7 @@
<argument index="0" name="inputs" type="Array" default="[]" />
<argument index="1" name="base_instance" type="Object" default="null" />
<argument index="2" name="show_error" type="bool" default="true" />
<argument index="3" name="const_calls_only" type="bool" default="false" />
<description>
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.

View file

@ -32,6 +32,7 @@
#include "editor/editor_resource_preview.h"
#include "editor/filesystem_dock.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor_node.h"
#include "editor_properties_array_dict.h"
#include "editor_scale.h"
@ -162,6 +163,9 @@ void EditorPropertyMultilineText::_notification(int p_what) {
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;
}
}
@ -169,7 +173,7 @@ void EditorPropertyMultilineText::_notification(int p_what) {
void EditorPropertyMultilineText::_bind_methods() {
}
EditorPropertyMultilineText::EditorPropertyMultilineText() {
EditorPropertyMultilineText::EditorPropertyMultilineText(bool p_expression) {
HBoxContainer *hb = memnew(HBoxContainer);
add_child(hb);
set_bottom_editor(hb);
@ -185,6 +189,12 @@ EditorPropertyMultilineText::EditorPropertyMultilineText() {
hb->add_child(open_big_text);
big_text_dialog = nullptr;
big_text = nullptr;
if (p_expression) {
expression = true;
Ref<EditorStandardSyntaxHighlighter> highlighter;
highlighter.instantiate();
text->set_syntax_highlighter(highlighter);
}
}
///////////////////// TEXT ENUM /////////////////////////
@ -3320,6 +3330,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);

View file

@ -80,6 +80,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;
@ -88,7 +89,7 @@ protected:
public:
virtual void update_property() override;
EditorPropertyMultilineText();
EditorPropertyMultilineText(bool p_expression = false);
};
class EditorPropertyTextEnum : public EditorProperty {

View file

@ -513,7 +513,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;
}

View file

@ -1535,7 +1535,7 @@ void ColladaImport::create_animation(int p_clip, bool p_import_value_tracks) {
bool has_rotation = false;
bool has_scale = false;
for (int i = 0; cn->xform_list.size(); i++) {
for (int i = 0; i < cn->xform_list.size(); i++) {
switch (cn->xform_list[i].op) {
case Collada::Node::XForm::OP_ROTATE: {
has_rotation = true;

View file

@ -1133,7 +1133,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() == "") {
results.push_back(whitespace + result.get_construct_string());
} else {

View file

@ -1446,7 +1446,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) {
@ -1622,7 +1622,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;
}

View file

@ -2154,6 +2154,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;
@ -2256,6 +2257,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:
@ -2298,6 +2300,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:

View file

@ -145,6 +145,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);
}

View file

@ -1679,6 +1679,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";
}

View file

@ -68,6 +68,32 @@ StringName AnimationNodeStateMachineTransition::get_advance_condition_name() con
return advance_condition_name;
}
void AnimationNodeStateMachineTransition::set_advance_expression(const String &p_expression) {
advance_expression = p_expression.strip_edges();
if (advance_expression.strip_edges() == String()) {
expression.unref();
return;
}
if (expression.is_null()) {
expression.instantiate();
}
expression->parse(advance_expression);
}
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;
@ -115,11 +141,22 @@ 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::BOOL, "auto_advance"), "set_auto_advance", "has_auto_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"), "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::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);
@ -423,6 +460,19 @@ double AnimationNodeStateMachinePlayback::process(AnimationNodeStateMachine *p_s
auto_advance = true;
}
if (p_state_machine->transitions[i].from == current && p_state_machine->transitions[i].transition->expression.is_valid()) {
Node *base = p_state_machine->get_animation_tree();
ERR_CONTINUE(base == nullptr);
Ref<Expression> exp = p_state_machine->transitions[i].transition->expression;
base = base->get_node_or_null(p_state_machine->transitions[i].transition->advance_expression_base_node);
if (base) {
bool ret = exp->execute(Array(), 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()) {
auto_advance = ret;
}
}
}
if (p_state_machine->transitions[i].from == current && auto_advance) {
if (p_state_machine->transitions[i].transition->get_priority() <= priority_best) {
priority_best = p_state_machine->transitions[i].transition->get_priority();

View file

@ -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,6 +52,11 @@ private:
float xfade = 0.0;
bool disabled = false;
int priority = 1;
String advance_expression;
NodePath advance_expression_base_node = NodePath(String(".."));
friend class AnimationNodeStateMachinePlayback;
Ref<Expression> expression;
protected:
static void _bind_methods();
@ -67,6 +73,12 @@ public:
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;

View file

@ -133,6 +133,11 @@ real_t 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;

View file

@ -102,6 +102,7 @@ protected:
real_t blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, real_t p_time, bool p_seek, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_optimize = true);
real_t blend_input(int p_input, real_t p_time, bool p_seek, 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();

View file

@ -61,7 +61,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);
}