Repository: godotengine/godot (language: cpp)

Issue:
4.4-dev6: Signals don't work in exported builds if .godot dir was created by dev6
### Tested versions

- Reproducible in: v4.4.dev6.official [1f47e4c4e]
- Not reproducible in: v4.4.dev5.official [9e6098432]

### System information

Godot v4.4.dev6 - Ubuntu 24.04.1 LTS 24.04 on X11 - X11 display driver, Multi-window, 2 monitors - OpenGL 3 (Compatibility) - NVIDIA GeForce GTX 1060 6GB (nvidia; 535.183.01) - Intel(R) Core(TM) i5-4570 CPU @ 3.20GHz (4 threads)

### Issue description

In 4.4-dev6, _signals do not work, at all_ in exported builds. The signal handlers are never called. I have tried both built-in signals (such as clicking a button) and custom signals (calling `.emit()`).

However, the circumstances are extremely weird. It is not tied to the Godot version you used to export, but rather, _the Godot version that created the `.godot` directory_.

To elaborate:
* If you create a new project in 4.4-dev6, then export it, signals are broken.
* If you create a project in 4.4-dev5, then upgrade to 4.4-dev6, then export, signals work.
* However, if you delete the `.godot` directory, then re-open with 4.4-dev6, signals are broken.
* But, if you then re-open the project (with the `.godot` dir created in dev6) in 4.4-dev5, and export, signals are still broken!

So this bug can manifest in 4.4-dev5 and earlier, but only if the `.godot` dir was created by dev6.

I'm at a loss to explain this behaviour, but I have tested it extensively (on Linux), both on a "real" project and in a minimal repro. I have compared the "good" and "bad" `.godot` directories and can't see much that would distinguish them - the binary `.scn` files are different but you would expect those to regenerate every time you re-save the scene. And shader cache files are different, but you wouldn't expect that to affect something as unrelated as signals.

And to be clear, everything works fine in the editor. It's only broken for exported builds.

I have tested both the Linux native and Web exports, and the issue affects both.

### Steps to reproduce

1. Create a new project using 4.4-dev6.
2. Create a new "User Interface" scene.
3. Attach a script to the scene.
4. Add a Button to the scene and give it some text.
5. In the editor, connect the Button's `pressed()` signal up to a new method.
6. Add the code `print('Button pressed')` to the signal handler. For good measure, also `$Button.text = 'Pressed'`.
7. Run the project (setting the scene as the main scene) and verify that clicking the button prints "Button pressed" to the console and changes the button text.
8. Project > Export and add a Linux native export, without changing any settings.
9. Export Project. Ensure that "Export With Debug" is ticked.
10. From the command line, run `New Game Project.x86_64`.
11. Observe that clicking the button has no effect, neither printing to the console, nor changing the text.

As mentioned above, there are some very weird interactions if we change Godot versions:
- Open the project in dev5 - it is still broken.
- Delete the `.godot` dir and re-open in dev5 - it now works.
- Open the project in dev6 - it still works.
- Delete the `.godot` dir and re-open in dev6 - it is now broken again.

### Minimal reproduction project (MRP)

