godot icon indicating copy to clipboard operation
godot copied to clipboard

GDExtension library cannot be reloaded while editor is running

Open Bromeon opened this issue 2 years ago • 39 comments

Godot version

4.0.dev (b770fa2791f1aeb3296ca91909dd316328582078)

System information

Windows 10

Issue description

On Windows, the Godot editor "locks" a DLL containing a GDExtension library and releases it only after shutdown. Native code can thus not be recompiled as long as the editor remains open.

I'm not exactly sure about the current behavior on Linux and Mac. A user reported the editor would immediately crash upon swapping a .so file (see below), but I already heard that people managed to get the launched game running with an updated library. What is the state here?

This is a considerable limitation for game developers actively using GDExtension. Unlike add-ons/tools that exist during the whole lifetime of the editor, GDExtension for games lives from being frequently updated. Requiring the user to reload the editor on every change not only makes a very common workflow impossible, but is also a regression from GDNative, where reloading (for non-tool classes) could be achieved while the editor was out of focus -- even if it had its bugs.


Now, it isn't my style to complain without trying to find a common solution 😉

I think this problem can be split into two parts, of which the first is likely easier to achieve and might already mitigate some problems:

  1. Do you think it's feasible to enable a "library swap" feature that would at least let the launched game make use of recompiled native code? The extension library loaded by the editor itself could remain the same.

  2. In the original GDExtension PR, hot reloading was listed under TODO. This feature has the potential to make GDExtension a true first-class citizen, and would likely encourage a huge amount of plugin and game development in the Godot ecosystem. In particular, it would make Godot much more attractive to C++ developers.

I'm fully aware that true hot-reloading is very difficult to achieve, as such I'd appreciate some insights on the topic from Godot developers. Have there already been (rough) plans or ideas how such a feature might play along with the ClassDB? If not, would it be appreciated if I offered my help in working out a high-level design together?

Some challenges I see:

  • A mechanism to mark instances of unloaded/reloaded classes. Define their behavior when referenced from GDScript code, scenes or Godot caches.
  • A specified lifecycle, clarifying which role engine and extension play in the init/shutdown. User callbacks to invoke for cleanup code.
  • Eventually, a safe way to reload/unload without UB.

This has been brought up before, but so far without a more detailed discussion. I'll try to use this issue also to summarize the efforts (but can also switch to godot-proposals in case we go into design).

Godot 3 (GDNative):

  • [ ] #48086
  • [ ] https://github.com/godotengine/godot-proposals/issues/5324

Godot 4 (GDExtension):

  • [ ] https://github.com/godotengine/godot-cpp/issues/638
  • [ ] https://github.com/godotengine/godot-proposals/issues/4437

Steps to reproduce

Recompile a GDExtension native library while the Godot 4 editor is open.

Minimal reproduction project

No response

Bromeon avatar Sep 21 '22 20:09 Bromeon

  1. Do you think it's feasible to enable a "library swap" feature that would at least let the launched game make use of recompiled native code? The extension library loaded by the editor itself could remain the same.

This can likely be achieved by having a list of library path remaps passed as a CLI argument to the project run from the editor, with new paths being temporary locations that will never be overwritten.

Calinou avatar Sep 22 '22 00:09 Calinou

The hardest part of this seems to be invalidating all of the pointers the library hands to the engine, like ~~init function pointers, GDNativeExtensionClassCreationInfo,~~ Extension Class method pointers, etc. All references to the library need to be dropped before the library can be unloaded.

Edit: Actually my first examples aren't that hard to deal with, it's mostly about objects like nodes or custom servers.

Waridley avatar Sep 27 '22 22:09 Waridley

Very rough idea, how the new DLL could at least be used for a game's launch (1st problem):

  1. Godot extension config file mentions path/to/library.dll
  2. Compiler generates path/to/library.dll
  3. Godot keeps a watch on that file, to be notified about changes
  4. On a change, Godot copies path/to/library.dll to .godot/libs/library-8172389312837.dll and loads that
  5. Next time, it will be .godot/libs/library-441902036275.dll

Main takeaway: Godot never directly loads path/to/library.dll, so the lock does not exist.

