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

GDExtension library cannot be reloaded while editor is running #66231

Closed
2 of 4 tasks
Bromeon opened this issue Sep 21, 2022 · 54 comments · Fixed by #80284
Closed
2 of 4 tasks

GDExtension library cannot be reloaded while editor is running #66231

Bromeon opened this issue Sep 21, 2022 · 54 comments · Fixed by #80284

Comments

@Bromeon
Copy link
Contributor

Bromeon commented Sep 21, 2022

Godot version

4.0.dev (b770fa2)

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):

Godot 4 (GDExtension):

Steps to reproduce

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

Minimal reproduction project

No response

@Calinou
Copy link
Member

Calinou commented Sep 22, 2022

  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.

@Waridley
Copy link
Contributor

Waridley commented Sep 27, 2022

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.

@Bromeon
Copy link
Contributor Author

Bromeon commented Oct 6, 2022

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?

@EngineGuy
Copy link

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.

@Bromeon
Copy link
Contributor Author

Bromeon commented Oct 12, 2022

@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).

@MachineMakesNoise
Copy link

MachineMakesNoise commented Nov 12, 2022

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

@setzer22
Copy link

setzer22 commented Nov 12, 2022

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 🙂.
  • 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.

@antonWetzel
Copy link
Contributor

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.

@dkaste
Copy link
Contributor

dkaste commented Nov 17, 2022

@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.

@setzer22
Copy link

setzer22 commented Nov 17, 2022

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.

@dvergeylen
Copy link

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

@jordo
Copy link
Contributor

jordo commented Nov 22, 2022

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.

@darthdeus
Copy link

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.

@MachineMakesNoise
Copy link

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 😅

@akien-mga
Copy link
Member

See also godotengine/godot-cpp#955 for @BastiaanOlij's take on (part of?) this problem.

@jordo
Copy link
Contributor

jordo commented Dec 12, 2022

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?

@BastiaanOlij
Copy link
Contributor

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.

@setzer22
Copy link

setzer22 commented Dec 13, 2022

@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 😅 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...)?

@jordo
Copy link
Contributor

jordo commented Dec 14, 2022

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?

@setzer22
Copy link

setzer22 commented Dec 14, 2022

@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.

@BastiaanOlij
Copy link
Contributor

@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.

@setzer22
Copy link

@BastiaanOlij Thanks 🙂 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.

@MachineMakesNoise
Copy link

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.

@BastiaanOlij
Copy link
Contributor

@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.

@TheMasterofBlubb
Copy link

Hey im fairly new to Godot and started with my first Godot 4 project just recently, but im pretty firm with C++ etc.

For reloading a Library, wouldnt it be an easy thing to distinguish between reloadable and non-reloadable libraries, by simply having 2 mandatory functions for all realodable libraries, which are a serialize and deserialize function.

Sure you would need to make them compatible when making iterations, which creates some extra work for the dev, but overall that would make reloading any custom extension easy and achievable.

When ever a certain extension is realoded:

  1. grab all objects that originated from there
  2. serialize them
  3. save the serialized data next to the object itself
  4. load new extension
  5. deserialize through new extension library
  6. grab new pointers for the objects and put them inplace of the original ones

This would bring some caveats with it, like what to do if the dev breaks his serializer? In that case the editor should shutdown gracefully and tell the dev he did an oops, or reload the old extension (because there the deserializer should 100% work).

A requirement would be to basically wrap all custom nodes into a container that has the extension origin and a data block for serializing.

Just throwing in some thoughs.

@SeleDreams
Copy link

I just encountered this issue. that's a big issue because it makes godot even LESS viable for c++ games when godot 4 was supposed to be a game changer for perf intensive 3d games

to me it makes no sense

@Dheatly23
Copy link
Contributor

May i suggest interim solution while hot-reloading is not supported?

  • I agree that the .dll should be copied into private location, so the file is not locked.
  • Meanwhile Godot watches it for changes, and if it is it prints a warning like:
    We detected changes to <file>. Unfortunately, currently Godot does not support hot-reloading GDExtension yet. Please reload the project to update.
  • The user then can have time to prepare stuff, like saving or even updating other DLLs.
  • When user hit reload project, it releases the lock, copies the changes, then reloads the project.