[signal-repro.zip](https://github.com/user-attachments/files/18038555/signal-repro.zip)



Discussion / hints:
Just looking at the dev6 changelog, a possible commit that might have caused this is #97303. (Because perhaps the "avoid duplicating signals" avoided something necessary to do with signals, and wrote that into the PackedScene inside the `.godot` directory, which means signals don't fire even if it's loaded in old versions.)
I tested the issue (on the Web platform, running dev5 and dev6 as explained) and I can confirm it.
I can confirm too that the regression is caused by #97303. I reverted the PR from master, then created a project with it. And it works when exported.
CC @cixil 
The problem seems to be how the `CONNECT_INHERITED` is applied.
https://github.com/godotengine/godot/blob/aa8d9b83f66488dbb2c9c918e9016ef80f821fb4/scene/resources/packed_scene.cpp#L609
Changing this to use `GEN_EDIT_STATE_MAIN` fixes the bug, but it's probably not the right fix.
https://github.com/godotengine/godot/blob/aa8d9b83f66488dbb2c9c918e9016ef80f821fb4/editor/export/editor_export_platform.cpp#L825
The bug happens, because scenes are processed during export if you have `editor/export/convert_text_resources_to_binary` enabled, and due to wrong inheritance state, the connections are lost.
Well there is another case of this bug (or at least I think it is related). Signal connections don't work in scenes instantiated by plugins, like custom main screens.

EDIT:
Ok confirmed it to be the same problem.

EDIT2:
It's actually caused by my specific way of creating some nodes. I pack a scene and then instantiate it multiple times.

EDIT3:
These might be related: #48064 #85372 #85606
Don't know if it matters, but you can get around this bug by connecting signals via code instead of the UI.
Confirmed on android XR builds. Good to know ^_^
Can anyone explain exactly what the role is for this flag:

https://github.com/godotengine/godot/blob/aa8d9b83f66488dbb2c9c918e9016ef80f821fb4/core/object/object.h#L575

and these enums?:
https://github.com/godotengine/godot/blob/aa8d9b83f66488dbb2c9c918e9016ef80f821fb4/scene/resources/packed_scene.h#L126-L131

re @KoBeWi's comment
I don't quite understand why the inherited flag would be *added*  during instantiation, isn't this flag just supposed to be a way for the editor to determine which connections are a part of the scene?
https://github.com/godotengine/godot/blob/aa8d9b83f66488dbb2c9c918e9016ef80f821fb4/scene/resources/packed_scene.cpp#L609

EDIT: Ah I see this is added when instantiated scenes are added as children in the editor? I forget the same code is used in the game runtime and in the editor
The flag is temporary. All connections that belong to a different scene than the currently opened one will get the flag once the scene is loaded; this applies to instances and inherited scenes.
I guess a simple fix to #97303 could be to add `#ifdef TOOLS_ENABLED` around the check for the inherited flag (haven't tested). But I'm not sure if this issue points to an incorrect use of `CONNECT_INHERITED` that should be fixed instead.
The problem exists in the editor.

Fix patch:
diff --git a/core/object/object.h b/core/object/object.h
index da8e8d1b86ee..11b94a7fbf0a 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -572,7 +572,7 @@ class Object {
 		CONNECT_PERSIST = 2, // hint for scene to save this connection
 		CONNECT_ONE_SHOT = 4,
 		CONNECT_REFERENCE_COUNTED = 8,
-		CONNECT_INHERITED = 16, // Whether or not the connection is in an instance of a scene.
+		CONNECT_INHERITED = 16, // Used in editor builds.
 	};
 
 	struct Connection {
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 8b1cbdf488a5..d6748ed2e64f 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -1060,12 +1060,6 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, HashMap<String
 				continue;
 			}
 
-			// Don't include signals that are from scene instances
-			// (they are already saved in the scenes themselves).
-			if (c.flags & CONNECT_INHERITED) {
-				continue;
-			}
-
 			// only connections that originate or end into main saved scene are saved
 			// everything else is discarded
 


Test patch:
diff --git a/tests/scene/test_packed_scene.h b/tests/scene/test_packed_scene.h
index 3698b00c6b86..df23c251c00b 100644
--- a/tests/scene/test_packed_scene.h
+++ b/tests/scene/test_packed_scene.h
@@ -96,6 +96,8 @@ TEST_CASE("[PackedScene] Signals Preserved when Packing Scene") {
 		CHECK_EQ(state->get_connection_count(), 3);
 	}
 
+	/*
+	// FIXME: This subcase requires GH-48064 to be fixed.
 	SUBCASE("Signals that should not be saved") {
 		int subscene_flags = Object::CONNECT_PERSIST | Object::CONNECT_INHERITED;
 		// subscene node to itself
@@ -115,6 +117,7 @@ TEST_CASE("[PackedScene] Signals Preserved when Packing Scene") {
 		Ref<SceneState> state = packed_scene->get_state();
 		CHECK_EQ(state->get_connection_count(), 0);
 	}
+	*/
 
 	memdelete(main_scene_root);
 }