Other ideas:

  • just move the file (may interfere with compiler)
  • copy entire DLL into memory (needs more RAM; big waste if only a small part of the library is used)
  • actively release the previously loaded library on events like focus-lost
    • might need hooks to notify the extension about unloading (i.e. solution to 2nd problem)

Is something like that realistic? Are file watches already implemented in Godot, e.g. for editor purposes?

Bromeon avatar Oct 06 '22 20:10 Bromeon

Main takeaway: Godot never directly loads path/to/library.dll, so the lock does not exist.

I think it's very important for the loaded file to be debuggable in the same way as the compiled one. Not sure if this change might interfere with that.

EngineGuy avatar Oct 12 '22 12:10 EngineGuy

@EngineGuy That's a very good point. 👍

The cleanest solution would probably be to release the lock (either manually triggered, or on FocusLost/Minimized events).

Bromeon avatar Oct 12 '22 12:10 Bromeon

I tried to create a plugin for Godot that uses NativeExtensionManager class in GDScript to manually control loaded extensions.

You can unload the extension, compile new dll, replace the old one and then load the extension again. Now this does really work but it breaks the engine.

It either

  • crashes after hitting play with new dll in place.
  • breaks gdscript somehow (my lambdas that worked fine before just break with obscure errors, memory corrupted maybe?).
  • outputs '"scene/resources/packed_scene.cpp:178 - Node './TestNode2D' was modified from inside an instance, but it has vanished."' and removes the whole extension node from saved scene (ouch, this should not happen right?!).

After these I just gave up. Unfortunately I need native extensions due to performance (and somewhat disliking GDScript) and restarting engine each time is annoying workflow for rapid development and IMO somewhat undermines the idea of dynamic GDExtensions.

I understand "hot reload" is hard to get right (UE as an example) and I don't think it even needs to be pursued but replacing libraries between running the game from editor should be doable without the "hot" magic?

edit: typos

MachineMakesNoise avatar Nov 12 '22 11:11 MachineMakesNoise

Now that we have https://github.com/godot-rust/gdextension/ I was able to play around a bit with the new system and, I don't want to sound too harsh, but GDExtension feels like a substantial usability downgrade for me as a game developer due to this issue. Let me elaborate:

The way I develop games with Godot 3.x using a native language (Rust in my case, but this applies just as well to C++ devs) is pretty much the same as one would do in regular GDScript: I write script classes, only that instead of using gd files, I use gdns files. This is and incredibly productive setup, because I get the full editor experience, but all my game logic is written in a language that makes refactoring and scaling my game much easier. Note that my use case for a native language has nothing to do with developing editor extensions or speeding up critical pieces of game logic: My game logic is entirely written in a native language so I can benefit from the mature static analysis tooling around it.

Now, I know I'm not the only one using Godot like this, but I can only speak for myself. My issue with the transition to GDNative to GDExtension is that it takes away value from me as a user without really giving me much in return. Let me explain with an example:

In Godot 3.x, this is how I work when I want to create some new behavior:

  • I start by creating a new rust file, writing a new struct (a godot class), and exposing some of those struct methods to godot. It looks like this. That link is a Zeppelin character controller. Does that read lightweight and simple like a version of GDSscript with curly braces and static typing? Well, that's how it feels to me :slightly_smiling_face:.
  • Next, I jump back to the editor, create a new node (in this case, a Spatial), and use the asset finder to locate the Zeppelin.gdns file. I drag and drop this file into the node, and the script is set automatically. Yup, I didn't have to do anything else, it's automated: The gdns file was generated by a build script and my dll was recompiled when saving the file (Rust has cargo watch, but C++ devs can use entr to recompile their code on file save).
  • At that point, I can run the scene and start iterating on the logic. That process will typically consist of launching and closing the game multiple times, making small tweaks to the code. During this process, I usually add properties to my class (The equivalent of export vars in GDScript), and tweak those in the editor. It is very important for me to be able to add new properties on the fly, because it's impossible to predict which parameters I will want to expose beforehand.