@rayanmargham
Copy link

What's going on currently with this issue?

@naeu
Copy link
Contributor

naeu commented Apr 20, 2023

@rayanmargham It looks like they're discussing use-cases, possible approaches, and solutions.

jpedrick added a commit to jpedrick/godot that referenced this issue May 4, 2023
jpedrick added a commit to jpedrick/godot that referenced this issue May 5, 2023
…editor restart and experimental work on dynamic reload
@jpedrick
Copy link

jpedrick commented May 5, 2023

@TheMasterofBlubb I was able to get godot to the point that it detects the gdextension library change after a recompile. It then automatically recommends a restart. You can check out my fork linked above.

@MachineMakesNoise sounds like you have some experience with this. I made an attempt and it doesn't crash on macos. The library loads, but at some point I believe the godot::Object, _extension and _extension_instance pointers need to be updated. I accomplished this by only swapping out the library pointer of the GDExtension object.

For godot-cpp I think this would involve having the classes/wrapped.hpp GDCLASS macro keep track of it's GDExtension and use some kind of signaling mechanism tell all the objects to refresh their internal pointers. There'll need to be some mechanism for saving/restoring properties.

Compile with my partially working code using CXXFLAGS="-DSUPPORT_DYNAMIC_GDEXTENSION_RELOAD"

Also see this regarding compiling dynamic libraries(at least on macos) godotengine/godot-docs#7284

@white-rabbit-1-sketch
Copy link

I join the problem. It is impossible to work without auto-reload of compiled code. Also GODOT is great engine and i really love it, but this is serious issue

@Bindernews
Copy link

This is certainly a big issue for me as well, but I've noticed something after reading all of this. One of the major points of issue for the engine developers seems to be that GDExtensions can register new types that would break the engine if they were removed at runtime. But as @Faless and @setzer22 talked about, that's not a requirement.

My suggestion is to have a set of registration operations that are considered "reload-safe". This would include registering new scripts and new resource types, since both are serializable. Any classes registered that are more complex would mark the extension as "reload-unsafe". The extension could include in its metadata if it's trying to be "reload-safe" and an error could pop up if that contract is violated.

Implementation-wise any non "reload-safe" ClassDB registrations set a boolean flag for the GDExtension that called them, and when loading is done the editor checks that flag against the extension's expected "safe-ness".

Combine that with the above recommendations of copying the dll/so/dylib into the .godot folder with a unique hash and, it seems to me, that you have the best of both worlds.

TL;DR: GDExtensions that ONLY register script classes and fully-serializable resources should be considered "hot-reload safe". Anything else should require an editor restart.

@neikeq
Copy link
Contributor

neikeq commented May 25, 2023

I don't think there's anything that can be considered safe to reload, not anything useful at least. One of the main problems is inherent to the fact that native extension classes are treated the same as engine classes by ClassDB. And any one can grab a pointer to a method of one of those classes and cache it. That's what language implementations do, otherwise calls would be too slow.

Fixing this could require changing native extension classes to be treated differently, which kind of defeats the point. Another option could be to make the methods (and other stuff) refcounted, to avoid references to freed memory after a reload. Making a call on one of these references should either work or result in a non-fatal error message if a method with that signature is no longer present after reloading. Additionally, ClassDB should notify anyone interested when something is reloaded, to allow them to adjust (their bindings, for example).

Even then, I don't know if that would work in practice, and it's only one of the problems.

This is the main reason I only treat native extensions as library dependencies. For code I need to iterate on a lot, there's only the script system for now. Unfortunately many language bindings (like cpp and rust I think?) that were using the scripting system in the GDNative days, are now using native extensions classes only.

@SeleDreams
Copy link

SeleDreams commented May 25, 2023 via email

@Bindernews
Copy link

Bindernews commented May 25, 2023

