|
@@ -42,7 +42,10 @@
|
|
|
#include "project_settings.h"
|
|
|
|
|
|
#include "../csharp_script.h"
|
|
|
+#include "../godotsharp_dirs.h"
|
|
|
#include "../utils/path_utils.h"
|
|
|
+#include "gd_mono_class.h"
|
|
|
+#include "gd_mono_marshal.h"
|
|
|
#include "gd_mono_utils.h"
|
|
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
@@ -208,7 +211,46 @@ void GDMono::initialize() {
|
|
|
_register_internal_calls();
|
|
|
|
|
|
// The following assemblies are not required at initialization
|
|
|
- _load_all_script_assemblies();
|
|
|
+#ifndef MONO_GLUE_DISABLED
|
|
|
+ if (_load_api_assemblies()) {
|
|
|
+ if (!core_api_assembly_out_of_sync && !editor_api_assembly_out_of_sync && GDMonoUtils::mono_cache.godot_api_cache_updated) {
|
|
|
+ // Everything is fine with the api assemblies, load the project assembly
|
|
|
+ _load_project_assembly();
|
|
|
+ } else {
|
|
|
+#ifdef TOOLS_ENABLED
|
|
|
+ // The assembly was successfuly loaded, but the full api could not be cached.
|
|
|
+ // This is most likely an outdated assembly loaded because of an invalid version in the metadata,
|
|
|
+ // so we invalidate the version in the metadata and unload the script domain.
|
|
|
+
|
|
|
+ if (core_api_assembly_out_of_sync) {
|
|
|
+ ERR_PRINT("The loaded Core API assembly is out of sync");
|
|
|
+ metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
|
|
|
+ } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
|
|
|
+ ERR_PRINT("The loaded Core API assembly is in sync, but the cache update failed");
|
|
|
+ metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (editor_api_assembly_out_of_sync) {
|
|
|
+ ERR_PRINT("The loaded Editor API assembly is out of sync");
|
|
|
+ metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ OS::get_singleton()->print("Mono: Proceeding to unload scripts domain because of invalid API assemblies\n");
|
|
|
+
|
|
|
+ Error err = _unload_scripts_domain();
|
|
|
+ if (err != OK) {
|
|
|
+ WARN_PRINT("Mono: Failed to unload scripts domain");
|
|
|
+ }
|
|
|
+#else
|
|
|
+ ERR_PRINT("The loaded API assembly is invalid");
|
|
|
+ CRASH_NOW();
|
|
|
+#endif
|
|
|
+ }
|
|
|
+ }
|
|
|
+#else
|
|
|
+ if (OS::get_singleton()->is_stdout_verbose())
|
|
|
+ OS::get_singleton()->print("Mono: Glue disabled, ignoring script assemblies\n");
|
|
|
+#endif
|
|
|
|
|
|
mono_install_unhandled_exception_hook(gdmono_unhandled_exception_hook, NULL);
|
|
|
|
|
@@ -219,7 +261,11 @@ void GDMono::initialize() {
|
|
|
namespace GodotSharpBindings {
|
|
|
|
|
|
uint64_t get_core_api_hash();
|
|
|
+#ifdef TOOLS_ENABLED
|
|
|
uint64_t get_editor_api_hash();
|
|
|
+#endif // TOOLS_ENABLED
|
|
|
+uint32_t get_bindings_version();
|
|
|
+uint32_t get_cs_glue_version();
|
|
|
|
|
|
void register_generated_icalls();
|
|
|
} // namespace GodotSharpBindings
|
|
@@ -313,6 +359,36 @@ bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMo
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, APIAssembly::Type p_api_type) {
|
|
|
+ APIAssembly::Version api_assembly_version;
|
|
|
+
|
|
|
+ const char *nativecalls_name = p_api_type == APIAssembly::API_CORE ?
|
|
|
+ BINDINGS_CLASS_NATIVECALLS :
|
|
|
+ BINDINGS_CLASS_NATIVECALLS_EDITOR;
|
|
|
+
|
|
|
+ GDMonoClass *nativecalls_klass = p_api_assembly->get_class(BINDINGS_NAMESPACE, nativecalls_name);
|
|
|
+
|
|
|
+ if (nativecalls_klass) {
|
|
|
+ GDMonoField *api_hash_field = nativecalls_klass->get_field("godot_api_hash");
|
|
|
+ if (api_hash_field)
|
|
|
+ api_assembly_version.godot_api_hash = GDMonoMarshal::unbox<uint64_t>(api_hash_field->get_value(NULL));
|
|
|
+
|
|
|
+ GDMonoField *binds_ver_field = nativecalls_klass->get_field("bindings_version");
|
|
|
+ if (binds_ver_field)
|
|
|
+ api_assembly_version.bindings_version = GDMonoMarshal::unbox<uint32_t>(binds_ver_field->get_value(NULL));
|
|
|
+
|
|
|
+ GDMonoField *cs_glue_ver_field = nativecalls_klass->get_field("cs_glue_version");
|
|
|
+ if (cs_glue_ver_field)
|
|
|
+ api_assembly_version.cs_glue_version = GDMonoMarshal::unbox<uint32_t>(cs_glue_ver_field->get_value(NULL));
|
|
|
+ }
|
|
|
+
|
|
|
+ return api_assembly_version;
|
|
|
+}
|
|
|
+
|
|
|
+String APIAssembly::to_string(APIAssembly::Type p_type) {
|
|
|
+ return p_type == APIAssembly::API_CORE ? "API_CORE" : "API_EDITOR";
|
|
|
+}
|
|
|
+
|
|
|
bool GDMono::_load_corlib_assembly() {
|
|
|
|
|
|
if (corlib_assembly)
|
|
@@ -328,13 +404,25 @@ bool GDMono::_load_corlib_assembly() {
|
|
|
|
|
|
bool GDMono::_load_core_api_assembly() {
|
|
|
|
|
|
- if (api_assembly)
|
|
|
+ if (core_api_assembly)
|
|
|
return true;
|
|
|
|
|
|
- bool success = load_assembly(API_ASSEMBLY_NAME, &api_assembly);
|
|
|
+#ifdef TOOLS_ENABLED
|
|
|
+ if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE))
|
|
|
+ return false;
|
|
|
+#endif
|
|
|
+
|
|
|
+ bool success = load_assembly(API_ASSEMBLY_NAME, &core_api_assembly);
|
|
|
|
|
|
- if (success)
|
|
|
+ if (success) {
|
|
|
+#ifndef MONO_GLUE_DISABLED
|
|
|
+ APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(core_api_assembly, APIAssembly::API_CORE);
|
|
|
+ core_api_assembly_out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash ||
|
|
|
+ GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
|
|
|
+ GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
|
|
|
+#endif
|
|
|
GDMonoUtils::update_godot_api_cache();
|
|
|
+ }
|
|
|
|
|
|
return success;
|
|
|
}
|
|
@@ -345,7 +433,23 @@ bool GDMono::_load_editor_api_assembly() {
|
|
|
if (editor_api_assembly)
|
|
|
return true;
|
|
|
|
|
|
- return load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly);
|
|
|
+#ifdef TOOLS_ENABLED
|
|
|
+ if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR))
|
|
|
+ return false;
|
|
|
+#endif
|
|
|
+
|
|
|
+ bool success = load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly);
|
|
|
+
|
|
|
+ if (success) {
|
|
|
+#ifndef MONO_GLUE_DISABLED
|
|
|
+ APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(editor_api_assembly, APIAssembly::API_EDITOR);
|
|
|
+ editor_api_assembly_out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash ||
|
|
|
+ GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
|
|
|
+ GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
|
|
|
+#endif
|
|
|
+ }
|
|
|
+
|
|
|
+ return success;
|
|
|
}
|
|
|
#endif
|
|
|
|
|
@@ -373,15 +477,18 @@ bool GDMono::_load_project_assembly() {
|
|
|
|
|
|
bool success = load_assembly(name, &project_assembly);
|
|
|
|
|
|
- if (success)
|
|
|
+ if (success) {
|
|
|
mono_assembly_set_main(project_assembly->get_assembly());
|
|
|
+ } else {
|
|
|
+ if (OS::get_singleton()->is_stdout_verbose())
|
|
|
+ OS::get_singleton()->printerr("Mono: Failed to load project assembly\n");
|
|
|
+ }
|
|
|
|
|
|
return success;
|
|
|
}
|
|
|
|
|
|
-bool GDMono::_load_all_script_assemblies() {
|
|
|
+bool GDMono::_load_api_assemblies() {
|
|
|
|
|
|
-#ifndef MONO_GLUE_DISABLED
|
|
|
if (!_load_core_api_assembly()) {
|
|
|
if (OS::get_singleton()->is_stdout_verbose())
|
|
|
OS::get_singleton()->printerr("Mono: Failed to load Core API assembly\n");
|
|
@@ -396,20 +503,72 @@ bool GDMono::_load_all_script_assemblies() {
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
- if (!_load_project_assembly()) {
|
|
|
- if (OS::get_singleton()->is_stdout_verbose())
|
|
|
- OS::get_singleton()->printerr("Mono: Failed to load project assembly\n");
|
|
|
- return false;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef TOOLS_ENABLED
|
|
|
+String GDMono::_get_api_assembly_metadata_path() {
|
|
|
+
|
|
|
+ return GodotSharpDirs::get_res_metadata_dir().plus_file("api_assemblies.cfg");
|
|
|
+}
|
|
|
+
|
|
|
+void GDMono::metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated) {
|
|
|
+
|
|
|
+ String section = APIAssembly::to_string(p_api_type);
|
|
|
+ String path = _get_api_assembly_metadata_path();
|
|
|
+
|
|
|
+ Ref<ConfigFile> metadata;
|
|
|
+ metadata.instance();
|
|
|
+ metadata->load(path);
|
|
|
+
|
|
|
+ metadata->set_value(section, "invalidated", p_invalidated);
|
|
|
+
|
|
|
+ String assembly_path = GodotSharpDirs::get_res_assemblies_dir()
|
|
|
+ .plus_file(p_api_type == APIAssembly::API_CORE ?
|
|
|
+ API_ASSEMBLY_NAME ".dll" :
|
|
|
+ EDITOR_API_ASSEMBLY_NAME ".dll");
|
|
|
+
|
|
|
+ ERR_FAIL_COND(!FileAccess::exists(assembly_path));
|
|
|
+
|
|
|
+ uint64_t modified_time = FileAccess::get_modified_time(assembly_path);
|
|
|
+
|
|
|
+ metadata->set_value(section, "invalidated_asm_modified_time", String::num_uint64(modified_time));
|
|
|
+
|
|
|
+ String dir = path.get_base_dir();
|
|
|
+ if (!DirAccess::exists(dir)) {
|
|
|
+ DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
|
|
+ ERR_FAIL_COND(!da);
|
|
|
+ Error err = da->make_dir_recursive(ProjectSettings::get_singleton()->globalize_path(dir));
|
|
|
+ ERR_FAIL_COND(err != OK);
|
|
|
}
|
|
|
|
|
|
- return true;
|
|
|
-#else
|
|
|
- if (OS::get_singleton()->is_stdout_verbose())
|
|
|
- OS::get_singleton()->print("Mono: Glue disbled, ignoring script assemblies\n");
|
|
|
+ Error save_err = metadata->save(path);
|
|
|
+ ERR_FAIL_COND(save_err != OK);
|
|
|
+}
|
|
|
|
|
|
- return true;
|
|
|
-#endif
|
|
|
+bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type) {
|
|
|
+
|
|
|
+ String section = APIAssembly::to_string(p_api_type);
|
|
|
+
|
|
|
+ Ref<ConfigFile> metadata;
|
|
|
+ metadata.instance();
|
|
|
+ metadata->load(_get_api_assembly_metadata_path());
|
|
|
+
|
|
|
+ String assembly_path = GodotSharpDirs::get_res_assemblies_dir()
|
|
|
+ .plus_file(p_api_type == APIAssembly::API_CORE ?
|
|
|
+ API_ASSEMBLY_NAME ".dll" :
|
|
|
+ EDITOR_API_ASSEMBLY_NAME ".dll");
|
|
|
+
|
|
|
+ if (!FileAccess::exists(assembly_path))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ uint64_t modified_time = FileAccess::get_modified_time(assembly_path);
|
|
|
+
|
|
|
+ uint64_t stored_modified_time = metadata->get_value(section, "invalidated_asm_modified_time", 0);
|
|
|
+
|
|
|
+ return metadata->get_value(section, "invalidated", false) && modified_time <= stored_modified_time;
|
|
|
}
|
|
|
+#endif
|
|
|
|
|
|
Error GDMono::_load_scripts_domain() {
|
|
|
|
|
@@ -452,12 +611,15 @@ Error GDMono::_unload_scripts_domain() {
|
|
|
|
|
|
_domain_assemblies_cleanup(mono_domain_get_id(scripts_domain));
|
|
|
|
|
|
- api_assembly = NULL;
|
|
|
+ core_api_assembly = NULL;
|
|
|
project_assembly = NULL;
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
editor_api_assembly = NULL;
|
|
|
#endif
|
|
|
|
|
|
+ core_api_assembly_out_of_sync = false;
|
|
|
+ editor_api_assembly_out_of_sync = false;
|
|
|
+
|
|
|
MonoDomain *domain = scripts_domain;
|
|
|
scripts_domain = NULL;
|
|
|
|
|
@@ -512,12 +674,45 @@ Error GDMono::reload_scripts_domain() {
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
- if (!_load_all_script_assemblies()) {
|
|
|
- if (OS::get_singleton()->is_stdout_verbose())
|
|
|
- OS::get_singleton()->printerr("Mono: Failed to load script assemblies\n");
|
|
|
+#ifndef MONO_GLUE_DISABLED
|
|
|
+ if (!_load_api_assemblies()) {
|
|
|
return ERR_CANT_OPEN;
|
|
|
}
|
|
|
|
|
|
+ if (!core_api_assembly_out_of_sync && !editor_api_assembly_out_of_sync && GDMonoUtils::mono_cache.godot_api_cache_updated) {
|
|
|
+ // Everything is fine with the api assemblies, load the project assembly
|
|
|
+ _load_project_assembly();
|
|
|
+ } else {
|
|
|
+ // The assembly was successfuly loaded, but the full api could not be cached.
|
|
|
+ // This is most likely an outdated assembly loaded because of an invalid version in the metadata,
|
|
|
+ // so we invalidate the version in the metadata and unload the script domain.
|
|
|
+
|
|
|
+ if (core_api_assembly_out_of_sync) {
|
|
|
+ metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
|
|
|
+ } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
|
|
|
+ ERR_PRINT("Core API assembly is in sync, but the cache update failed");
|
|
|
+ metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (editor_api_assembly_out_of_sync) {
|
|
|
+ metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ Error err = _unload_scripts_domain();
|
|
|
+ if (err != OK) {
|
|
|
+ WARN_PRINT("Mono: Failed to unload scripts domain");
|
|
|
+ }
|
|
|
+
|
|
|
+ return ERR_CANT_RESOLVE;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!_load_project_assembly())
|
|
|
+ return ERR_CANT_OPEN;
|
|
|
+#else
|
|
|
+ if (OS::get_singleton()->is_stdout_verbose())
|
|
|
+ OS::get_singleton()->print("Mono: Glue disabled, ignoring script assemblies\n");
|
|
|
+#endif
|
|
|
+
|
|
|
return OK;
|
|
|
}
|
|
|
#endif
|
|
@@ -604,8 +799,11 @@ GDMono::GDMono() {
|
|
|
tools_domain = NULL;
|
|
|
#endif
|
|
|
|
|
|
+ core_api_assembly_out_of_sync = false;
|
|
|
+ editor_api_assembly_out_of_sync = false;
|
|
|
+
|
|
|
corlib_assembly = NULL;
|
|
|
- api_assembly = NULL;
|
|
|
+ core_api_assembly = NULL;
|
|
|
project_assembly = NULL;
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
editor_api_assembly = NULL;
|