GDExtension: Copy DLL to a temp file before opening

This is done only in the editor and only on Windows, to avoid a file
lock that prevents the original library being updated (e.g. by a
compiler).

When the game runs it will load the original DLL and pick up any
changes, only the editor will stay with the copy (until it is restarted
and create a new copy).

The copy is done in place by prepending a `~` to the original file name,
so dependencies that are loaded with a relative file path still work.
When the library is unloaded the copy file is deleted. The copy is also
marked as hidden to not show up in explorer.
This commit is contained in:
George Marques 2023-08-02 17:14:03 -03:00
parent 4714e95896
commit cff69b0612
No known key found for this signature in database
GPG key ID: 046BD46A3201E43D
3 changed files with 54 additions and 1 deletions

View file

@ -485,6 +485,13 @@ void GDExtension::close_library() {
ERR_FAIL_COND(library == nullptr);
OS::get_singleton()->close_dynamic_library(library);
#if defined(TOOLS_ENABLED) && defined(WINDOWS_ENABLED)
// Delete temporary copy of library if it exists.
if (!temp_lib_path.is_empty() && Engine::get_singleton()->is_editor_hint()) {
DirAccess::remove_absolute(temp_lib_path);
}
#endif
library = nullptr;
}
@ -640,6 +647,40 @@ Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String
Ref<GDExtension> lib;
lib.instantiate();
String abs_path = ProjectSettings::get_singleton()->globalize_path(library_path);
#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
// If running on the editor on Windows, we copy the library and open the copy.
// This is so the original file isn't locked and can be updated by a compiler.
if (Engine::get_singleton()->is_editor_hint()) {
if (!FileAccess::exists(abs_path)) {
if (r_error) {
*r_error = ERR_FILE_NOT_FOUND;
}
ERR_PRINT("GDExtension library not found: " + library_path);
return Ref<Resource>();
}
// Copy the file to the same directory as the original with a prefix in the name.
// This is so relative path to dependencies are satisfied.
String copy_path = abs_path.get_base_dir().path_join("~" + abs_path.get_file());
Error copy_err = DirAccess::copy_absolute(abs_path, copy_path);
if (copy_err) {
if (r_error) {
*r_error = ERR_CANT_CREATE;
}
ERR_PRINT("Error copying GDExtension library: " + library_path);
return Ref<Resource>();
}
FileAccess::set_hidden_attribute(copy_path, true);
// Save the copied path so it can be deleted later.
lib->set_temp_library_path(copy_path);
// Use the copy to open the library.
abs_path = copy_path;
}
#endif
err = lib->open_library(abs_path, entry_symbol);
if (r_error) {

View file

@ -43,6 +43,9 @@ class GDExtension : public Resource {
void *library = nullptr; // pointer if valid,
String library_path;
#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
String temp_lib_path;
#endif
struct Extension {
ObjectGDExtension gdextension;
@ -76,6 +79,10 @@ public:
Error open_library(const String &p_path, const String &p_entry_symbol);
void close_library();
#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
void set_temp_library_path(const String &p_path) { temp_lib_path = p_path; }
#endif
enum InitializationLevel {
INITIALIZATION_LEVEL_CORE = GDEXTENSION_INITIALIZATION_CORE,
INITIALIZATION_LEVEL_SERVERS = GDEXTENSION_INITIALIZATION_SERVERS,

View file

@ -1540,6 +1540,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
}
#ifdef TOOLS_ENABLED
if (editor) {
Engine::get_singleton()->set_editor_hint(true);
}
#endif
// Initialize user data dir.
OS::get_singleton()->ensure_user_data_dir();
@ -1567,7 +1573,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
#ifdef TOOLS_ENABLED
if (editor) {
packed_data->set_disabled(true);
Engine::get_singleton()->set_editor_hint(true);
main_args.push_back("--editor");
if (!init_windowed) {
init_maximized = true;