godot
godot copied to clipboard
`Resource.duplicate(true)` doesn't duplicate subresources stored in Array or Dictionary properties
Bugsquad note: This issue has been confirmed several times already. No need to confirm it further.
Godot version
4.0.stable
System information
Manjaro Linux
Issue description
This behavior is poorly documented, and it breaks expectations. It is mentioned in the docs only under Array.duplicate(), but not Dictionary class, or Resource.duplicate().
This affects also "Make Unique" and "Make Unique (Recursive)" for custom resources.
Here's the problematic code: https://github.com/godotengine/godot/blob/fc7adaab7b3856a7831d402ea2bbb27efe7b7d8a/core/variant/variant_setget.cpp#L1889-L1900
Steps to reproduce
For simple code see reproduction project.
To reproduce on your own:
- Have a resource with a property of type Array[SomeOtherResource]
- One of the following: 2a) "Make Unique" on parent resource in inspector. 2b) Or duplicate(true) from code.
- Check if SomeOtherResource items inside array are unique.
Minimal reproduction project
Thank you! I believe I experienced this with 4.0-stable trying to duplicate a custom resource with a dictionary that contained additional dictionaries. I ended up writing a function to create a new resource and copy over the data piece-by-piece, but never figured out why duplicate(true) hadn’t worked. Some additional documentation would save the next person a lot of time.
It's troublesome that this isn't fixed yet, my game relies a lot on duplicating resources and arrays and I was really miffed when I just found out deep copying isn't functioning properly. This should be tracked and added to a milestone.
This also occurs on all 3.x variants.
I confim that my_resource = my_resource.duplicate(true) doesn't make unique sub-resources as the documentation says. Godot 4.1.
I also met this situation in c#,I set the resourcelocaltoscene = true, and the resource members are not shared except the array and dictionary. this really affect,it can provide one override copy function for resource.
Does anyone know if this is being looked at or at least planned to be included in a future release?
I hit this bug during a game jam (v4.1.2).
I'm guessing one of the issues with this (though perhaps not the only reason it was removed) is because resources can self-reference which would break this. Here is a pseudo-code thought on it:
Variant Variant::recursive_duplicate(bool p_deep, int recursion_count, std::unordered_map<Ref<Resource>, Ref<Resource>> refs) const {
switch (type) {
case OBJECT: {
Ref<Resource> resource = _get_obj().ref;
if (p_deep && !resource.is_null() && resource.is_valid()) {
if (refs.contains(resource))
return refs[resource];
Ref<Resource> resource_new = resource->duplicate(true, refs);
refs[resource] = resource_new;
return resource_new;
}
return *this;
} break;
This isn't in a working state but I am trying to look at what direction it might be able to go and help the conversation around this as I need this fixed myself.
Oh my god, I was confused why all my enemy mobs die at the same time like they have shared HP. Had to make multiple loops to manually duplicate subresources in my ready function to work around this. This is rough for stat heavy games out there.
I came across this issues when duplicating CollisionShape2D in the Scene... but i guess this is using the same function beneath
~~This seems to have been fixed. Using Resource.duplicate(true) duplicates the subresources as well in v4.2.1~~
Edit: This is not true for Array[Resource], though which is the intent of the issue. My bad!
This seems to have been fixed. Using
Resource.duplicate(true)duplicates the subresources as well in v4.2.1
I've tested this recently in 4.2.1 and no, it wasn't fixed when using Array at least. If you think it's fixed, please post a simple example of it working, as it might help solve the issue.
I can't believe this hasn't been fixed, my project relies heavily on resources that have arrays or dictionaries in them, for localToScene to not work with them is really bad. I'm now having to write duplication methods for more than 10 different classes.
Just hit this bug as well. The most frustrating part is that, as the original post already statet, the documentation does (still ?!?) not mention it in the most crucial places. Now I will have to retroactively add custom duplicate functions to most of my classes. Big thanks to anyone who is able to fix this.
Please don't bump without contributing significant new information. Use the :+1: reaction button on the first post instead.
Can this at least be updated in the documentation ASAP even if it won't be fixed any time soon? The Resource.duplicate docs confidently state:
If
subresourcesistrue, a deep copy is returned; nested subresources will be duplicated and are not shared.
Which is clearly not true and causing headaches for many users in this thread. It's now been over 1 year with no comments from the team.
it's not fix now. has any way to fix this problem?
I made a gdscript class, it's extend Resource to implement a function call _duplicate to fix this problem.
But please fix this in the engine as soon as possible.
the class code is :
class_name BaseResource
extends Resource
func _duplicate(flag):
var newRes = self.duplicate(flag)
if flag:
newRes._CheckDuplicateRes()
return newRes
func _ArrayCheck(type,pvalue):
var needcpobj = {}
for i in range(pvalue.size()):
var value = pvalue[i]
var vtype = typeof(value)
#handle type obj copy
if vtype == TYPE_OBJECT:
if not value.has_method("_duplicate"): continue
var dpvalue = value._duplicate(true)
needcpobj[i] = dpvalue
else:
self._CheckType(vtype,value)
for i in needcpobj.keys():
pvalue[i] = needcpobj[i]
return
func _DictionaryCheck(type,pvalue):
var needcpobj = {}
for key in pvalue:
var value = pvalue[key]
var vtype = typeof(value)
#handle type obj copy
if vtype == TYPE_OBJECT:
if not value.has_method("_duplicate"): continue
var dpvalue = value._duplicate(true)
needcpobj[key] = dpvalue
else:
self._CheckType(vtype,value)
for i in needcpobj.keys():
pvalue[i] = needcpobj[i]
return
func _CheckType(type,value):
match type:
TYPE_ARRAY:
_ArrayCheck(type,value)
return
TYPE_DICTIONARY:
_DictionaryCheck(type,value)
return
TYPE_OBJECT:
if value.has_method("_CheckDuplicateRes"):
value._CheckDuplicateRes()
return
func _CheckDuplicateRes():
for property in self.get_property_list():
if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE:
var value = self.get(property.name)
if value == null: continue
var type = property.type
self._CheckType(type,value)
return
it's not fix now. has any way to fix this problem?
I made a gdscript class, it's extend Resource to implement a function call _duplicate to fix this problem.
But please fix this in the engine as soon as possible.
the class code is :
class_name BaseResource
Edit: So, I kinda figured it out where my (and many others) issues come from regarding duplicating resources and saving and loading them.
The initial problem is that saving and loading subresources just does not work if you simply save and load using ResourceSaver and RexourceLoader IF any of your subresources are actually instanced. ResourceSaver will just save a reference to the project file instance.
Now, the "FLAG_BUNDLE_RESOURCES " should fix it, but it is actually not working right now (#65393)
If you want to save your instanced subresources you need to deep duplicate them using "BaseResource".
It's also important du make deep duplications when you have a resource "template" saved that you later want to add to an array List multiple times.
I am trying to use this, together with: https://github.com/godotengine/godot/issues/65393#issuecomment-1847601360 to save and load resources that contain an Array with other Resources.
It does not work unfortunately:
E 0:00:09:0688 SaveManager.gd:17 @ loadAll(): Attempted to assign an object into a TypedArray, that does not inherit from 'GDScript'.
<C++ Error> Condition "!other_script->inherits_script(script)" is true. Returning: false
<C++ Source> core/variant/container_type_validate.h:140 @ validate_object()
<Stack Trace> SaveManager.gd:17 @ loadAll()
SaveManager.gd:9 @ _init()
var loadfile = ResourceLoader.load(SAVEDIR,"GameStats", 0)
E 0:00:09:0688 SaveManager.gd:17 @ loadAll(): unsupported format character
<C++ Error> Condition "error" is true. Returning: String()
<C++ Source> ./core/variant/variant.h:834 @ vformat()
<Stack Trace> SaveManager.gd:17 @ loadAll()
SaveManager.gd:9 @ _init()
E 0:00:09:0688 SaveManager.gd:17 @ loadAll(): Method/function failed.
<C++ Source> core/variant/array.cpp:222 @ assign()
<Stack Trace> SaveManager.gd:17 @ loadAll()
SaveManager.gd:9 @ _init()
E 0:00:09:0695 SaveManager.gd:17 @ loadAll(): Attempted to assign an object into a TypedArray, that does not inherit from 'GDScript'.
<C++ Error> Condition "!other_script->inherits_script(script)" is true. Returning: false
<C++ Source> core/variant/container_type_validate.h:140 @ validate_object()
<Stack Trace> SaveManager.gd:17 @ loadAll()
SaveManager.gd:9 @ _init()
E 0:00:09:0695 SaveManager.gd:17 @ loadAll(): unsupported format character
<C++ Error> Condition "error" is true. Returning: String()
<C++ Source> ./core/variant/variant.h:834 @ vformat()
<Stack Trace> SaveManager.gd:17 @ loadAll()
SaveManager.gd:9 @ _init()
E 0:00:09:0695 SaveManager.gd:17 @ loadAll(): Method/function failed.
<C++ Source> core/variant/array.cpp:222 @ assign()
<Stack Trace> SaveManager.gd:17 @ loadAll()
SaveManager.gd:9 @ _init()
Has anyone managed to save and load resources that contain an Array of another resources?
Any news on this? It's pretty crucial for a resource heavy game :/
Yeah this seems like a pretty major bug to me. It's workaroundable but I don't particuarly like the elegancy of any of the workarounds.
I'm not sure if this is related or a new bug, but I'm finding nothing is duplicated in my custom resource. I've got a custom resource with a bunch of different variables including dictionaries and standalone variable ints and such. ~~And when attempting to use the duplicate(true) function none of it is copied over. The dictionaries are blank and the integers are also their default value.~~ It seems like integers based on enums are not copied, but regular integers are.... strange.
But essentially I have to manually copy over every value from one resource to another, which is far from ideal.
Also stumbled upon this issue after a day of frustration. The docs should really be updated as "If subresources is true, a deep copy is returned; nested subresources will be duplicated and are not shared." is factually not correct.
As nobody explicitly mentioned the simple (but dumb) workaround: after creating a duplicate using duplicate(true), you can iterate over the arrays and dictionaries in those resources (and the ones in those, if you have them nested) and replace the elements with duplicates of themselves. Like so:
func duplicate_deep_workaround() -> SomeCustomResource:
var dup: SomeCustomResource = duplicate(true) as SomeCustomResource
for i: int in dup.some_array.size():
dup.some_array[i] = dup.some_array[i].duplicate(true)
return dup
@AdriaandeJongh that was actually already said though. It's a workaround but it doesn't actually ease the pain of nested sub resources 💔
It seems to me to be a larger bug with duplication functionality. If I (on 4.2.2, OSX) duplicate an item, then change any of its properties, the properties of the original are also changed. Doesn't matter what kind of node it is or what property it is. Is there a memory reference issue going on maybe?
If I (on 4.2.2, OSX) duplicate an item
Are you duplicating a node or a resource? How are you duplicating it?
Duplicating a node does not automatically make its Resource-based properties (or their subresources) unique, with a handful of exceptions (properties marked with PROPERTY_USAGE_ALWAYS_DUPLICATE).
If I (on 4.2.2, OSX) duplicate an item
Are you duplicating a node or a resource? How are you duplicating it?
Duplicating a node does not automatically make its Resource-based properties (or their subresources) unique, with a handful of exceptions (properties marked with
PROPERTY_USAGE_ALWAYS_DUPLICATE).
I would expect the behavior would be that creating a duplicate of a node would make a new copy that is a unique node unconnected to the original. E.g. let's say I have to make two identical decks in a blackjack game, one for the dealer and one for the player, but I want their positions to be different (or, maybe their sizes, etc). The current behavior I'm seeing is that changing such parameters on the duplicate also changes them on the original.
I would expect the behavior would be that creating a duplicate of a node would make a new copy that is a unique node unconnected to the original.
This is being tracked in https://github.com/godotengine/godot-proposals/issues/317 and https://github.com/godotengine/godot-proposals/issues/4672.
After thinking for a while why copying resources nested in arrays didn't work, I opened github and searched for this issue as expected. The document should be adjusted quickly to avoid more victims. 🌝
Here's the PR I made to add this exception to the docs: https://github.com/godotengine/godot/pull/94142
For anyone wondering how to circumvent this while it gets fixed:
You can create a method in your resource that duplicates itself, then also duplicate each of the elements of the array in a for loop and reutrn the new resource. This should work in the mean time