Mono: Better handling of missing/outdated API assemblies

Remove the old API assembly invalidation system. It's pretty simple since now the editor has a hard dependency on the API assemblies and SCons takes care of prebuilding them.
If we fail to load a project's API assembly because it was either missing or outdated, we just copy the prebuilt assemblies to the project and try again. We also do this when creating the solution and before building, just in case the user removed them from the disk after they were loaded.
This way the API assemblies will be always loaded successfully. If they are not, it's a bug.

Also fixed:

- EditorDef was behaving like GlobalDef in GodotTools.
- NullReferenceException because we can't serialize System.WeakReference yet. Use Godot.WeakRef in the mean time.
This commit is contained in:
Ignacio Etcheverry 2019-07-11 14:01:25 +02:00
parent 4061e132ff
commit e59ac40712
7 changed files with 262 additions and 552 deletions

View file

@ -114,15 +114,20 @@ void CSharpLanguage::init() {
gdmono = memnew(GDMono);
gdmono->initialize();
#ifndef MONO_GLUE_ENABLED
WARN_PRINT("This binary is built with `mono_glue=no` and cannot be used for scripting");
#endif
#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED)
// Generate bindings here, before loading assemblies. `initialize_load_assemblies` aborts
// the applications if the api assemblies or the main tools assembly is missing, but this
// is not a problem for BindingsGenerator as it only needs the tools project editor assembly.
List<String> cmdline_args = OS::get_singleton()->get_cmdline_args();
BindingsGenerator::handle_cmdline_args(cmdline_args);
#endif
#ifndef MONO_GLUE_ENABLED
print_line("Run this binary with `--generate-mono-glue path/to/modules/mono/glue`");
#endif
gdmono->initialize_load_assemblies();
#ifdef TOOLS_ENABLED
EditorNode::add_init_callback(&_editor_init_callback);
@ -710,14 +715,6 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
return false; // No assembly to load
}
#ifdef TOOLS_ENABLED
if (!gdmono->get_core_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE))
return false; // The core API assembly to load is invalidated
if (!gdmono->get_editor_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR))
return false; // The editor API assembly to load is invalidated
#endif
return true;
}

View file