This all works wonderfully and makes me feel as productive as it gets! But now, let's take a look at my pain points in Godot 4 via GDExtension:

  • Creating a new class requires an editor restart. This is true even if the Windows DLL locking issue is solved. I'm not even on Windows. Unless GDExtension gets full hot-reloading like GDNative used to have, every time I create a new script I will have to restart the editor, loosing my state (all the open tabs, prompting to save unsaved changes I may not really want to save, ...). This breaks my flow and forces me to retreat to other styles of development that simply ignore the editor and use Godot as a rendering library, which IMO takes away most of its value.
  • But even worse: Adding or removing properties also requires an editor restart. This makes exporting vars from my native scripts far less useful, when it used to be one of the biggest productivity boosters for me in 3.x.

I really don't mean any of this in a bad way. My hope is that, by explaining my workflow in detail, I will help developers understand why so many people seem to care about this issue (I mean, look at the upvotes!). There is a niche (yes, we're not that many, but we exist) of developers that has seen a considerable degradation in their workflow when transitioning from Godot 3 -> 4.

Basically, I just came to suggest that, even if the DLL locking bug is solved, what lies underneath is not the productive experience developers used to have in 3.x. I understand that might not be a priority, and I know I'm hardly Godot's target user by any means, but I'd like to raise awareness about why this is an issue and how it breaks developer's workflows exactly.

setzer22 avatar Nov 12 '22 16:11 setzer22

