C#: Re-implement assembly reloading with ALCs

This commit is contained in:
Ignacio Roldán Etcheverry 2022-05-28 04:56:46 +02:00
parent d78e0a8426
commit e235cef09f
37 changed files with 1534 additions and 562 deletions

View File

@ -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")

View File

@ -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");
}

View File

@ -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;

View File

@ -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)

View File

@ -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));
}
}
}

View File

@ -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;

View File

@ -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()

View File

@ -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 -->

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -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");

View File

@ -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>

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -11,7 +11,7 @@ namespace Godot.Bridge
{
try
{
GCHandle.FromIntPtr(gcHandlePtr).Free();
CustomGCHandle.Free(GCHandle.FromIntPtr(gcHandlePtr));
}
catch (Exception e)
{

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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>

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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" />

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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