@ -193,134 +193,14 @@ namespace GodotTools
return false;
}
private static bool CopyApiAssembly(string srcDir, string dstDir, string assemblyName, ApiAssemblyType apiType)
{
// Create destination directory if needed
if (!Directory.Exists(dstDir))
{
try
{
Directory.CreateDirectory(dstDir);
}
catch (IOException e)
{
ShowBuildErrorDialog($"Failed to create destination directory for the API assemblies. Exception message: {e.Message}");
return false;
}
}
string assemblyFile = assemblyName + ".dll";
string assemblySrc = Path.Combine(srcDir, assemblyFile);
string assemblyDst = Path.Combine(dstDir, assemblyFile);
if (!File.Exists(assemblyDst) || File.GetLastWriteTime(assemblySrc) > File.GetLastWriteTime(assemblyDst) ||
Internal.MetadataIsApiAssemblyInvalidated(apiType))
{
string xmlFile = $"{assemblyName}.xml";
string pdbFile = $"{assemblyName}.pdb";
try
{
File.Copy(Path.Combine(srcDir, xmlFile), Path.Combine(dstDir, xmlFile));
}
catch (IOException e)
{
Godot.GD.PushWarning(e.ToString());
}
try
{
File.Copy(Path.Combine(srcDir, pdbFile), Path.Combine(dstDir, pdbFile));
}
catch (IOException e)
{
Godot.GD.PushWarning(e.ToString());
}
try
{
File.Copy(assemblySrc, assemblyDst);
}
catch (IOException e)
{
ShowBuildErrorDialog($"Failed to copy {assemblyFile}. Exception message: {e.Message}");
return false;
}
Internal.MetadataSetApiAssemblyInvalidated(apiType, false);
}
return true;
}
public static bool MakeApiAssembly(ApiAssemblyType apiType, string config)
{
string apiName = apiType == ApiAssemblyType.Core ? ApiAssemblyNames.Core : ApiAssemblyNames.Editor;
string editorPrebuiltApiDir = Path.Combine(GodotSharpDirs.DataEditorPrebuiltApiDir, config);
string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, config);
if (File.Exists(Path.Combine(editorPrebuiltApiDir, $"{apiName}.dll")))
{
using (var copyProgress = new EditorProgress("mono_copy_prebuilt_api_assembly", $"Copying prebuilt {apiName} assembly...", 1))
{
copyProgress.Step($"Copying {apiName} assembly", 0);
return CopyApiAssembly(editorPrebuiltApiDir, resAssembliesDir, apiName, apiType);
}
}
const string apiSolutionName = ApiAssemblyNames.SolutionName;
using (var pr = new EditorProgress($"mono_build_release_{apiSolutionName}", $"Building {apiSolutionName} solution...", 3))
{
pr.Step($"Generating {apiSolutionName} solution", 0);
string apiSlnDir = Path.Combine(GodotSharpDirs.MonoSolutionsDir, _ApiFolderName(ApiAssemblyType.Core));
string apiSlnFile = Path.Combine(apiSlnDir, $"{apiSolutionName}.sln");
if (!Directory.Exists(apiSlnDir) || !File.Exists(apiSlnFile))
{
var bindingsGenerator = new BindingsGenerator();
if (!Godot.OS.IsStdoutVerbose())
bindingsGenerator.LogPrintEnabled = false;
Error err = bindingsGenerator.GenerateCsApi(apiSlnDir);
if (err != Error.Ok)
{
ShowBuildErrorDialog($"Failed to generate {apiSolutionName} solution. Error: {err}");
return false;
}
}
pr.Step($"Building {apiSolutionName} solution", 1);
if (!BuildApiSolution(apiSlnDir, config))
return false;
pr.Step($"Copying {apiName} assembly", 2);
// Copy the built assembly to the assemblies directory
string apiAssemblyDir = Path.Combine(apiSlnDir, apiName, "bin", config);
if (!CopyApiAssembly(apiAssemblyDir, resAssembliesDir, apiName, apiType))
return false;
}
return true;
}
public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
string apiConfig = config == "Release" ? "Release" : "Debug";
if (!MakeApiAssembly(ApiAssemblyType.Core, apiConfig))
return false;
if (!MakeApiAssembly(ApiAssemblyType.Editor, apiConfig))
return false;
// Make sure to update the API assemblies if they happen to be missing. Just in
// case the user decided to delete them at some point after they were loaded.
Internal.UpdateApiAssembliesFromPrebuilt();
using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
{

View file

@ -27,7 +27,7 @@ namespace GodotTools
private MonoDevelopInstance monoDevelopInstance;
private MonoDevelopInstance visualStudioForMacInstance;
private WeakReference<GodotSharpExport> exportPluginWeak;
private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization
public MonoBottomPanel MonoBottomPanel { get; private set; }
@ -72,13 +72,9 @@ namespace GodotTools
return false;
}
string apiConfig = "Debug";
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, apiConfig))
return false;
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, apiConfig))
return false;
// Make sure to update the API assemblies if they happen to be missing. Just in
// case the user decided to delete them at some point after they were loaded.
Internal.UpdateApiAssembliesFromPrebuilt();
pr.Step("Done".TTR());
@ -94,70 +90,6 @@ namespace GodotTools
}
}
private static int _makeApiSolutionsAttempts = 100;
private static bool _makeApiSolutionsRecursionGuard = false;
private void _MakeApiSolutionsIfNeeded()
{
// I'm sick entirely of ProgressDialog
if (Internal.IsMessageQueueFlushing() || Engine.GetMainLoop() == null)
{
if (_makeApiSolutionsAttempts == 0) // This better never happen or I swear...
throw new TimeoutException();
if (Engine.GetMainLoop() != null)
{
if (!Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)))
Engine.GetMainLoop().Connect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded));
}
else
{
CallDeferred(nameof(_MakeApiSolutionsIfNeededImpl));
}
_makeApiSolutionsAttempts--;
return;
}
// Recursion guard needed because signals don't play well with ProgressDialog either, but unlike
// the message queue, with signals the collateral damage should be minimal in the worst case.
if (!_makeApiSolutionsRecursionGuard)
{
_makeApiSolutionsRecursionGuard = true;
// Oneshot signals don't play well with ProgressDialog either, so we do it this way instead
if (Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)))
Engine.GetMainLoop().Disconnect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded));
_MakeApiSolutionsIfNeededImpl();
_makeApiSolutionsRecursionGuard = false;
}
}
private void _MakeApiSolutionsIfNeededImpl()
{
// If the project has a solution and C# project make sure the API assemblies are present and up to date
string api_config = "Debug";
string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, api_config);
if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Core}.dll")) ||
Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Core))
{
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, api_config))
return;
}
if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Editor}.dll")) ||
Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Editor))
{
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, api_config))
return; // Redundant? I don't think so!
}
}
private void _RemoveCreateSlnMenuOption()
{
menuPopup.RemoveItem(menuPopup.GetItemIndex((int) MenuOptions.CreateSln));
@ -465,9 +397,6 @@ namespace GodotTools
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
// Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized.
CallDeferred(nameof(_MakeApiSolutionsIfNeeded));
// Make sure the existing project has Api assembly references configured correctly
CSharpProject.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath);
}
@ -521,7 +450,7 @@ namespace GodotTools
// Export plugin
var exportPlugin = new GodotSharpExport();
AddExportPlugin(exportPlugin);
exportPluginWeak = new WeakReference<GodotSharpExport>(exportPlugin);
exportPluginWeak = WeakRef(exportPlugin);
GodotSharpBuilds.Initialize();
}
@ -530,13 +459,15 @@ namespace GodotTools
{
base.Dispose(disposing);
if (exportPluginWeak.TryGetTarget(out var exportPlugin))
if (exportPluginWeak != null)
{
// We need to dispose our export plugin before the editor destroys EditorSettings.
// Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid
// will be freed after EditorSettings already was, and its device polling thread
// will try to access the EditorSettings singleton, resulting in null dereferencing.
exportPlugin.Dispose();
(exportPluginWeak.GetRef() as GodotSharpExport)?.Dispose();
exportPluginWeak.Dispose();
}
}