I would like to have a Button in the Editor to compile (like C#?) which triggers

  1. save all open scenes to a hidden location
  2. unload "scripting" gdextension (free shared library)
  3. trigger a compilation
  4. reload "scripting" gdextension
  5. reload all scenes from the hidden location

To work Godot needs some Information

  • which gdextension to reload (path should be enough)
  • how to compile (terminal command and working directory?)

Maybe a special resouces with the information is enough. The import process would trigger the workflow. Extenal compilations could touch the resource file to automate the process. (somehow avoid recompilation) (This feels more like a hack then a solution)

sava solution sounds similar, so I have no idea if this can work.

antonWetzel avatar Nov 13 '22 20:11 antonWetzel

@setzer22

Creating a new class requires an editor restart

But even worse: Adding or removing properties also requires an editor restart

This isn't exactly true (in my experience on Linux, without the file lock, at least). The editor doesn't reload the extension, but the game loads the updated one each time you launch it. Only if you need to create or modify those classes/properties in the editor do you need to restart (which admittedly does suck).

I have been using cargo watch -s scons -C . successfully to rebuild my godot-cpp extension with the editor open on Linux this way.

That being said, I think the experience on Windows should be at least as good as the current one on Linux.

Side note: Personally, I have found GDExtension most useful for code that needs to be fast and changes frequently, but isn't directly tied to the node system (i.e. classes that extend RefCounted). In my case, I use it for a binary protocol and dealing with large arrays. Then I use signals or function calls to pass data to GDScript that will handle the node stuff.

dkaste avatar Nov 17 '22 17:11 dkaste

The editor doesn't reload the extension, but the game loads the updated one each time you launch it.

Ah, sorry about that. I should've made it more clear in my wording. Yes, this matches my experience on Linux too with Rust. My main concern here is that for a workflow that is tied to the node system (the mainstream way to develop things in Godot), it gets much more difficult because creating a new script-like class requires an editor restart. Otherwise, there's no way to put that node on the scene. Well, technically there is, but it's not a comfortable way to do it.

I have found GDExtension most useful for code that needs to be fast and changes frequently, but isn't directly tied to the node system

Yes, I totally agree. This seems to be the main use case behind its design.

setzer22 avatar Nov 17 '22 18:11 setzer22

Might this be of interest? https://github.com/fungos/cr/ 🤔

dvergeylen avatar Nov 18 '22 09:11 dvergeylen

The way I develop games with Godot 3.x using a native language (Rust in my case, but this applies just as well to C++ devs) is pretty much the same as one would do in regular GDScript: I write script classes, only that instead of using gd files, I use gdns files. This is and incredibly productive setup, because I get the full editor experience, but all my game logic is written in a language that makes refactoring and scaling my game much easier. Note that my use case for a native language has nothing to do with developing editor extensions or speeding up critical pieces of game logic: My game logic is entirely written in a native language so I can benefit from the mature static analysis tooling around it.

Just want to echo and really emphasize this comment, as I think this is the main use case for extending the engine for use with other languages, which is primarily at the scripting layer not necessarily core engine layer.

jordo avatar Nov 22 '22 22:11 jordo

Just want to echo and really emphasize this comment, as I think this is the main use case for extending the engine for use with other languages, which is primarily at the scripting layer not necessarily core engine layer.

Personally I'm not even sure if I ran into someone who tried to extend the core engine this way. Maybe I'm biased since I personally also used it for "scripting" (or specifically replacing GDScript with Rust to get both performance and more developer friendly environment), but it does seem like this is the actual use case?

Having released one non-trivially sized game with this in 3.x and been looking forward to 4.x for a while, I don't want to be overly dramatic, but this issue is enough of a reason for me to not consider using Godot 4.x for any future projects. At least for me the main reason to consider Godot is getting an editor that's integrated into the dev workflow. One already has to make a sacrifice compared to Unity and UE4 where inspecting the scene during play mode is much more interactive, while in Godot it's much more detached. But this is a sacrifice that is worth making for some other benefits, namely the nice integration with a native language 3.x provided.

But if this relative interactivity with the editor is taken away with 4.x, it creates another point of friction between development and using the editor., making the editor even less useful for people who use native scripts. Maybe some can develop without using the editor and only using Godot as a renderer, but at that point I'd question the benefits of using Godot at all, as many viable alternatives exist. To me the main benefit of Godot and GDNative was that I could get both simple scripting, as well as tight integration with Rust. It wasn't perfect, but it worked well enough where after the initial setup I could just build my game without thinking about it too much.

Being simply a consumer of the engine and being 100% focused on making games I don't see inside the engine and don't understand the intricacies or needs for doing a big rewrite to GDExtension. But from purely a game developers perspective, I really don't understand why I'd even want GDExtension, and would 100% be fine with having "4.x features" with the same API as GDNative in 3.x. I understand there are probably internal reasons, and that this is really not what people want to hear after having spent months working on it, but for me as a simple consumer who had many issues with Godot 3.x, GDNative was really not one of them. If there was an option to get the nice things in 4.x (better 2d sprite batching, better tilemaps, etc.) while having the same GDNative extension, I'd take it over any extra power from GDExtension, simply because as a small game developer I don't imagine ever needing to extend the engine over just building things as a feature. Maybe this enables certain class of more ambitious games that were harder to make with engine mods previously, though if that's the case I'd have to question if this is actually Godot's target audience.

darthdeus avatar Nov 26 '22 11:11 darthdeus

Having released one non-trivially sized game with this in 3.x and been looking forward to 4.x for a while, I don't want to be overly dramatic, but this issue is enough of a reason for me to not consider using Godot 4.x for any future projects.

In my opinion this is not being dramatic, this is a valid point and I'm at the same boat. Having just prototyped an idea with Unity I have been eyeing Godot 4.x as the main platform. This unfortunately might be big enough of an issue for me to pass Godot for that idea and wait if this side of the engine matures better with time.

Like @setzer22 wrote well, having a mature static analysis tooling makes all the difference when writing game logic/systems. GDScript (without going into detail here) has too many flaws for me to enjoy writing large parts of the game in - smaller bits are fine though. And on top of that the prototype in question has liquid dynamics associated with the gameplay and GDScript performance is not suited for such a thing.

Might this be of interest? https://github.com/fungos/cr/ 🤔

Hmm, quite an interesting library. Maybe writing an engine module for Godot that acts as a host for a plugin might work but that could be quite hacky, require lots of macrofoo (for proxying) and maybe - as this is quite large of an issue - this would be better to fix at engine level rather than bubblegum fix 😅

MachineMakesNoise avatar Nov 26 '22 12:11 MachineMakesNoise

See also https://github.com/godotengine/godot-cpp/issues/955 for @BastiaanOlij's take on (part of?) this problem.

akien-mga avatar Dec 12 '22 17:12 akien-mga

With this solution the editor won't pick up added classes, properties or methods, that will still require a restart, but it will greatly reduce the need for restarting the editor in many situations

This comment @BastiaanOlij is a big concerning, and for me kinda misses a major goal of extension system's potential. I'm curious what the technical challenges/issues are compared to gdnative in 3.x, because this is currently doable in 3.X correct?

Those 3 things (adding/modifying classes, properties, and methods) are pretty much the main use case for gdextention for a lot of developers no? The workflow goal would be to:

  1. write native code/scripts (in whatever lang).
  2. trigger native compilation
  3. reload the dynlib into editor's process space, with a variety of hooks to support implementation of edge case handling.

Ignoring and putting all other native problems aside (that can potentially totally break everything, like say storing memory addresses within your dynamic library across reloads, statics, symbol names, etc), ClassDB in and of itself should be able to support modification all of registered classes, methods, and properties at runtime no?

jordo avatar Dec 12 '22 18:12 jordo

Sorry though I was writing in my other thread :), Ok proper reaction...

