-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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
C#: Automatically download and lazily load C# dependencies #74089
base: master
Are you sure you want to change the base?
Conversation
7358188
to
2443d0a
Compare
2443d0a
to
d0900b9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall it seems pretty good already even though it's a draft. Here are some observations after testing the PR.
I have been unable to make the downloader work, I always get RESULT_CANT_RESOLVE
. I have been using https://tmpfiles.org to upload the GodotSharp.zip, I haven't tested with a different host.
When trying to create a C# script for the first time (without the C# dependencies):
- It launches the C# downloader but also immediately prints errors in the console since the editor plugin is not initialized. As an user I feel like something went wrong, but as I understand it this is by design since we don't want to wait for the C# dependencies to download and freeze the editor.
- After the downloader has failed to download the C# dependencies it doesn't try again when creating new C# scripts until restarting the editor, this can feel a bit annoying.
I feel like some indicator that shows if .NET features are enabled could be nice to have. It could also be used to open a C# dependencies manager to allow retrying manually. This can be done in a follow-up PR but just wanted to throw the idea out there.
When trying to create a C# script for the first time (with the C# dependencies):
- It prints errors about creating the csproj because of a condition was backwards. (See C#: Automatically download and lazily load C# dependencies #74089 (comment)).
- It also seems like it calls
HotReloadAssemblyWatcher.RestartTimer
before_Ready
because_watchTimer
is null and throws aSystem.NullReferenceException
. - When launching the game
_try_load_godotsharp_editor
prints an error:Cannot initialize C# editor plugin
. This is caused byensure_dotnet_initialized
. (See C#: Automatically download and lazily load C# dependencies #74089 (comment)).
CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr); | ||
ERR_FAIL_COND_V_MSG(GDMono::get_singleton()->is_runtime_initialized(), false, | ||
"Cannot create C# project because the .NET runtime is not loaded."); | ||
ERR_FAIL_COND_V_MSG(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr, false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ERR_FAIL_COND_V_MSG(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr, false, | |
ERR_FAIL_NULL_V_MSG(CSharpLanguage::get_singleton()->get_godotsharp_editor(), false, |
@@ -1078,10 +1191,15 @@ void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const | |||
|
|||
#ifdef TOOLS_ENABLED | |||
Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { | |||
ERR_FAIL_COND_V(get_godotsharp_editor() == nullptr, ERR_UNAVAILABLE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ERR_FAIL_COND_V(get_godotsharp_editor() == nullptr, ERR_UNAVAILABLE); | |
ERR_FAIL_NULL_V(get_godotsharp_editor(), ERR_UNAVAILABLE); |
if (!FileAccess::exists(godot_tools_assembly_path)) { | ||
print_line("The C# editor plugin is missing."); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this use ERR_FAIL_COND_MSG
?
if (!FileAccess::exists(godot_tools_assembly_path)) { | |
print_line("The C# editor plugin is missing."); | |
return; | |
} | |
ERR_FAIL_COND_MSG(!FileAccess::exists(godot_tools_assembly_path), "The C# editor plugin is missing."); |
Object *editor_plugin_obj = GDMono::get_singleton()->get_plugin_callbacks().LoadToolsAssemblyCallback( | ||
godot_tools_assembly_path.utf16(), | ||
interop_funcs, interop_funcs_size); | ||
ERR_FAIL_COND_MSG(editor_plugin_obj == nullptr, "Cannot initialize C# editor plugin."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ERR_FAIL_COND_MSG(editor_plugin_obj == nullptr, "Cannot initialize C# editor plugin."); | |
ERR_FAIL_NULL_MSG(editor_plugin_obj, "Cannot initialize C# editor plugin."); |
r_missing_runtime_config = true; | ||
ERR_FAIL_V_MSG(nullptr, ".NET: Cannot initialize because runtimeconfig is missing"); | ||
} | ||
|
||
load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = | ||
initialize_hostfxr_for_config(get_data(config_path)); | ||
|
||
if (load_assembly_and_get_function_pointer == nullptr) { | ||
// Show a message box to the user to make the problem explicit (and explain a potential crash). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment can be removed since the alert
was removed.
EditorNode::get_singleton()->add_io_error(TTR("Cannot remove temporary file:") + "\n" + path + "\n"); | ||
} | ||
} else { | ||
EditorNode::get_singleton()->add_io_error(vformat(TTR("Templates installation failed.\nThe problematic templates archives can be found at '%s'."), path)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EditorNode::get_singleton()->add_io_error(vformat(TTR("Templates installation failed.\nThe problematic templates archives can be found at '%s'."), path)); | |
EditorNode::get_singleton()->add_io_error(vformat(TTR("C# dependencies installation failed.\nThe problematic C# dependencies archives can be found at '%s'."), path)); |
} | ||
int ret = unzGoToFirstFile(pkg); | ||
|
||
// Count them and find version. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Count them and find version. | |
// Count them. |
if (is_dir) { | ||
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); | ||
ERR_CONTINUE(da.is_null()); | ||
|
||
if (!DirAccess::exists(to_write)) { | ||
Error mkdir_err = da->make_dir_recursive(to_write); | ||
ERR_CONTINUE(mkdir_err != OK); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section does not exist in export_template_manager
. Why do we need it? It seems to me like the subdirectories will be created by the code above that checks the base_dir
, no?
@@ -67,7 +67,11 @@ | |||
|
|||
#ifdef TOOLS_ENABLED | |||
static bool _create_project_solution_if_needed() { | |||
CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr); | |||
ERR_FAIL_COND_V_MSG(GDMono::get_singleton()->is_runtime_initialized(), false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition is backwards, it should be checking if the runtime is not initialized.
ERR_FAIL_COND_V_MSG(GDMono::get_singleton()->is_runtime_initialized(), false, | |
ERR_FAIL_COND_V_MSG(!GDMono::get_singleton()->is_runtime_initialized(), false, |
if (get_godotsharp_editor() == nullptr) { | ||
_try_load_godotsharp_editor(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably check if Engine::get_singleton()->is_editor_hint()
to prevent trying to load GodotSharpEditor
when the assemblies haven't been loaded.
if (get_godotsharp_editor() == nullptr) { | |
_try_load_godotsharp_editor(); | |
} | |
if (Engine::get_singleton()->is_editor_hint() && get_godotsharp_editor() == nullptr) { | |
_try_load_godotsharp_editor(); | |
} |
String project_assembly_name = (String)ProjectSettings::get_singleton()->get_setting("dotnet/project/assembly_name"); | ||
if (project_assembly_name.is_empty()) { | ||
String appname = GLOBAL_GET("application/config/name"); | ||
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); | ||
if (appname_safe.is_empty()) { | ||
appname_safe = "UnnamedProject"; | ||
} | ||
project_assembly_name = appname_safe; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be able to use path::get_csharp_project_name
here.
String project_assembly_name = (String)ProjectSettings::get_singleton()->get_setting("dotnet/project/assembly_name"); | |
if (project_assembly_name.is_empty()) { | |
String appname = GLOBAL_GET("application/config/name"); | |
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); | |
if (appname_safe.is_empty()) { | |
appname_safe = "UnnamedProject"; | |
} | |
project_assembly_name = appname_safe; | |
} | |
String project_assembly_name = path::get_csharp_project_name(); |
This is the first step towards a unified editor. If the C# dependencies are not available, the editor will continue to work with C# features disabled, and attempts to download them.
The downloader is a carbon copy of the export templates downloader, except we use EditorProgress to display (and I'm planning to replace it with EditorProgressBG or an alternative that also displays the status).
Production edit: closes godotengine/godot-roadmap#48