View file

@ -10,6 +10,9 @@ namespace GodotTools.Internals
public const string CSharpLanguageType = "CSharpScript";
public const string CSharpLanguageExtension = "cs";
public static string UpdateApiAssembliesFromPrebuilt() =>
internal_UpdateApiAssembliesFromPrebuilt();
public static string FullTemplatesDir =>
internal_FullTemplatesDir();
@ -17,14 +20,6 @@ namespace GodotTools.Internals
public static bool IsOsxAppBundleInstalled(string bundleId) => internal_IsOsxAppBundleInstalled(bundleId);
public static bool MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType) =>
internal_MetadataIsApiAssemblyInvalidated(apiType);
public static void MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated) =>
internal_MetadataSetApiAssemblyInvalidated(apiType, invalidated);
public static bool IsMessageQueueFlushing() => internal_IsMessageQueueFlushing();
public static bool GodotIs32Bits() => internal_GodotIs32Bits();
public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble();
@ -53,6 +48,9 @@ namespace GodotTools.Internals
// Internal Calls
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_UpdateApiAssembliesFromPrebuilt();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_FullTemplatesDir();
@ -62,15 +60,6 @@ namespace GodotTools.Internals
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_IsOsxAppBundleInstalled(string bundleId);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_IsMessageQueueFlushing();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_GodotIs32Bits();

View file

@ -30,7 +30,6 @@
#include "editor_internal_calls.h"
#include "core/message_queue.h"
#include "core/os/os.h"
#include "core/version.h"
#include "editor/editor_node.h"
@ -245,7 +244,7 @@ MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_d
MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
}
@ -254,6 +253,11 @@ MonoString *godot_icall_Globals_TTR(MonoString *p_text) {
return GDMonoMarshal::mono_string_from_godot(TTR(text));
}
MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt() {
String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt();
return GDMonoMarshal::mono_string_from_godot(error_str);
}
MonoString *godot_icall_Internal_FullTemplatesDir() {
String full_templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);
return GDMonoMarshal::mono_string_from_godot(full_templates_dir);
@ -274,18 +278,6 @@ MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id
#endif
}
MonoBoolean godot_icall_Internal_MetadataIsApiAssemblyInvalidated(int32_t p_api_type) {
return GDMono::get_singleton()->metadata_is_api_assembly_invalidated((APIAssembly::Type)p_api_type);
}
void godot_icall_Internal_MetadataSetApiAssemblyInvalidated(int32_t p_api_type, MonoBoolean p_invalidated) {
GDMono::get_singleton()->metadata_set_api_assembly_invalidated((APIAssembly::Type)p_api_type, (bool)p_invalidated);
}
MonoBoolean godot_icall_Internal_IsMessageQueueFlushing() {
return (MonoBoolean)MessageQueue::get_singleton()->is_flushing();
}
MonoBoolean godot_icall_Internal_GodotIs32Bits() {
return sizeof(void *) == 4;
}
@ -407,12 +399,10 @@ void register_editor_internal_calls() {
mono_add_internal_call("GodotTools.GodotSharpExport::internal_GetExportedAssemblyDependencies", (void *)godot_icall_GodotSharpExport_GetExportedAssemblyDependencies);
// Internals
mono_add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", (void *)godot_icall_Internal_UpdateApiAssembliesFromPrebuilt);
mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir);
mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath);
mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled);
mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataIsApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataIsApiAssemblyInvalidated);
mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataSetApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataSetApiAssemblyInvalidated);
mono_add_internal_call("GodotTools.Internals.Internal::internal_IsMessageQueueFlushing", (void *)godot_icall_Internal_IsMessageQueueFlushing);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration);