So as @akien-mga pointed to my PR, that is a workaround solution that would probably be the least painful in the short term but doesn't solve enough long term.

Reading some of the other suggestions here they seem to be variations on the same deal, start up a copy of the DLL so the original one won't be locked and can be overwritten. The question then is, how to hot load the new DLL.

First, to answer @EngineGuy concern, the debugger doesn't care where the DLL is, as long as it has the right debug symbols your debugger should be able to step through the code, so the copy approach should be fine.

The real issue is the ability to reload DLLs and this is what @saviilsy kinda ran into. In the Godot 4 approach the extension fully updates the ClassDB with meta data. The major improvement over Godot 3 is that the editor becomes fully aware of the objects in your external and all your nodes work like normal Godot nodes, the major drawback is that unloading your external pulls all this data out (and possibly not cleanly which would be a bug) and you blow a big hole in your project. As a result your scenes that rely on all this information become corrupted and things deteriorate from there.

Now GDScript can handle this, and there may be an answer there, but just like GDNative, GDScript doesn't change the node, it just adds a script to it and if the script isn't there temporarily, the node just doesn't have the extra functionality. With GDExtension the actual class that the node is instantiated as has disappeared.

If this is solvable I'm not sure but that is the two steps forward, one step back issue GDExtension has atm.

BastiaanOlij avatar Dec 12 '22 21:12 BastiaanOlij

@BastiaanOlij In my opinion, the main appeal of GDExtension is for extension developers. Say you're developing a terrain system, or a new low-latency audio server for Godot. GDExtension is clearly better in those scenarios because you can build classes that feel just like regular godot nodes to their users. Everything is integrated, the class appears in the "Add node" menu, and you even get things like autocomplete in GDScript. This is great because more experienced developers can become almost-core engine developers and provide a lot of value to Godot's target audience. With this use case in mind, it makes sense that there will be a bit more pain involved (i.e. frequent editor restarts): Developing these extensions is a fundamentally different workflow than what regular GDScript users are expected, and the experience has to be optimized for the latter.

But, speaking as someone who has done a lot of GDNative work, I find none of the extra features in GDExtension actually useful to my work (I don't mean that in a bad way: I completely understand the use case, I'm just not an engine developer, I am a game developer). IMHO, I would very much prefer if something like GDNative / Nativescript were brought back so that I could continue developing like before, with each "class" I create being equivalent to a script I can attach to nodes, and not a node type itself.

I think enabling proper hot reloading only when certain criteria is met would be a reasonable compromise here. Implement reloading, but if Godot detects some DLL registered a new native class after a reload, simply crash (in a safe way, and not letting memory be corrupted). Then we can start thinking about a mechanism to enable iterative game development in native languages, which could be built on top of the current extension system, instead of around it.

That's my idea anyway :sweat_smile: I'd be interested on hearing about other people's opinions. Would you be happy with a NativeScript-like approach? Or is real native classes, GDExtension-style, something you were looking forward to when in order to build games using native languages (C++, Rust...)?

setzer22 avatar Dec 13 '22 09:12 setzer22

I find none of the extra features in GDExtension actually useful to my work (I don't mean that in a bad way: I completely understand the use case, I'm just not an engine developer, I am a game developer). IMHO, I would very much prefer if something like GDNative / Nativescript were brought back so that I could continue developing like before, with each "class" I create being equivalent to a script I can attach to nodes, and not a node type itself.

