C#: Re-implement assembly reloading with ALCs
This commit is contained in:
parent
d78e0a8426
commit
e235cef09f
|
@ -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")
|
||||
|
||||
|
|
|
@ -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 assembly_path = gdmono->get_project_assembly_path();
|
||||
|
||||
String appname_safe = ProjectSettings::get_singleton()->get_safe_project_name();
|
||||
|
||||
appname_safe += ".dll";
|
||||
|
||||
if (proj_assembly) {
|
||||
String proj_asm_path = proj_assembly->get_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();
|
||||
|
||||
ERR_CONTINUE(managed_callable->delegate_handle.value == nullptr);
|
||||
|
||||
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_TrySerializeDelegateWithGCHandle
|
||||
.invoke(managed_callable->delegate_handle,
|
||||
managed_serialized_data, &exc);
|
||||
|
||||
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();
|
||||
bool success = GDMonoCache::managed_callbacks.ScriptManagerBridge_TryReloadRegisteredScriptWithClass(script.ptr());
|
||||
|
||||
// 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);
|
||||
|
||||
#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
|
||||
if (!success) {
|
||||
// Couldn't reload
|
||||
script->pending_reload_instances.clear();
|
||||
script->pending_reload_state.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
|
||||
script->pending_reload_instances.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;
|
||||
Dictionary properties;
|
||||
|
||||
HashMap<StringName, GDMonoField *>::Iterator match = script->event_signals.find(name);
|
||||
|
||||
if (!match) {
|
||||
// The event or its signal attribute were removed
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
const CSharpScript *top = this;
|
||||
while (top != nullptr) {
|
||||
List<PropertyInfo> props;
|
||||
|
||||
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
|
||||
|
||||
for (const PropertyInfo &prop : props) {
|
||||
r_list->push_back(prop);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
|
||||
ScriptManagerBridge.LookupScriptsInAssembly(_projectAssembly);
|
||||
string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath;
|
||||
*outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace Godot.Bridge
|
|||
{
|
||||
try
|
||||
{
|
||||
GCHandle.FromIntPtr(gcHandlePtr).Free();
|
||||
CustomGCHandle.Free(GCHandle.FromIntPtr(gcHandlePtr));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
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();
|
||||
[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 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;
|
||||
|
||||
_scriptTypeMap.Add(scriptPtr, scriptType);
|
||||
_typeScriptMap.Add(scriptType, scriptPtr);
|
||||
|
||||
NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr);
|
||||
// This path is slower, but it's only executed for the first instantiation of the type
|
||||
CreateScriptBridgeForType(scriptType, outScript);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -530,14 +639,75 @@ 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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
String loaded_assembly_path;
|
||||
bool success = plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16(), &loaded_assembly_path);
|
||||
|
||||
if (success) {
|
||||
project_assembly_path = loaded_assembly_path.simplify_path();
|
||||
project_assembly_modified_time = FileAccess::get_modified_time(loaded_assembly_path);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
#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);
|
||||
}
|
||||
#ifdef GD_MONO_HOT_RELOAD
|
||||
Error GDMono::reload_project_assemblies() {
|
||||
ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG);
|
||||
|
||||
finalizing_scripts_domain = true;
|
||||
|
||||
if (!mono_domain_finalize(scripts_domain, 2000)) {
|
||||
ERR_PRINT("Mono: Domain finalization timeout.");
|
||||
CSharpLanguage::get_singleton()->_on_scripts_domain_about_to_unload();
|
||||
|
||||
if (!get_plugin_callbacks().UnloadProjectPluginCallback()) {
|
||||
ERR_FAIL_V_MSG(Error::FAILED, ".NET: Failed to unload assemblies.");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef GD_MONO_HOT_RELOAD
|
||||
Error GDMono::reload_scripts_domain() {
|
||||
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.");
|
||||
|
||||
// Load assemblies. The API and tools assemblies are required,
|
||||
// the application is aborted if these assemblies cannot be loaded.
|
||||
|
||||
if (!_try_load_api_assemblies()) {
|
||||
CRASH_NOW_MSG("Failed to load one of the API 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
|
||||
|
||||
// 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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue