godot/core/variant/array.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

962 lines
27 KiB
C++
Raw Normal View History

2014-02-09 22:10:30 -03:00
/**************************************************************************/
/* array.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2014-02-09 22:10:30 -03:00
#include "array.h"
#include "container_type_validate.h"
#include "core/math/math_funcs.h"
#include "core/object/script_language.h"
#include "core/templates/hashfuncs.h"
#include "core/templates/vector.h"
#include "core/variant/callable.h"
2022-12-05 21:46:47 -05:00
#include "core/variant/dictionary.h"
2014-02-09 22:10:30 -03:00
struct ArrayPrivate {
2014-02-09 22:10:30 -03:00
SafeRefCount refcount;
Vector<Variant> array;
Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values.
ContainerTypeValidate typed;
2024-03-22 22:53:26 +01:00
ArrayPrivate() {}
ArrayPrivate(std::initializer_list<Variant> p_init) :
array(p_init) {}
2014-02-09 22:10:30 -03:00
};
void Array::_ref(const Array &p_from) const {
ArrayPrivate *_fp = p_from._p;
ERR_FAIL_NULL(_fp); // Should NOT happen.
2014-02-09 22:10:30 -03:00
if (_fp == _p) {
return; // whatever it is, nothing to do here move along
}
2014-02-09 22:10:30 -03:00
bool success = _fp->refcount.ref();
ERR_FAIL_COND(!success); // should really not happen either
2014-02-09 22:10:30 -03:00
_unref();
2022-11-27 09:56:53 +02:00
_p = _fp;
2014-02-09 22:10:30 -03:00
}
void Array::_unref() const {
if (!_p) {
2014-02-09 22:10:30 -03:00
return;
}
2014-02-09 22:10:30 -03:00
if (_p->refcount.unref()) {
if (_p->read_only) {
memdelete(_p->read_only);
}
2014-02-09 22:10:30 -03:00
memdelete(_p);
}
2020-04-02 01:20:12 +02:00
_p = nullptr;
2014-02-09 22:10:30 -03:00
}
Array::Iterator Array::begin() {
return Iterator(_p->array.ptrw(), _p->read_only);
}
Array::Iterator Array::end() {
return Iterator(_p->array.ptrw() + _p->array.size(), _p->read_only);
}
Array::ConstIterator Array::begin() const {
return ConstIterator(_p->array.ptr());
}
Array::ConstIterator Array::end() const {
return ConstIterator(_p->array.ptr() + _p->array.size());
}
2014-02-09 22:10:30 -03:00
Variant &Array::operator[](int p_idx) {
if (unlikely(_p->read_only)) {
*_p->read_only = _p->array[p_idx];
return *_p->read_only;
}
return _p->array.write[p_idx];
2014-02-09 22:10:30 -03:00
}
const Variant &Array::operator[](int p_idx) const {
return _p->array[p_idx];
}
int Array::size() const {
return _p->array.size();
}
2020-12-15 12:04:21 +00:00
bool Array::is_empty() const {
return _p->array.is_empty();
2014-02-09 22:10:30 -03:00
}
2014-02-09 22:10:30 -03:00
void Array::clear() {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
2014-02-09 22:10:30 -03:00
_p->array.clear();
}
bool Array::operator==(const Array &p_array) const {
return recursive_equal(p_array, 0);
2014-02-09 22:10:30 -03:00
}
bool Array::operator!=(const Array &p_array) const {
return !recursive_equal(p_array, 0);
}
bool Array::recursive_equal(const Array &p_array, int recursion_count) const {
// Cheap checks
if (_p == p_array._p) {
return true;
}
const Vector<Variant> &a1 = _p->array;
const Vector<Variant> &a2 = p_array._p->array;
const int size = a1.size();
if (size != a2.size()) {
return false;
}
// Heavy O(n) check
if (recursion_count > MAX_RECURSION) {
ERR_PRINT("Max recursion reached");
return true;
}
recursion_count++;
for (int i = 0; i < size; i++) {
if (!a1[i].hash_compare(a2[i], recursion_count, false)) {
return false;
}
}
return true;
}
bool Array::operator<(const Array &p_array) const {
int a_len = size();
int b_len = p_array.size();
int min_cmp = MIN(a_len, b_len);
for (int i = 0; i < min_cmp; i++) {
if (operator[](i) < p_array[i]) {
return true;
} else if (p_array[i] < operator[](i)) {
return false;
}
}
return a_len < b_len;
}
bool Array::operator<=(const Array &p_array) const {
return !operator>(p_array);
}
bool Array::operator>(const Array &p_array) const {
return p_array < *this;
}
bool Array::operator>=(const Array &p_array) const {
return !operator<(p_array);
}
2014-02-09 22:10:30 -03:00
uint32_t Array::hash() const {
return recursive_hash(0);
}
2014-02-09 22:10:30 -03:00
uint32_t Array::recursive_hash(int recursion_count) const {
if (recursion_count > MAX_RECURSION) {
ERR_PRINT("Max recursion reached");
return 0;
}
uint32_t h = hash_murmur3_one_32(Variant::ARRAY);
recursion_count++;
2014-02-09 22:10:30 -03:00
for (int i = 0; i < _p->array.size(); i++) {
h = hash_murmur3_one_32(_p->array[i].recursive_hash(recursion_count), h);
2014-02-09 22:10:30 -03:00
}
return hash_fmix32(h);
2014-02-09 22:10:30 -03:00
}
2022-11-27 09:56:53 +02:00
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;
2022-12-05 21:46:47 -05:00
2022-11-27 09:56:53 +02:00
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;
2022-11-27 09:56:53 +02:00
return;
}
2022-11-27 09:56:53 +02:00
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 %d from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type)));
}
2022-11-27 09:56:53 +02:00
}
_p->array = p_array._p->array;
return;
}
if (typed.type == Variant::OBJECT || source_typed.type == Variant::OBJECT) {
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)));
}
2022-11-27 09:56:53 +02:00
Vector<Variant> 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(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
2022-11-27 09:56:53 +02:00
}
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 %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
2022-11-27 09:56:53 +02:00
}
} else if (Variant::can_convert_strict(source_typed.type, typed.type)) {
// from primitives to different convertible primitives
2022-11-27 09:56:53 +02:00
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 %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type)));
}
} else {
2022-11-27 09:56:53 +02:00
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)));
}
2022-11-27 09:56:53 +02:00
_p->array = array;
2014-02-09 22:10:30 -03:00
}
2014-02-09 22:10:30 -03:00
void Array::push_back(const Variant &p_value) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND(!_p->typed.validate(value, "push_back"));
_p->array.push_back(std::move(value));
2014-02-09 22:10:30 -03:00
}
void Array::append_array(const Array &p_array) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
2022-12-05 21:46:47 -05:00
if (!is_typed() || _p->typed.can_reference(p_array._p->typed)) {
_p->array.append_array(p_array._p->array);
return;
}
2022-12-05 21:46:47 -05:00
Vector<Variant> validated_array = p_array._p->array;
Variant *write = validated_array.ptrw();
2022-12-05 21:46:47 -05:00
for (int i = 0; i < validated_array.size(); ++i) {
ERR_FAIL_COND(!_p->typed.validate(write[i], "append_array"));
}
2022-12-05 21:46:47 -05:00
_p->array.append_array(validated_array);
}
2014-02-09 22:10:30 -03:00
Error Array::resize(int p_new_size) {
ERR_FAIL_COND_V_MSG(_p->read_only, ERR_LOCKED, "Array is in read-only state.");
2022-11-27 09:56:53 +02:00
Variant::Type &variant_type = _p->typed.type;
int old_size = _p->array.size();
Error err = _p->array.resize_initialized(p_new_size);
2022-11-27 09:56:53 +02:00
if (!err && variant_type != Variant::NIL && variant_type != Variant::OBJECT) {
Variant *write = _p->array.ptrw();
2022-11-27 09:56:53 +02:00
for (int i = old_size; i < p_new_size; i++) {
VariantInternal::initialize(&write[i], variant_type);
2022-11-27 09:56:53 +02:00
}
}
return err;
2014-02-09 22:10:30 -03:00
}
Error Array::insert(int p_pos, const Variant &p_value) {
ERR_FAIL_COND_V_MSG(_p->read_only, ERR_LOCKED, "Array is in read-only state.");
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND_V(!_p->typed.validate(value, "insert"), ERR_INVALID_PARAMETER);
if (p_pos < 0) {
// Relative offset from the end.
p_pos = _p->array.size() + p_pos;
}
2025-04-12 22:11:53 -04:00
ERR_FAIL_INDEX_V_MSG(p_pos, _p->array.size() + 1, ERR_INVALID_PARAMETER, vformat("The calculated index %d is out of bounds (the array has %d elements). Leaving the array untouched.", p_pos, _p->array.size()));
return _p->array.insert(p_pos, std::move(value));
2014-02-09 22:10:30 -03:00
}
void Array::fill(const Variant &p_value) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND(!_p->typed.validate(value, "fill"));
_p->array.fill(std::move(value));
}
2014-02-09 22:10:30 -03:00
void Array::erase(const Variant &p_value) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND(!_p->typed.validate(value, "erase"));
_p->array.erase(value);
2014-02-09 22:10:30 -03:00
}
2016-11-18 18:30:16 -02:00
Variant Array::front() const {
ERR_FAIL_COND_V_MSG(_p->array.is_empty(), Variant(), "Can't take value from empty array.");
2016-11-18 18:30:16 -02:00
return operator[](0);
}
Variant Array::back() const {
ERR_FAIL_COND_V_MSG(_p->array.is_empty(), Variant(), "Can't take value from empty array.");
2016-11-18 18:30:16 -02:00
return operator[](_p->array.size() - 1);
}
Variant Array::pick_random() const {
ERR_FAIL_COND_V_MSG(_p->array.is_empty(), Variant(), "Can't take value from empty array.");
return operator[](Math::rand() % _p->array.size());
}
2016-06-10 14:57:56 -03:00
int Array::find(const Variant &p_value, int p_from) const {
2025-03-20 00:07:31 +08:00
if (_p->array.is_empty()) {
2022-12-05 21:46:47 -05:00
return -1;
}
Variant value = p_value;
ERR_FAIL_COND_V(!_p->typed.validate(value, "find"), -1);
int ret = -1;
if (p_from < 0 || size() == 0) {
return ret;
}
for (int i = p_from; i < size(); i++) {
if (StringLikeVariantComparator::compare(_p->array[i], value)) {
ret = i;
break;
}
}
return ret;
2014-02-09 22:10:30 -03:00
}
int Array::find_custom(const Callable &p_callable, int p_from) const {
int ret = -1;
if (p_from < 0 || size() == 0) {
return ret;
}
const Variant *argptrs[1];
for (int i = p_from; i < size(); i++) {
const Variant &val = _p->array[i];
argptrs[0] = &val;
Variant res;
Callable::CallError ce;
p_callable.callp(argptrs, 1, res, ce);
if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
ERR_FAIL_V_MSG(ret, vformat("Error calling method from 'find_custom': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, ret, "Error on method from 'find_custom': Return type of callable must be boolean.");
if (res.operator bool()) {
return i;
}
}
return ret;
}
2016-06-10 17:28:09 -03:00
int Array::rfind(const Variant &p_value, int p_from) const {
2025-03-20 00:07:31 +08:00
if (_p->array.is_empty()) {
return -1;
}
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND_V(!_p->typed.validate(value, "rfind"), -1);
2016-06-10 17:28:09 -03:00
if (p_from < 0) {
// Relative offset from the end
p_from = _p->array.size() + p_from;
}
if (p_from < 0 || p_from >= _p->array.size()) {
// Limit to array boundaries
p_from = _p->array.size() - 1;
}
for (int i = p_from; i >= 0; i--) {
2022-12-05 21:46:47 -05:00
if (StringLikeVariantComparator::compare(_p->array[i], value)) {
return i;
}
}
return -1;
}
int Array::rfind_custom(const Callable &p_callable, int p_from) const {
2025-03-20 00:07:31 +08:00
if (_p->array.is_empty()) {
return -1;
}
if (p_from < 0) {
// Relative offset from the end.
p_from = _p->array.size() + p_from;
}
if (p_from < 0 || p_from >= _p->array.size()) {
// Limit to array boundaries.
p_from = _p->array.size() - 1;
}
const Variant *argptrs[1];
for (int i = p_from; i >= 0; i--) {
const Variant &val = _p->array[i];
argptrs[0] = &val;
Variant res;
Callable::CallError ce;
p_callable.callp(argptrs, 1, res, ce);
if (unlikely(ce.error != Callable::CallError::CALL_OK)) {
ERR_FAIL_V_MSG(-1, vformat("Error calling method from 'rfind_custom': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
ERR_FAIL_COND_V_MSG(res.get_type() != Variant::Type::BOOL, -1, "Error on method from 'rfind_custom': Return type of callable must be boolean.");
if (res.operator bool()) {
return i;
}
}
return -1;
}
int Array::count(const Variant &p_value) const {
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND_V(!_p->typed.validate(value, "count"), 0);
2025-03-20 00:07:31 +08:00
if (_p->array.is_empty()) {
return 0;
}
int amount = 0;
for (int i = 0; i < _p->array.size(); i++) {
2022-12-05 21:46:47 -05:00
if (StringLikeVariantComparator::compare(_p->array[i], value)) {
amount++;
}
}
return amount;
}
2016-07-02 19:03:35 +02:00
bool Array::has(const Variant &p_value) const {
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND_V(!_p->typed.validate(value, "use 'has'"), false);
2022-12-05 21:46:47 -05:00
return find(value) != -1;
2016-07-02 19:03:35 +02:00
}
void Array::remove_at(int p_pos) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
if (p_pos < 0) {
// Relative offset from the end.
p_pos = _p->array.size() + p_pos;
}
ERR_FAIL_INDEX_MSG(p_pos, _p->array.size(), vformat("The calculated index %d is out of bounds (the array has %d elements). Leaving the array untouched.", p_pos, _p->array.size()));
_p->array.remove_at(p_pos);
2014-02-09 22:10:30 -03:00
}
void Array::set(int p_idx, const Variant &p_value) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND(!_p->typed.validate(value, "set"));
_p->array.write[p_idx] = std::move(value);
2014-02-09 22:10:30 -03:00
}
const Variant &Array::get(int p_idx) const {
return operator[](p_idx);
}
Array Array::duplicate(bool p_deep) const {
Overhaul `Variant::duplicate()` for resources This in the scope of a duplication triggered via any type in the `Variant` realm. that is, the following: `Variant` itself, `Array` and `Dictionary`. That includes invoking `duplicate()` from scripts. A `duplicate_deep(deep_subresources_mode)` method is added to `Variant`, `Array` and `Dictionary` (for compatibility reasons, simply adding an extra parameter was not possible). The default value for it is `RESOURCE_DEEP_DUPLICATE_NONE`, which is like calling `duplicate(true)`. Remarks: - The results of copying resources via those `Variant` types are exactly the same as if the copy were initiated from the `Resource` type at C++. - In order to keep some separation between `Variant` and the higher-level animal which is `Resource`, `Variant` still contains the original code for that, so it's self-sufficient unless there's a `Resource` involved. Once the deep copy finds a `Resource` that has to be copied according to the duplication parameters, the algorithm invokes the `Resource` duplication machinery. When the stack is unwind back to a nesting level `Variant` can handle, `Variant` duplication logic keeps functioning. While that is good from a responsibility separation standpoint, that would have a caveat: `Variant` would not be aware of the mapping between original and duplicate subresources and so wouldn't be able to keep preventing multiple duplicates. To avoid that, this commit also introduces a wormwhole, a sharing mechanism by which `Variant` and `Resource` can collaborate in managing the lifetime of the original-to-duplicates map. The user-visible benefit is that the overduplicate prevention works as broadly as the whole `Variant` entity being copied, including all nesting levels, regardless how disconnected the data members containing resources may be across al the nesting levels. In other words, despite the aforementioned division of duties between `Variant` and `Resource` duplication logic, the duplicates map is shared among them. It's created when first finding a `Resource` and, however how deep the copy was working at that point, the map kept alive unitl the stack is unwind to the root user call, until the first step of the recursion. Thanks to that common map of duplicates, this commit is able to fix the issue that `Resource::duplicate_for_local_scene()` used to ignore overridden duplicate logic.
2025-01-21 11:55:23 +01:00
return recursive_duplicate(p_deep, RESOURCE_DEEP_DUPLICATE_NONE, 0);
}
Overhaul `Variant::duplicate()` for resources This in the scope of a duplication triggered via any type in the `Variant` realm. that is, the following: `Variant` itself, `Array` and `Dictionary`. That includes invoking `duplicate()` from scripts. A `duplicate_deep(deep_subresources_mode)` method is added to `Variant`, `Array` and `Dictionary` (for compatibility reasons, simply adding an extra parameter was not possible). The default value for it is `RESOURCE_DEEP_DUPLICATE_NONE`, which is like calling `duplicate(true)`. Remarks: - The results of copying resources via those `Variant` types are exactly the same as if the copy were initiated from the `Resource` type at C++. - In order to keep some separation between `Variant` and the higher-level animal which is `Resource`, `Variant` still contains the original code for that, so it's self-sufficient unless there's a `Resource` involved. Once the deep copy finds a `Resource` that has to be copied according to the duplication parameters, the algorithm invokes the `Resource` duplication machinery. When the stack is unwind back to a nesting level `Variant` can handle, `Variant` duplication logic keeps functioning. While that is good from a responsibility separation standpoint, that would have a caveat: `Variant` would not be aware of the mapping between original and duplicate subresources and so wouldn't be able to keep preventing multiple duplicates. To avoid that, this commit also introduces a wormwhole, a sharing mechanism by which `Variant` and `Resource` can collaborate in managing the lifetime of the original-to-duplicates map. The user-visible benefit is that the overduplicate prevention works as broadly as the whole `Variant` entity being copied, including all nesting levels, regardless how disconnected the data members containing resources may be across al the nesting levels. In other words, despite the aforementioned division of duties between `Variant` and `Resource` duplication logic, the duplicates map is shared among them. It's created when first finding a `Resource` and, however how deep the copy was working at that point, the map kept alive unitl the stack is unwind to the root user call, until the first step of the recursion. Thanks to that common map of duplicates, this commit is able to fix the issue that `Resource::duplicate_for_local_scene()` used to ignore overridden duplicate logic.
2025-01-21 11:55:23 +01:00
Array Array::duplicate_deep(ResourceDeepDuplicateMode p_deep_subresources_mode) const {
return recursive_duplicate(true, p_deep_subresources_mode, 0);
}
Array Array::recursive_duplicate(bool p_deep, ResourceDeepDuplicateMode p_deep_subresources_mode, int recursion_count) const {
Array new_arr;
2022-11-27 09:56:53 +02:00
new_arr._p->typed = _p->typed;
if (recursion_count > MAX_RECURSION) {
ERR_PRINT("Max recursion reached");
return new_arr;
}
if (p_deep) {
Overhaul `Variant::duplicate()` for resources This in the scope of a duplication triggered via any type in the `Variant` realm. that is, the following: `Variant` itself, `Array` and `Dictionary`. That includes invoking `duplicate()` from scripts. A `duplicate_deep(deep_subresources_mode)` method is added to `Variant`, `Array` and `Dictionary` (for compatibility reasons, simply adding an extra parameter was not possible). The default value for it is `RESOURCE_DEEP_DUPLICATE_NONE`, which is like calling `duplicate(true)`. Remarks: - The results of copying resources via those `Variant` types are exactly the same as if the copy were initiated from the `Resource` type at C++. - In order to keep some separation between `Variant` and the higher-level animal which is `Resource`, `Variant` still contains the original code for that, so it's self-sufficient unless there's a `Resource` involved. Once the deep copy finds a `Resource` that has to be copied according to the duplication parameters, the algorithm invokes the `Resource` duplication machinery. When the stack is unwind back to a nesting level `Variant` can handle, `Variant` duplication logic keeps functioning. While that is good from a responsibility separation standpoint, that would have a caveat: `Variant` would not be aware of the mapping between original and duplicate subresources and so wouldn't be able to keep preventing multiple duplicates. To avoid that, this commit also introduces a wormwhole, a sharing mechanism by which `Variant` and `Resource` can collaborate in managing the lifetime of the original-to-duplicates map. The user-visible benefit is that the overduplicate prevention works as broadly as the whole `Variant` entity being copied, including all nesting levels, regardless how disconnected the data members containing resources may be across al the nesting levels. In other words, despite the aforementioned division of duties between `Variant` and `Resource` duplication logic, the duplicates map is shared among them. It's created when first finding a `Resource` and, however how deep the copy was working at that point, the map kept alive unitl the stack is unwind to the root user call, until the first step of the recursion. Thanks to that common map of duplicates, this commit is able to fix the issue that `Resource::duplicate_for_local_scene()` used to ignore overridden duplicate logic.
2025-01-21 11:55:23 +01:00
bool is_call_chain_end = recursion_count == 0;
recursion_count++;
2022-11-27 09:56:53 +02:00
int element_count = size();
new_arr.resize(element_count);
Variant *write = new_arr._p->array.ptrw();
for (int i = 0; i < element_count; i++) {
Overhaul `Variant::duplicate()` for resources This in the scope of a duplication triggered via any type in the `Variant` realm. that is, the following: `Variant` itself, `Array` and `Dictionary`. That includes invoking `duplicate()` from scripts. A `duplicate_deep(deep_subresources_mode)` method is added to `Variant`, `Array` and `Dictionary` (for compatibility reasons, simply adding an extra parameter was not possible). The default value for it is `RESOURCE_DEEP_DUPLICATE_NONE`, which is like calling `duplicate(true)`. Remarks: - The results of copying resources via those `Variant` types are exactly the same as if the copy were initiated from the `Resource` type at C++. - In order to keep some separation between `Variant` and the higher-level animal which is `Resource`, `Variant` still contains the original code for that, so it's self-sufficient unless there's a `Resource` involved. Once the deep copy finds a `Resource` that has to be copied according to the duplication parameters, the algorithm invokes the `Resource` duplication machinery. When the stack is unwind back to a nesting level `Variant` can handle, `Variant` duplication logic keeps functioning. While that is good from a responsibility separation standpoint, that would have a caveat: `Variant` would not be aware of the mapping between original and duplicate subresources and so wouldn't be able to keep preventing multiple duplicates. To avoid that, this commit also introduces a wormwhole, a sharing mechanism by which `Variant` and `Resource` can collaborate in managing the lifetime of the original-to-duplicates map. The user-visible benefit is that the overduplicate prevention works as broadly as the whole `Variant` entity being copied, including all nesting levels, regardless how disconnected the data members containing resources may be across al the nesting levels. In other words, despite the aforementioned division of duties between `Variant` and `Resource` duplication logic, the duplicates map is shared among them. It's created when first finding a `Resource` and, however how deep the copy was working at that point, the map kept alive unitl the stack is unwind to the root user call, until the first step of the recursion. Thanks to that common map of duplicates, this commit is able to fix the issue that `Resource::duplicate_for_local_scene()` used to ignore overridden duplicate logic.
2025-01-21 11:55:23 +01:00
write[i] = get(i).recursive_duplicate(true, p_deep_subresources_mode, recursion_count);
}
// Variant::recursive_duplicate() may have created a remap cache by now.
if (is_call_chain_end) {
Resource::_teardown_duplicate_from_variant();
}
} else {
2022-11-27 09:56:53 +02:00
new_arr._p->array = _p->array;
}
return new_arr;
}
2019-07-16 18:31:58 -07:00
Array Array::slice(int p_begin, int p_end, int p_step, bool p_deep) const {
Array result;
result._p->typed = _p->typed;
2019-07-16 18:31:58 -07:00
ERR_FAIL_COND_V_MSG(p_step == 0, result, "Slice step cannot be zero.");
2019-07-16 18:31:58 -07:00
const int s = size();
2019-07-16 18:31:58 -07:00
2023-07-06 15:03:17 +02:00
if (s == 0 || (p_begin < -s && p_step < 0) || (p_begin >= s && p_step > 0)) {
return result;
}
int begin = CLAMP(p_begin, -s, s - 1);
if (begin < 0) {
begin += s;
}
2023-07-06 15:03:17 +02:00
int end = CLAMP(p_end, -s - 1, s);
if (end < 0) {
end += s;
}
2020-05-01 17:06:36 +01:00
2023-07-06 15:03:17 +02:00
ERR_FAIL_COND_V_MSG(p_step > 0 && begin > end, result, "Slice step is positive, but bounds are decreasing.");
ERR_FAIL_COND_V_MSG(p_step < 0 && begin < end, result, "Slice step is negative, but bounds are increasing.");
2020-05-01 17:06:36 +01:00
int result_size = (end - begin) / p_step + (((end - begin) % p_step != 0) ? 1 : 0);
result.resize(result_size);
Variant *write = result._p->array.ptrw();
for (int src_idx = begin, dest_idx = 0; dest_idx < result_size; ++dest_idx) {
write[dest_idx] = p_deep ? get(src_idx).duplicate(true) : get(src_idx);
src_idx += p_step;
2019-07-16 18:31:58 -07:00
}
return result;
2019-07-16 18:31:58 -07:00
}
2020-10-19 21:21:16 +02:00
Array Array::filter(const Callable &p_callable) const {
Array new_arr;
new_arr.resize(size());
new_arr._p->typed = _p->typed;
2020-10-19 21:21:16 +02:00
int accepted_count = 0;
const Variant *argptrs[1];
Variant *write = new_arr._p->array.ptrw();
2020-10-19 21:21:16 +02:00
for (int i = 0; i < size(); i++) {
argptrs[0] = &get(i);
Variant result;
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
2020-10-19 21:21:16 +02:00
if (ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_V_MSG(Array(), vformat("Error calling method from 'filter': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
2020-10-19 21:21:16 +02:00
}
if (result.operator bool()) {
write[accepted_count] = get(i);
2020-10-19 21:21:16 +02:00
accepted_count++;
}
}
new_arr.resize(accepted_count);
return new_arr;
}
Array Array::map(const Callable &p_callable) const {
Array new_arr;
new_arr.resize(size());
const Variant *argptrs[1];
Variant *write = new_arr._p->array.ptrw();
2020-10-19 21:21:16 +02:00
for (int i = 0; i < size(); i++) {
argptrs[0] = &get(i);
Callable::CallError ce;
p_callable.callp(argptrs, 1, write[i], ce);
2020-10-19 21:21:16 +02:00
if (ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_V_MSG(Array(), vformat("Error calling method from 'map': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
2020-10-19 21:21:16 +02:00
}
}
return new_arr;
}
Variant Array::reduce(const Callable &p_callable, const Variant &p_accum) const {
int start = 0;
Variant ret = p_accum;
if (ret == Variant() && size() > 0) {
ret = front();
start = 1;
}
const Variant *argptrs[2];
2020-10-19 21:21:16 +02:00
for (int i = start; i < size(); i++) {
argptrs[0] = &ret;
argptrs[1] = &get(i);
Variant result;
Callable::CallError ce;
p_callable.callp(argptrs, 2, result, ce);
2020-10-19 21:21:16 +02:00
if (ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_V_MSG(Variant(), vformat("Error calling method from 'reduce': %s.", Variant::get_callable_error_text(p_callable, argptrs, 2, ce)));
2020-10-19 21:21:16 +02:00
}
ret = result;
}
return ret;
}
bool Array::any(const Callable &p_callable) const {
const Variant *argptrs[1];
for (int i = 0; i < size(); i++) {
argptrs[0] = &get(i);
Variant result;
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_V_MSG(false, vformat("Error calling method from 'any': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (result.operator bool()) {
// Return as early as possible when one of the conditions is `true`.
// This improves performance compared to relying on `filter(...).size() >= 1`.
return true;
}
}
return false;
}
bool Array::all(const Callable &p_callable) const {
const Variant *argptrs[1];
for (int i = 0; i < size(); i++) {
argptrs[0] = &get(i);
Variant result;
Callable::CallError ce;
p_callable.callp(argptrs, 1, result, ce);
if (ce.error != Callable::CallError::CALL_OK) {
ERR_FAIL_V_MSG(false, vformat("Error calling method from 'all': %s.", Variant::get_callable_error_text(p_callable, argptrs, 1, ce)));
}
if (!(result.operator bool())) {
// Return as early as possible when one of the inverted conditions is `false`.
// This improves performance compared to relying on `filter(...).size() >= array_size().`.
return false;
}
}
return true;
}
2014-02-09 22:10:30 -03:00
struct _ArrayVariantSort {
_FORCE_INLINE_ bool operator()(const Variant &p_l, const Variant &p_r) const {
bool valid = false;
Variant res;
Variant::evaluate(Variant::OP_LESS, p_l, p_r, res, valid);
if (!valid) {
2014-02-09 22:10:30 -03:00
res = false;
}
2014-02-09 22:10:30 -03:00
return res;
}
};
void Array::sort() {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
2014-02-09 22:10:30 -03:00
_p->array.sort_custom<_ArrayVariantSort>();
}
void Array::sort_custom(const Callable &p_callable) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
_p->array.sort_custom<CallableComparator, true>(p_callable);
2014-02-09 22:10:30 -03:00
}
2018-01-10 19:36:53 +01:00
void Array::shuffle() {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
2018-01-10 19:36:53 +01:00
const int n = _p->array.size();
if (n < 2) {
2018-01-10 19:36:53 +01:00
return;
}
2018-01-10 19:36:53 +01:00
Variant *data = _p->array.ptrw();
for (int i = n - 1; i >= 1; i--) {
const int j = Math::rand() % (i + 1);
SWAP(data[i], data[j]);
2018-01-10 19:36:53 +01:00
}
}
int Array::bsearch(const Variant &p_value, bool p_before) const {
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND_V(!_p->typed.validate(value, "binary search"), -1);
return _p->array.span().bisect<_ArrayVariantSort>(value, p_before);
}
int Array::bsearch_custom(const Variant &p_value, const Callable &p_callable, bool p_before) const {
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND_V(!_p->typed.validate(value, "custom binary search"), -1);
2022-12-05 21:46:47 -05:00
return _p->array.bsearch_custom<CallableComparator>(value, p_before, p_callable);
}
void Array::reverse() {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
_p->array.reverse();
2014-02-09 22:10:30 -03:00
}
void Array::push_front(const Variant &p_value) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
2022-12-05 21:46:47 -05:00
Variant value = p_value;
ERR_FAIL_COND(!_p->typed.validate(value, "push_front"));
_p->array.insert(0, std::move(value));
}
Variant Array::pop_back() {
ERR_FAIL_COND_V_MSG(_p->read_only, Variant(), "Array is in read-only state.");
2020-12-15 12:04:21 +00:00
if (!_p->array.is_empty()) {
const int n = _p->array.size() - 1;
const Variant ret = _p->array.get(n);
_p->array.resize(n);
return ret;
}
return Variant();
}
Variant Array::pop_front() {
ERR_FAIL_COND_V_MSG(_p->read_only, Variant(), "Array is in read-only state.");
2020-12-15 12:04:21 +00:00
if (!_p->array.is_empty()) {
const Variant ret = _p->array.get(0);
_p->array.remove_at(0);
return ret;
}
return Variant();
}
Variant Array::pop_at(int p_pos) {
ERR_FAIL_COND_V_MSG(_p->read_only, Variant(), "Array is in read-only state.");
if (_p->array.is_empty()) {
// Return `null` without printing an error to mimic `pop_back()` and `pop_front()` behavior.
return Variant();
}
if (p_pos < 0) {
// Relative offset from the end
p_pos = _p->array.size() + p_pos;
}
ERR_FAIL_INDEX_V_MSG(
p_pos,
_p->array.size(),
Variant(),
vformat(
"The calculated index %s is out of bounds (the array has %s elements). Leaving the array untouched and returning `null`.",
p_pos,
_p->array.size()));
const Variant ret = _p->array.get(p_pos);
_p->array.remove_at(p_pos);
return ret;
}
Variant Array::min() const {
int array_size = size();
if (array_size == 0) {
return Variant();
}
int min_index = 0;
Variant is_less;
for (int i = 1; i < array_size; i++) {
bool valid;
Variant::evaluate(Variant::OP_LESS, _p->array[i], _p->array[min_index], is_less, valid);
if (!valid) {
return Variant(); //not a valid comparison
}
if (bool(is_less)) {
min_index = i;
}
}
return _p->array[min_index];
}
Variant Array::max() const {
int array_size = size();
if (array_size == 0) {
return Variant();
}
int max_index = 0;
Variant is_greater;
for (int i = 1; i < array_size; i++) {
bool valid;
Variant::evaluate(Variant::OP_GREATER, _p->array[i], _p->array[max_index], is_greater, valid);
if (!valid) {
return Variant(); //not a valid comparison
}
if (bool(is_greater)) {
max_index = i;
}
}
return _p->array[max_index];
}
const void *Array::id() const {
return _p;
}
Array::Array(const Array &p_from, uint32_t p_type, const StringName &p_class_name, const Variant &p_script) {
_p = memnew(ArrayPrivate);
_p->refcount.init();
set_typed(p_type, p_class_name, p_script);
2022-11-27 09:56:53 +02:00
assign(p_from);
}
void Array::set_typed(const ContainerType &p_element_type) {
set_typed(p_element_type.builtin_type, p_element_type.class_name, p_element_type.script);
}
void Array::set_typed(uint32_t p_type, const StringName &p_class_name, const Variant &p_script) {
ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state.");
ERR_FAIL_COND_MSG(_p->array.size() > 0, "Type can only be set when array is empty.");
ERR_FAIL_COND_MSG(_p->refcount.get() > 1, "Type can only be set when array has no more than one user.");
ERR_FAIL_COND_MSG(_p->typed.type != Variant::NIL, "Type can only be set once.");
ERR_FAIL_COND_MSG(p_class_name != StringName() && p_type != Variant::OBJECT, "Class names can only be set for type OBJECT");
Ref<Script> script = p_script;
ERR_FAIL_COND_MSG(script.is_valid() && p_class_name == StringName(), "Script class can only be set together with base class name");
_p->typed.type = Variant::Type(p_type);
_p->typed.class_name = p_class_name;
_p->typed.script = script;
_p->typed.where = "TypedArray";
}
bool Array::is_typed() const {
return _p->typed.type != Variant::NIL;
}
2022-11-27 09:56:53 +02:00
bool Array::is_same_typed(const Array &p_other) const {
return _p->typed == p_other._p->typed;
}
bool Array::is_same_instance(const Array &p_other) const {
return _p == p_other._p;
}
ContainerType Array::get_element_type() const {
ContainerType type;
type.builtin_type = _p->typed.type;
type.class_name = _p->typed.class_name;
type.script = _p->typed.script;
return type;
}
uint32_t Array::get_typed_builtin() const {
return _p->typed.type;
}
StringName Array::get_typed_class_name() const {
return _p->typed.class_name;
}
Variant Array::get_typed_script() const {
return _p->typed.script;
}
Array Array::create_read_only() {
Array array;
array.make_read_only();
return array;
}
void Array::make_read_only() {
if (_p->read_only == nullptr) {
_p->read_only = memnew(Variant);
}
}
bool Array::is_read_only() const {
return _p->read_only != nullptr;
}
Span<Variant> Array::span() const {
return _p->array.span();
}
2014-02-09 22:10:30 -03:00
Array::Array(const Array &p_from) {
2020-04-02 01:20:12 +02:00
_p = nullptr;
2014-02-09 22:10:30 -03:00
_ref(p_from);
}
2023-12-10 17:54:35 -05:00
Array::Array(std::initializer_list<Variant> p_init) {
_p = memnew(ArrayPrivate);
_p->refcount.init();
_p->array = Vector<Variant>(p_init);
}
Array::Array() {
2014-02-09 22:10:30 -03:00
_p = memnew(ArrayPrivate);
_p->refcount.init();
}
2014-02-09 22:10:30 -03:00
Array::~Array() {
_unref();
}