Merge pull request #100673 from RandomShaper/res_duplicate

Overhaul resource duplication
This commit is contained in:
Thaddeus Crews
2025-05-27 09:39:25 -05:00
19 changed files with 951 additions and 140 deletions

View File

@@ -4268,7 +4268,7 @@ Image::Image(const uint8_t *p_mem_png_jpg, int p_len) {
}
}
Ref<Resource> Image::duplicate(bool p_subresources) const {
Ref<Resource> Image::_duplicate(const DuplicateParams &p_params) const {
Ref<Image> copy;
copy.instantiate();
copy->_copy_internals_from(*this);

View File

@@ -244,6 +244,8 @@ public:
static Ref<Image> (*basis_universal_unpacker_ptr)(const uint8_t *p_data, int p_size);
protected:
virtual Ref<Resource> _duplicate(const DuplicateParams &p_params) const override;
static void _bind_methods();
private:
@@ -425,8 +427,6 @@ public:
void convert_ra_rgba8_to_rg();
void convert_rgba8_to_bgra8();
virtual Ref<Resource> duplicate(bool p_subresources = false) const override;
UsedChannels detect_used_channels(CompressSource p_source = COMPRESS_SOURCE_GENERIC) const;
void optimize_channels();

View File

@@ -34,6 +34,7 @@
#include "core/math/math_funcs.h"
#include "core/math/random_pcg.h"
#include "core/os/os.h"
#include "core/variant/container_type_validate.h"
#include "scene/main/node.h" //only so casting works
void Resource::emit_changed() {
@@ -265,76 +266,178 @@ void Resource::reload_from_file() {
copy_from(s);
}
void Resource::_dupe_sub_resources(Variant &r_variant, Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache) {
switch (r_variant.get_type()) {
case Variant::ARRAY: {
Array a = r_variant;
for (int i = 0; i < a.size(); i++) {
_dupe_sub_resources(a[i], p_for_scene, p_remap_cache);
}
} break;
case Variant::DICTIONARY: {
Dictionary d = r_variant;
for (Variant &k : d.get_key_list()) {
if (k.get_type() == Variant::OBJECT) {
// Replace in dictionary key.
Ref<Resource> sr = k;
if (sr.is_valid() && sr->is_local_to_scene()) {
if (p_remap_cache.has(sr)) {
d[p_remap_cache[sr]] = d[k];
d.erase(k);
} else {
Ref<Resource> dupe = sr->duplicate_for_local_scene(p_for_scene, p_remap_cache);
d[dupe] = d[k];
d.erase(k);
p_remap_cache[sr] = dupe;
Variant Resource::_duplicate_recursive(const Variant &p_variant, const DuplicateParams &p_params, uint32_t p_usage) const {
// Anything other than object can be simply skipped in case of a shallow copy.
if (!p_params.deep && p_variant.get_type() != Variant::OBJECT) {
return p_variant;
}
switch (p_variant.get_type()) {
case Variant::OBJECT: {
const Ref<Resource> &sr = p_variant;
bool should_duplicate = false;
if (sr.is_valid()) {
if ((p_usage & PROPERTY_USAGE_ALWAYS_DUPLICATE)) {
should_duplicate = true;
} else if ((p_usage & PROPERTY_USAGE_NEVER_DUPLICATE)) {
should_duplicate = false;
} else if (p_params.local_scene) {
should_duplicate = sr->is_local_to_scene();
} else {
switch (p_params.subres_mode) {
case RESOURCE_DEEP_DUPLICATE_NONE: {
should_duplicate = false;
} break;
case RESOURCE_DEEP_DUPLICATE_INTERNAL: {
should_duplicate = p_params.deep && sr->is_built_in();
} break;
case RESOURCE_DEEP_DUPLICATE_ALL: {
should_duplicate = p_params.deep;
} break;
default: {
DEV_ASSERT(false);
}
}
} else {
_dupe_sub_resources(k, p_for_scene, p_remap_cache);
}
_dupe_sub_resources(d[k], p_for_scene, p_remap_cache);
}
if (should_duplicate) {
if (thread_duplicate_remap_cache->has(sr)) {
return thread_duplicate_remap_cache->get(sr);
} else {
const Ref<Resource> &dupe = p_params.local_scene
? sr->duplicate_for_local_scene(p_params.local_scene, *thread_duplicate_remap_cache)
: sr->_duplicate(p_params);
thread_duplicate_remap_cache->insert(sr, dupe);
return dupe;
}
} else {
return p_variant;
}
} break;
case Variant::OBJECT: {
Ref<Resource> sr = r_variant;
if (sr.is_valid() && sr->is_local_to_scene()) {
if (p_remap_cache.has(sr)) {
r_variant = p_remap_cache[sr];
} else {
Ref<Resource> dupe = sr->duplicate_for_local_scene(p_for_scene, p_remap_cache);
r_variant = dupe;
p_remap_cache[sr] = dupe;
}
case Variant::ARRAY: {
const Array &src = p_variant;
Array dst;
if (src.is_typed()) {
dst.set_typed(src.get_element_type());
}
dst.resize(src.size());
for (int i = 0; i < src.size(); i++) {
dst[i] = _duplicate_recursive(src[i], p_params);
}
return dst;
} break;
case Variant::DICTIONARY: {
const Dictionary &src = p_variant;
Dictionary dst;
if (src.is_typed()) {
dst.set_typed(src.get_key_type(), src.get_value_type());
}
for (const Variant &k : src.get_key_list()) {
const Variant &v = src[k];
dst.set(
_duplicate_recursive(k, p_params),
_duplicate_recursive(v, p_params));
}
return dst;
} break;
case Variant::PACKED_BYTE_ARRAY:
case Variant::PACKED_INT32_ARRAY:
case Variant::PACKED_INT64_ARRAY:
case Variant::PACKED_FLOAT32_ARRAY:
case Variant::PACKED_FLOAT64_ARRAY:
case Variant::PACKED_STRING_ARRAY:
case Variant::PACKED_VECTOR2_ARRAY:
case Variant::PACKED_VECTOR3_ARRAY:
case Variant::PACKED_COLOR_ARRAY:
case Variant::PACKED_VECTOR4_ARRAY: {
return p_variant.duplicate();
} break;
default: {
return p_variant;
}
}
}
Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache) {
Ref<Resource> Resource::_duplicate(const DuplicateParams &p_params) const {
ERR_FAIL_COND_V_MSG(p_params.local_scene && p_params.subres_mode != RESOURCE_DEEP_DUPLICATE_MAX, Ref<Resource>(), "Duplication for local-to-scene can't specify a deep duplicate mode.");
DuplicateRemapCacheT *remap_cache_backup = thread_duplicate_remap_cache;
// These are for avoiding potential duplicates that can happen in custom code
// from participating in the same duplication session (remap cache).
#define BEFORE_USER_CODE thread_duplicate_remap_cache = nullptr;
#define AFTER_USER_CODE thread_duplicate_remap_cache = remap_cache_backup;
List<PropertyInfo> plist;
get_property_list(&plist);
BEFORE_USER_CODE
Ref<Resource> r = Object::cast_to<Resource>(ClassDB::instantiate(get_class()));
AFTER_USER_CODE
ERR_FAIL_COND_V(r.is_null(), Ref<Resource>());
r->local_scene = p_for_scene;
thread_duplicate_remap_cache->insert(Ref<Resource>(this), r);
if (p_params.local_scene) {
r->local_scene = p_params.local_scene;
}
// Duplicate script first, so the scripted properties are considered.
BEFORE_USER_CODE
r->set_script(get_script());
AFTER_USER_CODE
for (const PropertyInfo &E : plist) {
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
continue;
}
Variant p = get(E.name).duplicate(true);
if (E.name == "script") {
continue;
}
_dupe_sub_resources(p, p_for_scene, p_remap_cache);
BEFORE_USER_CODE
Variant p = get(E.name);
AFTER_USER_CODE
p = _duplicate_recursive(p, p_params, E.usage);
BEFORE_USER_CODE
r->set(E.name, p);
AFTER_USER_CODE
}
return r;
#undef BEFORE_USER_CODE
#undef AFTER_USER_CODE
}
Ref<Resource> Resource::duplicate_for_local_scene(Node *p_for_scene, DuplicateRemapCacheT &p_remap_cache) const {
#ifdef DEBUG_ENABLED
// The only possibilities for the remap cache passed being valid are these:
// a) It's the same already used as the one of the thread. That happens when this function
// is called within some recursion level within a duplication.
// b) There's no current thread remap cache, which means this function is acting as an entry point.
// This check failing means that this function is being called as an entry point during an ongoing
// duplication, likely due to custom instantiation or setter code. It would be an engine bug because
// code starting or joining a duplicate session must ensure to exit it temporarily when making calls
// that may in turn invoke such custom code.
if (thread_duplicate_remap_cache && &p_remap_cache != thread_duplicate_remap_cache) {
ERR_PRINT("Resource::duplicate_for_local_scene() called during an ongoing duplication session. This is an engine bug.");
}
#endif
DuplicateRemapCacheT *remap_cache_backup = thread_duplicate_remap_cache;
thread_duplicate_remap_cache = &p_remap_cache;
DuplicateParams params;
params.deep = true;
params.local_scene = p_for_scene;
const Ref<Resource> &dupe = _duplicate(params);
thread_duplicate_remap_cache = remap_cache_backup;
return dupe;
}
void Resource::_find_sub_resources(const Variant &p_variant, HashSet<Ref<Resource>> &p_resources_found) {
@@ -363,7 +466,7 @@ void Resource::_find_sub_resources(const Variant &p_variant, HashSet<Ref<Resourc
}
}
void Resource::configure_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache) {
void Resource::configure_for_local_scene(Node *p_for_scene, DuplicateRemapCacheT &p_remap_cache) {
List<PropertyInfo> plist;
get_property_list(&plist);
@@ -390,53 +493,90 @@ void Resource::configure_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource
}
}
Ref<Resource> Resource::duplicate(bool p_subresources) const {
List<PropertyInfo> plist;
get_property_list(&plist);
Ref<Resource> Resource::duplicate(bool p_deep) const {
DuplicateRemapCacheT remap_cache;
bool started_session = false;
if (!thread_duplicate_remap_cache) {
thread_duplicate_remap_cache = &remap_cache;
started_session = true;
}
Ref<Resource> r = static_cast<Resource *>(ClassDB::instantiate(get_class()));
ERR_FAIL_COND_V(r.is_null(), Ref<Resource>());
DuplicateParams params;
params.deep = p_deep;
params.subres_mode = RESOURCE_DEEP_DUPLICATE_INTERNAL;
const Ref<Resource> &dupe = _duplicate(params);
for (const PropertyInfo &E : plist) {
if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
continue;
}
Variant p = get(E.name);
if (started_session) {
thread_duplicate_remap_cache = nullptr;
}
switch (p.get_type()) {
case Variant::Type::DICTIONARY:
case Variant::Type::ARRAY:
case Variant::Type::PACKED_BYTE_ARRAY:
case Variant::Type::PACKED_COLOR_ARRAY:
case Variant::Type::PACKED_INT32_ARRAY:
case Variant::Type::PACKED_INT64_ARRAY:
case Variant::Type::PACKED_FLOAT32_ARRAY:
case Variant::Type::PACKED_FLOAT64_ARRAY:
case Variant::Type::PACKED_STRING_ARRAY:
case Variant::Type::PACKED_VECTOR2_ARRAY:
case Variant::Type::PACKED_VECTOR3_ARRAY:
case Variant::Type::PACKED_VECTOR4_ARRAY: {
r->set(E.name, p.duplicate(p_subresources));
} break;
return dupe;
}
case Variant::Type::OBJECT: {
if (!(E.usage & PROPERTY_USAGE_NEVER_DUPLICATE) && (p_subresources || (E.usage & PROPERTY_USAGE_ALWAYS_DUPLICATE))) {
Ref<Resource> sr = p;
if (sr.is_valid()) {
r->set(E.name, sr->duplicate(p_subresources));
}
} else {
r->set(E.name, p);
}
} break;
Ref<Resource> Resource::duplicate_deep(ResourceDeepDuplicateMode p_deep_subresources_mode) const {
ERR_FAIL_INDEX_V(p_deep_subresources_mode, RESOURCE_DEEP_DUPLICATE_MAX, Ref<Resource>());
default: {
r->set(E.name, p);
}
DuplicateRemapCacheT remap_cache;
bool started_session = false;
if (!thread_duplicate_remap_cache) {
thread_duplicate_remap_cache = &remap_cache;
started_session = true;
}
DuplicateParams params;
params.deep = true;
params.subres_mode = p_deep_subresources_mode;
const Ref<Resource> &dupe = _duplicate(params);
if (started_session) {
thread_duplicate_remap_cache = nullptr;
}
return dupe;
}
Ref<Resource> Resource::_duplicate_from_variant(bool p_deep, ResourceDeepDuplicateMode p_deep_subresources_mode, int p_recursion_count) const {
// A call without deep duplication would have been early-rejected at Variant::duplicate() unless it's the root call.
DEV_ASSERT(!(p_recursion_count > 0 && p_deep_subresources_mode == RESOURCE_DEEP_DUPLICATE_NONE));
// When duplicating from Variant, this function may be called multiple times from
// different parts of the data structure being copied. Therefore, we need to create
// a remap cache instance in a way that can be shared among all of the calls.
// Whatever Variant, Array or Dictionary that initiated the call chain will eventually
// claim it, when the stack unwinds up to the root call.
// One exception is that this is the root call.
if (p_recursion_count == 0) {
if (p_deep) {
return duplicate_deep(p_deep_subresources_mode);
} else {
return duplicate(false);
}
}
return r;
if (thread_duplicate_remap_cache) {
Resource::DuplicateRemapCacheT::Iterator E = thread_duplicate_remap_cache->find(Ref<Resource>(this));
if (E) {
return E->value;
}
} else {
thread_duplicate_remap_cache = memnew(DuplicateRemapCacheT);
}
DuplicateParams params;
params.deep = p_deep;
params.subres_mode = p_deep_subresources_mode;
const Ref<Resource> dupe = _duplicate(params);
return dupe;
}
void Resource::_teardown_duplicate_from_variant() {
if (thread_duplicate_remap_cache) {
memdelete(thread_duplicate_remap_cache);
thread_duplicate_remap_cache = nullptr;
}
}
void Resource::_set_path(const String &p_path) {
@@ -583,7 +723,14 @@ void Resource::_bind_methods() {
ClassDB::bind_method(D_METHOD("emit_changed"), &Resource::emit_changed);
ClassDB::bind_method(D_METHOD("duplicate", "subresources"), &Resource::duplicate, DEFVAL(false));
ClassDB::bind_method(D_METHOD("duplicate", "deep"), &Resource::duplicate, DEFVAL(false));
ClassDB::bind_method(D_METHOD("duplicate_deep", "deep_subresources_mode"), &Resource::duplicate_deep, DEFVAL(RESOURCE_DEEP_DUPLICATE_INTERNAL));
// For the bindings, it's much more natural to expose this enum from the Variant realm via Resource.
ClassDB::bind_integer_constant(get_class_static(), StringName("ResourceDeepDuplicateMode"), "RESOURCE_DEEP_DUPLICATE_NONE", RESOURCE_DEEP_DUPLICATE_NONE);
ClassDB::bind_integer_constant(get_class_static(), StringName("ResourceDeepDuplicateMode"), "RESOURCE_DEEP_DUPLICATE_INTERNAL", RESOURCE_DEEP_DUPLICATE_INTERNAL);
ClassDB::bind_integer_constant(get_class_static(), StringName("ResourceDeepDuplicateMode"), "RESOURCE_DEEP_DUPLICATE_ALL", RESOURCE_DEEP_DUPLICATE_ALL);
ADD_SIGNAL(MethodInfo("changed"));
ADD_SIGNAL(MethodInfo("setup_local_to_scene_requested"));

View File

@@ -57,6 +57,13 @@ public:
static void register_custom_data_to_otdb() { ClassDB::add_resource_base_extension("res", get_class_static()); }
virtual String get_base_extension() const { return "res"; }
protected:
struct DuplicateParams {
bool deep = false;
ResourceDeepDuplicateMode subres_mode = RESOURCE_DEEP_DUPLICATE_MAX;
Node *local_scene = nullptr;
};
private:
friend class ResBase;
friend class ResourceCache;
@@ -83,7 +90,10 @@ private:
SelfList<Resource> remapped_list;
void _dupe_sub_resources(Variant &r_variant, Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache);
using DuplicateRemapCacheT = HashMap<Ref<Resource>, Ref<Resource>>;
static thread_local inline DuplicateRemapCacheT *thread_duplicate_remap_cache = nullptr;
Variant _duplicate_recursive(const Variant &p_variant, const DuplicateParams &p_params, uint32_t p_usage = 0) const;
void _find_sub_resources(const Variant &p_variant, HashSet<Ref<Resource>> &p_resources_found);
protected:
@@ -104,6 +114,8 @@ protected:
GDVIRTUAL1C(_set_path_cache, String);
GDVIRTUAL0(_reset_state);
virtual Ref<Resource> _duplicate(const DuplicateParams &p_params) const;
public:
static Node *(*_get_local_scene_func)(); //used by editor
static void (*_update_configuration_warning)(); //used by editor
@@ -131,8 +143,11 @@ public:
void set_scene_unique_id(const String &p_id);
String get_scene_unique_id() const;
virtual Ref<Resource> duplicate(bool p_subresources = false) const;
Ref<Resource> duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache);
Ref<Resource> duplicate(bool p_deep = false) const;
Ref<Resource> duplicate_deep(ResourceDeepDuplicateMode p_deep_subresources_mode = RESOURCE_DEEP_DUPLICATE_INTERNAL) const;
Ref<Resource> _duplicate_from_variant(bool p_deep, ResourceDeepDuplicateMode p_deep_subresources_mode, int p_recursion_count) const;
static void _teardown_duplicate_from_variant();
Ref<Resource> duplicate_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache) const;
void configure_for_local_scene(Node *p_for_scene, HashMap<Ref<Resource>, Ref<Resource>> &p_remap_cache);
void set_local_to_scene(bool p_enable);
@@ -168,6 +183,8 @@ public:
~Resource();
};
VARIANT_ENUM_CAST(ResourceDeepDuplicateMode);
class ResourceCache {
friend class Resource;
friend class ResourceLoader; //need the lock

View File

@@ -37,7 +37,6 @@
#include "core/templates/vector.h"
#include "core/variant/callable.h"
#include "core/variant/dictionary.h"
#include "core/variant/variant.h"
struct ArrayPrivate {
SafeRefCount refcount;
@@ -518,10 +517,14 @@ const Variant &Array::get(int p_idx) const {
}
Array Array::duplicate(bool p_deep) const {
return recursive_duplicate(p_deep, 0);
return recursive_duplicate(p_deep, RESOURCE_DEEP_DUPLICATE_NONE, 0);
}
Array Array::recursive_duplicate(bool p_deep, int recursion_count) const {
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;
new_arr._p->typed = _p->typed;
@@ -531,12 +534,19 @@ Array Array::recursive_duplicate(bool p_deep, int recursion_count) const {
}
if (p_deep) {
bool is_call_chain_end = recursion_count == 0;
recursion_count++;
int element_count = size();
new_arr.resize(element_count);
Variant *write = new_arr._p->array.ptrw();
for (int i = 0; i < element_count; i++) {
write[i] = get(i).recursive_duplicate(true, recursion_count);
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 {
new_arr._p->array = _p->array;

View File

@@ -31,6 +31,7 @@
#pragma once
#include "core/typedefs.h"
#include "core/variant/variant_deep_duplicate.h"
#include <climits>
#include <initializer_list>
@@ -164,7 +165,8 @@ public:
Variant pop_at(int p_pos);
Array duplicate(bool p_deep = false) const;
Array recursive_duplicate(bool p_deep, int recursion_count) const;
Array duplicate_deep(ResourceDeepDuplicateMode p_deep_subresources_mode = RESOURCE_DEEP_DUPLICATE_INTERNAL) const;
Array recursive_duplicate(bool p_deep, ResourceDeepDuplicateMode p_deep_subresources_mode, int recursion_count) const;
Array slice(int p_begin, int p_end = INT_MAX, int p_step = 1, bool p_deep = false) const;
Array filter(const Callable &p_callable) const;

View File

@@ -569,7 +569,11 @@ const Variant *Dictionary::next(const Variant *p_key) const {
}
Dictionary Dictionary::duplicate(bool p_deep) const {
return recursive_duplicate(p_deep, 0);
return recursive_duplicate(p_deep, RESOURCE_DEEP_DUPLICATE_NONE, 0);
}
Dictionary Dictionary::duplicate_deep(ResourceDeepDuplicateMode p_deep_subresources_mode) const {
return recursive_duplicate(true, p_deep_subresources_mode, 0);
}
void Dictionary::make_read_only() {
@@ -581,7 +585,7 @@ bool Dictionary::is_read_only() const {
return _p->read_only != nullptr;
}
Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) const {
Dictionary Dictionary::recursive_duplicate(bool p_deep, ResourceDeepDuplicateMode p_deep_subresources_mode, int recursion_count) const {
Dictionary n;
n._p->typed_key = _p->typed_key;
n._p->typed_value = _p->typed_value;
@@ -592,9 +596,16 @@ Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) con
}
if (p_deep) {
bool is_call_chain_end = recursion_count == 0;
recursion_count++;
for (const KeyValue<Variant, Variant> &E : _p->variant_map) {
n[E.key.recursive_duplicate(true, recursion_count)] = E.value.recursive_duplicate(true, recursion_count);
n[E.key.recursive_duplicate(true, p_deep_subresources_mode, recursion_count)] = E.value.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 {
for (const KeyValue<Variant, Variant> &E : _p->variant_map) {
@@ -643,6 +654,10 @@ bool Dictionary::is_typed_value() const {
return _p->typed_value.type != Variant::NIL;
}
bool Dictionary::is_same_instance(const Dictionary &p_other) const {
return _p == p_other._p;
}
bool Dictionary::is_same_typed(const Dictionary &p_other) const {
return is_same_typed_key(p_other) && is_same_typed_value(p_other);
}

View File

@@ -35,6 +35,7 @@
#include "core/templates/local_vector.h"
#include "core/templates/pair.h"
#include "core/variant/array.h"
#include "core/variant/variant_deep_duplicate.h"
class Variant;
@@ -98,7 +99,8 @@ public:
Array values() const;
Dictionary duplicate(bool p_deep = false) const;
Dictionary recursive_duplicate(bool p_deep, int recursion_count) const;
Dictionary duplicate_deep(ResourceDeepDuplicateMode p_deep_subresources_mode = RESOURCE_DEEP_DUPLICATE_INTERNAL) const;
Dictionary recursive_duplicate(bool p_deep, ResourceDeepDuplicateMode p_deep_subresources_mode, int recursion_count) const;
void set_typed(const ContainerType &p_key_type, const ContainerType &p_value_type);
void set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script);
@@ -106,6 +108,7 @@ public:
bool is_typed() const;
bool is_typed_key() const;
bool is_typed_value() const;
bool is_same_instance(const Dictionary &p_other) const;
bool is_same_typed(const Dictionary &p_other) const;
bool is_same_typed_key(const Dictionary &p_other) const;
bool is_same_typed_value(const Dictionary &p_other) const;

View File

@@ -61,6 +61,7 @@
#include "core/variant/array.h"
#include "core/variant/callable.h"
#include "core/variant/dictionary.h"
#include "core/variant/variant_deep_duplicate.h"
class Object;
class RefCounted;
@@ -612,7 +613,8 @@ public:
void zero();
Variant duplicate(bool p_deep = false) const;
Variant recursive_duplicate(bool p_deep, int recursion_count) const;
Variant duplicate_deep(ResourceDeepDuplicateMode p_deep_subresources_mode = RESOURCE_DEEP_DUPLICATE_INTERNAL) const;
Variant recursive_duplicate(bool p_deep, ResourceDeepDuplicateMode p_deep_subresources_mode, int recursion_count) const;
/* Built-In Methods */

View File

@@ -2408,6 +2408,7 @@ static void _register_variant_builtin_methods_misc() {
bind_method(Dictionary, keys, sarray(), varray());
bind_method(Dictionary, values, sarray(), varray());
bind_method(Dictionary, duplicate, sarray("deep"), varray(false));
bind_method(Dictionary, duplicate_deep, sarray("deep_subresources_mode"), varray(RESOURCE_DEEP_DUPLICATE_INTERNAL));
bind_method(Dictionary, get, sarray("key", "default"), varray(Variant()));
bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant()));
bind_method(Dictionary, set, sarray("key", "value"), varray());
@@ -2466,6 +2467,7 @@ static void _register_variant_builtin_methods_array() {
bind_method(Array, bsearch_custom, sarray("value", "func", "before"), varray(true));
bind_method(Array, reverse, sarray(), varray());
bind_method(Array, duplicate, sarray("deep"), varray(false));
bind_method(Array, duplicate_deep, sarray("deep_subresources_mode"), varray(RESOURCE_DEEP_DUPLICATE_INTERNAL));
bind_method(Array, slice, sarray("begin", "end", "step", "deep"), varray(INT_MAX, 1, false));
bind_method(Array, filter, sarray("method"), varray());
bind_method(Array, map, sarray("method"), varray());

View File

@@ -0,0 +1,41 @@
/**************************************************************************/
/* variant_deep_duplicate.h */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#pragma once
// This would be ideally declared nested in Variant, but that would cause circular
// includes with Array and Dictionary, for instance.
// Also, this enum is be exposed via Resource.
enum ResourceDeepDuplicateMode {
RESOURCE_DEEP_DUPLICATE_NONE,
RESOURCE_DEEP_DUPLICATE_INTERNAL,
RESOURCE_DEEP_DUPLICATE_ALL,
RESOURCE_DEEP_DUPLICATE_MAX
};

View File

@@ -29,9 +29,10 @@
/**************************************************************************/
#include "variant_setget.h"
#include "variant_callable.h"
#include "core/io/resource.h"
struct VariantSetterGetterInfo {
void (*setter)(Variant *base, const Variant *value, bool &valid);
void (*getter)(const Variant *base, Variant *value);
@@ -1969,26 +1970,33 @@ Variant Variant::iter_get(const Variant &r_iter, bool &r_valid) const {
}
Variant Variant::duplicate(bool p_deep) const {
return recursive_duplicate(p_deep, 0);
return recursive_duplicate(p_deep, RESOURCE_DEEP_DUPLICATE_NONE, 0);
}
Variant Variant::recursive_duplicate(bool p_deep, int recursion_count) const {
Variant Variant::duplicate_deep(ResourceDeepDuplicateMode p_deep_subresources_mode) const {
ERR_FAIL_INDEX_V(p_deep_subresources_mode, RESOURCE_DEEP_DUPLICATE_MAX, Variant());
return recursive_duplicate(true, p_deep_subresources_mode, 0);
}
Variant Variant::recursive_duplicate(bool p_deep, ResourceDeepDuplicateMode p_deep_subresources_mode, int recursion_count) const {
switch (type) {
case OBJECT: {
/* breaks stuff :(
if (p_deep && !_get_obj().ref.is_null()) {
Ref<Resource> resource = _get_obj().ref;
if (resource.is_valid()) {
return resource->duplicate(true);
}
// If the root target of duplicate() is a Resource, we can't early-reject because that
// resource itself must be duplicated, much as if Resource::duplicate() had been called.
if (p_deep_subresources_mode == RESOURCE_DEEP_DUPLICATE_NONE && recursion_count > 0) {
return *this;
}
Resource *res = Object::cast_to<Resource>(_get_obj().obj);
if (res) {
return res->_duplicate_from_variant(p_deep, p_deep_subresources_mode, recursion_count);
} else {
return *this;
}
*/
return *this;
} break;
case DICTIONARY:
return operator Dictionary().recursive_duplicate(p_deep, recursion_count);
return operator Dictionary().recursive_duplicate(p_deep, p_deep_subresources_mode, recursion_count);
case ARRAY:
return operator Array().recursive_duplicate(p_deep, recursion_count);
return operator Array().recursive_duplicate(p_deep, p_deep_subresources_mode, recursion_count);
case PACKED_BYTE_ARRAY:
return operator Vector<uint8_t>().duplicate();
case PACKED_INT32_ARRAY: