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

C#: Automatically download and lazily load C# dependencies #74089

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

neikeq
Copy link
Contributor

@neikeq neikeq commented Feb 27, 2023

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

@neikeq neikeq added this to the 4.1 milestone Feb 27, 2023
@neikeq neikeq requested a review from a team as a code owner February 27, 2023 22:57
@neikeq neikeq marked this pull request as draft February 27, 2023 22:57
@neikeq neikeq force-pushed the wip-unified-editor branch 2 times, most recently from 7358188 to 2443d0a Compare February 28, 2023 00:33
@neikeq neikeq force-pushed the wip-unified-editor branch from 2443d0a to d0900b9 Compare February 28, 2023 00:37
Copy link
Member

@raulsntos raulsntos left a 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):

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ERR_FAIL_COND_V(get_godotsharp_editor() == nullptr, ERR_UNAVAILABLE);
ERR_FAIL_NULL_V(get_godotsharp_editor(), ERR_UNAVAILABLE);

Comment on lines +121 to +124
if (!FileAccess::exists(godot_tools_assembly_path)) {
print_line("The C# editor plugin is missing.");
return;
}
Copy link
Member

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?

Suggested change
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.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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).
Copy link
Member

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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Count them and find version.
// Count them.

Comment on lines +363 to +370
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);
}
Copy link
Member

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,
Copy link
Member

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.

Suggested change
ERR_FAIL_COND_V_MSG(GDMono::get_singleton()->is_runtime_initialized(), false,
ERR_FAIL_COND_V_MSG(!GDMono::get_singleton()->is_runtime_initialized(), false,

Comment on lines +195 to +197
if (get_godotsharp_editor() == nullptr) {
_try_load_godotsharp_editor();
}
Copy link
Member

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.

Suggested change
if (get_godotsharp_editor() == nullptr) {
_try_load_godotsharp_editor();
}
if (Engine::get_singleton()->is_editor_hint() && get_godotsharp_editor() == nullptr) {
_try_load_godotsharp_editor();
}

@YuriSizov YuriSizov modified the milestones: 4.1, 4.x Jun 14, 2023
Comment on lines +380 to +388
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;
}
Copy link
Member

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.

Suggested change
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();

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

Successfully merging this pull request may close these issues.

3 participants