summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-05-28 04:56:46 +0200
committerIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-08-22 03:36:51 +0200
commite235cef09f71d0cd752ba4931640be24dcb551ab (patch)
treebb347c5defc17beb54490d48a91edef9da2b0d1d /modules
parentd78e0a842638df9c98a8f7637b125d36e488a367 (diff)
C#: Re-implement assembly reloading with ALCs
Diffstat (limited to 'modules')
-rw-r--r--modules/mono/SCsub1
-rw-r--r--modules/mono/csharp_script.cpp351
-rw-r--r--modules/mono/csharp_script.h75
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs10
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs217
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs23
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs16
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj1
-rw-r--r--modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml5
-rw-r--r--modules/mono/glue/GodotSharp/GodotPlugins/Main.cs147
-rw-r--r--modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs5
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings1
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs18
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs104
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs67
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs12
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs315
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs92
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs98
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs176
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs18
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs24
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs8
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs28
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs5
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs75
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs9
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj5
-rw-r--r--modules/mono/glue/runtime_interop.cpp14
-rw-r--r--modules/mono/managed_callable.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp88
-rw-r--r--modules/mono/mono_gd/gd_mono.h36
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp5
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h15
37 files changed, 1529 insertions, 557 deletions
diff --git a/modules/mono/SCsub b/modules/mono/SCsub
index ab7a2bf761..7764ba0b45 100644
--- a/modules/mono/SCsub
+++ b/modules/mono/SCsub
@@ -15,7 +15,6 @@ mono_configure.configure(env, env_mono)
env_mono.add_source_files(env.modules_sources, "*.cpp")
env_mono.add_source_files(env.modules_sources, "glue/*.cpp")
-env_mono.add_source_files(env.modules_sources, "glue/mono_glue.gen.cpp")
env_mono.add_source_files(env.modules_sources, "mono_gd/*.cpp")
env_mono.add_source_files(env.modules_sources, "utils/*.cpp")
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index f74c32ba04..e54e5ac0bb 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -104,7 +104,7 @@ Error CSharpLanguage::execute_file(const String &p_path) {
return OK;
}
-extern void *godotsharp_pinvoke_funcs[185];
+extern void *godotsharp_pinvoke_funcs[186];
[[maybe_unused]] volatile void **do_not_strip_godotsharp_pinvoke_funcs;
#ifdef TOOLS_ENABLED
extern void *godotsharp_editor_pinvoke_funcs[30];
@@ -646,6 +646,28 @@ void CSharpLanguage::frame() {
}
}
+struct CSharpScriptDepSort {
+ // Must support sorting so inheritance works properly (parent must be reloaded first)
+ bool operator()(const Ref<CSharpScript> &A, const Ref<CSharpScript> &B) const {
+ if (A == B) {
+ // Shouldn't happen but just in case...
+ return false;
+ }
+ const Script *I = B->get_base_script().ptr();
+ while (I) {
+ if (I == A.ptr()) {
+ // A is a base of B
+ return true;
+ }
+
+ I = I->get_base_script().ptr();
+ }
+
+ // A isn't a base of B
+ return false;
+ }
+};
+
void CSharpLanguage::reload_all_scripts() {
#ifdef GD_MONO_HOT_RELOAD
if (is_assembly_reloading_needed()) {
@@ -676,38 +698,29 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
return false;
}
-#warning TODO
-#if 0
- GDMonoAssembly *proj_assembly = gdmono->get_project_assembly();
-
- String appname_safe = ProjectSettings::get_singleton()->get_safe_project_name();
-
- appname_safe += ".dll";
-
- if (proj_assembly) {
- String proj_asm_path = proj_assembly->get_path();
+ String assembly_path = gdmono->get_project_assembly_path();
- if (!FileAccess::exists(proj_asm_path)) {
- // Maybe it wasn't loaded from the default path, so check this as well
- proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe);
- if (!FileAccess::exists(proj_asm_path)) {
- return false; // No assembly to load
- }
+ if (!assembly_path.is_empty()) {
+ if (!FileAccess::exists(assembly_path)) {
+ return false; // No assembly to load
}
- if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) {
+ if (FileAccess::get_modified_time(assembly_path) <= gdmono->get_project_assembly_modified_time()) {
return false; // Already up to date
}
} else {
- if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe))) {
+ String appname_safe = ProjectSettings::get_singleton()->get_safe_project_name();
+
+ assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir()
+ .plus_file(appname_safe + ".dll");
+ assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path);
+
+ if (!FileAccess::exists(assembly_path)) {
return false; // No assembly to load
}
}
return true;
-#else
- return false;
-#endif
}
void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
@@ -715,27 +728,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
return;
}
-#warning TODO ALCs after switching to .NET 6
+ // TODO:
+ // Currently, this reloads all scripts, including those whose class is not part of the
+ // assembly load context being unloaded. As such, we unnecessarily reload GodotTools.
- // Try to load the project assembly if it was not yet loaded
- // (while hot-reload is not yet implemented)
- gdmono->initialize_load_assemblies();
+ print_verbose(".NET: Reloading assemblies...");
- {
- MutexLock lock(script_instances_mutex);
-
- for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
- Ref<CSharpScript> script(elem->self());
-
- script->exports_invalidated = true;
-
- if (!script->get_path().is_empty()) {
- script->reload(p_soft_reload);
- }
- }
- }
-
-#if 0
// There is no soft reloading with Mono. It's always hard reloading.
List<Ref<CSharpScript>> scripts;
@@ -758,18 +756,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) {
ManagedCallable *managed_callable = elem->self();
- Array serialized_data;
- MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
+ ERR_CONTINUE(managed_callable->delegate_handle.value == nullptr);
- MonoException *exc = nullptr;
- bool success = (bool)GDMonoCache::managed_callbacks.methodthunk_DelegateUtils_TrySerializeDelegateWithGCHandle
- .invoke(managed_callable->delegate_handle,
- managed_serialized_data, &exc);
+ Array serialized_data;
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- continue;
- }
+ bool success = GDMonoCache::managed_callbacks.DelegateUtils_TrySerializeDelegateWithGCHandle(
+ managed_callable->delegate_handle, &serialized_data);
if (success) {
ManagedCallable::instances_pending_reload.insert(managed_callable, serialized_data);
@@ -798,17 +790,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
// If someone removes a script from a node, deletes the script, builds, adds a script to the
// same node, then builds again, the script might have no path and also no script_class. In
// that case, we can't (and don't need to) reload it.
- if (script->get_path().is_empty() && !script->script_class) {
+ if (script->get_path().is_empty() && !script->valid) {
continue;
}
to_reload.push_back(script);
- if (script->get_path().is_empty()) {
- script->tied_class_name_for_reload = script->script_class->get_name_for_lookup();
- script->tied_class_namespace_for_reload = script->script_class->get_namespace();
- }
-
// Script::instances are deleted during managed object disposal, which happens on domain finalize.
// Only placeholders are kept. Therefore we need to keep a copy before that happens.
@@ -841,17 +828,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
- // Call OnBeforeSerialize
- if (csi->script->script_class->implements_interface(GDMonoCache::cached_data.class_ISerializationListener)) {
- obj->get_script_instance()->call(string_names.on_before_serialize);
- }
+ // Call OnBeforeSerialize and save instance info
- // Save instance info
CSharpScript::StateBackup state;
- // TODO: Proper state backup (Not only variants, serialize managed state of scripts)
- csi->get_properties_state_for_reloading(state.properties);
- csi->get_event_signals_state_for_reloading(state.event_signals);
+ Dictionary properties;
+
+ GDMonoCache::managed_callbacks.CSharpInstanceBridge_SerializeState(
+ csi->get_gchandle_intptr(), &properties, &state.event_signals);
+
+ for (const Variant *s = properties.next(nullptr); s != nullptr; s = properties.next(s)) {
+ StringName name = *s;
+ Variant value = properties[*s];
+ state.properties.push_back(Pair<StringName, Variant>(name, value));
+ }
owners_map[obj->get_instance_id()] = state;
}
@@ -868,7 +858,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
// Do domain reload
- if (gdmono->reload_scripts_domain() != OK) {
+ if (gdmono->reload_project_assemblies() != OK) {
// Failed to reload the scripts domain
// Make sure to add the scripts back to their owners before returning
for (Ref<CSharpScript> &scr : to_reload) {
@@ -899,6 +889,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
scr->pending_reload_state.erase(obj_id);
}
+
+ scr->pending_reload_instances.clear();
+ scr->pending_reload_state.clear();
}
return;
@@ -916,46 +909,21 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
if (!script->valid) {
script->pending_reload_instances.clear();
+ script->pending_reload_state.clear();
continue;
}
} else {
- const StringName &class_namespace = script->tied_class_namespace_for_reload;
- const StringName &class_name = script->tied_class_name_for_reload;
- GDMonoAssembly *project_assembly = gdmono->get_project_assembly();
-
- // Search in project and tools assemblies first as those are the most likely to have the class
- GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : nullptr);
+ bool success = GDMonoCache::managed_callbacks.ScriptManagerBridge_TryReloadRegisteredScriptWithClass(script.ptr());
-#ifdef TOOLS_ENABLED
- if (!script_class) {
- GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly();
- script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : nullptr);
- }
-#endif
-
- if (!script_class) {
- script_class = gdmono->get_class(class_namespace, class_name);
- }
-
- if (!script_class) {
- // The class was removed, can't reload
- script->pending_reload_instances.clear();
- continue;
- }
-
- bool obj_type = GDMonoCache::cached_data.class_GodotObject->is_assignable_from(script_class);
- if (!obj_type) {
- // The class no longer inherits Godot.Object, can't reload
+ if (!success) {
+ // Couldn't reload
script->pending_reload_instances.clear();
+ script->pending_reload_state.clear();
continue;
}
-
- GDMonoClass *native = GDMonoUtils::get_class_native_base(script_class);
-
- CSharpScript::reload_registered_script(script, script_class, native);
}
- StringName native_name = NATIVE_GDMONOCLASS_NAME(script->native);
+ StringName native_name = script->get_instance_base_type();
{
for (const ObjectID &obj_id : script->pending_reload_instances) {
@@ -1020,57 +988,25 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
ERR_CONTINUE(!obj->get_script_instance());
- // TODO: Restore serialized state
-
CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
- for (const Pair<StringName, Variant> &G : state_backup.properties) {
- obj->get_script_instance()->set(G.first, G.second);
- }
-
CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance());
if (csi) {
- for (const Pair<StringName, Array> &G : state_backup.event_signals) {
- const StringName &name = G.first;
- const Array &serialized_data = G.second;
-
- HashMap<StringName, GDMonoField *>::Iterator match = script->event_signals.find(name);
-
- if (!match) {
- // The event or its signal attribute were removed
- continue;
- }
+ Dictionary properties;
- GDMonoField *event_signal_field = match->value;
-
- MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
- MonoDelegate *delegate = nullptr;
-
- MonoException *exc = nullptr;
- bool success = (bool)GDMonoCache::managed_callbacks.methodthunk_DelegateUtils_TryDeserializeDelegate.invoke(managed_serialized_data, &delegate, &exc);
-
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- continue;
- }
-
- if (success) {
- ERR_CONTINUE(delegate == nullptr);
- event_signal_field->set_value(csi->get_mono_object(), (MonoObject *)delegate);
- } else if (OS::get_singleton()->is_stdout_verbose()) {
- OS::get_singleton()->print("Failed to deserialize event signal delegate\n");
- }
+ for (const Pair<StringName, Variant> &G : state_backup.properties) {
+ properties[G.first] = G.second;
}
- // Call OnAfterDeserialization
- if (csi->script->script_class->implements_interface(GDMonoCache::cached_data.class_ISerializationListener)) {
- obj->get_script_instance()->call(string_names.on_after_deserialize);
- }
+ // Restore serialized state and call OnAfterDeserialization
+ GDMonoCache::managed_callbacks.CSharpInstanceBridge_DeserializeState(
+ csi->get_gchandle_intptr(), &properties, &state_backup.event_signals);
}
}
script->pending_reload_instances.clear();
+ script->pending_reload_state.clear();
}
// Deserialize managed callables
@@ -1081,20 +1017,13 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
ManagedCallable *managed_callable = elem.key;
const Array &serialized_data = elem.value;
- MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
- void *delegate = nullptr;
+ GCHandleIntPtr delegate = { nullptr };
- MonoException *exc = nullptr;
- bool success = (bool)GDMonoCache::managed_callbacks.methodthunk_DelegateUtils_TryDeserializeDelegateWithGCHandle
- .invoke(managed_serialized_data, &delegate, &exc);
-
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- continue;
- }
+ bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle(
+ &serialized_data, &delegate);
if (success) {
- ERR_CONTINUE(delegate == nullptr);
+ ERR_CONTINUE(delegate.value == nullptr);
managed_callable->delegate_handle = delegate;
} else if (OS::get_singleton()->is_stdout_verbose()) {
OS::get_singleton()->print("Failed to deserialize delegate\n");
@@ -1111,7 +1040,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
NodeDock::get_singleton()->update_lists();
}
#endif
-#endif
}
#endif
@@ -1155,12 +1083,6 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
}
void CSharpLanguage::_on_scripts_domain_about_to_unload() {
- for (KeyValue<Object *, CSharpScriptBinding> &E : script_bindings) {
- CSharpScriptBinding &script_binding = E.value;
- script_binding.gchandle.release();
- script_binding.inited = false;
- }
-
#ifdef GD_MONO_HOT_RELOAD
{
MutexLock lock(ManagedCallable::instances_mutex);
@@ -1263,7 +1185,8 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
#endif
GCHandleIntPtr strong_gchandle =
- GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectBinding(&type_name, p_object);
+ GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectBinding(
+ &type_name, p_object);
ERR_FAIL_NULL_V(strong_gchandle.value, false);
@@ -1604,75 +1527,6 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
return false;
}
-#warning TODO
-#if 0
-void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state) {
- List<PropertyInfo> property_list;
- get_property_list(&property_list);
-
- for (const PropertyInfo &prop_info : property_list) {
- Pair<StringName, Variant> state_pair;
- state_pair.first = prop_info.name;
-
- ManagedType managedType;
-
- GDMonoField *field = nullptr;
- GDMonoClass *top = script->script_class;
- while (top && top != script->native) {
- field = top->get_field(state_pair.first);
- if (field) {
- break;
- }
-
- top = top->get_parent_class();
- }
- if (!field) {
- continue; // Properties ignored. We get the property baking fields instead.
- }
-
- managedType = field->get_type();
-
- if (GDMonoMarshal::managed_to_variant_type(managedType) != Variant::NIL) { // If we can marshal it
- if (get(state_pair.first, state_pair.second)) {
- r_state.push_back(state_pair);
- }
- }
- }
-}
-
-void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state) {
- MonoObject *owner_managed = get_mono_object();
- ERR_FAIL_NULL(owner_managed);
-
- for (const KeyValue<StringName, GDMonoField *> &E : script->event_signals) {
- GDMonoField *event_signal_field = E.value;
-
- MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal_field->get_value(owner_managed);
- if (!delegate_field_value) {
- continue; // Empty
- }
-
- Array serialized_data;
- MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
-
- MonoException *exc = nullptr;
- bool success = (bool)GDMonoCache::managed_callbacks.methodthunk_DelegateUtils_TrySerializeDelegate
- .invoke(delegate_field_value, managed_serialized_data, &exc);
-
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- continue;
- }
-
- if (success) {
- r_state.push_back(Pair<StringName, Array>(event_signal_field->get_name(), serialized_data));
- } else if (OS::get_singleton()->is_stdout_verbose()) {
- OS::get_singleton()->print("Failed to serialize event signal delegate\n");
- }
- }
-}
-#endif
-
void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
List<PropertyInfo> props;
script->get_script_property_list(&props);
@@ -1906,6 +1760,7 @@ void CSharpInstance::mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_f
// If the native instance is still alive and Dispose() was called
// (instead of the finalizer), then we remove the script instance.
r_remove_script_instance = true;
+ // TODO: Last usage of 'is_finalizing_scripts_domain'. It should be replaced with a check to determine if the load context is being unloaded.
} else if (!GDMono::get_singleton()->is_finalizing_scripts_domain()) {
// If the native instance is still alive and this is called from the finalizer,
// then it was referenced from another thread before the finalizer could
@@ -2156,8 +2011,8 @@ void CSharpScript::_update_exports_values(HashMap<StringName, Variant> &values,
propnames.push_back(prop_info);
}
- if (base_cache.is_valid()) {
- base_cache->_update_exports_values(values, propnames);
+ if (base_script.is_valid()) {
+ base_script->_update_exports_values(values, propnames);
}
}
#endif
@@ -2319,13 +2174,16 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
// only for this, so need to call the destructor manually before passing this to C#.
rpc_functions_dict.~Dictionary();
+ Ref<CSharpScript> base_script;
GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
- p_script.ptr(), &tool, &rpc_functions_dict);
+ p_script.ptr(), &tool, &rpc_functions_dict, &base_script);
p_script->tool = tool;
p_script->rpc_config.clear();
p_script->rpc_config = rpc_functions_dict;
+
+ p_script->base_script = base_script;
}
bool CSharpScript::can_instantiate() const {
@@ -2586,8 +2444,8 @@ bool CSharpScript::get_property_default_value(const StringName &p_property, Vari
return true;
}
- if (base_cache.is_valid()) {
- return base_cache->get_property_default_value(p_property, r_value);
+ if (base_script.is_valid()) {
+ return base_script->get_property_default_value(p_property, r_value);
}
#endif
@@ -2671,26 +2529,35 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
}
Ref<Script> CSharpScript::get_base_script() const {
- // TODO search in metadata file once we have it, not important any way?
- return Ref<Script>();
+ return base_script;
}
void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const {
- List<PropertyInfo> props;
-
#ifdef TOOLS_ENABLED
- for (const PropertyInfo &E : exported_members_cache) {
- props.push_back(E);
+ const CSharpScript *top = this;
+ while (top != nullptr) {
+ for (const PropertyInfo &E : top->exported_members_cache) {
+ r_list->push_back(E);
+ }
+
+ top = top->base_script.ptr();
}
#else
- for (const KeyValue<StringName, PropertyInfo> &E : member_info) {
- props.push_front(E.value);
- }
-#endif
+ const CSharpScript *top = this;
+ while (top != nullptr) {
+ List<PropertyInfo> props;
- for (const PropertyInfo &prop : props) {
- r_list->push_back(prop);
+ for (const KeyValue<StringName, PropertyInfo> &E : top->member_info) {
+ props.push_front(E.value);
+ }
+
+ for (const PropertyInfo &prop : props) {
+ r_list->push_back(prop);
+ }
+
+ top = top->base_script.ptr();
}
+#endif
}
int CSharpScript::get_member_line(const StringName &p_member) const {
@@ -2852,6 +2719,4 @@ CSharpLanguage::StringNameCache::StringNameCache() {
_property_can_revert = StaticCString::create("_property_can_revert");
_property_get_revert = StaticCString::create("_property_get_revert");
_script_source = StaticCString::create("script/source");
- on_before_serialize = StaticCString::create("OnBeforeSerialize");
- on_after_deserialize = StaticCString::create("OnAfterDeserialize");
}
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 1ca4e20047..29a36bcc1e 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -68,14 +68,6 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
class CSharpScript : public Script {
GDCLASS(CSharpScript, Script);
-public:
- struct SignalParameter {
- String name;
- Variant::Type type;
- bool nil_is_variant = false;
- };
-
-private:
friend class CSharpInstance;
friend class CSharpLanguage;
@@ -83,7 +75,7 @@ private:
bool valid = false;
bool reload_invalidated = false;
- Ref<CSharpScript> base_cache; // TODO what's this for?
+ Ref<CSharpScript> base_script;
HashSet<Object *> instances;
@@ -93,13 +85,11 @@ private:
// Replace with buffer containing the serialized state of managed scripts.
// Keep variant state backup to use only with script instance placeholders.
List<Pair<StringName, Variant>> properties;
- List<Pair<StringName, Array>> event_signals;
+ Dictionary event_signals;
};
HashSet<ObjectID> pending_reload_instances;
RBMap<ObjectID, StateBackup> pending_reload_state;
- StringName tied_class_name_for_reload;
- StringName tied_class_namespace_for_reload;
#endif
String source;
@@ -174,8 +164,12 @@ public:
void get_members(HashSet<StringName> *p_members) override;
- bool is_tool() const override { return tool; }
- bool is_valid() const override { return valid; }
+ bool is_tool() const override {
+ return tool;
+ }
+ bool is_valid() const override {
+ return valid;
+ }
bool inherits_script(const Ref<Script> &p_script) const override;
@@ -191,7 +185,9 @@ public:
const Variant get_rpc_config() const override;
#ifdef TOOLS_ENABLED
- bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
+ bool is_placeholder_fallback_enabled() const override {
+ return placeholder_fallback_enabled;
+ }
#endif
Error load_source_code(const String &p_path);
@@ -231,9 +227,6 @@ class CSharpInstance : public ScriptInstance {
// Do not use unless you know what you are doing
static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle);
- void get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state);
- void get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state);
-
public:
_FORCE_INLINE_ bool is_destructing_script_instance() { return destructing_script_instance; }
@@ -325,8 +318,6 @@ class CSharpLanguage : public ScriptLanguage {
StringName _property_can_revert;
StringName _property_get_revert;
StringName _script_source;
- StringName on_before_serialize; // OnBeforeSerialize
- StringName on_after_deserialize; // OnAfterDeserialize
StringNameCache();
};
@@ -361,18 +352,30 @@ public:
StringNameCache string_names;
- const Mutex &get_language_bind_mutex() { return language_bind_mutex; }
- const Mutex &get_script_instances_mutex() { return script_instances_mutex; }
+ const Mutex &get_language_bind_mutex() {
+ return language_bind_mutex;
+ }
+ const Mutex &get_script_instances_mutex() {
+ return script_instances_mutex;
+ }
- _FORCE_INLINE_ int get_language_index() { return lang_idx; }
+ _FORCE_INLINE_ int get_language_index() {
+ return lang_idx;
+ }
void set_language_index(int p_idx);
- _FORCE_INLINE_ const StringNameCache &get_string_names() { return string_names; }
+ _FORCE_INLINE_ const StringNameCache &get_string_names() {
+ return string_names;
+ }
- _FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; }
+ _FORCE_INLINE_ static CSharpLanguage *get_singleton() {
+ return singleton;
+ }
#ifdef TOOLS_ENABLED
- _FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
+ _FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const {
+ return godotsharp_editor;
+ }
#endif
static void release_script_gchandle(MonoGCHandleData &p_gchandle);
@@ -387,7 +390,9 @@ public:
void reload_assemblies(bool p_soft_reload);
#endif
- _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }
+ _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const {
+ return managed_callable_middleman;
+ }
String get_name() const override;
@@ -416,7 +421,9 @@ public:
Script *create_script() const override;
bool has_named_classes() const override;
bool supports_builtin_mode() const override;
- /* TODO? */ int find_function(const String &p_function, const String &p_code) const override { return -1; }
+ /* TODO? */ int find_function(const String &p_function, const String &p_code) const override {
+ return -1;
+ }
String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override;
virtual String _get_indentation() const;
/* TODO? */ void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override {}
@@ -431,14 +438,20 @@ public:
/* TODO */ void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
/* TODO */ void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
/* TODO */ void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
- /* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override { return ""; }
+ /* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override {
+ return "";
+ }
Vector<StackInfo> debug_get_current_stack_info() override;
/* PROFILING FUNCTIONS */
/* TODO */ void profiling_start() override {}
/* TODO */ void profiling_stop() override {}
- /* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; }
- /* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; }
+ /* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override {
+ return 0;
+ }
+ /* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override {
+ return 0;
+ }
void frame() override;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
index 4ac7274e41..c218212f04 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
@@ -224,8 +224,9 @@ namespace Godot.SourceGenerators
{
foreach (var property in properties)
{
- // Ignore properties without a getter. Godot properties must be readable.
- if (property.IsWriteOnly)
+ // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
+ // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
+ if (property.IsWriteOnly || property.IsReadOnly)
continue;
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache);
@@ -244,6 +245,11 @@ namespace Godot.SourceGenerators
{
foreach (var field in fields)
{
+ // TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
+ // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
+ if (field.IsReadOnly)
+ continue;
+
var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache);
if (marshalType == null)
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
new file mode 100644
index 0000000000..3a7086a2be
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
@@ -0,0 +1,217 @@
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators
+{
+ [Generator]
+ public class ScriptSerializationGenerator : ISourceGenerator
+ {
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ if (context.AreGodotSourceGeneratorsDisabled())
+ return;
+
+ INamedTypeSymbol[] godotClasses = context
+ .Compilation.SyntaxTrees
+ .SelectMany(tree =>
+ tree.GetRoot().DescendantNodes()
+ .OfType<ClassDeclarationSyntax>()
+ .SelectGodotScriptClasses(context.Compilation)
+ // Report and skip non-partial classes
+ .Where(x =>
+ {
+ if (x.cds.IsPartial())
+ {
+ if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
+ {
+ Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!);
+ return false;
+ }
+
+ return true;
+ }
+
+ Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
+ return false;
+ })
+ .Select(x => x.symbol)
+ )
+ .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
+ .ToArray();
+
+ if (godotClasses.Length > 0)
+ {
+ var typeCache = new MarshalUtils.TypeCache(context);
+
+ foreach (var godotClass in godotClasses)
+ {
+ VisitGodotScriptClass(context, typeCache, godotClass);
+ }
+ }
+ }
+
+ private static void VisitGodotScriptClass(
+ GeneratorExecutionContext context,
+ MarshalUtils.TypeCache typeCache,
+ INamedTypeSymbol symbol
+ )
+ {
+ INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
+ string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
+ namespaceSymbol.FullQualifiedName() :
+ string.Empty;
+ bool hasNamespace = classNs.Length != 0;
+
+ bool isInnerClass = symbol.ContainingType != null;
+
+ string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ + "_ScriptSerialization_Generated";
+
+ var source = new StringBuilder();
+
+ source.Append("using Godot;\n");
+ source.Append("using Godot.NativeInterop;\n");
+ source.Append("\n");
+
+ if (hasNamespace)
+ {
+ source.Append("namespace ");
+ source.Append(classNs);
+ source.Append(" {\n\n");
+ }
+
+ if (isInnerClass)
+ {
+ var containingType = symbol.ContainingType;
+
+ while (containingType != null)
+ {
+ source.Append("partial ");
+ source.Append(containingType.GetDeclarationKeyword());
+ source.Append(" ");
+ source.Append(containingType.NameWithTypeParameters());
+ source.Append("\n{\n");
+
+ containingType = containingType.ContainingType;
+ }
+ }
+
+ source.Append("partial class ");
+ source.Append(symbol.NameWithTypeParameters());
+ source.Append("\n{\n");
+
+ var members = symbol.GetMembers();
+
+ var propertySymbols = members
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
+ .Cast<IPropertySymbol>();
+
+ var fieldSymbols = members
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
+ .Cast<IFieldSymbol>();
+
+ var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+ var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+
+ source.Append(
+ " protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n");
+ source.Append(" base.SaveGodotObjectData(info);\n");
+
+ foreach (var property in godotClassProperties)
+ {
+ string propertyName = property.PropertySymbol.Name;
+
+ source.Append(" info.AddProperty(GodotInternal.PropName_")
+ .Append(propertyName)
+ .Append(", this.")
+ .Append(propertyName)
+ .Append(");\n");
+ }
+
+ foreach (var field in godotClassFields)
+ {
+ string fieldName = field.FieldSymbol.Name;
+
+ source.Append(" info.AddProperty(GodotInternal.PropName_")
+ .Append(fieldName)
+ .Append(", this.")
+ .Append(fieldName)
+ .Append(");\n");
+ }
+
+ source.Append(" }\n");
+
+ source.Append(
+ " protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n");
+ source.Append(" base.RestoreGodotObjectData(info);\n");
+
+ foreach (var property in godotClassProperties)
+ {
+ string propertyName = property.PropertySymbol.Name;
+ string propertyTypeQualifiedName = property.PropertySymbol.Type.FullQualifiedName();
+
+ source.Append(" if (info.TryGetProperty<")
+ .Append(propertyTypeQualifiedName)
+ .Append(">(GodotInternal.PropName_")
+ .Append(propertyName)
+ .Append(", out var _value_")
+ .Append(propertyName)
+ .Append("))\n")
+ .Append(" this.")
+ .Append(propertyName)
+ .Append(" = _value_")
+ .Append(propertyName)
+ .Append(";\n");
+ }
+
+ foreach (var field in godotClassFields)
+ {
+ string fieldName = field.FieldSymbol.Name;
+ string fieldTypeQualifiedName = field.FieldSymbol.Type.FullQualifiedName();
+
+ source.Append(" if (info.TryGetProperty<")
+ .Append(fieldTypeQualifiedName)
+ .Append(">(GodotInternal.PropName_")
+ .Append(fieldName)
+ .Append(", out var _value_")
+ .Append(fieldName)
+ .Append("))\n")
+ .Append(" this.")
+ .Append(fieldName)
+ .Append(" = _value_")
+ .Append(fieldName)
+ .Append(";\n");
+ }
+
+ source.Append(" }\n");
+
+ source.Append("}\n"); // partial class
+
+ if (isInnerClass)
+ {
+ var containingType = symbol.ContainingType;
+
+ while (containingType != null)
+ {
+ source.Append("}\n"); // outer class
+
+ containingType = containingType.ContainingType;
+ }
+ }
+
+ if (hasNamespace)
+ {
+ source.Append("\n}\n");
+ }
+
+ context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
index 18073d6492..f8a810fd44 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
@@ -11,16 +11,16 @@ namespace GodotTools.Build
[Serializable]
public sealed partial class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization
{
- public string Solution { get; }
- public string Configuration { get; }
- public string? RuntimeIdentifier { get; }
- public string? PublishOutputDir { get; }
- public bool Restore { get; }
- public bool Rebuild { get; }
- public bool OnlyClean { get; }
+ public string Solution { get; private set; }
+ public string Configuration { get; private set; }
+ public string? RuntimeIdentifier { get; private set; }
+ public string? PublishOutputDir { get; private set; }
+ public bool Restore { get; private set; }
+ public bool Rebuild { get; private set; }
+ public bool OnlyClean { get; private set; }
// TODO Use List once we have proper serialization
- public Array<string> CustomProperties { get; } = new Array<string>();
+ public Array<string> CustomProperties { get; private set; } = new Array<string>();
public string LogsDirPath =>
Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}");
@@ -56,6 +56,13 @@ namespace GodotTools.Build
}
}
+ // Needed for instantiation from Godot, after reloading assemblies
+ private BuildInfo()
+ {
+ Solution = string.Empty;
+ Configuration = string.Empty;
+ }
+
public BuildInfo(string solution, string configuration, bool restore, bool rebuild, bool onlyClean)
{
Solution = solution;
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
index f7b8c6bffd..bac5464fd6 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
@@ -58,7 +58,7 @@ namespace GodotTools.Build
}
// TODO Use List once we have proper serialization.
- private readonly Array<BuildIssue> _issues = new Array<BuildIssue>();
+ private Array<BuildIssue> _issues = new Array<BuildIssue>();
private ItemList _issuesList;
private PopupMenu _issuesListContextMenu;
private TextEdit _buildLog;
@@ -133,7 +133,9 @@ namespace GodotTools.Build
if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
return;
- string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : _buildInfo.Solution.GetBaseDir();
+ string projectDir = issue.ProjectFile.Length > 0 ?
+ issue.ProjectFile.GetBaseDir() :
+ _buildInfo.Solution.GetBaseDir();
string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath());
@@ -412,6 +414,16 @@ namespace GodotTools.Build
{
// In case it didn't update yet. We don't want to have to serialize any pending output.
UpdateBuildLogText();
+
+ // NOTE:
+ // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are.
+ // Until that changes, we need workarounds like this one because events keep strong references to disposed objects.
+ BuildManager.BuildLaunchFailed -= BuildLaunchFailed;
+ BuildManager.BuildStarted -= BuildStarted;
+ BuildManager.BuildFinished -= BuildFinished;
+ // StdOutput/Error can be received from different threads, so we need to use CallDeferred
+ BuildManager.StdOutputReceived -= StdOutputReceived;
+ BuildManager.StdErrorReceived -= StdErrorReceived;
}
public void OnAfterDeserialize()
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index 2765391a27..f5734e6e69 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -9,6 +9,7 @@
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
<GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
<GodotApiAssembliesDir>$(GodotOutputDataDir)/Api/$(GodotApiConfiguration)</GodotApiAssembliesDir>
+ <ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<!-- Needed for our source generators to work despite this not being a Godot game project -->
diff --git a/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml b/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml
new file mode 100644
index 0000000000..2dc350d4f2
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml
@@ -0,0 +1,5 @@
+<assembly name="System.Runtime.InteropServices">
+ <member name="T:System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute">
+ <attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
+ </member>
+</assembly>
diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
index 2a2e147eaa..395cc9bf66 100644
--- a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
+++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using Godot.Bridge;
@@ -11,10 +12,55 @@ namespace GodotPlugins
{
public static class Main
{
+ // IMPORTANT:
+ // Keeping strong references to the AssemblyLoadContext (our PluginLoadContext) prevents
+ // it from being unloaded. To avoid issues, we wrap the reference in this class, and mark
+ // all the methods that access it as non-inlineable. This way we prevent local references
+ // (either real or introduced by the JIT) to escape the scope of these methods due to
+ // inlining, which could keep the AssemblyLoadContext alive while trying to unload.
+ private sealed class PluginLoadContextWrapper
+ {
+ private PluginLoadContext? _pluginLoadContext;
+
+ public string? AssemblyLoadedPath
+ {
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ get => _pluginLoadContext?.AssemblyLoadedPath;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static (Assembly, PluginLoadContextWrapper) CreateAndLoadFromAssemblyName(
+ AssemblyName assemblyName,
+ string pluginPath,
+ ICollection<string> sharedAssemblies,
+ AssemblyLoadContext mainLoadContext
+ )
+ {
+ var wrapper = new PluginLoadContextWrapper();
+ wrapper._pluginLoadContext = new PluginLoadContext(
+ pluginPath, sharedAssemblies, mainLoadContext);
+ var assembly = wrapper._pluginLoadContext.LoadFromAssemblyName(assemblyName);
+ return (assembly, wrapper);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public WeakReference CreateWeakReference()
+ {
+ return new WeakReference(_pluginLoadContext, trackResurrection: true);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal void Unload()
+ {
+ _pluginLoadContext?.Unload();
+ _pluginLoadContext = null;
+ }
+ }
+
private static readonly List<AssemblyName> SharedAssemblies = new();
private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly;
private static Assembly? _editorApiAssembly;
- private static Assembly? _projectAssembly;
+ private static PluginLoadContextWrapper? _projectLoadContext;
private static readonly AssemblyLoadContext MainLoadContext =
AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ??
@@ -35,6 +81,8 @@ namespace GodotPlugins
SharedAssemblies.Add(CoreApiAssembly.GetName());
NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver);
+ AlcReloadCfg.Configure(alcReloadEnabled: editorHint.ToBool());
+
if (editorHint.ToBool())
{
_editorApiAssembly = Assembly.Load("GodotSharpEditor");
@@ -46,6 +94,7 @@ namespace GodotPlugins
{
LoadProjectAssemblyCallback = &LoadProjectAssembly,
LoadToolsAssemblyCallback = &LoadToolsAssembly,
+ UnloadProjectPluginCallback = &UnloadProjectPlugin,
};
*managedCallbacks = ManagedCallbacks.Create();
@@ -55,37 +104,41 @@ namespace GodotPlugins
catch (Exception e)
{
Console.Error.WriteLine(e);
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
[StructLayout(LayoutKind.Sequential)]
private struct PluginsCallbacks
{
- public unsafe delegate* unmanaged<char*, godot_bool> LoadProjectAssemblyCallback;
+ public unsafe delegate* unmanaged<char*, godot_string*, godot_bool> LoadProjectAssemblyCallback;
public unsafe delegate* unmanaged<char*, IntPtr> LoadToolsAssemblyCallback;
+ public unsafe delegate* unmanaged<godot_bool> UnloadProjectPluginCallback;
}
[UnmanagedCallersOnly]
- private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath)
+ private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath, godot_string* outLoadedAssemblyPath)
{
try
{
- if (_projectAssembly != null)
+ if (_projectLoadContext != null)
return godot_bool.True; // Already loaded
string assemblyPath = new(nAssemblyPath);
- _projectAssembly = LoadPlugin(assemblyPath);
+ (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath);
+
+ string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath;
+ *outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath);
- ScriptManagerBridge.LookupScriptsInAssembly(_projectAssembly);
+ ScriptManagerBridge.LookupScriptsInAssembly(projectAssembly);
return godot_bool.True;
}
catch (Exception e)
{
Console.Error.WriteLine(e);
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
@@ -99,7 +152,7 @@ namespace GodotPlugins
if (_editorApiAssembly == null)
throw new InvalidOperationException("The Godot editor API assembly is not loaded");
- var assembly = LoadPlugin(assemblyPath);
+ var (assembly, _) = LoadPlugin(assemblyPath);
NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!);
@@ -122,7 +175,7 @@ namespace GodotPlugins
}
}
- private static Assembly LoadPlugin(string assemblyPath)
+ private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath)
{
string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
@@ -135,8 +188,78 @@ namespace GodotPlugins
sharedAssemblies.Add(sharedAssemblyName);
}
- var loadContext = new PluginLoadContext(assemblyPath, sharedAssemblies, MainLoadContext);
- return loadContext.LoadFromAssemblyName(new AssemblyName(assemblyName));
+ return PluginLoadContextWrapper.CreateAndLoadFromAssemblyName(
+ new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext);
+ }
+
+ [UnmanagedCallersOnly]
+ private static godot_bool UnloadProjectPlugin()
+ {
+ try
+ {
+ return UnloadPlugin(ref _projectLoadContext).ToGodotBool();
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine(e);
+ return godot_bool.False;
+ }
+ }
+
+ private static bool UnloadPlugin(ref PluginLoadContextWrapper? pluginLoadContext)
+ {
+ try
+ {
+ if (pluginLoadContext == null)
+ return true;
+
+ Console.WriteLine("Unloading assembly load context...");
+
+ var alcWeakReference = pluginLoadContext.CreateWeakReference();
+
+ pluginLoadContext.Unload();
+ pluginLoadContext = null;
+
+ int startTimeMs = Environment.TickCount;
+ bool takingTooLong = false;
+
+ while (alcWeakReference.IsAlive)
+ {
+ GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
+ GC.WaitForPendingFinalizers();
+
+ if (!alcWeakReference.IsAlive)
+ break;
+
+ int elapsedTimeMs = Environment.TickCount - startTimeMs;
+
+ if (!takingTooLong && elapsedTimeMs >= 2000)
+ {
+ takingTooLong = true;
+
+ // TODO: How to log from GodotPlugins? (delegate pointer?)
+ Console.Error.WriteLine("Assembly unloading is taking longer than expected...");
+ }
+ else if (elapsedTimeMs >= 5000)
+ {
+ // TODO: How to log from GodotPlugins? (delegate pointer?)
+ Console.Error.WriteLine(
+ "Failed to unload assemblies. Possible causes: Strong GC handles, running threads, etc.");
+
+ return false;
+ }
+ }
+
+ Console.WriteLine("Assembly load context unloaded successfully.");
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ // TODO: How to log exceptions from GodotPlugins? (delegate pointer?)
+ Console.Error.WriteLine(e);
+ return false;
+ }
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
index 982549fff7..dcd572c65e 100644
--- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
+++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
@@ -12,8 +12,11 @@ namespace GodotPlugins
private readonly ICollection<string> _sharedAssemblies;
private readonly AssemblyLoadContext _mainLoadContext;
+ public string? AssemblyLoadedPath { get; private set; }
+
public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies,
AssemblyLoadContext mainLoadContext)
+ : base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
_sharedAssemblies = sharedAssemblies;
@@ -31,6 +34,8 @@ namespace GodotPlugins
string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
+ AssemblyLoadedPath = assemblyPath;
+
// Load in memory to prevent locking the file
using var assemblyFile = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read);
string pdbPath = Path.ChangeExtension(assemblyPath, ".pdb");
diff --git a/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings b/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings
index 3103fa78c7..ba65b61e95 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings
+++ b/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings
@@ -1,5 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GC/@EntryIndexedValue">GC</s:String>
+ <s:Boolean x:Key="/Default/UserDictionary/Words/=alcs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gdnative/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=godotsharp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=icall/@EntryIndexedValue">True</s:Boolean>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs
new file mode 100644
index 0000000000..ac2e2fae3c
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs
@@ -0,0 +1,18 @@
+namespace Godot.Bridge;
+
+public static class AlcReloadCfg
+{
+ private static bool _configured = false;
+
+ public static void Configure(bool alcReloadEnabled)
+ {
+ if (_configured)
+ return;
+
+ _configured = true;
+
+ IsAlcReloadingEnabled = alcReloadEnabled;
+ }
+
+ internal static bool IsAlcReloadingEnabled = false;
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
index db53a508ef..9ede67b285 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
@@ -18,7 +18,7 @@ namespace Godot.Bridge
{
*ret = default;
(*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL;
- return false.ToGodotBool();
+ return godot_bool.False;
}
bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method),
@@ -31,17 +31,17 @@ namespace Godot.Bridge
// This is important, as it tells Object::call that no method was called.
// Otherwise, it would prevent Object::call from calling native methods.
(*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD;
- return false.ToGodotBool();
+ return godot_bool.False;
}
*ret = retValue;
- return true.ToGodotBool();
+ return godot_bool.True;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*ret = default;
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
@@ -57,7 +57,7 @@ namespace Godot.Bridge
if (godotObject.SetGodotClassPropertyValue(CustomUnsafe.AsRef(name), CustomUnsafe.AsRef(value)))
{
- return true.ToGodotBool();
+ return godot_bool.True;
}
var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
@@ -70,7 +70,7 @@ namespace Godot.Bridge
catch (Exception e)
{
ExceptionUtils.LogException(e);
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
@@ -88,7 +88,7 @@ namespace Godot.Bridge
if (godotObject.GetGodotClassPropertyValue(CustomUnsafe.AsRef(name), out godot_variant outRetValue))
{
*outRet = outRetValue;
- return true.ToGodotBool();
+ return godot_bool.True;
}
var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
@@ -99,17 +99,17 @@ namespace Godot.Bridge
if (ret == null)
{
*outRet = default;
- return false.ToGodotBool();
+ return godot_bool.False;
}
*outRet = Marshaling.ConvertManagedObjectToVariant(ret);
- return true.ToGodotBool();
+ return godot_bool.True;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*outRet = default;
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
@@ -141,7 +141,7 @@ namespace Godot.Bridge
if (self == null)
{
*outRes = default;
- *outValid = false.ToGodotBool();
+ *outValid = godot_bool.False;
return;
}
@@ -150,18 +150,18 @@ namespace Godot.Bridge
if (resultStr == null)
{
*outRes = default;
- *outValid = false.ToGodotBool();
+ *outValid = godot_bool.False;
return;
}
*outRes = Marshaling.ConvertStringToNative(resultStr);
- *outValid = true.ToGodotBool();
+ *outValid = godot_bool.True;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*outRes = default;
- *outValid = false.ToGodotBool();
+ *outValid = godot_bool.False;
}
}
@@ -173,14 +173,86 @@ namespace Godot.Bridge
var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
if (godotObject == null)
- return false.ToGodotBool();
+ return godot_bool.False;
return godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(method)).ToGodotBool();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
- return false.ToGodotBool();
+ return godot_bool.False;
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ internal static unsafe void SerializeState(
+ IntPtr godotObjectGCHandle,
+ godot_dictionary* propertiesState,
+ godot_dictionary* signalEventsState
+ )
+ {
+ try
+ {
+ var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
+
+ if (godotObject == null)
+ return;
+
+ // Call OnBeforeSerialize
+
+ // ReSharper disable once SuspiciousTypeConversion.Global
+ if (godotObject is ISerializationListener serializationListener)
+ serializationListener.OnBeforeSerialize();
+
+ // Save instance state
+
+ var info = new GodotSerializationInfo(
+ Collections.Dictionary<StringName, object>.CreateTakingOwnershipOfDisposableValue(
+ NativeFuncs.godotsharp_dictionary_new_copy(*propertiesState)),
+ Collections.Dictionary<StringName, Collections.Array>.CreateTakingOwnershipOfDisposableValue(
+ NativeFuncs.godotsharp_dictionary_new_copy(*signalEventsState)));
+
+ godotObject.SaveGodotObjectData(info);
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.LogException(e);
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ internal static unsafe void DeserializeState(
+ IntPtr godotObjectGCHandle,
+ godot_dictionary* propertiesState,
+ godot_dictionary* signalEventsState
+ )
+ {
+ try
+ {
+ var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
+
+ if (godotObject == null)
+ return;
+
+ // Restore instance state
+
+ var info = new GodotSerializationInfo(
+ Collections.Dictionary<StringName, object>.CreateTakingOwnershipOfDisposableValue(
+ NativeFuncs.godotsharp_dictionary_new_copy(*propertiesState)),
+ Collections.Dictionary<StringName, Collections.Array>.CreateTakingOwnershipOfDisposableValue(
+ NativeFuncs.godotsharp_dictionary_new_copy(*signalEventsState)));
+
+ godotObject.RestoreGodotObjectData(info);
+
+ // Call OnAfterDeserialize
+
+ // ReSharper disable once SuspiciousTypeConversion.Global
+ if (godotObject is ISerializationListener serializationListener)
+ serializationListener.OnAfterDeserialize();
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.LogException(e);
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs
index bd11811c7d..456a118b90 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs
@@ -11,7 +11,7 @@ namespace Godot.Bridge
{
try
{
- GCHandle.FromIntPtr(gcHandlePtr).Free();
+ CustomGCHandle.Free(GCHandle.FromIntPtr(gcHandlePtr));
}
catch (Exception e)
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs
new file mode 100644
index 0000000000..26fbed8cac
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Godot.Bridge;
+
+public class GodotSerializationInfo
+{
+ private readonly Collections.Dictionary<StringName, object> _properties = new();
+ private readonly Collections.Dictionary<StringName, Collections.Array> _signalEvents = new();
+
+ internal GodotSerializationInfo()
+ {
+ }
+
+ internal GodotSerializationInfo(
+ Collections.Dictionary<StringName, object> properties,
+ Collections.Dictionary<StringName, Collections.Array> signalEvents
+ )
+ {
+ _properties = properties;
+ _signalEvents = signalEvents;
+ }
+
+ public void AddProperty(StringName name, object value)
+ {
+ _properties[name] = value;
+ }
+
+ public bool TryGetProperty<T>(StringName name, [MaybeNullWhen(false)] out T value)
+ {
+ return _properties.TryGetValueAsType(name, out value);
+ }
+
+ public void AddSignalEventDelegate(StringName name, Delegate eventDelegate)
+ {
+ var serializedData = new Collections.Array();
+
+ if (DelegateUtils.TrySerializeDelegate(eventDelegate, serializedData))
+ {
+ _signalEvents[name] = serializedData;
+ }
+ else if (OS.IsStdoutVerbose())
+ {
+ Console.WriteLine($"Failed to serialize event signal delegate: {name}");
+ }
+ }
+
+ public Delegate GetSignalEventDelegate(StringName name)
+ {
+ if (DelegateUtils.TryDeserializeDelegate(_signalEvents[name], out var eventDelegate))
+ {
+ return eventDelegate;
+ }
+ else if (OS.IsStdoutVerbose())
+ {
+ Console.WriteLine($"Failed to deserialize event signal delegate: {name}");
+ }
+
+ return null;
+ }
+
+ public IEnumerable<StringName> GetSignalEventsList()
+ {
+ return _signalEvents.Keys;
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index 9f9d740659..a6e5f6da1a 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -11,6 +11,8 @@ namespace Godot.Bridge
public delegate* unmanaged<IntPtr, godot_variant**, int, godot_bool*, void> SignalAwaiter_SignalCallback;
public delegate* unmanaged<IntPtr, godot_variant**, uint, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs;
public delegate* unmanaged<IntPtr, IntPtr, godot_bool> DelegateUtils_DelegateEquals;
+ public delegate* unmanaged<IntPtr, godot_array*, godot_bool> DelegateUtils_TrySerializeDelegateWithGCHandle;
+ public delegate* unmanaged<godot_array*, IntPtr*, godot_bool> DelegateUtils_TryDeserializeDelegateWithGCHandle;
public delegate* unmanaged<void> ScriptManagerBridge_FrameCallback;
public delegate* unmanaged<godot_string_name*, IntPtr, IntPtr> ScriptManagerBridge_CreateManagedForGodotObjectBinding;
public delegate* unmanaged<IntPtr, IntPtr, godot_variant**, int, godot_bool> ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
@@ -23,7 +25,8 @@ namespace Godot.Bridge
public delegate* unmanaged<IntPtr, godot_string*, godot_bool> ScriptManagerBridge_AddScriptBridge;
public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
- public delegate* unmanaged<IntPtr, godot_bool*, godot_dictionary*, void> ScriptManagerBridge_UpdateScriptClassInfo;
+ public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
+ public delegate* unmanaged<IntPtr, godot_bool*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;
@@ -33,6 +36,8 @@ namespace Godot.Bridge
public delegate* unmanaged<IntPtr, godot_bool, void> CSharpInstanceBridge_CallDispose;
public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, void> CSharpInstanceBridge_CallToString;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_bool> CSharpInstanceBridge_HasMethodUnknownParams;
+ public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_SerializeState;
+ public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_DeserializeState;
public delegate* unmanaged<IntPtr, void> GCHandleBridge_FreeGCHandle;
public delegate* unmanaged<void*, void> DebuggingUtils_GetCurrentStackInfo;
public delegate* unmanaged<void> DisposablesTracker_OnGodotShuttingDown;
@@ -47,6 +52,8 @@ namespace Godot.Bridge
SignalAwaiter_SignalCallback = &SignalAwaiter.SignalCallback,
DelegateUtils_InvokeWithVariantArgs = &DelegateUtils.InvokeWithVariantArgs,
DelegateUtils_DelegateEquals = &DelegateUtils.DelegateEquals,
+ DelegateUtils_TrySerializeDelegateWithGCHandle = &DelegateUtils.TrySerializeDelegateWithGCHandle,
+ DelegateUtils_TryDeserializeDelegateWithGCHandle = &DelegateUtils.TryDeserializeDelegateWithGCHandle,
ScriptManagerBridge_FrameCallback = &ScriptManagerBridge.FrameCallback,
ScriptManagerBridge_CreateManagedForGodotObjectBinding = &ScriptManagerBridge.CreateManagedForGodotObjectBinding,
ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = &ScriptManagerBridge.CreateManagedForGodotObjectScriptInstance,
@@ -59,6 +66,7 @@ namespace Godot.Bridge
ScriptManagerBridge_AddScriptBridge = &ScriptManagerBridge.AddScriptBridge,
ScriptManagerBridge_GetOrCreateScriptBridgeForPath = &ScriptManagerBridge.GetOrCreateScriptBridgeForPath,
ScriptManagerBridge_RemoveScriptBridge = &ScriptManagerBridge.RemoveScriptBridge,
+ ScriptManagerBridge_TryReloadRegisteredScriptWithClass = &ScriptManagerBridge.TryReloadRegisteredScriptWithClass,
ScriptManagerBridge_UpdateScriptClassInfo = &ScriptManagerBridge.UpdateScriptClassInfo,
ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType,
ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList,
@@ -69,6 +77,8 @@ namespace Godot.Bridge
CSharpInstanceBridge_CallDispose = &CSharpInstanceBridge.CallDispose,
CSharpInstanceBridge_CallToString = &CSharpInstanceBridge.CallToString,
CSharpInstanceBridge_HasMethodUnknownParams = &CSharpInstanceBridge.HasMethodUnknownParams,
+ CSharpInstanceBridge_SerializeState = &CSharpInstanceBridge.SerializeState,
+ CSharpInstanceBridge_DeserializeState = &CSharpInstanceBridge.DeserializeState,
GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle,
DebuggingUtils_GetCurrentStackInfo = &DebuggingUtils.GetCurrentStackInfo,
DisposablesTracker_OnGodotShuttingDown = &DisposablesTracker.OnGodotShuttingDown,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 61987c6466..40f1235e7e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -1,22 +1,77 @@
+#nullable enable
+
using System;
+using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Loader;
using System.Runtime.Serialization;
using Godot.Collections;
using Godot.NativeInterop;
namespace Godot.Bridge
{
- public static class ScriptManagerBridge
+ // TODO: Make class internal once we replace LookupScriptsInAssembly (the only public member) with source generators
+ public static partial class ScriptManagerBridge
{
- private static System.Collections.Generic.Dictionary<string, Type> _pathScriptMap = new();
+ private static ConcurrentDictionary<AssemblyLoadContext, ConcurrentDictionary<Type, byte>>
+ _alcData = new();
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void OnAlcUnloading(AssemblyLoadContext alc)
+ {
+ if (_alcData.TryRemove(alc, out var typesInAlc))
+ {
+ foreach (var type in typesInAlc.Keys)
+ {
+ if (_scriptTypeBiMap.RemoveByScriptType(type, out IntPtr scriptPtr) &&
+ !_pathTypeBiMap.TryGetScriptPath(type, out _))
+ {
+ // For scripts without a path, we need to keep the class qualified name for reloading
+ _scriptDataForReload.TryAdd(scriptPtr,
+ (type.Assembly.GetName().Name, type.FullName ?? type.ToString()));
+ }
+
+ _pathTypeBiMap.RemoveByScriptType(type);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void AddTypeForAlcReloading(Type type)
+ {
+ var alc = AssemblyLoadContext.GetLoadContext(type.Assembly);
+ if (alc == null)
+ return;
+
+ var typesInAlc = _alcData.GetOrAdd(alc,
+ static alc =>
+ {
+ alc.Unloading += OnAlcUnloading;
+ return new();
+ });
+ typesInAlc.TryAdd(type, 0);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void TrackAlcForUnloading(AssemblyLoadContext alc)
+ {
+ _ = _alcData.GetOrAdd(alc,
+ static alc =>
+ {
+ alc.Unloading += OnAlcUnloading;
+ return new();
+ });
+ }
+
+ private static ScriptTypeBiMap _scriptTypeBiMap = new();
+ private static PathScriptTypeBiMap _pathTypeBiMap = new();
- private static readonly object ScriptBridgeLock = new();
- private static System.Collections.Generic.Dictionary<IntPtr, Type> _scriptTypeMap = new();
- private static System.Collections.Generic.Dictionary<Type, IntPtr> _typeScriptMap = new();
+ private static ConcurrentDictionary<IntPtr, (string? assemblyName, string classFullName)>
+ _scriptDataForReload = new();
[UnmanagedCallersOnly]
internal static void FrameCallback()
@@ -55,7 +110,7 @@ namespace Godot.Bridge
_ = ctor!.Invoke(obj, null);
- return GCHandle.ToIntPtr(GCHandle.Alloc(obj));
+ return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(obj));
}
catch (Exception e)
{
@@ -74,7 +129,7 @@ namespace Godot.Bridge
try
{
// Performance is not critical here as this will be replaced with source generators.
- Type scriptType = _scriptTypeMap[scriptPtr];
+ Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
var obj = (Object)FormatterServices.GetUninitializedObject(scriptType);
var ctor = scriptType
@@ -99,7 +154,7 @@ namespace Godot.Bridge
var parameters = ctor.GetParameters();
int paramCount = parameters.Length;
- object[] invokeParams = new object[paramCount];
+ var invokeParams = new object?[paramCount];
for (int i = 0; i < paramCount; i++)
{
@@ -112,12 +167,12 @@ namespace Godot.Bridge
_ = ctor.Invoke(obj, invokeParams);
- return true.ToGodotBool();
+ return godot_bool.True;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
@@ -127,7 +182,7 @@ namespace Godot.Bridge
try
{
// Performance is not critical here as this will be replaced with source generators.
- if (!_scriptTypeMap.TryGetValue(scriptPtr, out var scriptType))
+ if (!_scriptTypeBiMap.TryGetScriptType(scriptPtr, out Type? scriptType))
{
*outRes = default;
return;
@@ -144,7 +199,7 @@ namespace Godot.Bridge
return;
}
- var nativeName = (StringName)field.GetValue(null);
+ var nativeName = (StringName?)field.GetValue(null);
if (nativeName == null)
{
@@ -166,7 +221,7 @@ namespace Godot.Bridge
{
try
{
- var target = (Object)GCHandle.FromIntPtr(gcHandlePtr).Target;
+ var target = (Object?)GCHandle.FromIntPtr(gcHandlePtr).Target;
if (target != null)
target.NativePtr = newPtr;
}
@@ -176,14 +231,14 @@ namespace Godot.Bridge
}
}
- private static Type TypeGetProxyClass(string nativeTypeNameStr)
+ private static Type? TypeGetProxyClass(string nativeTypeNameStr)
{
// Performance is not critical here as this will be replaced with a generated dictionary.
if (nativeTypeNameStr[0] == '_')
nativeTypeNameStr = nativeTypeNameStr.Substring(1);
- Type wrapperType = typeof(Object).Assembly.GetType("Godot." + nativeTypeNameStr);
+ Type? wrapperType = typeof(Object).Assembly.GetType("Godot." + nativeTypeNameStr);
if (wrapperType == null)
{
@@ -216,7 +271,12 @@ namespace Godot.Bridge
if (scriptPathAttr == null)
return;
- _pathScriptMap[scriptPathAttr.Path] = type;
+ _pathTypeBiMap.Add(scriptPathAttr.Path, type);
+
+ if (AlcReloadCfg.IsAlcReloadingEnabled)
+ {
+ AddTypeForAlcReloading(type);
+ }
}
var assemblyHasScriptsAttr = assembly.GetCustomAttributes(inherit: false)
@@ -267,15 +327,15 @@ namespace Godot.Bridge
{
try
{
- var owner = (Object)GCHandle.FromIntPtr(ownerGCHandlePtr).Target;
+ var owner = (Object?)GCHandle.FromIntPtr(ownerGCHandlePtr).Target;
if (owner == null)
{
- *outOwnerIsNull = true.ToGodotBool();
+ *outOwnerIsNull = godot_bool.True;
return;
}
- *outOwnerIsNull = false.ToGodotBool();
+ *outOwnerIsNull = godot_bool.False;
owner.InternalRaiseEventSignal(CustomUnsafe.AsRef(eventSignalName),
new NativeVariantPtrArgs(args), argCount);
@@ -283,7 +343,7 @@ namespace Godot.Bridge
catch (Exception e)
{
ExceptionUtils.LogException(e);
- *outOwnerIsNull = false.ToGodotBool();
+ *outOwnerIsNull = godot_bool.False;
}
}
@@ -295,7 +355,7 @@ namespace Godot.Bridge
// Performance is not critical here as this will be replaced with source generators.
using var signals = new Dictionary();
- Type top = _scriptTypeMap[scriptPtr];
+ Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
Type native = Object.InternalGetClassNativeBase(top);
while (top != null && top != native)
@@ -391,7 +451,7 @@ namespace Godot.Bridge
string signalNameStr = Marshaling.ConvertStringToManaged(*signalName);
- Type top = _scriptTypeMap[scriptPtr];
+ Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
Type native = Object.InternalGetClassNativeBase(top);
while (top != null && top != native)
@@ -405,7 +465,7 @@ namespace Godot.Bridge
.Any(signalDelegate => signalDelegate.Name == signalNameStr)
)
{
- return true.ToGodotBool();
+ return godot_bool.True;
}
// Event signals
@@ -417,18 +477,18 @@ namespace Godot.Bridge
.Any(eventSignal => eventSignal.Name == signalNameStr)
)
{
- return true.ToGodotBool();
+ return godot_bool.True;
}
top = top.BaseType;
}
- return false.ToGodotBool();
+ return godot_bool.False;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
@@ -437,18 +497,18 @@ namespace Godot.Bridge
{
try
{
- if (!_scriptTypeMap.TryGetValue(scriptPtr, out var scriptType))
- return false.ToGodotBool();
+ if (!_scriptTypeBiMap.TryGetScriptType(scriptPtr, out Type? scriptType))
+ return godot_bool.False;
- if (!_scriptTypeMap.TryGetValue(scriptPtrMaybeBase, out var maybeBaseType))
- return false.ToGodotBool();
+ if (!_scriptTypeBiMap.TryGetScriptType(scriptPtrMaybeBase, out Type? maybeBaseType))
+ return godot_bool.False;
return (scriptType == maybeBaseType || maybeBaseType.IsAssignableFrom(scriptType)).ToGodotBool();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
@@ -457,26 +517,25 @@ namespace Godot.Bridge
{
try
{
- lock (ScriptBridgeLock)
+ lock (_scriptTypeBiMap.ReadWriteLock)
{
- if (!_scriptTypeMap.ContainsKey(scriptPtr))
+ if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr))
{
string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
- if (!_pathScriptMap.TryGetValue(scriptPathStr, out Type scriptType))
- return false.ToGodotBool();
+ if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType))
+ return godot_bool.False;
- _scriptTypeMap.Add(scriptPtr, scriptType);
- _typeScriptMap.Add(scriptType, scriptPtr);
+ _scriptTypeBiMap.Add(scriptPtr, scriptType);
}
}
- return true.ToGodotBool();
+ return godot_bool.True;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
@@ -485,7 +544,7 @@ namespace Godot.Bridge
{
string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
- if (!_pathScriptMap.TryGetValue(scriptPathStr, out Type scriptType))
+ if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType))
{
NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
return;
@@ -494,34 +553,84 @@ namespace Godot.Bridge
GetOrCreateScriptBridgeForType(scriptType, outScript);
}
- internal static unsafe void GetOrCreateScriptBridgeForType(Type scriptType, godot_ref* outScript)
+ private static unsafe void GetOrCreateScriptBridgeForType(Type scriptType, godot_ref* outScript)
{
- lock (ScriptBridgeLock)
+ lock (_scriptTypeBiMap.ReadWriteLock)
{
- if (_typeScriptMap.TryGetValue(scriptType, out IntPtr scriptPtr))
+ if (_scriptTypeBiMap.TryGetScriptPtr(scriptType, out IntPtr scriptPtr))
{
+ // Use existing
NativeFuncs.godotsharp_ref_new_from_ref_counted_ptr(out *outScript, scriptPtr);
return;
}
- NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
- scriptPtr = outScript->Reference;
+ // This path is slower, but it's only executed for the first instantiation of the type
+ CreateScriptBridgeForType(scriptType, outScript);
+ }
+ }
- _scriptTypeMap.Add(scriptPtr, scriptType);
- _typeScriptMap.Add(scriptType, scriptPtr);
+ internal static unsafe void GetOrLoadOrCreateScriptForType(Type scriptType, godot_ref* outScript)
+ {
+ static bool GetPathOtherwiseGetOrCreateScript(Type scriptType, godot_ref* outScript,
+ [MaybeNullWhen(false)] out string scriptPath)
+ {
+ lock (_scriptTypeBiMap.ReadWriteLock)
+ {
+ if (_scriptTypeBiMap.TryGetScriptPtr(scriptType, out IntPtr scriptPtr))
+ {
+ // Use existing
+ NativeFuncs.godotsharp_ref_new_from_ref_counted_ptr(out *outScript, scriptPtr);
+ scriptPath = null;
+ return false;
+ }
- NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr);
+ // This path is slower, but it's only executed for the first instantiation of the type
+
+ if (_pathTypeBiMap.TryGetScriptPath(scriptType, out scriptPath))
+ return true;
+
+ CreateScriptBridgeForType(scriptType, outScript);
+ scriptPath = null;
+ return false;
+ }
+ }
+
+ if (GetPathOtherwiseGetOrCreateScript(scriptType, outScript, out string? scriptPath))
+ {
+ // This path is slower, but it's only executed for the first instantiation of the type
+
+ // This must be done outside the read-write lock, as the script resource loading can lock it
+ using godot_string scriptPathIn = Marshaling.ConvertStringToNative(scriptPath);
+ if (!NativeFuncs.godotsharp_internal_script_load(scriptPathIn, outScript).ToBool())
+ {
+ GD.PushError($"Cannot load script for type '{scriptType.FullName}'. Path: '{scriptPath}'.");
+
+ // If loading of the script fails, best we can do create a new script
+ // with no path, as we do for types without an associated script file.
+ GetOrCreateScriptBridgeForType(scriptType, outScript);
+ }
}
}
+ private static unsafe void CreateScriptBridgeForType(Type scriptType, godot_ref* outScript)
+ {
+ NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
+ IntPtr scriptPtr = outScript->Reference;
+
+ // Caller takes care of locking
+ _scriptTypeBiMap.Add(scriptPtr, scriptType);
+
+ NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr);
+ }
+
[UnmanagedCallersOnly]
internal static void RemoveScriptBridge(IntPtr scriptPtr)
{
try
{
- lock (ScriptBridgeLock)
+ lock (_scriptTypeBiMap.ReadWriteLock)
{
- _ = _scriptTypeMap.Remove(scriptPtr);
+ _scriptTypeBiMap.Remove(scriptPtr);
}
}
catch (Exception e)
@@ -531,13 +640,74 @@ namespace Godot.Bridge
}
[UnmanagedCallersOnly]
+ internal static godot_bool TryReloadRegisteredScriptWithClass(IntPtr scriptPtr)
+ {
+ try
+ {
+ lock (_scriptTypeBiMap.ReadWriteLock)
+ {
+ if (_scriptTypeBiMap.TryGetScriptType(scriptPtr, out _))
+ {
+ // NOTE:
+ // Currently, we reload all scripts, not only the ones from the unloaded ALC.
+ // As such, we need to handle this case instead of treating it as an error.
+ NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr);
+ return godot_bool.True;
+ }
+
+ if (!_scriptDataForReload.TryGetValue(scriptPtr, out var dataForReload))
+ {
+ GD.PushError("Missing class qualified name for reloading script");
+ return godot_bool.False;
+ }
+
+ _ = _scriptDataForReload.TryRemove(scriptPtr, out _);
+
+ if (dataForReload.assemblyName == null)
+ {
+ GD.PushError(
+ $"Missing assembly name of class '{dataForReload.classFullName}' for reloading script");
+ return godot_bool.False;
+ }
+
+ var scriptType = ReflectionUtils.FindTypeInLoadedAssemblies(dataForReload.assemblyName,
+ dataForReload.classFullName);
+
+ if (scriptType == null)
+ {
+ // The class was removed, can't reload
+ return godot_bool.False;
+ }
+
+ // ReSharper disable once RedundantNameQualifier
+ if (!typeof(Godot.Object).IsAssignableFrom(scriptType))
+ {
+ // The class no longer inherits Godot.Object, can't reload
+ return godot_bool.False;
+ }
+
+ _scriptTypeBiMap.Add(scriptPtr, scriptType);
+
+ NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr);
+
+ return godot_bool.True;
+ }
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.LogException(e);
+ return godot_bool.False;
+ }
+ }
+
+ [UnmanagedCallersOnly]
internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool,
- godot_dictionary* outRpcFunctionsDest)
+ godot_dictionary* outRpcFunctionsDest, godot_ref* outBaseScript)
{
try
{
// Performance is not critical here as this will be replaced with source generators.
- var scriptType = _scriptTypeMap[scriptPtr];
+ var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
*outTool = scriptType.GetCustomAttributes(inherit: false)
.OfType<ToolAttribute>()
@@ -551,13 +721,13 @@ namespace Godot.Bridge
}
if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools")
- *outTool = true.ToGodotBool();
+ *outTool = godot_bool.True;
// RPC functions
Dictionary<string, Dictionary> rpcFunctions = new();
- Type top = scriptType;
+ Type? top = scriptType;
Type native = Object.InternalGetClassNativeBase(top);
while (top != null && top != native)
@@ -595,11 +765,21 @@ namespace Godot.Bridge
*outRpcFunctionsDest =
NativeFuncs.godotsharp_dictionary_new_copy(
(godot_dictionary)((Dictionary)rpcFunctions).NativeValue);
+
+ var baseType = scriptType.BaseType;
+ if (baseType != null && baseType != native)
+ {
+ GetOrLoadOrCreateScriptForType(baseType, outBaseScript);
+ }
+ else
+ {
+ *outBaseScript = default;
+ }
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
- *outTool = false.ToGodotBool();
+ *outTool = godot_bool.False;
*outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
}
}
@@ -612,28 +792,29 @@ namespace Godot.Bridge
{
var oldGCHandle = GCHandle.FromIntPtr(oldGCHandlePtr);
- object target = oldGCHandle.Target;
+ object? target = oldGCHandle.Target;
if (target == null)
{
- oldGCHandle.Free();
+ CustomGCHandle.Free(oldGCHandle);
*outNewGCHandlePtr = IntPtr.Zero;
- return false.ToGodotBool(); // Called after the managed side was collected, so nothing to do here
+ return godot_bool.False; // Called after the managed side was collected, so nothing to do here
}
// Release the current weak handle and replace it with a strong handle.
- var newGCHandle = GCHandle.Alloc(target,
- createWeak.ToBool() ? GCHandleType.Weak : GCHandleType.Normal);
+ var newGCHandle = createWeak.ToBool() ?
+ CustomGCHandle.AllocWeak(target) :
+ CustomGCHandle.AllocStrong(target);
- oldGCHandle.Free();
+ CustomGCHandle.Free(oldGCHandle);
*outNewGCHandlePtr = GCHandle.ToIntPtr(newGCHandle);
- return true.ToGodotBool();
+ return godot_bool.True;
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
*outNewGCHandlePtr = IntPtr.Zero;
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
@@ -662,7 +843,7 @@ namespace Godot.Bridge
{
try
{
- Type scriptType = _scriptTypeMap[scriptPtr];
+ Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
GetPropertyInfoListForType(scriptType, scriptPtr, addPropInfoFunc);
}
catch (Exception e)
@@ -684,7 +865,7 @@ namespace Godot.Bridge
if (getGodotPropertiesMetadataMethod == null)
return;
- var properties = (System.Collections.Generic.List<PropertyInfo>)
+ var properties = (System.Collections.Generic.List<PropertyInfo>?)
getGodotPropertiesMetadataMethod.Invoke(null, null);
if (properties == null || properties.Count <= 0)
@@ -774,7 +955,7 @@ namespace Godot.Bridge
{
try
{
- Type top = _scriptTypeMap[scriptPtr];
+ Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
Type native = Object.InternalGetClassNativeBase(top);
while (top != null && top != native)
@@ -804,7 +985,7 @@ namespace Godot.Bridge
if (getGodotPropertyDefaultValuesMethod == null)
return;
- var defaultValues = (System.Collections.Generic.Dictionary<StringName, object>)
+ var defaultValues = (System.Collections.Generic.Dictionary<StringName, object>?)
getGodotPropertyDefaultValuesMethod.Invoke(null, null);
if (defaultValues == null || defaultValues.Count <= 0)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
new file mode 100644
index 0000000000..a58f6849ad
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Godot.Bridge;
+
+#nullable enable
+
+public static partial class ScriptManagerBridge
+{
+ private class ScriptTypeBiMap
+ {
+ public readonly object ReadWriteLock = new();
+ private System.Collections.Generic.Dictionary<IntPtr, Type> _scriptTypeMap = new();
+ private System.Collections.Generic.Dictionary<Type, IntPtr> _typeScriptMap = new();
+
+ public void Add(IntPtr scriptPtr, Type scriptType)
+ {
+ // TODO: What if this is called while unloading a load context, but after we already did cleanup in preparation for unloading?
+
+ _scriptTypeMap.Add(scriptPtr, scriptType);
+ _typeScriptMap.Add(scriptType, scriptPtr);
+
+ if (AlcReloadCfg.IsAlcReloadingEnabled)
+ {
+ AddTypeForAlcReloading(scriptType);
+ }
+ }
+
+ public void Remove(IntPtr scriptPtr)
+ {
+ if (_scriptTypeMap.Remove(scriptPtr, out Type? scriptType))
+ _ = _typeScriptMap.Remove(scriptType);
+ }
+
+ public bool RemoveByScriptType(Type scriptType, out IntPtr scriptPtr)
+ {
+ if (_typeScriptMap.Remove(scriptType, out scriptPtr))
+ return _scriptTypeMap.Remove(scriptPtr);
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Type GetScriptType(IntPtr scriptPtr) => _scriptTypeMap[scriptPtr];
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryGetScriptType(IntPtr scriptPtr, [MaybeNullWhen(false)] out Type scriptType) =>
+ _scriptTypeMap.TryGetValue(scriptPtr, out scriptType);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryGetScriptPtr(Type scriptType, out IntPtr scriptPtr) =>
+ _typeScriptMap.TryGetValue(scriptType, out scriptPtr);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsScriptRegistered(IntPtr scriptPtr) => _scriptTypeMap.ContainsKey(scriptPtr);
+ }
+
+ private class PathScriptTypeBiMap
+ {
+ private System.Collections.Generic.Dictionary<string, Type> _pathTypeMap = new();
+ private System.Collections.Generic.Dictionary<Type, string> _typePathMap = new();
+
+ public void Add(string scriptPath, Type scriptType)
+ {
+ _pathTypeMap.Add(scriptPath, scriptType);
+
+ // Due to partial classes, more than one file can point to the same type, so
+ // there could be duplicate keys in this case. We only add a type as key once.
+ _typePathMap.TryAdd(scriptType, scriptPath);
+ }
+
+ public void RemoveByScriptType(Type scriptType)
+ {
+ foreach (var pair in _pathTypeMap
+ .Where(p => p.Value == scriptType).ToArray())
+ {
+ _pathTypeMap.Remove(pair.Key);
+ }
+
+ _typePathMap.Remove(scriptType);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryGetScriptType(string scriptPath, [MaybeNullWhen(false)] out Type scriptType) =>
+ _pathTypeMap.TryGetValue(scriptPath, out scriptType);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryGetScriptPath(Type scriptType, [MaybeNullWhen(false)] out string scriptPath) =>
+ _typePathMap.TryGetValue(scriptType, out scriptPath);
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs
new file mode 100644
index 0000000000..42f19ace1a
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs
@@ -0,0 +1,98 @@
+#nullable enable
+
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Loader;
+using Godot.Bridge;
+
+namespace Godot;
+
+/// <summary>
+/// Provides a GCHandle that becomes weak when unloading the assembly load context, without having
+/// to manually replace the GCHandle. This hides all the complexity of releasing strong GC handles
+/// to allow the assembly load context to unload properly.
+///
+/// Internally, a strong CustomGCHandle actually contains a weak GCHandle, while the actual strong
+/// reference is stored in a static table.
+/// </summary>
+public static class CustomGCHandle
+{
+ // ConditionalWeakTable uses DependentHandle, so it stores weak references.
+ // Having the assembly load context as key won't prevent it from unloading.
+ private static ConditionalWeakTable<AssemblyLoadContext, object?> _alcsBeingUnloaded = new();
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static bool IsAlcBeingUnloaded(AssemblyLoadContext alc) => _alcsBeingUnloaded.TryGetValue(alc, out _);
+
+ // ReSharper disable once RedundantNameQualifier
+ private static ConcurrentDictionary<
+ AssemblyLoadContext,
+ ConcurrentDictionary<GCHandle, object>
+ > _strongReferencesByAlc = new();
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void OnAlcUnloading(AssemblyLoadContext alc)
+ {
+ _alcsBeingUnloaded.Add(alc, null);
+
+ if (_strongReferencesByAlc.TryRemove(alc, out var strongReferences))
+ {
+ strongReferences.Clear();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static GCHandle AllocStrong(object value)
+ => AllocStrong(value, value.GetType());
+
+ public static GCHandle AllocStrong(object value, Type valueType)
+ {
+ if (AlcReloadCfg.IsAlcReloadingEnabled)
+ {
+ var alc = AssemblyLoadContext.GetLoadContext(valueType.Assembly);
+
+ if (alc != null)
+ {
+ var weakHandle = GCHandle.Alloc(value, GCHandleType.Weak);
+
+ if (!IsAlcBeingUnloaded(alc))
+ {
+ var strongReferences = _strongReferencesByAlc.GetOrAdd(alc,
+ static alc =>
+ {
+ alc.Unloading += OnAlcUnloading;
+ return new();
+ });
+ strongReferences.TryAdd(weakHandle, value);
+ }
+
+ return weakHandle;
+ }
+ }
+
+ return GCHandle.Alloc(value, GCHandleType.Normal);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static GCHandle AllocWeak(object value) => GCHandle.Alloc(value, GCHandleType.Weak);
+
+ public static void Free(GCHandle handle)
+ {
+ if (AlcReloadCfg.IsAlcReloadingEnabled)
+ {
+ var target = handle.Target;
+
+ if (target != null)
+ {
+ var alc = AssemblyLoadContext.GetLoadContext(target.GetType().Assembly);
+
+ if (alc != null && _strongReferencesByAlc.TryGetValue(alc, out var strongReferences))
+ _ = strongReferences.TryRemove(handle, out _);
+ }
+ }
+
+ handle.Free();
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
index fb5e3c6dda..48eec66182 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
@@ -1,6 +1,8 @@
+#nullable enable
+
using System;
using System.Collections.Generic;
-using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
@@ -16,14 +18,14 @@ namespace Godot
{
try
{
- var @delegateA = (Delegate)GCHandle.FromIntPtr(delegateGCHandleA).Target;
- var @delegateB = (Delegate)GCHandle.FromIntPtr(delegateGCHandleB).Target;
- return (@delegateA == @delegateB).ToGodotBool();
+ var @delegateA = (Delegate?)GCHandle.FromIntPtr(delegateGCHandleA).Target;
+ var @delegateB = (Delegate?)GCHandle.FromIntPtr(delegateGCHandleB).Target;
+ return (@delegateA! == @delegateB!).ToGodotBool();
}
catch (Exception e)
{
ExceptionUtils.LogException(e);
- return false.ToGodotBool();
+ return godot_bool.False;
}
}
@@ -34,10 +36,10 @@ namespace Godot
try
{
// TODO: Optimize
- var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target;
- var managedArgs = new object[argc];
+ var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!;
+ var managedArgs = new object?[argc];
- var parameterInfos = @delegate!.Method.GetParameters();
+ var parameterInfos = @delegate.Method.GetParameters();
var paramsLength = parameterInfos.Length;
if (argc != paramsLength)
@@ -52,7 +54,7 @@ namespace Godot
*args[i], parameterInfos[i].ParameterType);
}
- object invokeRet = @delegate.DynamicInvoke(managedArgs);
+ object? invokeRet = @delegate.DynamicInvoke(managedArgs);
*outRet = Marshaling.ConvertManagedObjectToVariant(invokeRet);
}
@@ -72,10 +74,7 @@ namespace Godot
CompilerGenerated
}
- internal static bool TrySerializeDelegateWithGCHandle(IntPtr delegateGCHandle, Collections.Array serializedData)
- => TrySerializeDelegate((Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target, serializedData);
-
- private static bool TrySerializeDelegate(Delegate @delegate, Collections.Array serializedData)
+ internal static bool TrySerializeDelegate(Delegate @delegate, Collections.Array serializedData)
{
if (@delegate is MulticastDelegate multicastDelegate)
{
@@ -98,7 +97,7 @@ namespace Godot
}
}
- if (TrySerializeSingleDelegate(@delegate, out byte[] buffer))
+ if (TrySerializeSingleDelegate(@delegate, out byte[]? buffer))
{
serializedData.Add(buffer);
return true;
@@ -107,11 +106,11 @@ namespace Godot
return false;
}
- private static bool TrySerializeSingleDelegate(Delegate @delegate, out byte[] buffer)
+ private static bool TrySerializeSingleDelegate(Delegate @delegate, [MaybeNullWhen(false)] out byte[] buffer)
{
buffer = null;
- object target = @delegate.Target;
+ object? target = @delegate.Target;
switch (target)
{
@@ -200,9 +199,6 @@ namespace Godot
private static bool TrySerializeMethodInfo(BinaryWriter writer, MethodInfo methodInfo)
{
- if (methodInfo == null)
- return false;
-
SerializeType(writer, methodInfo.DeclaringType);
writer.Write(methodInfo.Name);
@@ -241,7 +237,7 @@ namespace Godot
return true;
}
- private static void SerializeType(BinaryWriter writer, Type type)
+ private static void SerializeType(BinaryWriter writer, Type? type)
{
if (type == null)
{
@@ -256,9 +252,8 @@ namespace Godot
int genericArgumentsCount = genericArgs.Length;
writer.Write(genericArgumentsCount);
- string assemblyQualifiedName = genericTypeDef.AssemblyQualifiedName;
- Debug.Assert(assemblyQualifiedName != null);
- writer.Write(assemblyQualifiedName);
+ writer.Write(genericTypeDef.Assembly.GetName().Name ?? "");
+ writer.Write(genericTypeDef.FullName ?? genericTypeDef.ToString());
for (int i = 0; i < genericArgs.Length; i++)
SerializeType(writer, genericArgs[i]);
@@ -268,21 +263,62 @@ namespace Godot
int genericArgumentsCount = 0;
writer.Write(genericArgumentsCount);
- string assemblyQualifiedName = type.AssemblyQualifiedName;
- Debug.Assert(assemblyQualifiedName != null);
- writer.Write(assemblyQualifiedName);
+ writer.Write(type.Assembly.GetName().Name ?? "");
+ writer.Write(type.FullName ?? type.ToString());
}
}
- private static bool TryDeserializeDelegateWithGCHandle(Collections.Array serializedData,
- out IntPtr delegateGCHandle)
+ [UnmanagedCallersOnly]
+ internal static unsafe godot_bool TrySerializeDelegateWithGCHandle(IntPtr delegateGCHandle,
+ godot_array* nSerializedData)
{
- bool res = TryDeserializeDelegate(serializedData, out Delegate @delegate);
- delegateGCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(@delegate));
- return res;
+ try
+ {
+ var serializedData = Collections.Array.CreateTakingOwnershipOfDisposableValue(
+ NativeFuncs.godotsharp_array_new_copy(*nSerializedData));
+
+ var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!;
+
+ return TrySerializeDelegate(@delegate, serializedData)
+ .ToGodotBool();
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.LogException(e);
+ return godot_bool.False;
+ }
}
- private static bool TryDeserializeDelegate(Collections.Array serializedData, out Delegate @delegate)
+ [UnmanagedCallersOnly]
+ internal static unsafe godot_bool TryDeserializeDelegateWithGCHandle(godot_array* nSerializedData,
+ IntPtr* delegateGCHandle)
+ {
+ try
+ {
+ var serializedData = Collections.Array.CreateTakingOwnershipOfDisposableValue(
+ NativeFuncs.godotsharp_array_new_copy(*nSerializedData));
+
+ if (TryDeserializeDelegate(serializedData, out Delegate? @delegate))
+ {
+ *delegateGCHandle = GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(@delegate));
+ return godot_bool.True;
+ }
+ else
+ {
+ *delegateGCHandle = IntPtr.Zero;
+ return godot_bool.False;
+ }
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.LogException(e);
+ *delegateGCHandle = default;
+ return godot_bool.False;
+ }
+ }
+
+ internal static bool TryDeserializeDelegate(Collections.Array serializedData,
+ [MaybeNullWhen(false)] out Delegate @delegate)
{
if (serializedData.Count == 1)
{
@@ -302,12 +338,12 @@ namespace Godot
{
if (elem is Collections.Array multiCastData)
{
- if (TryDeserializeDelegate(multiCastData, out Delegate oneDelegate))
+ if (TryDeserializeDelegate(multiCastData, out Delegate? oneDelegate))
delegates.Add(oneDelegate);
}
else
{
- if (TryDeserializeSingleDelegate((byte[])elem, out Delegate oneDelegate))
+ if (TryDeserializeSingleDelegate((byte[])elem, out Delegate? oneDelegate))
delegates.Add(oneDelegate);
}
}
@@ -315,11 +351,11 @@ namespace Godot
if (delegates.Count <= 0)
return false;
- @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray());
+ @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray())!;
return true;
}
- private static bool TryDeserializeSingleDelegate(byte[] buffer, out Delegate @delegate)
+ private static bool TryDeserializeSingleDelegate(byte[] buffer, [MaybeNullWhen(false)] out Delegate @delegate)
{
@delegate = null;
@@ -332,14 +368,18 @@ namespace Godot
{
case TargetKind.Static:
{
- Type delegateType = DeserializeType(reader);
+ Type? delegateType = DeserializeType(reader);
if (delegateType == null)
return false;
- if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo))
+ if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo))
+ return false;
+
+ @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo, throwOnBindFailure: false);
+
+ if (@delegate == null)
return false;
- @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo);
return true;
}
case TargetKind.GodotObject:
@@ -350,32 +390,37 @@ namespace Godot
if (godotObject == null)
return false;
- Type delegateType = DeserializeType(reader);
+ Type? delegateType = DeserializeType(reader);
if (delegateType == null)
return false;
- if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo))
+ if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo))
+ return false;
+
+ @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo,
+ throwOnBindFailure: false);
+
+ if (@delegate == null)
return false;
- @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo);
return true;
}
case TargetKind.CompilerGenerated:
{
- Type targetType = DeserializeType(reader);
+ Type? targetType = DeserializeType(reader);
if (targetType == null)
return false;
- Type delegateType = DeserializeType(reader);
+ Type? delegateType = DeserializeType(reader);
if (delegateType == null)
return false;
- if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo))
+ if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo))
return false;
int fieldCount = reader.ReadInt32();
- object recreatedTarget = Activator.CreateInstance(targetType);
+ object recreatedTarget = Activator.CreateInstance(targetType)!;
for (int i = 0; i < fieldCount; i++)
{
@@ -383,12 +428,17 @@ namespace Godot
int valueBufferLength = reader.ReadInt32();
byte[] valueBuffer = reader.ReadBytes(valueBufferLength);
- FieldInfo fieldInfo =
- targetType.GetField(name, BindingFlags.Instance | BindingFlags.Public);
+ FieldInfo? fieldInfo = targetType.GetField(name,
+ BindingFlags.Instance | BindingFlags.Public);
fieldInfo?.SetValue(recreatedTarget, GD.Bytes2Var(valueBuffer));
}
- @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo);
+ @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo,
+ throwOnBindFailure: false);
+
+ if (@delegate == null)
+ return false;
+
return true;
}
default:
@@ -397,18 +447,22 @@ namespace Godot
}
}
- private static bool TryDeserializeMethodInfo(BinaryReader reader, out MethodInfo methodInfo)
+ private static bool TryDeserializeMethodInfo(BinaryReader reader,
+ [MaybeNullWhen(false)] out MethodInfo methodInfo)
{
methodInfo = null;
- Type declaringType = DeserializeType(reader);
+ Type? declaringType = DeserializeType(reader);
+
+ if (declaringType == null)
+ return false;
string methodName = reader.ReadString();
int flags = reader.ReadInt32();
bool hasReturn = reader.ReadBoolean();
- Type returnType = hasReturn ? DeserializeType(reader) : typeof(void);
+ Type? returnType = hasReturn ? DeserializeType(reader) : typeof(void);
int parametersCount = reader.ReadInt32();
@@ -418,7 +472,7 @@ namespace Godot
for (int i = 0; i < parametersCount; i++)
{
- Type parameterType = DeserializeType(reader);
+ Type? parameterType = DeserializeType(reader);
if (parameterType == null)
return false;
parameterTypes[i] = parameterType;
@@ -432,15 +486,23 @@ namespace Godot
return methodInfo != null && methodInfo.ReturnType == returnType;
}
- private static Type DeserializeType(BinaryReader reader)
+ private static Type? DeserializeType(BinaryReader reader)
{
int genericArgumentsCount = reader.ReadInt32();
if (genericArgumentsCount == -1)
return null;
- string assemblyQualifiedName = reader.ReadString();
- var type = Type.GetType(assemblyQualifiedName);
+ string assemblyName = reader.ReadString();
+
+ if (assemblyName.Length == 0)
+ {
+ GD.PushError($"Missing assembly name of type when attempting to deserialize delegate");
+ return null;
+ }
+
+ string typeFullName = reader.ReadString();
+ var type = ReflectionUtils.FindTypeInLoadedAssemblies(assemblyName, typeFullName);
if (type == null)
return null; // Type not found
@@ -451,7 +513,7 @@ namespace Godot
for (int i = 0; i < genericArgumentsCount; i++)
{
- Type genericArgumentType = DeserializeType(reader);
+ Type? genericArgumentType = DeserializeType(reader);
if (genericArgumentType == null)
return null;
genericArgumentTypes[i] = genericArgumentType;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index 2bea2f3b4f..2523728c8b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -578,6 +578,24 @@ namespace Godot.Collections
return found;
}
+ // TODO: This is temporary. It's needed for the serialization generator. It won't be needed once we replace Sysme.Object with a Variant type.
+ internal bool TryGetValueAsType<TValueCustom>(TKey key, [MaybeNullWhen(false)] out TValueCustom value)
+ {
+ using godot_variant variantKey = Marshaling.ConvertManagedObjectToVariant(key);
+ var self = (godot_dictionary)_underlyingDict.NativeValue;
+ bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
+ variantKey, out godot_variant retValue).ToBool();
+
+ using (retValue)
+ {
+ value = found ?
+ (TValueCustom)Marshaling.ConvertVariantToManagedObjectOfType(retValue, typeof(TValueCustom)) :
+ default;
+ }
+
+ return found;
+ }
+
// ICollection<KeyValuePair<TKey, TValue>>
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs
index 4e15b37e44..75793ea446 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
-using System.Runtime.Loader;
using Godot.NativeInterop;
#nullable enable
@@ -10,17 +9,12 @@ namespace Godot
{
internal static class DisposablesTracker
{
- static DisposablesTracker()
- {
- AssemblyLoadContext.Default.Unloading += _ => OnUnloading();
- }
-
[UnmanagedCallersOnly]
internal static void OnGodotShuttingDown()
{
try
{
- OnUnloading();
+ OnGodotShuttingDownImpl();
}
catch (Exception e)
{
@@ -28,7 +22,7 @@ namespace Godot
}
}
- private static void OnUnloading()
+ private static void OnGodotShuttingDownImpl()
{
bool isStdoutVerbose;
@@ -66,30 +60,30 @@ namespace Godot
}
// ReSharper disable once RedundantNameQualifier
- private static ConcurrentDictionary<WeakReference<Godot.Object>, object?> GodotObjectInstances { get; } =
+ private static ConcurrentDictionary<WeakReference<Godot.Object>, byte> GodotObjectInstances { get; } =
new();
- private static ConcurrentDictionary<WeakReference<IDisposable>, object?> OtherInstances { get; } =
+ private static ConcurrentDictionary<WeakReference<IDisposable>, byte> OtherInstances { get; } =
new();
public static WeakReference<Object> RegisterGodotObject(Object godotObject)
{
var weakReferenceToSelf = new WeakReference<Object>(godotObject);
- GodotObjectInstances.TryAdd(weakReferenceToSelf, null);
+ GodotObjectInstances.TryAdd(weakReferenceToSelf, 0);
return weakReferenceToSelf;
}
public static WeakReference<IDisposable> RegisterDisposable(IDisposable disposable)
{
var weakReferenceToSelf = new WeakReference<IDisposable>(disposable);
- OtherInstances.TryAdd(weakReferenceToSelf, null);
+ OtherInstances.TryAdd(weakReferenceToSelf, 0);
return weakReferenceToSelf;
}
- public static void UnregisterGodotObject(WeakReference<Object> weakReference)
+ public static void UnregisterGodotObject(Object godotObject, WeakReference<Object> weakReferenceToSelf)
{
- if (!GodotObjectInstances.TryRemove(weakReference, out _))
- throw new ArgumentException("Godot Object not registered", nameof(weakReference));
+ if (!GodotObjectInstances.TryRemove(weakReferenceToSelf, out _))
+ throw new ArgumentException("Godot Object not registered", nameof(weakReferenceToSelf));
}
public static void UnregisterDisposable(WeakReference<IDisposable> weakReference)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
index 7a2f205632..5a0ea2ba13 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
@@ -95,7 +95,7 @@ namespace Godot.NativeInterop
}
NativeFuncs.godotsharp_internal_script_debugger_send_error(nFunc, nFile, line,
- nErrorMsg, nExcMsg, p_warning: false.ToGodotBool(), stackInfoVector);
+ nErrorMsg, nExcMsg, p_warning: godot_bool.False, stackInfoVector);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs
index f6f0186016..82f1c04d40 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs
@@ -50,7 +50,9 @@ namespace Godot.NativeInterop
public static void TieManagedToUnmanaged(Object managed, IntPtr unmanaged,
StringName nativeName, bool refCounted, Type type, Type nativeType)
{
- var gcHandle = GCHandle.Alloc(managed, refCounted ? GCHandleType.Weak : GCHandleType.Normal);
+ var gcHandle = refCounted ?
+ CustomGCHandle.AllocWeak(managed) :
+ CustomGCHandle.AllocStrong(managed, type);
if (type == nativeType)
{
@@ -65,7 +67,7 @@ namespace Godot.NativeInterop
// We don't dispose `script` ourselves here.
// `tie_user_managed_to_unmanaged` does it for us to avoid another P/Invoke call.
godot_ref script;
- ScriptManagerBridge.GetOrCreateScriptBridgeForType(type, &script);
+ ScriptManagerBridge.GetOrLoadOrCreateScriptForType(type, &script);
// IMPORTANT: This must be called after GetOrCreateScriptBridgeForType
NativeFuncs.godotsharp_internal_tie_user_managed_to_unmanaged(
@@ -80,7 +82,7 @@ namespace Godot.NativeInterop
if (type == nativeType)
return;
- var strongGCHandle = GCHandle.Alloc(managed, GCHandleType.Normal);
+ var strongGCHandle = CustomGCHandle.AllocStrong(managed);
NativeFuncs.godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup(
GCHandle.ToIntPtr(strongGCHandle), unmanaged);
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
index fc11f56680..1a0d9946d2 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
@@ -577,7 +577,29 @@ namespace Godot.NativeInterop
{
if (typeof(Godot.Object).IsAssignableFrom(type))
{
- var godotObject = VariantUtils.ConvertToGodotObject(p_var);
+ if (p_var.Type == Variant.Type.Nil)
+ {
+ res = null;
+ return true;
+ }
+
+ if (p_var.Type != Variant.Type.Object)
+ {
+ GD.PushError("Invalid cast when marshaling Godot.Object type." +
+ $" Variant type is `{p_var.Type}`; expected `{p_var.Object}`.");
+ res = null;
+ return true;
+ }
+
+ var godotObjectPtr = VariantUtils.ConvertToGodotObjectPtr(p_var);
+
+ if (godotObjectPtr == IntPtr.Zero)
+ {
+ res = null;
+ return true;
+ }
+
+ var godotObject = InteropUtils.UnmanagedGetManaged(godotObjectPtr);
if (!type.IsInstanceOfType(godotObject))
{
@@ -864,9 +886,9 @@ namespace Godot.NativeInterop
{
if (p_managed_callable.Delegate != null)
{
+ var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate);
NativeFuncs.godotsharp_callable_new_with_delegate(
- GCHandle.ToIntPtr(GCHandle.Alloc(p_managed_callable.Delegate)),
- out godot_callable callable);
+ GCHandle.ToIntPtr(gcHandle), out godot_callable callable);
return callable;
}
else
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index d7c57fa260..1ab2b4c0bf 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
@@ -95,7 +95,10 @@ namespace Godot.NativeInterop
IntPtr oldGCHandlePtr);
[DllImport(GodotDllName)]
- internal static extern void godotsharp_internal_new_csharp_script(godot_ref* r_script);
+ internal static extern void godotsharp_internal_new_csharp_script(godot_ref* r_dest);
+
+ [DllImport(GodotDllName)]
+ internal static extern godot_bool godotsharp_internal_script_load(in godot_string p_path, godot_ref* r_dest);
[DllImport(GodotDllName)]
internal static extern void godotsharp_internal_reload_registered_script(IntPtr scriptPtr);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
index 71a620716f..8d683c6d1e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
@@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
+using Godot.Bridge;
using Godot.NativeInterop;
namespace Godot
@@ -150,7 +151,7 @@ namespace Godot
NativePtr = IntPtr.Zero;
}
- DisposablesTracker.UnregisterGodotObject(_weakReferenceToSelf);
+ DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf);
}
/// <summary>
@@ -328,5 +329,77 @@ namespace Godot
return nativeConstructor;
}
+
+ protected internal virtual void SaveGodotObjectData(GodotSerializationInfo info)
+ {
+ // Temporary solution via reflection until we add a signals events source generator
+
+ Type top = GetType();
+ Type native = InternalGetClassNativeBase(top);
+
+ while (top != null && top != native)
+ {
+ var foundEventSignals = top.GetEvents(
+ BindingFlags.DeclaredOnly | BindingFlags.Instance |
+ BindingFlags.NonPublic | BindingFlags.Public)
+ .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any())
+ .Select(ev => ev.Name);
+
+ var fields = top.GetFields(
+ BindingFlags.DeclaredOnly | BindingFlags.Instance |
+ BindingFlags.NonPublic | BindingFlags.Public);
+
+ foreach (var eventSignalField in fields
+ .Where(f => typeof(Delegate).IsAssignableFrom(f.FieldType))
+ .Where(f => foundEventSignals.Contains(f.Name)))
+ {
+ var eventSignalDelegate = (Delegate)eventSignalField.GetValue(this);
+ info.AddSignalEventDelegate(eventSignalField.Name, eventSignalDelegate);
+ }
+
+ top = top.BaseType;
+ }
+ }
+
+ // TODO: Should this be a constructor overload?
+ protected internal virtual void RestoreGodotObjectData(GodotSerializationInfo info)
+ {
+ // Temporary solution via reflection until we add a signals events source generator
+
+ void RestoreSignalEvent(StringName signalEventName)
+ {
+ Type top = GetType();
+ Type native = InternalGetClassNativeBase(top);
+
+ while (top != null && top != native)
+ {
+ var foundEventSignal = top.GetEvent(signalEventName,
+ BindingFlags.DeclaredOnly | BindingFlags.Instance |
+ BindingFlags.NonPublic | BindingFlags.Public);
+
+ if (foundEventSignal != null &&
+ foundEventSignal.GetCustomAttributes().OfType<SignalAttribute>().Any())
+ {
+ var field = top.GetField(foundEventSignal.Name,
+ BindingFlags.DeclaredOnly | BindingFlags.Instance |
+ BindingFlags.NonPublic | BindingFlags.Public);
+
+ if (field != null && typeof(Delegate).IsAssignableFrom(field.FieldType))
+ {
+ var eventSignalDelegate = info.GetSignalEventDelegate(signalEventName);
+ field.SetValue(this, eventSignalDelegate);
+ return;
+ }
+ }
+
+ top = top.BaseType;
+ }
+ }
+
+ foreach (var signalEventName in info.GetSignalEventsList())
+ {
+ RestoreSignalEvent(signalEventName);
+ }
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs
new file mode 100644
index 0000000000..ee605f8d8f
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Linq;
+
+#nullable enable
+
+namespace Godot;
+
+internal class ReflectionUtils
+{
+ public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName)
+ {
+ return AppDomain.CurrentDomain.GetAssemblies()
+ .FirstOrDefault(a => a.GetName().Name == assemblyName)?
+ .GetType(typeFullName);
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs
index fb72d706c7..8ba3c403fa 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs
@@ -12,10 +12,11 @@ namespace Godot
public SignalAwaiter(Object source, StringName signal, Object target)
{
+ var awaiterGcHandle = CustomGCHandle.AllocStrong(this);
using godot_string_name signalSrc = NativeFuncs.godotsharp_string_name_new_copy(
(godot_string_name)(signal?.NativeValue ?? default));
NativeFuncs.godotsharp_internal_signal_awaiter_connect(Object.GetPtr(source), in signalSrc,
- Object.GetPtr(target), GCHandle.ToIntPtr(GCHandle.Alloc(this)));
+ Object.GetPtr(target), GCHandle.ToIntPtr(awaiterGcHandle));
}
public bool IsCompleted => _completed;
@@ -39,11 +40,11 @@ namespace Godot
if (awaiter == null)
{
- *outAwaiterIsNull = true.ToGodotBool();
+ *outAwaiterIsNull = godot_bool.True;
return;
}
- *outAwaiterIsNull = false.ToGodotBool();
+ *outAwaiterIsNull = godot_bool.False;
awaiter._completed = true;
@@ -59,7 +60,7 @@ namespace Godot
catch (Exception e)
{
ExceptionUtils.LogException(e);
- *outAwaiterIsNull = false.ToGodotBool();
+ *outAwaiterIsNull = godot_bool.False;
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 2e121bb789..d0897fe85e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -49,6 +49,8 @@
<!-- Sources -->
<ItemGroup>
<Compile Include="Core\AABB.cs" />
+ <Compile Include="Core\Bridge\GodotSerializationInfo.cs" />
+ <Compile Include="Core\CustomGCHandle.cs" />
<Compile Include="Core\Array.cs" />
<Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
<Compile Include="Core\Attributes\ExportAttribute.cs" />
@@ -59,9 +61,11 @@
<Compile Include="Core\Basis.cs" />
<Compile Include="Core\Bridge\CSharpInstanceBridge.cs" />
<Compile Include="Core\Bridge\GCHandleBridge.cs" />
+ <Compile Include="Core\Bridge\AlcReloadCfg.cs" />
<Compile Include="Core\Bridge\ManagedCallbacks.cs" />
<Compile Include="Core\Bridge\PropertyInfo.cs" />
<Compile Include="Core\Bridge\ScriptManagerBridge.cs" />
+ <Compile Include="Core\Bridge\ScriptManagerBridge.types.cs" />
<Compile Include="Core\Callable.cs" />
<Compile Include="Core\Color.cs" />
<Compile Include="Core\Colors.cs" />
@@ -100,6 +104,7 @@
<Compile Include="Core\Quaternion.cs" />
<Compile Include="Core\Rect2.cs" />
<Compile Include="Core\Rect2i.cs" />
+ <Compile Include="Core\ReflectionUtils.cs" />
<Compile Include="Core\RID.cs" />
<Compile Include="Core\NativeInterop\NativeFuncs.cs" />
<Compile Include="Core\NativeInterop\InteropStructs.cs" />
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index df72413fcc..637db00706 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -320,6 +320,17 @@ GD_PINVOKE_EXPORT void godotsharp_internal_new_csharp_script(Ref<CSharpScript> *
memnew_placement(r_dest, Ref<CSharpScript>(memnew(CSharpScript)));
}
+GD_PINVOKE_EXPORT bool godotsharp_internal_script_load(const String *p_path, Ref<CSharpScript> *r_dest) {
+ Ref<Resource> res = ResourceLoader::load(*p_path);
+ if (res.is_valid()) {
+ memnew_placement(r_dest, Ref<CSharpScript>(res));
+ return true;
+ } else {
+ memnew_placement(r_dest, Ref<CSharpScript>());
+ return false;
+ }
+}
+
GD_PINVOKE_EXPORT void godotsharp_internal_reload_registered_script(CSharpScript *p_script) {
CRASH_COND(!p_script);
CSharpScript::reload_registered_script(Ref<CSharpScript>(p_script));
@@ -1311,7 +1322,7 @@ GD_PINVOKE_EXPORT void godotsharp_object_to_string(Object *p_ptr, godot_string *
#endif
// We need this to prevent the functions from being stripped.
-void *godotsharp_pinvoke_funcs[185] = {
+void *godotsharp_pinvoke_funcs[186] = {
(void *)godotsharp_method_bind_get_method,
(void *)godotsharp_get_class_constructor,
(void *)godotsharp_engine_get_singleton,
@@ -1331,6 +1342,7 @@ void *godotsharp_pinvoke_funcs[185] = {
(void *)godotsharp_internal_tie_user_managed_to_unmanaged,
(void *)godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup,
(void *)godotsharp_internal_new_csharp_script,
+ (void *)godotsharp_internal_script_load,
(void *)godotsharp_internal_reload_registered_script,
(void *)godotsharp_array_filter_godot_objects_by_native,
(void *)godotsharp_array_filter_godot_objects_by_non_native,
diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp
index 334de14a6b..9305dc645a 100644
--- a/modules/mono/managed_callable.cpp
+++ b/modules/mono/managed_callable.cpp
@@ -87,6 +87,8 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant
r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better
r_return_value = Variant();
+ ERR_FAIL_COND(delegate_handle.value == nullptr);
+
GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs(
delegate_handle, p_arguments, p_argcount, &r_return_value);
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index d57ad2831a..f5e38c2e61 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -501,106 +501,48 @@ void GDMono::_init_godot_api_hashes() {
#ifdef TOOLS_ENABLED
bool GDMono::_load_project_assembly() {
- String appname = ProjectSettings::get_singleton()->get("application/config/name");
- String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
- if (appname_safe.is_empty()) {
- appname_safe = "UnnamedProject";
- }
+ String appname_safe = ProjectSettings::get_singleton()->get_safe_project_name();
String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir()
.plus_file(appname_safe + ".dll");
assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path);
- return plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16());
-}
-#endif
-
-#warning TODO hot-reload
-#if 0
-Error GDMono::_unload_scripts_domain() {
- ERR_FAIL_NULL_V(scripts_domain, ERR_BUG);
-
- CSharpLanguage::get_singleton()->_on_scripts_domain_about_to_unload();
-
- print_verbose("Mono: Finalizing scripts domain...");
-
- if (mono_domain_get() != root_domain) {
- mono_domain_set(root_domain, true);
- }
-
- finalizing_scripts_domain = true;
+ String loaded_assembly_path;
+ bool success = plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16(), &loaded_assembly_path);
- if (!mono_domain_finalize(scripts_domain, 2000)) {
- ERR_PRINT("Mono: Domain finalization timeout.");
+ if (success) {
+ project_assembly_path = loaded_assembly_path.simplify_path();
+ project_assembly_modified_time = FileAccess::get_modified_time(loaded_assembly_path);
}
- finalizing_scripts_domain = false;
-
- mono_gc_collect(mono_gc_max_generation());
-
- core_api_assembly = nullptr;
-#ifdef TOOLS_ENABLED
- editor_api_assembly = nullptr;
-#endif
-
- project_assembly = nullptr;
-#ifdef TOOLS_ENABLED
- tools_assembly = nullptr;
-#endif
-
- MonoDomain *domain = scripts_domain;
- scripts_domain = nullptr;
-
- print_verbose("Mono: Unloading scripts domain...");
-
- MonoException *exc = nullptr;
- mono_domain_try_unload(domain, (MonoObject **)&exc);
-
- if (exc) {
- ERR_PRINT("Exception thrown when unloading scripts domain.");
- GDMonoUtils::debug_unhandled_exception(exc);
- return FAILED;
- }
-
- return OK;
+ return success;
}
+#endif
#ifdef GD_MONO_HOT_RELOAD
-Error GDMono::reload_scripts_domain() {
+Error GDMono::reload_project_assemblies() {
ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG);
- if (scripts_domain) {
- Error domain_unload_err = _unload_scripts_domain();
- ERR_FAIL_COND_V_MSG(domain_unload_err != OK, domain_unload_err, "Mono: Failed to unload scripts domain.");
- }
-
- Error domain_load_err = _load_scripts_domain();
- ERR_FAIL_COND_V_MSG(domain_load_err != OK, domain_load_err, "Mono: Failed to load scripts domain.");
+ finalizing_scripts_domain = true;
- // Load assemblies. The API and tools assemblies are required,
- // the application is aborted if these assemblies cannot be loaded.
+ CSharpLanguage::get_singleton()->_on_scripts_domain_about_to_unload();
- if (!_try_load_api_assemblies()) {
- CRASH_NOW_MSG("Failed to load one of the API assemblies.");
+ if (!get_plugin_callbacks().UnloadProjectPluginCallback()) {
+ ERR_FAIL_V_MSG(Error::FAILED, ".NET: Failed to unload assemblies.");
}
-#if defined(TOOLS_ENABLED)
- bool tools_assemblies_loaded = _load_tools_assemblies();
- CRASH_COND_MSG(!tools_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies.");
-#endif
+ finalizing_scripts_domain = false;
// 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");
+ print_error(".NET: Failed to load project assembly.");
return ERR_CANT_OPEN;
}
return OK;
}
#endif
-#endif
GDMono::GDMono() {
singleton = this;
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 301782575c..776399a544 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -45,10 +45,12 @@ namespace gdmono {
#ifdef TOOLS_ENABLED
struct PluginCallbacks {
- using FuncLoadProjectAssemblyCallback = bool(GD_CLR_STDCALL *)(const char16_t *);
+ using FuncLoadProjectAssemblyCallback = bool(GD_CLR_STDCALL *)(const char16_t *, String *);
using FuncLoadToolsAssemblyCallback = Object *(GD_CLR_STDCALL *)(const char16_t *);
+ using FuncUnloadProjectPluginCallback = bool(GD_CLR_STDCALL *)();
FuncLoadProjectAssemblyCallback LoadProjectAssemblyCallback = nullptr;
FuncLoadToolsAssemblyCallback LoadToolsAssemblyCallback = nullptr;
+ FuncUnloadProjectPluginCallback UnloadProjectPluginCallback = nullptr;
};
#endif
@@ -63,14 +65,13 @@ class GDMono {
void *hostfxr_dll_handle = nullptr;
bool is_native_aot = false;
+ String project_assembly_path;
+ uint64_t project_assembly_modified_time = 0;
+
#ifdef TOOLS_ENABLED
bool _load_project_assembly();
#endif
- bool _try_load_api_assemblies();
-
- Error _unload_scripts_domain();
-
uint64_t api_core_hash;
#ifdef TOOLS_ENABLED
uint64_t api_editor_hash;
@@ -114,17 +115,32 @@ public:
#endif
}
- static GDMono *get_singleton() { return singleton; }
+ static GDMono *get_singleton() {
+ return singleton;
+ }
+
+ _FORCE_INLINE_ bool is_runtime_initialized() const {
+ return runtime_initialized;
+ }
+ _FORCE_INLINE_ bool is_finalizing_scripts_domain() {
+ return finalizing_scripts_domain;
+ }
- _FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized; }
- _FORCE_INLINE_ bool is_finalizing_scripts_domain() { return finalizing_scripts_domain; }
+ _FORCE_INLINE_ const String &get_project_assembly_path() const {
+ return project_assembly_path;
+ }
+ _FORCE_INLINE_ uint64_t get_project_assembly_modified_time() const {
+ return project_assembly_modified_time;
+ }
#ifdef TOOLS_ENABLED
- const gdmono::PluginCallbacks &get_plugin_callbacks() { return plugin_callbacks; }
+ const gdmono::PluginCallbacks &get_plugin_callbacks() {
+ return plugin_callbacks;
+ }
#endif
#ifdef GD_MONO_HOT_RELOAD
- Error reload_scripts_domain();
+ Error reload_project_assemblies();
#endif
void initialize();
diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp
index fc47a0e09b..37433c59ee 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -52,6 +52,8 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
CHECK_CALLBACK_NOT_NULL(SignalAwaiter, SignalCallback);
CHECK_CALLBACK_NOT_NULL(DelegateUtils, InvokeWithVariantArgs);
CHECK_CALLBACK_NOT_NULL(DelegateUtils, DelegateEquals);
+ CHECK_CALLBACK_NOT_NULL(DelegateUtils, TrySerializeDelegateWithGCHandle);
+ CHECK_CALLBACK_NOT_NULL(DelegateUtils, TryDeserializeDelegateWithGCHandle);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, FrameCallback);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectBinding);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectScriptInstance);
@@ -64,6 +66,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, AddScriptBridge);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetOrCreateScriptBridgeForPath);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RemoveScriptBridge);
+ CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, TryReloadRegisteredScriptWithClass);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, UpdateScriptClassInfo);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyInfoList);
@@ -74,6 +77,8 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallDispose);
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallToString);
CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, HasMethodUnknownParams);
+ CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, SerializeState);
+ CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, DeserializeState);
CHECK_CALLBACK_NOT_NULL(GCHandleBridge, FreeGCHandle);
CHECK_CALLBACK_NOT_NULL(DebuggingUtils, GetCurrentStackInfo);
CHECK_CALLBACK_NOT_NULL(DisposablesTracker, OnGodotShuttingDown);
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index 763a7f3e5e..92778cc2c9 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -74,6 +74,8 @@ struct ManagedCallbacks {
using FuncSignalAwaiter_SignalCallback = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, int32_t, bool *);
using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, uint32_t, const Variant *);
using FuncDelegateUtils_DelegateEquals = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr);
+ using FuncDelegateUtils_TrySerializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const Array *);
+ using FuncDelegateUtils_TryDeserializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(const Array *, GCHandleIntPtr *);
using FuncScriptManagerBridge_FrameCallback = void(GD_CLR_STDCALL *)();
using FuncScriptManagerBridge_CreateManagedForGodotObjectBinding = GCHandleIntPtr(GD_CLR_STDCALL *)(const StringName *, Object *);
using FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = bool(GD_CLR_STDCALL *)(const CSharpScript *, Object *, const Variant **, int32_t);
@@ -86,7 +88,8 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_AddScriptBridge = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *);
using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *);
- using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Dictionary *);
+ using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *);
+ using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Dictionary *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add);
@@ -96,6 +99,8 @@ struct ManagedCallbacks {
using FuncCSharpInstanceBridge_CallDispose = void(GD_CLR_STDCALL *)(GCHandleIntPtr, bool);
using FuncCSharpInstanceBridge_CallToString = void(GD_CLR_STDCALL *)(GCHandleIntPtr, String *, bool *);
using FuncCSharpInstanceBridge_HasMethodUnknownParams = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *);
+ using FuncCSharpInstanceBridge_SerializeState = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Dictionary *, const Dictionary *);
+ using FuncCSharpInstanceBridge_DeserializeState = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Dictionary *, const Dictionary *);
using FuncGCHandleBridge_FreeGCHandle = void(GD_CLR_STDCALL *)(GCHandleIntPtr);
using FuncDebuggingUtils_GetCurrentStackInfo = void(GD_CLR_STDCALL *)(Vector<ScriptLanguage::StackInfo> *);
using FuncDisposablesTracker_OnGodotShuttingDown = void(GD_CLR_STDCALL *)();
@@ -104,6 +109,8 @@ struct ManagedCallbacks {
FuncSignalAwaiter_SignalCallback SignalAwaiter_SignalCallback;
FuncDelegateUtils_InvokeWithVariantArgs DelegateUtils_InvokeWithVariantArgs;
FuncDelegateUtils_DelegateEquals DelegateUtils_DelegateEquals;
+ FuncDelegateUtils_TrySerializeDelegateWithGCHandle DelegateUtils_TrySerializeDelegateWithGCHandle;
+ FuncDelegateUtils_TryDeserializeDelegateWithGCHandle DelegateUtils_TryDeserializeDelegateWithGCHandle;
FuncScriptManagerBridge_FrameCallback ScriptManagerBridge_FrameCallback;
FuncScriptManagerBridge_CreateManagedForGodotObjectBinding ScriptManagerBridge_CreateManagedForGodotObjectBinding;
FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
@@ -116,6 +123,7 @@ struct ManagedCallbacks {
FuncScriptManagerBridge_AddScriptBridge ScriptManagerBridge_AddScriptBridge;
FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
FuncScriptManagerBridge_RemoveScriptBridge ScriptManagerBridge_RemoveScriptBridge;
+ FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
FuncScriptManagerBridge_UpdateScriptClassInfo ScriptManagerBridge_UpdateScriptClassInfo;
FuncScriptManagerBridge_SwapGCHandleForType ScriptManagerBridge_SwapGCHandleForType;
FuncScriptManagerBridge_GetPropertyInfoList ScriptManagerBridge_GetPropertyInfoList;
@@ -126,6 +134,8 @@ struct ManagedCallbacks {
FuncCSharpInstanceBridge_CallDispose CSharpInstanceBridge_CallDispose;
FuncCSharpInstanceBridge_CallToString CSharpInstanceBridge_CallToString;
FuncCSharpInstanceBridge_HasMethodUnknownParams CSharpInstanceBridge_HasMethodUnknownParams;
+ FuncCSharpInstanceBridge_SerializeState CSharpInstanceBridge_SerializeState;
+ FuncCSharpInstanceBridge_DeserializeState CSharpInstanceBridge_DeserializeState;
FuncGCHandleBridge_FreeGCHandle GCHandleBridge_FreeGCHandle;
FuncDebuggingUtils_GetCurrentStackInfo DebuggingUtils_GetCurrentStackInfo;
FuncDisposablesTracker_OnGodotShuttingDown DisposablesTracker_OnGodotShuttingDown;
@@ -137,9 +147,6 @@ extern bool godot_api_cache_updated;
void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks);
-inline void clear_godot_api_cache() {
- managed_callbacks = ManagedCallbacks();
-}
} // namespace GDMonoCache
#undef GD_CLR_STDCALL