godot-cpp icon indicating copy to clipboard operation
godot-cpp copied to clipboard

Hot reloading does not reload nodes in editor viewport

Open Zacrain opened this issue 1 year ago • 11 comments
trafficstars

Godot version

v4.2.2.stable.official [15073afe3]

godot-cpp version

4.2 [9da6ecd14]

System information

Windows 10, Intel Core i5-9300H

Issue description

Testing out the new hot-reloading feature, I have observed that changes do not appear in the editor view after recompiling a GDExtension. Or let's say, they only appear partly.

Two examples where I encountered this:

  • Attributes like changed text labels on buttons appear when running a scene, but not in editor view. This remains unchanged regardless of running the scene. (For example, set some text upon _ready() and change it every time a button is pressed.) The only fix I see so far is to reload the project.
  • Consider renaming a method binding for a signal: It will be shown in it's updated version when picking a method for a signal via the "Connect a Signal to a a Method" prompt, but not in the node's inspector. Even worse, Godot still considers the old name to exist and doesn't complain when running the scene, even though the method does no longer exist due to the renaming.

In order to visualize what I mean (I'll also provide a minimal reproducible example project), I'll detail those two examples now. Let's say we start with this in a button class:

void StartStopButton::_ready() {
    this->set_text("Start Moving");
}

void StartStopButton::_pressed() {
    if (!flipMode)
        this->set_text("Stop Moving");
    else
        this->set_text("Start Moving");

    flipMode = !flipMode;   
}

This is reflected in the editor view: image

However, now change the text in the code, compile and see that the change is not reflected within the editor view. But, if the scene / game is run, the changes are correctly shown. image

Now an example for renamed method bindings. Let's say we have a method like:

void GDExample::_on_startstop_button_pressed() {
	move = !move;
}

in the Sprite2D child. (And correctly bound in the _bind_methods() method.) This method is connected to the pressed() signal of the button. Rename it to something else, for example on_start_button_pressed, recompile and then we have this:

The old name of the connected method is still visible: image

If I now double click on the signal in order to pick another method, and I'm looking for the renamed one, it is correctly listed: image

Yielding two methods, if the connection to the old one is not removed: image

Running the scene like that is totally fine. Which is good but also problematic as I would expect Godot to throw an error or display an error message if the outdated signal handler can not be called. Which it really can't as it does not exist anymore in the code and resulting library.

If the whole project is reloaded after recompiling the code, those issues disappear. But that's not what I would expect from hot reloading.

Steps to reproduce

  1. Create a C++ GDExtension project.
  2. Create some node which should have visible changes in the editor, like a labeled button or a method name for signal handling.
  3. Compile.
  4. Change something about it in the code, e.g., the text label or the method name.
  5. Compile again.
  6. See that the changes are not or only partly reflected in the editor, but running the game or scene is fine.

Minimal reproduction project

The minimal example, which I provided, should be fine to use for that. Note, that you need to change the env path in the SConstruct file as I placed the godot-cpp compiled C++ API in a different directory and left the path in a probably unusable state now. And of course, don't forget to compile otherwise Godot will not find the libraries as I have not bundled them with the zip archive.

GDExtension CPP Example V2.zip

Zacrain avatar Jun 23 '24 22:06 Zacrain

I had a similar, although not sure if it's the same, problem.

I have a C++ gdextension where I create a few Resource classes, and then a scene where I have several of those classes active. I observed the following issues after making changes followed by a hotreload:

  • If the scene is closed, everything works as expected
  • If the scene is open in the main editor, the resources that are active in the scene are not updated correctly and are showing odd behaviour, including the reported issue of callbacks not running.
  • If I then close the scene and reopen the scene, the resources (most of the time) work fine again with the new behaviour.

So I was doing some good ol' printf-debugging and found out the following with regard to the hot reloading sequence:

  • It correctly saves the exposed properties from my resources
  • It correctly calls the destructors of my resources
  • It correctly constructs new instances of my resources
  • It correctly assigns the properties back to my resources
  • I get a notification NOTIFICATION_EXTENSION_RELOADED firing on the new instances of my resources

So far so good. Especially that last step shows that the hot reload has succesfully tracked the new objects being created. Also when I compare values such as get_instance_id() it shows that my instances are correctly tied to the already existing Godot Object _owner. But whenever I create a Callable(this, ...) on my new instances then these fail complaining about objects being null pointers.

Long story, it turns out that godot-cpp calls into Godot with a call gdextension_interface_object_get_instance_binding() to lookup my instance pointer that is tied to the _owner, but this binding has been cleared by the unloading of library.

Possible solution

There is a small kludge in the Wrapped constructor that upon hot reload restores the link to the original _owner. I think at this time, we also need to restore the instance_binding to the new this-pointer. This is also required to call the correct destructor (free_callback) for the new instances.

I made a small change to wrapped.cpp to accomplish this:

diff --git a/src/classes/wrapped.cpp b/src/classes/wrapped.cpp
index ffca4f9..3728d25 100644
--- a/src/classes/wrapped.cpp
+++ b/src/classes/wrapped.cpp
@@ -61,6 +61,10 @@ Wrapped::Wrapped(const StringName p_godot_class) {
                while (recreate_data) {
                        if (recreate_data->wrapper == this) {
                                _owner = recreate_data->owner;
+                               if (likely(_constructing_class_binding_callbacks)) {
+                                       godot::internal::gdextension_interface_object_set_instance_binding(_owner, godot::internal::token, this, _constructing_class_binding_callbacks);
+                                       _constructing_class_binding_callbacks = nullptr;
+                               }
                                if (previous) {
                                        previous->next = recreate_data->next;
                                } else {

I don't know for sure if this fix is complete, but it at least fixes my problem.

EDIT: my version information:

  • Godot Engine v4.3.rc.gentoo.3978628c6
  • godot-cpp 4.3 [https://github.com/godotengine/godot-cpp/commit/8b80d9146bc4773e8d49b59ada64539972e3a4f0]

wakeofluna avatar Aug 22 '24 14:08 wakeofluna

Also see #1589

wakeofluna avatar Sep 17 '24 11:09 wakeofluna

I just posted PR https://github.com/godotengine/godot-cpp/pull/1590 which should fix this! If anyone has time to test it with their project, I'd appreciate it :-)

dsnopek avatar Sep 17 '24 14:09 dsnopek

I just posted PR #1590 which should fix this! If anyone has time to test it with their project, I'd appreciate it :-)

Nope, sadly did not resolve the issue. :( Tested with a minimal example similar to the one provided in the issue here

Zacrain avatar Sep 21 '24 12:09 Zacrain

Ok, thanks for testing!

dsnopek avatar Sep 26 '24 13:09 dsnopek

I'm not entirely sure if this issue is related or a separate bug. If it turns out to be something different, I'll open a separate issue for it. While following the GDExtension C++ example on an M1 Mac running macOS 13.6.7, I noticed that properties set in C++ are lost when the Godot window loses and then regains focus. The only way to recover these properties is by restarting the editor or reloading the current project. I'm using Godot 4.3 stable.

https://github.com/user-attachments/assets/4dd5560b-7bb2-4dbe-95f2-336173e5c751

zero334 avatar Nov 09 '24 12:11 zero334

@zero334 Could this be related to the issue on MacOS where reload doesn't work if using relative (rather than absolute) paths in the .gdextension file? See https://github.com/godotengine/godot/issues/90108#issuecomment-2400353562

dsnopek avatar Nov 11 '24 16:11 dsnopek

@dsnopek I'm using absolute paths, exactly as described in the documentation like:

macos.debug = "res://bin/libgdexample.macos.template_debug.framework"
macos.release = "res://bin/libgdexample.macos.template_release.framework"

zero334 avatar Nov 14 '24 11:11 zero334

@zero334 @dsnopek I'm experiencing the same issue on Linux, so it's not Mac-related. Paths are relative to the project root (linux.debug.x86_64 = "res://bin/libgdexample.linux.template_debug.x86_64.so"). (I guess there is some weird terminological confusion, as absolute paths can't be used with res://. However, using absolute paths like linux.debug.x86_64 = "/home/user/wherever/is/the.so" results in the same behaviour)

Properties, signals, documentation, all is lost after reloading the library. The steps are:

  • open a scene containing a Node subclass coming from a gdextension library
  • observe the properties/signals/docs of the node available
  • unfocus the editor window
  • touch the .so file of the library
  • focus the editor window again

Expected: the properties/signals/docs are present (nothing changes) Actual: they aren't Recovery: restart the editor

godot version: v4.3.stable.gentoo [77dcf97d8] godot-cpp: 56571dc584ee7d14919996c6d58fb5b35e64af63 (yesterday's 4.3 branch)

Equidamoid avatar Nov 28 '24 09:11 Equidamoid

I played around with some logs, and there are 2 possibly independent problems:

  • the library is not reloaded (old logs are printed, __attribute__((constructor)) function is never called again, etc), possible due to gcc's "unique symbols"
  • the _bind_methods method of my c++ class is not called during the reload

Equidamoid avatar Nov 28 '24 14:11 Equidamoid

This issue still seems to appear in Godot 4.4.1. I've observed the same issues while following the GDExtension tutorial (documentation lost, extension is not reloaded, node properties are not repopulated in the editor).

https://github.com/user-attachments/assets/90f72e9e-ff6d-469a-b324-0bd563973583

I've tried recompiling my library with a number of additional parameters (all permutations of use_llvm=yes, use_hot_reload=yes and custom_api_file=extension_api.json"), but ultimately the outcome always remained unchanged.

Cre8or avatar May 12 '25 18:05 Cre8or