Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Node duplicate mapping of child properties. #96018

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

0xcafeb33f
Copy link

In Godot 4.3, the way the duplicate function copied properties was changed. This results in many error messages when any Node with internal nodes as children is duplicated, as there is not a one-to-one correspondence between the original and copied children indices. Instead, Node paths need to be used, as they are when duplicating signals. This commit fixes the log spam when duplicating Nodes with internal children by finding the correct corresponding child to copy properties to.

Fixes #92880 . Please cherrypick to 4.3 if possible.

P.S. this is my first pull request, please let me know if any of the metadata is incorrect.

@0xcafeb33f 0xcafeb33f requested a review from a team as a code owner August 23, 2024 23:56
@Chaosus Chaosus added this to the 4.4 milestone Aug 24, 2024
@100gold
Copy link
Contributor

100gold commented Aug 27, 2024

In my case, I get the "Child node disappeared while duplicating" message when I'm using duplicate() for a scene with dynamically added objects. By default, the duplicate function sets the DUPLICATE_USE_INSTANTIATION flag, which is not compatible with dynamically modified scenes (#92829). I think this means the original problem with GraphEdit is not in the 'duplicate' function.

@0xcafeb33f
Copy link
Author

In my case, I get the "Child node disappeared while duplicating" message when I'm using duplicate() for a scene with dynamically added objects. By default, the duplicate function sets the DUPLICATE_USE_INSTANTIATION flag, which is not compatible with dynamically modified scenes (#92829). I think this means the original problem with GraphEdit is not in the 'duplicate' function.

Yes, before this commit, the "Child node disappeared while duplicating" error appears whenever internal nodes / nodes added by scripts are duplicated (depending on the exact node tree layout, possibly also a crash). GraphEdit adds internal nodes when instantiated:

godot/scene/gui/graph_edit.cpp

Lines 2775 to 2793 in db76de5

GraphEdit::GraphEdit() {
set_focus_mode(FOCUS_ALL);
// Allow dezooming 8 times from the default zoom level.
// At low zoom levels, text is unreadable due to its small size and poor filtering,
// but this is still useful for previewing and navigation.
zoom_min = (1 / Math::pow(zoom_step, 8));
// Allow zooming 4 times from the default zoom level.
zoom_max = (1 * Math::pow(zoom_step, 4));
panner.instantiate();
panner->set_callbacks(callable_mp(this, &GraphEdit::_pan_callback), callable_mp(this, &GraphEdit::_zoom_callback));
top_layer = memnew(Control);
add_child(top_layer, false, INTERNAL_MODE_BACK);
top_layer->set_mouse_filter(MOUSE_FILTER_IGNORE);
top_layer->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
top_layer->connect(SceneStringName(draw), callable_mp(this, &GraphEdit::_top_layer_draw));
top_layer->connect(SceneStringName(focus_exited), callable_mp(panner.ptr(), &ViewPanner::release_pan_key));

This commit fixes the property mapping so that regardless of internal nodes, it will still copy the properties correctly. This fixes the duplicate() behavior on GraphEdit nodes as a byproduct.

The internal nodes being skipped mentioned in #92829 seems to be expected behavior, and is in another function:

godot/scene/main/node.cpp

Lines 2719 to 2721 in f1d43ed

if (!descendant->get_owner()) {
continue; // Internal nodes or nodes added by scripts.
}

@wagnerfs
Copy link

Just coming by to say this PR doesn't work either, breaks on dynamically created objects, so for it to work like it used to it should actually only be:

for (int i = 0; i < p_copy->get_child_count(); i++) {
    Node *copy_child = p_copy->get_child(i);
    ERR_FAIL_NULL_MSG(copy_child, "Child node disappeared while duplicating.");
    _duplicate_properties(p_root, p_original->get_child(i), copy_child, p_flags);
}

@0xcafeb33f
Copy link
Author

Just coming by to say this PR doesn't work either, breaks on dynamically created objects, so for it to work like it used to it should actually only be:

for (int i = 0; i < p_copy->get_child_count(); i++) {
    Node *copy_child = p_copy->get_child(i);
    ERR_FAIL_NULL_MSG(copy_child, "Child node disappeared while duplicating.");
    _duplicate_properties(p_root, p_original->get_child(i), copy_child, p_flags);
}

This code won't work as-is because p_copy->get_child(i); does not correspond to p_original->get_child(i); when internal nodes exist.

PR #89442 fixes this by converting the function calls to get_child(i, false) which ignores internal nodes. After reviewing that PR, I am in favor of closing this one as it looks like #89442 has a more succinct approach to resolving this issue.

One thing to note: _duplicate_signals currently uses this same code pattern of get_path_to and get_node.

godot/scene/main/node.cpp

Lines 2979 to 2980 in 2c136e6

NodePath p = p_original->get_path_to(n);
Node *copy = p_copy->get_node(p);

Can you check if, on the master branch, DUPLICATE_SIGNALS currently copies signals of dynamically created objects with internal nodes correctly? If not, I will submit a separate PR to make the _duplicate_signals function consistent with the fix in #89442 (thereby fixing the [potential] bug). I was not able to reproduce the problem with dynamically created objects myself.

@wagnerfs
Copy link

@0xcafeb33f here's the thing, neither of the PRs solve dynamically created objects.

image

PR #89442 doesn't change a thing when it comes to that, this one also somewhat doesn't work because it works on some dynamic added objects (like Meshinstance3D) but breaks for Timer.

At this point I believe the regression might be somewhere else.

@0xcafeb33f
Copy link
Author

@wagnerfs Thanks for sharing the error messages. You said it breaks on Timer, and it looks like there's an error with Skeleton(2D/3D)? Would you be able to share a minimally reproducible project or scene, or point me to a bug report that describes this specific issue? I'd like to track down the cause.

@wagnerfs
Copy link

@0xcafeb33f it's a little late around here already, I'll most likely debug the _duplicate_properties method tomorrow to see where exactly the copy nodes are skipped, I'll drop a small project here as well.

@wagnerfs
Copy link

Okay so as I expected, _duplicate_properties will always fail because the nodes added by script are just being fully ignored by f19c419 :

if (!descendant->get_owner()) {
	continue; // Internal nodes or nodes added by scripts.
}

So it'll always fail when p_copy is matched with p_original no matter what.
I did try to manually set owner for my script added nodes but no luck.
Oddly enough, fully removing the lines above fully fixes duplication, of course at the cost of duplicating internal nodes as well, so MAYBE #89442 should fix the internal node duplication issue allowing the PR that prevents nodes added by script to be removed.

Oh, the PR here is actually giving me crashes, probably because it's still trying to duplicate a property of a child that simply doesn't exist, I guess that's why I got a crash with the Timer on the error output, I got a timer somewhere added to the scene by script and then have the entire scene duplicated.

I personally don't understand why preventing the user to have a node they added through code themselves from being duplicated, and adding several steps to make it work like adding owner and all those shenanigans.
But anyway, I'll probably test KoBeWi's PR along with removing the descendant->get_owner() line and see how it goes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Duplicating GraphEdit in editor prints error
4 participants