From 5909f9f07547895de24fb6965d44c859b69a54a2 Mon Sep 17 00:00:00 2001 From: Dmitrii Maganov Date: Sun, 27 Nov 2022 09:56:53 +0200 Subject: GDScript: Fix issues with typed arrays --- core/variant/array.cpp | 137 +++++++++++++++++------------- core/variant/array.h | 9 +- core/variant/container_type_validate.h | 11 ++- core/variant/typed_array.h | 42 +++++----- core/variant/variant_call.cpp | 11 +-- core/variant/variant_internal.h | 19 +++-- core/variant/variant_parser.cpp | 148 +++++++++++++++++++++++++++++---- 7 files changed, 259 insertions(+), 118 deletions(-) (limited to 'core/variant') diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 94d1596514..d3c5ca801f 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -64,7 +64,7 @@ void Array::_ref(const Array &p_from) const { _unref(); - _p = p_from._p; + _p = _fp; } void Array::_unref() const { @@ -191,62 +191,73 @@ uint32_t Array::recursive_hash(int recursion_count) const { return hash_fmix32(h); } -bool Array::_assign(const Array &p_array) { - bool can_convert = p_array._p->typed.type == Variant::NIL; - can_convert |= _p->typed.type == Variant::STRING && p_array._p->typed.type == Variant::STRING_NAME; - can_convert |= _p->typed.type == Variant::STRING_NAME && p_array._p->typed.type == Variant::STRING; +void Array::operator=(const Array &p_array) { + if (this == &p_array) { + return; + } + _ref(p_array); +} + +void Array::assign(const Array &p_array) { + const ContainerTypeValidate &typed = _p->typed; + const ContainerTypeValidate &source_typed = p_array._p->typed; - if (_p->typed.type != Variant::OBJECT && _p->typed.type == p_array._p->typed.type) { - //same type or untyped, just reference, should be fine - _ref(p_array); - } else if (_p->typed.type == Variant::NIL) { //from typed to untyped, must copy, but this is cheap anyway + if (typed == source_typed || typed.type == Variant::NIL || (source_typed.type == Variant::OBJECT && typed.can_reference(source_typed))) { + // from same to same or + // from anything to variants or + // from subclasses to base classes _p->array = p_array._p->array; - } else if (can_convert) { //from untyped to typed, must try to check if they are all valid - if (_p->typed.type == Variant::OBJECT) { - //for objects, it needs full validation, either can be converted or fail - for (int i = 0; i < p_array._p->array.size(); i++) { - const Variant &element = p_array._p->array[i]; - if (element.get_type() != Variant::OBJECT || !_p->typed.validate_object(element, "assign")) { - return false; - } - } - _p->array = p_array._p->array; //then just copy, which is cheap anyway + return; + } - } else { - //for non objects, we need to check if there is a valid conversion, which needs to happen one by one, so this is the worst case. - Vector new_array; - new_array.resize(p_array._p->array.size()); - for (int i = 0; i < p_array._p->array.size(); i++) { - Variant src_val = p_array._p->array[i]; - if (src_val.get_type() == _p->typed.type) { - new_array.write[i] = src_val; - } else if (Variant::can_convert_strict(src_val.get_type(), _p->typed.type)) { - Variant *ptr = &src_val; - Callable::CallError ce; - Variant::construct(_p->typed.type, new_array.write[i], (const Variant **)&ptr, 1, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_FAIL_V_MSG(false, "Unable to convert array index " + itos(i) + " from '" + Variant::get_type_name(src_val.get_type()) + "' to '" + Variant::get_type_name(_p->typed.type) + "'."); - } - } else { - ERR_FAIL_V_MSG(false, "Unable to convert array index " + itos(i) + " from '" + Variant::get_type_name(src_val.get_type()) + "' to '" + Variant::get_type_name(_p->typed.type) + "'."); - } + const Variant *source = p_array._p->array.ptr(); + int size = p_array._p->array.size(); + + if ((source_typed.type == Variant::NIL && typed.type == Variant::OBJECT) || (source_typed.type == Variant::OBJECT && source_typed.can_reference(typed))) { + // from variants to objects or + // from base classes to subclasses + for (int i = 0; i < size; i++) { + const Variant &element = source[i]; + if (element.get_type() != Variant::NIL && (element.get_type() != Variant::OBJECT || !typed.validate_object(element, "assign"))) { + ERR_FAIL_MSG(vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type))); } + } + _p->array = p_array._p->array; + return; + } - _p->array = new_array; + Vector array; + array.resize(size); + Variant *data = array.ptrw(); + + if (source_typed.type == Variant::NIL && typed.type != Variant::OBJECT) { + // from variants to primitives + for (int i = 0; i < size; i++) { + const Variant *value = source + i; + if (value->get_type() == typed.type) { + data[i] = *value; + continue; + } + if (!Variant::can_convert_strict(value->get_type(), typed.type)) { + ERR_FAIL_MSG("Unable to convert array index " + itos(i) + " from '" + Variant::get_type_name(value->get_type()) + "' to '" + Variant::get_type_name(typed.type) + "'."); + } + Callable::CallError ce; + Variant::construct(typed.type, data[i], &value, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + } + } else if (Variant::can_convert_strict(source_typed.type, typed.type)) { + // from primitives to different convertable primitives + for (int i = 0; i < size; i++) { + const Variant *value = source + i; + Callable::CallError ce; + Variant::construct(typed.type, data[i], &value, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } - } else if (_p->typed.can_reference(p_array._p->typed)) { //same type or compatible - _ref(p_array); } else { - ERR_FAIL_V_MSG(false, "Assignment of arrays of incompatible types."); + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Array[%s]" to "Array[%s]".)", Variant::get_type_name(source_typed.type), Variant::get_type_name(typed.type))); } - return true; -} -void Array::operator=(const Array &p_array) { - if (this == &p_array) { - return; - } - _ref(p_array); + _p->array = array; } void Array::push_back(const Variant &p_value) { @@ -269,7 +280,15 @@ void Array::append_array(const Array &p_array) { Error Array::resize(int p_new_size) { ERR_FAIL_COND_V_MSG(_p->read_only, ERR_LOCKED, "Array is in read-only state."); - return _p->array.resize(p_new_size); + Variant::Type &variant_type = _p->typed.type; + int old_size = _p->array.size(); + Error err = _p->array.resize_zeroed(p_new_size); + if (!err && variant_type != Variant::NIL && variant_type != Variant::OBJECT) { + for (int i = old_size; i < p_new_size; i++) { + VariantInternal::initialize(&_p->array.write[i], variant_type); + } + } + return err; } Error Array::insert(int p_pos, const Variant &p_value) { @@ -403,24 +422,22 @@ Array Array::duplicate(bool p_deep) const { Array Array::recursive_duplicate(bool p_deep, int recursion_count) const { Array new_arr; + new_arr._p->typed = _p->typed; if (recursion_count > MAX_RECURSION) { ERR_PRINT("Max recursion reached"); return new_arr; } - int element_count = size(); - new_arr.resize(element_count); - new_arr._p->typed = _p->typed; if (p_deep) { recursion_count++; + int element_count = size(); + new_arr.resize(element_count); for (int i = 0; i < element_count; i++) { new_arr[i] = get(i).recursive_duplicate(true, recursion_count); } } else { - for (int i = 0; i < element_count; i++) { - new_arr[i] = get(i); - } + new_arr._p->array = _p->array; } return new_arr; @@ -737,11 +754,7 @@ Array::Array(const Array &p_from, uint32_t p_type, const StringName &p_class_nam _p = memnew(ArrayPrivate); _p->refcount.init(); set_typed(p_type, p_class_name, p_script); - _assign(p_from); -} - -bool Array::typed_assign(const Array &p_other) { - return _assign(p_other); + assign(p_from); } void Array::set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script) { @@ -763,6 +776,10 @@ bool Array::is_typed() const { return _p->typed.type != Variant::NIL; } +bool Array::is_same_typed(const Array &p_other) const { + return _p->typed == p_other._p->typed; +} + uint32_t Array::get_typed_builtin() const { return _p->typed.type; } diff --git a/core/variant/array.h b/core/variant/array.h index d9ca3278fb..4ef8ba8ce7 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -43,13 +43,11 @@ class Callable; class Array { mutable ArrayPrivate *_p; - void _ref(const Array &p_from) const; void _unref() const; -protected: - bool _assign(const Array &p_array); - public: + void _ref(const Array &p_from) const; + Variant &operator[](int p_idx); const Variant &operator[](int p_idx) const; @@ -68,6 +66,7 @@ public: uint32_t recursive_hash(int recursion_count) const; void operator=(const Array &p_array); + void assign(const Array &p_array); void push_back(const Variant &p_value); _FORCE_INLINE_ void append(const Variant &p_value) { push_back(p_value); } //for python compatibility void append_array(const Array &p_array); @@ -120,9 +119,9 @@ public: const void *id() const; - bool typed_assign(const Array &p_other); void set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script); bool is_typed() const; + bool is_same_typed(const Array &p_other) const; uint32_t get_typed_builtin() const; StringName get_typed_class_name() const; Variant get_typed_script() const; diff --git a/core/variant/container_type_validate.h b/core/variant/container_type_validate.h index 377be03bdf..796b66dc77 100644 --- a/core/variant/container_type_validate.h +++ b/core/variant/container_type_validate.h @@ -74,8 +74,15 @@ struct ContainerTypeValidate { return true; } + _FORCE_INLINE_ bool operator==(const ContainerTypeValidate &p_type) const { + return type == p_type.type && class_name == p_type.class_name && script == p_type.script; + } + _FORCE_INLINE_ bool operator!=(const ContainerTypeValidate &p_type) const { + return type != p_type.type || class_name != p_type.class_name || script != p_type.script; + } + // Coerces String and StringName into each other when needed. - _FORCE_INLINE_ bool validate(Variant &inout_variant, const char *p_operation = "use") { + _FORCE_INLINE_ bool validate(Variant &inout_variant, const char *p_operation = "use") const { if (type == Variant::NIL) { return true; } @@ -102,7 +109,7 @@ struct ContainerTypeValidate { return validate_object(inout_variant, p_operation); } - _FORCE_INLINE_ bool validate_object(const Variant &p_variant, const char *p_operation = "use") { + _FORCE_INLINE_ bool validate_object(const Variant &p_variant, const char *p_operation = "use") const { ERR_FAIL_COND_V(p_variant.get_type() != Variant::OBJECT, false); #ifdef DEBUG_ENABLED diff --git a/core/variant/typed_array.h b/core/variant/typed_array.h index 5aeabbacf8..03e557819b 100644 --- a/core/variant/typed_array.h +++ b/core/variant/typed_array.h @@ -40,14 +40,9 @@ template class TypedArray : public Array { public: - template - _FORCE_INLINE_ void operator=(const TypedArray &p_array) { - static_assert(__is_base_of(T, U)); - _assign(p_array); - } - _FORCE_INLINE_ void operator=(const Array &p_array) { - _assign(p_array); + ERR_FAIL_COND_MSG(!is_same_typed(p_array), "Cannot assign an array with a different element type."); + _ref(p_array); } _FORCE_INLINE_ TypedArray(const Variant &p_variant) : Array(Array(p_variant), Variant::OBJECT, T::get_class_static(), Variant()) { @@ -62,22 +57,23 @@ public: //specialization for the rest of variant types -#define MAKE_TYPED_ARRAY(m_type, m_variant_type) \ - template <> \ - class TypedArray : public Array { \ - public: \ - _FORCE_INLINE_ void operator=(const Array &p_array) { \ - _assign(p_array); \ - } \ - _FORCE_INLINE_ TypedArray(const Variant &p_variant) : \ - Array(Array(p_variant), m_variant_type, StringName(), Variant()) { \ - } \ - _FORCE_INLINE_ TypedArray(const Array &p_array) : \ - Array(p_array, m_variant_type, StringName(), Variant()) { \ - } \ - _FORCE_INLINE_ TypedArray() { \ - set_typed(m_variant_type, StringName(), Variant()); \ - } \ +#define MAKE_TYPED_ARRAY(m_type, m_variant_type) \ + template <> \ + class TypedArray : public Array { \ + public: \ + _FORCE_INLINE_ void operator=(const Array &p_array) { \ + ERR_FAIL_COND_MSG(!is_same_typed(p_array), "Cannot assign an array with a different element type."); \ + _ref(p_array); \ + } \ + _FORCE_INLINE_ TypedArray(const Variant &p_variant) : \ + Array(Array(p_variant), m_variant_type, StringName(), Variant()) { \ + } \ + _FORCE_INLINE_ TypedArray(const Array &p_array) : \ + Array(p_array, m_variant_type, StringName(), Variant()) { \ + } \ + _FORCE_INLINE_ TypedArray() { \ + set_typed(m_variant_type, StringName(), Variant()); \ + } \ }; MAKE_TYPED_ARRAY(bool, Variant::BOOL) diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index e7d1a2478f..0c0c8f657a 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2189,6 +2189,7 @@ static void _register_variant_builtin_methods() { bind_method(Array, is_empty, sarray(), varray()); bind_method(Array, clear, sarray(), varray()); bind_method(Array, hash, sarray(), varray()); + bind_method(Array, assign, sarray("array"), varray()); bind_method(Array, push_back, sarray("value"), varray()); bind_method(Array, push_front, sarray("value"), varray()); bind_method(Array, append, sarray("value"), varray()); @@ -2223,8 +2224,8 @@ static void _register_variant_builtin_methods() { bind_method(Array, all, sarray("method"), varray()); bind_method(Array, max, sarray(), varray()); bind_method(Array, min, sarray(), varray()); - bind_method(Array, typed_assign, sarray("array"), varray()); bind_method(Array, is_typed, sarray(), varray()); + bind_method(Array, is_same_typed, sarray("array"), varray()); bind_method(Array, get_typed_builtin, sarray(), varray()); bind_method(Array, get_typed_class_name, sarray(), varray()); bind_method(Array, get_typed_script, sarray(), varray()); @@ -2402,7 +2403,7 @@ static void _register_variant_builtin_methods() { bind_method(PackedStringArray, remove_at, sarray("index"), varray()); bind_method(PackedStringArray, insert, sarray("at_index", "value"), varray()); bind_method(PackedStringArray, fill, sarray("value"), varray()); - bind_method(PackedStringArray, resize, sarray("new_size"), varray()); + bind_methodv(PackedStringArray, resize, &PackedStringArray::resize_zeroed, sarray("new_size"), varray()); bind_method(PackedStringArray, clear, sarray(), varray()); bind_method(PackedStringArray, has, sarray("value"), varray()); bind_method(PackedStringArray, reverse, sarray(), varray()); @@ -2426,7 +2427,7 @@ static void _register_variant_builtin_methods() { bind_method(PackedVector2Array, remove_at, sarray("index"), varray()); bind_method(PackedVector2Array, insert, sarray("at_index", "value"), varray()); bind_method(PackedVector2Array, fill, sarray("value"), varray()); - bind_method(PackedVector2Array, resize, sarray("new_size"), varray()); + bind_methodv(PackedVector2Array, resize, &PackedVector2Array::resize_zeroed, sarray("new_size"), varray()); bind_method(PackedVector2Array, clear, sarray(), varray()); bind_method(PackedVector2Array, has, sarray("value"), varray()); bind_method(PackedVector2Array, reverse, sarray(), varray()); @@ -2450,7 +2451,7 @@ static void _register_variant_builtin_methods() { bind_method(PackedVector3Array, remove_at, sarray("index"), varray()); bind_method(PackedVector3Array, insert, sarray("at_index", "value"), varray()); bind_method(PackedVector3Array, fill, sarray("value"), varray()); - bind_method(PackedVector3Array, resize, sarray("new_size"), varray()); + bind_methodv(PackedVector3Array, resize, &PackedVector3Array::resize_zeroed, sarray("new_size"), varray()); bind_method(PackedVector3Array, clear, sarray(), varray()); bind_method(PackedVector3Array, has, sarray("value"), varray()); bind_method(PackedVector3Array, reverse, sarray(), varray()); @@ -2474,7 +2475,7 @@ static void _register_variant_builtin_methods() { bind_method(PackedColorArray, remove_at, sarray("index"), varray()); bind_method(PackedColorArray, insert, sarray("at_index", "value"), varray()); bind_method(PackedColorArray, fill, sarray("value"), varray()); - bind_method(PackedColorArray, resize, sarray("new_size"), varray()); + bind_methodv(PackedColorArray, resize, &PackedColorArray::resize_zeroed, sarray("new_size"), varray()); bind_method(PackedColorArray, clear, sarray(), varray()); bind_method(PackedColorArray, has, sarray("value"), varray()); bind_method(PackedColorArray, reverse, sarray(), varray()); diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h index 21e1d21342..0d55ee4ae2 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -58,7 +58,13 @@ public: init_basis(v); break; case Variant::TRANSFORM3D: - init_transform(v); + init_transform3d(v); + break; + case Variant::PROJECTION: + init_projection(v); + break; + case Variant::COLOR: + init_color(v); break; case Variant::STRING_NAME: init_string_name(v); @@ -209,13 +215,12 @@ public: // Should be in the same order as Variant::Type for consistency. // Those primitive and vector types don't need an `init_` method: - // Nil, bool, float, Vector2/i, Rect2/i, Vector3/i, Plane, Quat, Color, RID. + // Nil, bool, float, Vector2/i, Rect2/i, Vector3/i, Plane, Quat, RID. // Object is a special case, handled via `object_assign_null`. _FORCE_INLINE_ static void init_string(Variant *v) { memnew_placement(v->_data._mem, String); v->type = Variant::STRING; } - _FORCE_INLINE_ static void init_transform2d(Variant *v) { v->_data._transform2d = (Transform2D *)Variant::Pools::_bucket_small.alloc(); memnew_placement(v->_data._transform2d, Transform2D); @@ -231,7 +236,7 @@ public: memnew_placement(v->_data._basis, Basis); v->type = Variant::BASIS; } - _FORCE_INLINE_ static void init_transform(Variant *v) { + _FORCE_INLINE_ static void init_transform3d(Variant *v) { v->_data._transform3d = (Transform3D *)Variant::Pools::_bucket_medium.alloc(); memnew_placement(v->_data._transform3d, Transform3D); v->type = Variant::TRANSFORM3D; @@ -241,6 +246,10 @@ public: memnew_placement(v->_data._projection, Projection); v->type = Variant::PROJECTION; } + _FORCE_INLINE_ static void init_color(Variant *v) { + memnew_placement(v->_data._mem, Color); + v->type = Variant::COLOR; + } _FORCE_INLINE_ static void init_string_name(Variant *v) { memnew_placement(v->_data._mem, StringName); v->type = Variant::STRING_NAME; @@ -1191,7 +1200,7 @@ struct VariantInitializer { template <> struct VariantInitializer { - static _FORCE_INLINE_ void init(Variant *v) { VariantInternal::init_transform(v); } + static _FORCE_INLINE_ void init(Variant *v) { VariantInternal::init_transform3d(v); } }; template <> struct VariantInitializer { diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index 87874deb8d..a40fcfbd47 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -910,7 +910,6 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, bool at_key = true; String key; - Token token2; bool need_comma = false; while (true) { @@ -920,18 +919,18 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, } if (at_key) { - Error err = get_token(p_stream, token2, line, r_err_str); + Error err = get_token(p_stream, token, line, r_err_str); if (err != OK) { return err; } - if (token2.type == TK_PARENTHESIS_CLOSE) { + if (token.type == TK_PARENTHESIS_CLOSE) { value = ref.is_valid() ? Variant(ref) : Variant(obj); return OK; } if (need_comma) { - if (token2.type != TK_COMMA) { + if (token.type != TK_COMMA) { r_err_str = "Expected '}' or ','"; return ERR_PARSE_ERROR; } else { @@ -940,31 +939,31 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, } } - if (token2.type != TK_STRING) { + if (token.type != TK_STRING) { r_err_str = "Expected property name as string"; return ERR_PARSE_ERROR; } - key = token2.value; + key = token.value; - err = get_token(p_stream, token2, line, r_err_str); + err = get_token(p_stream, token, line, r_err_str); if (err != OK) { return err; } - if (token2.type != TK_COLON) { + if (token.type != TK_COLON) { r_err_str = "Expected ':'"; return ERR_PARSE_ERROR; } at_key = false; } else { - Error err = get_token(p_stream, token2, line, r_err_str); + Error err = get_token(p_stream, token, line, r_err_str); if (err != OK) { return err; } Variant v; - err = parse_value(token2, v, p_stream, line, r_err_str, p_res_parser); + err = parse_value(token, v, p_stream, line, r_err_str, p_res_parser); if (err) { return err; } @@ -1026,6 +1025,89 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, return ERR_PARSE_ERROR; } } + } else if (id == "Array") { + Error err = OK; + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_BRACKET_OPEN) { + r_err_str = "Expected '['"; + return ERR_PARSE_ERROR; + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_IDENTIFIER) { + r_err_str = "Expected type identifier"; + return ERR_PARSE_ERROR; + } + + static HashMap builtin_types; + if (builtin_types.is_empty()) { + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; + } + } + + Array array = Array(); + bool got_bracket_token = false; + if (builtin_types.has(token.value)) { + array.set_typed(builtin_types.get(token.value), StringName(), Variant()); + } else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") { + Variant resource; + err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser); + if (err) { + if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_BRACKET_CLOSE) { + err = OK; + r_err_str = String(); + array.set_typed(Variant::OBJECT, token.value, Variant()); + got_bracket_token = true; + } else { + return err; + } + } else { + Ref