I don't think these have to be mutually exclusive! A 'NativeScript' (attaching a native compiled script to any generic node) has a really strong appeal as a 'component' based approach to game development that can be used in replacement of a GDScript attachment... i.e. really useful for implementing logic in another language, be for whatever other reason. (performance, static analysis, tooling, etc).

GDExtension can be targeted for engine or module developers who are integrating into the engine tighter than at the game logic / scripting level.

I think the later will be a use-case that is utilized to a lesser extent than something like 'NativeScript' attachment to any node, so the workflow around a native script attachment use-case imo should be prioritized. This also starts the scratch the surface of 'inheritance' vs 'composition', as the current behaviour of GDNative in 3.x is perhaps more component based (attaching a script resource to any node), whereas it seems GDExtension is targeted towards an inheritance pattern where the customization of behaviour is more rigidly attached to a defined subclass (which appears to be adding an additional layer of constraints and potential issues).

Perhaps a middle ground can support both approaches?

jordo avatar Dec 14 '22 18:12 jordo

@jordo To clarify my comment above, this is also what I was suggesting. :smile: Not remove GDExtension, but introduce something else that works similar to the old GDNative for those wanting to program godot scripts in non-GDSCript languages.

setzer22 avatar Dec 14 '22 19:12 setzer22

@jordo To clarify my comment above, this is also what I was suggesting. 😄 Not remove GDExtension, but introduce something else that works similar to the old GDNative for those wanting to program godot scripts in non-GDSCript languages.

That's a really tricky one. Reading thought the feedback here I can totally see the root problem. GDNative was written as a solution to do what we can do with GDScript but through a plugin that uses some other language, so the way you attached a GDNative "Script" made reloading possible. The problem however was that GDNative did not go far enough for those who wish to make plugins that extent the capabilities of the engine in meaningful ways.

GDExternal is an evolution that specifically addresses that problem, the much tighter integration allows for nearly any functionality that would normally be implemented as a module and compiled into the engine, to be moved into a plugin instead making the engine far more extendable.

So to some extend they fullfill two different niches but it is not practical to maintain two systems here. The group of contributors that is invested in this is only a small group, kind of part and parcel of this being a niche feature of Godot. We're currently stretched out as it is to try and improve the system we have.

Anyway, baby steps, it's clear there is a need for reloadability of the GDExternal plugins so I'm sure we'll find a way to do it in due time.

BastiaanOlij avatar Dec 14 '22 22:12 BastiaanOlij

@BastiaanOlij Thanks :slightly_smiling_face: I appreciate your effort looking into this!

I was suggesting adding something else (possibly on top of GDExtension itself?) because I was kind of assuming a better solution isn't possible, but you're the expert here! If you think a way to reload GDExtension is possible, then that would be preferrable.

setzer22 avatar Dec 15 '22 09:12 setzer22

Sorry if this is a dumb question as I don't have that much knowledge how gdextensions are handled inside the engine but isn't one of the main purposes of InitObject::register_terminator to enable runtime uninitialization of loaded classes?

As in the reload would be something akin to :

  1. Lock the engine so it won't call anything during reregister (don't know if this kind of lock exists)
  2. Call "termintor" to unregister all the extension things
  3. Load new dll
  4. Call "initializer" to register all the new extension things
  5. Unlock the engine again

I'm sure InitObject could be extended to handle old -> new state transfers by introducting callbacks for it if necessary?

I am more worried about the way how Godot handles "orphan" or unexisting classes as it just removes them! Hopefully this can be easily fixed by just showing errors and assigning existing classes to some kind of null/dummy object or similar that does not do anything until the actual implementation is found again. In any case removing the whole object and all things attached to it means lost work...

For me the requirement of restarting to get new properties/methods is not that much of a problem. It is pain yes, but manageable pain :) I just want to iterate fast when coding and usually that iteration happens inside few known functions/methods.

MachineMakesNoise avatar Dec 15 '22 12:12 MachineMakesNoise

@saviilsy yes, the problem isn't that we can't unload the library, the problem is what happens when we do. As you say:

I am more worried about the way how Godot handles "orphan" or unexisting classes as it just removes them