I don't think there's anything that can be considered safe to reload, not anything useful at least. One of the main problems is inherent to the fact that native extension classes are treated the same as engine classes by ClassDB. And any one can grab a pointer to a method of one of those classes and cache it. That's what language implementations do, otherwise calls would be too slow.

That's why I'm making a differentiation between extension classes and extension scripts. Scripts are a subclass of Resource, and all Resources MUST be serializable according to the documentation. If you look at the Script Class you'll notice it has three sub-types: CSharpScript, GDScript, and ScriptExtension. If Godot adds a fourth sub-type, for example called NativeScript, and provides similar APIs as GDNative had to register create NativeScript classes, then you have a GDExtension that doesn't register any new classes, only new resources. Of course Godot is able to reload resources while the editor/engine is running.

EDIT: That all being said I suspect it would be possible to implement a GDExtension to add this functionality, but the extension would have to provide its own API which feels like the fast-path to ecosystem fracturing. It would likely be good as a proof-of-concept. Unfortunately I don't currently have enough time for my own projects, much less this one.

@Klaim
Copy link

Klaim commented Jun 13, 2023

Hi, I'm adding myself to the discussion as this is an important issue for me too.

I'm currently evaluating (through prototypes) using Godot with GDExtension so that I have most logic and "model" of my game in C++ while using Godot to implement the "view" (including input handling) of the game.
This seems to work as expected so far so I'm almost quite happy - except this hot-reloading issue.

In my case, I need both kinds of reloading, script and class, I think, but it's not completely clear to me yet how different these are. Also I think it's ok if I have to explicitly reload for class changes and hot-reload scripts. Would that means we would ideally I have to separate the scripts code into a separate extension?

I also intend to add a Godot Editor GDExtension because I use an unusual build-system (build2) and I would also like to have Godot call it when I focus on the Godot Editor so that if I ever change some C++ code it's immediately reflected in the editor once hot-reloading works, the same way I can edit GDSCripts outside of Godot Editor and focusing on the editor windows will take the changes into account. Not sure if a GDExtension is actually necessary for that, I was expecting a field in the project/editor for a command to run that must succeed when the editor is focused on, but didnt find it (if it exists already I'm interested).

I have some experience in implementing hot-reloading on Windows (long ago, in C++) but I am not familiar with the codebase of Godot and I guess my time is too limited for diving in this big thing, but if I can help in some limited ways (testing? debugging?) feel free to ask 👍🏽

@dsnopek
Copy link
Contributor

dsnopek commented Sep 14, 2023

FYI, PR #80284 (which attempts to implement hot reloading for GDExtension) is ready for testing.

It doesn't do everything described in this issue, and there may still be a use case for something like cppscript even if/when GDExtension supports hot reloading, but it's a start! If we can get the PR tested, reviewed and merged in the next ~2-3 weeks, then it'll be included in Godot 4.2. (If not, that just means it'll have to wait until Godot 4.3. :-))

@mageonline
Copy link

mageonline commented Sep 21, 2023

Back to the good old times when it took quite an effort (and 10+ floppy swaps) to compile my Amiga games :)

I would love to see this hot swap feature.

Perhaps, have a toggle button which when toggled off would unload all libraries (release locks), let the user recompile, then user could toggle it back on.

Editor could automagically re-lock when it detects file changes after the library was built.

Of course, it would be best to find solution that doesn't require any button push :)

You guys are awesome, ty for Godot!

@akien-mga akien-mga modified the milestones: 4.x, 4.2 Sep 26, 2023
@Bromeon
Copy link
Contributor Author

Bromeon commented Sep 26, 2023

Very happy to see this addressed! Great work everyone 🚀

@mageonline
Copy link

works nicely! ty!

@LaneSun
Copy link

LaneSun commented Sep 29, 2023

I can't believe this has been solved, I was expecting this to take a couple of years, it's really a pleasant surprise

@callumbirks
Copy link

I just started learning godot, and pairing it with godot-rust for similar reasons that others have mentioned - to get a more pleasant coding experience.
This issue really started to bug me, landed here and I am so pleased to learn it’s already fixed in 4.2, and implemented in godot-rust also.
All praise open source

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

Successfully merging a pull request may close this issue.