View file

@ -59,10 +59,6 @@
#include "android_mono_config.gen.h"
#endif
#define OUT_OF_SYNC_ERR_MESSAGE(m_assembly_name) "The assembly '" m_assembly_name "' is out of sync. " \
"This error is expected if you just upgraded to a newer Godot version. " \
"Building the project will update the assembly to the correct version."
GDMono *GDMono::singleton = NULL;
namespace {
@ -304,26 +300,9 @@ void GDMono::initialize() {
mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL);
#ifndef TOOLS_ENABLED
if (!DirAccess::exists("res://.mono")) {
// 'res://.mono/' is missing so there is nothing to load. We don't need to initialize mono, but
// we still do so unless mscorlib is missing (which is the case for projects that don't use C#).
String mscorlib_fname("mscorlib.dll");
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs);
bool found = false;
for (int i = 0; i < search_dirs.size(); i++) {
if (FileAccess::exists(search_dirs[i].plus_file(mscorlib_fname))) {
found = true;
break;
}
}
if (!found)
return; // mscorlib is missing, do not initialize mono
}
// Export templates only load the Mono runtime if the project uses it
if (!DirAccess::exists("res://.mono"))
return;
#endif
root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319");
@ -354,63 +333,48 @@ void GDMono::initialize() {
_register_internal_calls();
// The following assemblies are not required at initialization
#ifdef MONO_GLUE_ENABLED
if (_load_api_assemblies()) {
// Everything is fine with the api assemblies, load the tools and project assemblies
#if defined(TOOLS_ENABLED)
ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies");
ERR_FAIL_COND(!_load_tools_assemblies());
#endif
_load_project_assembly();
} else {
if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated))
#ifdef TOOLS_ENABLED
|| (editor_api_assembly && editor_api_assembly_out_of_sync)
#endif
) {
#ifdef TOOLS_ENABLED
// The assembly was successfully 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(OUT_OF_SYNC_ERR_MESSAGE(CORE_API_ASSEMBLY_NAME));
metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true);
} else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' 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(OUT_OF_SYNC_ERR_MESSAGE(EDITOR_API_ASSEMBLY_NAME));
metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true);
}
print_line("Mono: Proceeding to unload scripts domain because of invalid API assemblies.");
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 // TOOLS_ENABLED
}
}
#else
print_verbose("Mono: Glue disabled, ignoring script assemblies.");
#endif // MONO_GLUE_ENABLED
print_verbose("Mono: INITIALIZED");
}
#ifdef MONO_GLUE_ENABLED
void GDMono::initialize_load_assemblies() {
#ifndef MONO_GLUE_ENABLED
ERR_EXPLAIN("Mono: This binary was built with `mono_glue=no`; cannot load assemblies");
CRASH_NOW();
#endif
// Load assemblies. The API and tools assemblies are required,
// the application is aborted if these assemblies cannot be loaded.
_load_api_assemblies();
#if defined(TOOLS_ENABLED)
if (!_load_tools_assemblies()) {
ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies");
CRASH_NOW();
}
#endif
// Load the project's main assembly. This doesn't necessarily need to succeed.
// The game may not be using .NET at all, or if the project does use .NET and
// we're running in the editor, it may just happen to be it wasn't built yet.
if (!_load_project_assembly()) {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load project assembly");
}
}
bool GDMono::_are_api_assemblies_out_of_sync() {
bool out_of_sync = core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated);
#ifdef TOOLS_ENABLED
if (!out_of_sync)
out_of_sync = editor_api_assembly && editor_api_assembly_out_of_sync;
#endif
return out_of_sync;
}
namespace GodotSharpBindings {
#ifdef MONO_GLUE_ENABLED
uint64_t get_core_api_hash();
#ifdef TOOLS_ENABLED
@ -419,13 +383,33 @@ uint64_t get_editor_api_hash();
uint32_t get_bindings_version();
void register_generated_icalls();
} // namespace GodotSharpBindings
#else
uint64_t get_core_api_hash() {
CRASH_NOW();
GD_UNREACHABLE();
}
#ifdef TOOLS_ENABLED
uint64_t get_editor_api_hash() {
CRASH_NOW();
GD_UNREACHABLE();
}
#endif
uint32_t get_bindings_version() {
CRASH_NOW();
GD_UNREACHABLE();
}
void register_generated_icalls() {
/* Fine, just do nothing */
}
#endif // MONO_GLUE_ENABLED
} // namespace GodotSharpBindings
void GDMono::_register_internal_calls() {
#ifdef MONO_GLUE_ENABLED
GodotSharpBindings::register_generated_icalls();
#endif
}
void GDMono::_initialize_and_check_api_hashes() {
@ -565,12 +549,21 @@ bool GDMono::_load_corlib_assembly() {
}
#ifdef TOOLS_ENABLED
static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) {
bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) {
bool &api_assembly_out_of_sync = (p_api_type == APIAssembly::API_CORE) ?
GDMono::get_singleton()->core_api_assembly_out_of_sync :
GDMono::get_singleton()->editor_api_assembly_out_of_sync;
String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
String dst_dir = GodotSharpDirs::get_res_assemblies_dir();
String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
// Create destination directory if needed
if (!DirAccess::exists(p_dst_dir)) {
DirAccess *da = DirAccess::create_for_path(p_dst_dir);
Error err = da->make_dir_recursive(p_dst_dir);
if (!DirAccess::exists(dst_dir)) {
DirAccess *da = DirAccess::create_for_path(dst_dir);
Error err = da->make_dir_recursive(dst_dir);
memdelete(da);
if (err != OK) {
@ -579,21 +572,19 @@ static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir,
}
}
String assembly_file = p_assembly_name + ".dll";
String assembly_src = p_src_dir.plus_file(assembly_file);
String assembly_dst = p_dst_dir.plus_file(assembly_file);
String assembly_file = assembly_name + ".dll";
String assembly_src = src_dir.plus_file(assembly_file);
String assembly_dst = dst_dir.plus_file(assembly_file);
if (!FileAccess::exists(assembly_dst) ||
FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) ||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
if (!FileAccess::exists(assembly_dst) || api_assembly_out_of_sync) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String xml_file = p_assembly_name + ".xml";
if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK)
String xml_file = assembly_name + ".xml";
if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK)
WARN_PRINTS("Failed to copy " + xml_file);
String pdb_file = p_assembly_name + ".pdb";
if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK)
String pdb_file = assembly_name + ".pdb";
if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK)
WARN_PRINTS("Failed to copy " + pdb_file);
Error err = da->copy(assembly_src, assembly_dst);
@ -603,11 +594,46 @@ static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir,
return false;
}
GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false);
api_assembly_out_of_sync = false;
}
return true;
}
String GDMono::update_api_assemblies_from_prebuilt() {
#define FAIL_REASON(m_out_of_sync, m_prebuilt_exist) \
( \
(m_out_of_sync ? \
String("The assembly is invalidated") : \
String("The assembly was not found")) + \
(m_prebuilt_exist ? \
String(" and the prebuilt assemblies are missing") : \
String(" and we failed to copy the prebuilt assemblies")))
bool api_assembly_out_of_sync = core_api_assembly_out_of_sync || editor_api_assembly_out_of_sync;
String core_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String editor_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path))
return String(); // No update needed
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path))
return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exist: */ false);
// Copy the prebuilt Api
if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE) || !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR))
return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exist: */ true);
return String(); // Updated successfully
#undef FAIL_REASON
}
#endif
bool GDMono::_load_core_api_assembly() {
@ -616,35 +642,15 @@ bool GDMono::_load_core_api_assembly() {
return true;
#ifdef TOOLS_ENABLED
if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) {
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
String prebuilt_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_CORE);
if (!FileAccess::exists(prebuilt_dll_path) ||
FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) {
print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated");
return false;
} else {
// Copy the prebuilt Api
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, CORE_API_ASSEMBLY_NAME, APIAssembly::API_CORE) ||
!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) {
print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Core API assembly because it was invalidated");
return false;
}
}
}
// For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date
String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll");
bool success = FileAccess::exists(assembly_path) &&
load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly);
#else
bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly);
#endif
String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll");
bool success = (FileAccess::exists(assembly_path) &&
load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly)) ||
load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly);
if (success) {
#ifdef MONO_GLUE_ENABLED
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 ||
@ -654,9 +660,8 @@ bool GDMono::_load_core_api_assembly() {
_install_trace_listener();
}
#else
GDMonoUtils::update_godot_api_cache();
#endif
} else {
core_api_assembly_out_of_sync = false;
}
return success;
@ -668,44 +673,100 @@ bool GDMono::_load_editor_api_assembly() {
if (editor_api_assembly)
return true;
if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) {
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
String prebuilt_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_EDITOR);
if (!FileAccess::exists(prebuilt_dll_path) ||
FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) {
print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated");
return false;
} else {
// Copy the prebuilt editor Api (no need to copy the core api if we got to this point)
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) {
print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Editor API assembly because it was invalidated");
return false;
}
}
}
// For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date
String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
bool success = (FileAccess::exists(assembly_path) &&
load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly)) ||
load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly);
bool success = FileAccess::exists(assembly_path) &&
load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly);
if (success) {
#ifdef MONO_GLUE_ENABLED
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 ||
CS_GLUE_VERSION != api_assembly_ver.cs_glue_version;
#endif
} else {
editor_api_assembly_out_of_sync = false;
}
return success;
}
#endif
bool GDMono::_try_load_api_assemblies() {
if (!_load_core_api_assembly()) {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load Core API assembly");
return false;
}
if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)
return false;
#ifdef TOOLS_ENABLED
if (!_load_editor_api_assembly()) {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load Editor API assembly");
return false;
}
if (editor_api_assembly_out_of_sync)
return false;
#endif
return true;
}
void GDMono::_load_api_assemblies() {
if (!_try_load_api_assemblies()) {
// The API assemblies are out of sync. Fine, try one more time, but this time
// update them from the prebuilt assemblies directory before trying to load them.
// 1. Unload the scripts domain
if (_unload_scripts_domain() != OK) {
ERR_EXPLAIN("Mono: Failed to unload scripts domain");
CRASH_NOW();
}
// 2. Update the API assemblies
String update_error = update_api_assemblies_from_prebuilt();
if (!update_error.empty()) {
ERR_EXPLAIN(update_error);
CRASH_NOW();
}
// 3. Load the scripts domain again
if (_load_scripts_domain() != OK) {
ERR_EXPLAIN("Mono: Failed to load scripts domain");
CRASH_NOW();
}
// 4. Try loading the updated assemblies
if (!_try_load_api_assemblies()) {
// welp... too bad
if (_are_api_assemblies_out_of_sync()) {
if (core_api_assembly_out_of_sync) {
ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync");
} else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed");
}
#ifdef TOOLS_ENABLED
if (editor_api_assembly_out_of_sync) {
ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync");
}
#endif
CRASH_NOW();
} else {
ERR_EXPLAIN("Failed to load one of the API assemblies");
CRASH_NOW();
}
}
}
}
#ifdef TOOLS_ENABLED
bool GDMono::_load_tools_assemblies() {
@ -734,39 +795,11 @@ bool GDMono::_load_project_assembly() {
if (success) {
mono_assembly_set_main(project_assembly->get_assembly());
} else {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load project assembly");
}
return success;
}
bool GDMono::_load_api_assemblies() {
if (!_load_core_api_assembly()) {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load Core API assembly");
return false;
}
if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)
return false;
#ifdef TOOLS_ENABLED
if (!_load_editor_api_assembly()) {
if (OS::get_singleton()->is_stdout_verbose())
print_error("Mono: Failed to load Editor API assembly");
return false;
}
if (editor_api_assembly_out_of_sync)
return false;
#endif
return true;
}
void GDMono::_install_trace_listener() {
#ifdef DEBUG_ENABLED
@ -784,78 +817,6 @@ void GDMono::_install_trace_listener() {
#endif
}
#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 ?
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);
}
Error save_err = metadata->save(path);
ERR_FAIL_COND(save_err != OK);
}
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 ?
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;
}
String GDMono::get_invalidated_api_assembly_path(APIAssembly::Type p_api_type) {
return GodotSharpDirs::get_res_assemblies_dir()
.plus_file(p_api_type == APIAssembly::API_CORE ?
CORE_API_ASSEMBLY_NAME ".dll" :
EDITOR_API_ASSEMBLY_NAME ".dll");
}
#endif
Error GDMono::_load_scripts_domain() {
ERR_FAIL_COND_V(scripts_domain != NULL, ERR_BUG);
@ -903,11 +864,6 @@ Error GDMono::_unload_scripts_domain() {
tools_project_editor_assembly = NULL;
#endif
core_api_assembly_out_of_sync = false;
#ifdef TOOLS_ENABLED
editor_api_assembly_out_of_sync = false;
#endif
MonoDomain *domain = scripts_domain;
scripts_domain = NULL;
@ -944,57 +900,25 @@ Error GDMono::reload_scripts_domain() {
return err;
}
#ifdef MONO_GLUE_ENABLED
if (!_load_api_assemblies()) {
if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated))
#ifdef TOOLS_ENABLED
|| (editor_api_assembly && editor_api_assembly_out_of_sync)
#endif
) {
#ifdef TOOLS_ENABLED
// The assembly was successfully 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.
// Load assemblies. The API and tools assemblies are required,
// the application is aborted if these assemblies cannot be loaded.
if (core_api_assembly_out_of_sync) {
ERR_PRINT(OUT_OF_SYNC_ERR_MESSAGE(CORE_API_ASSEMBLY_NAME));
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);
}
_load_api_assemblies();
if (editor_api_assembly_out_of_sync) {
ERR_PRINT(OUT_OF_SYNC_ERR_MESSAGE(EDITOR_API_ASSEMBLY_NAME));
metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true);
}
err = _unload_scripts_domain();
if (err != OK) {
WARN_PRINT("Mono: Failed to unload scripts domain");
}
return ERR_CANT_RESOLVE;
#else
ERR_PRINT("The loaded API assembly is invalid");
CRASH_NOW();
#endif
} else {
return ERR_CANT_OPEN;
}
#if defined(TOOLS_ENABLED)
if (!_load_tools_assemblies()) {
ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies");
CRASH_NOW();
}
#ifdef TOOLS_ENABLED
ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies");
ERR_FAIL_COND_V(!_load_tools_assemblies(), ERR_CANT_OPEN);
#endif
// Load the project's main assembly. Here, during hot-reloading, we do
// consider failing to load the project's main assembly to be an error.
// However, unlike the API and tools assemblies, the application can continue working.
if (!_load_project_assembly()) {
print_error("Mono: Failed to load project assembly");
return ERR_CANT_OPEN;
}
#else
print_verbose("Mono: Glue disabled, ignoring script assemblies.");
#endif // MONO_GLUE_ENABLED
return OK;
}

View file

@ -104,6 +104,8 @@ class GDMono {
void _domain_assemblies_cleanup(uint32_t p_domain_id);
bool _are_api_assemblies_out_of_sync();
bool _load_corlib_assembly();
bool _load_core_api_assembly();
#ifdef TOOLS_ENABLED
@ -112,11 +114,8 @@ class GDMono {
#endif
bool _load_project_assembly();
bool _load_api_assemblies();
#ifdef TOOLS_ENABLED
String _get_api_assembly_metadata_path();
#endif
bool _try_load_api_assemblies();
void _load_api_assemblies();
void _install_trace_listener();
@ -157,9 +156,8 @@ public:
#endif
#ifdef TOOLS_ENABLED
void metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated);
bool metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type);
String get_invalidated_api_assembly_path(APIAssembly::Type p_api_type);
bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type);
String update_api_assemblies_from_prebuilt();
#endif
static GDMono *get_singleton() { return singleton; }
@ -203,6 +201,7 @@ public:
Error finalize_and_unload_domain(MonoDomain *p_domain);
void initialize();
void initialize_load_assemblies();
GDMono();
~GDMono();