They don't just get orphaned, they get destroyed. So say your external implements a class that you've used for a bunch of nodes in the scene you're editing. When you unload that external, it will remove all those nodes from that scene, and once they are gone, they are gone.

Whats worse, if you've just added those nodes to your scene, they're part of the undo/redo structure, which will become corrupted as we're now pointing to instances that no longer exist.

One suggestion that was offered while we were discussing this in the contributors chat server yesterday is that we'd save and unload all scenes before reloading externals. It'll still be annoying that you need to re-open your scenes but you'll save some time in having to close and re-open the editor fully.

That all said, I do believe that step one is to ensure the editor always makes copies of the DLLs it is about to open so the originals can be overwritten. All the different reload scenarios will require that as a base.

BastiaanOlij avatar Dec 15 '22 23:12 BastiaanOlij

we'd save and unload all scenes before reloading externals

That was my thought. To build that idea out a little more:

  • Add a "reloadable" configuration setting to .gdextension files.
  • Extensions without this property act the way they currently do. This could make debugging easier in some cases, and is a minor optimization for mature extensions.
  • When loading a "reloadable" extension, make a copy to a private location and load that.
  • Watch the original extension file for changes. When a change is detected:
  • Save current project to a private location
  • Save various metadata to a private location (original project location, list of open tabs, file modified/unmodified states, undo stack, etc)
  • Close the project.
  • Make a new copy of the extension, overwriting the old copy.
  • Open the copied project.
  • Reload the saved metadata.

Skrapion avatar Dec 17 '22 21:12 Skrapion

One idea I had, from the contributors chat:

Since deleting extension instances is not an option (as it would tear apart scenes), is it maybe possible to replace them with "ghosts" as long as they're unloaded?

Let's say you have such a scene:

Node
+-- MySuperCamera (inherits Camera3D)
    +-- Node3D
    +-- Light3D

Then, after unloading the extension, the UI would display it like this:

Node
+-- Ghost (inherits Camera3D)
    +-- Node3D
    +-- Light3D

Internally, Godot would still memorize the type of ghost, and could reassign "MySuperCamera" once reloaded.

If access to a ghost happened while unloaded, there could be multiple ways to deal with it:

  • error
  • fallback to base class (Camera3D)
  • do nothing / dummy impl

Answer from BastiaanOlij:

i was thinking about that too, in theory in the background we could keep the ‘godot class’ that the gdnative class is based on alive and only destroy the extension side, and than recreate on load. It’s not true inheritance we do here, the gdextention instance is separate.

But whether the internal structure supports that, i’m not sure, i still don’t have a complete picture in my head of this.

Bromeon avatar Dec 17 '22 21:12 Bromeon

Isn't that already implemented to some extent by #60597?

neikeq avatar Dec 18 '22 01:12 neikeq

@neikeq the problem I see with MissingNo is it is a distinct type, so it would require reallocating all relevant objects and swapping pointers out, similar to "Change Type". Doing this for all node and resource types, including nested resources, might be difficult.

That said, the import system is able to do something similar to reload resources after a reimport, so it could be structured similarly to that.

My suggestion would be to avoid error-prone cases by (with user confirmation) closing scenes and editor plugins during the reload and reopening all scenes/plugins after the GDExtension is reloaded, since it's not that common. I still think that would work for most editor use-cases. I've heard arguments that scene loading is slow, but that ought to be solved as a separate issue. My approach doesn't solve resources referenced by the engine itself, since those will need to be reloaded even if no scenes are open.

lyuma avatar Dec 20 '22 00:12 lyuma

It'll still be annoying that you need to re-open your scenes but you'll save some time in having to close and re-open the editor fully.

This could probably be avoided by saving the previously opened scenes before closing the project and then after reloading, trying to open them again.

ARez2 avatar Dec 20 '22 09:12 ARez2

Do we have a method on GDScript side to close a scene? I could not find one 🤔

Is this the one that would need to be exposed? https://github.com/godotengine/godot/blob/ceca46078358543a22dec85e9c9b525ddaf8110e/editor/editor_data.cpp#L595

MachineMakesNoise avatar Dec 20 '22 09:12 MachineMakesNoise