From 23d51ac3255dc40f0ca9bd1142648c8169e275be Mon Sep 17 00:00:00 2001 From: pepegadeveloper123 Date: Sat, 16 May 2020 11:52:46 +0900 Subject: [PATCH] Fix inherited C# scene not inheriting parent's fields (3.2) When a child scene inherits a parent scene with a C# root node, the parent scene's export variables appear to assume values set in the parent scene, in the child scene's Inspector. However, when the child scene is played, the parent scene's export variables assume default values. When a node is created, it inherits its parent C# script's fields from the map CSharpScriptInstance::script->member_info. However this map was not initialized outside the editor, and this commit ensured it is. This fixes issues #36480 and #37581. This is a manual backport of PR #38638 for 3.2. --- modules/mono/csharp_script.cpp | 181 +++++++++++++++++++-------------- modules/mono/csharp_script.h | 2 +- 2 files changed, 105 insertions(+), 78 deletions(-) diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 55022036425..6deab0cf566 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -2315,58 +2315,69 @@ void CSharpScript::_update_member_info_no_exports() { bool CSharpScript::_update_exports() { #ifdef TOOLS_ENABLED - if (!Engine::get_singleton()->is_editor_hint()) - return false; - - placeholder_fallback_enabled = true; // until proven otherwise + bool is_editor = Engine::get_singleton()->is_editor_hint(); + if (is_editor) + placeholder_fallback_enabled = true; // until proven otherwise +#endif if (!valid) return false; bool changed = false; - if (exports_invalidated) { +#ifdef TOOLS_ENABLED + if (!is_editor || exports_invalidated) +#endif + { GD_MONO_SCOPE_THREAD_ATTACH; - exports_invalidated = false; - changed = true; member_info.clear(); - exported_members_cache.clear(); - exported_members_defval_cache.clear(); - // Here we create a temporary managed instance of the class to get the initial values +#ifdef TOOLS_ENABLED + MonoObject *tmp_object = nullptr; + Object *tmp_native = nullptr; + uint32_t tmp_pinned_gchandle = 0; - MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); + if (is_editor) { + exports_invalidated = false; - if (!tmp_object) { - ERR_PRINT("Failed to allocate temporary MonoObject."); - return false; - } - - uint32_t tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed) - - GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - - ERR_FAIL_NULL_V_MSG(ctor, NULL, - "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); - - MonoException *ctor_exc = NULL; - ctor->invoke(tmp_object, NULL, &ctor_exc); - - Object *tmp_native = GDMonoMarshal::unbox(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); - - if (ctor_exc) { - // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? - - MonoGCHandle::free_handle(tmp_pinned_gchandle); - tmp_object = NULL; - - ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(ctor_exc); - return false; + exported_members_cache.clear(); + exported_members_defval_cache.clear(); + + // Here we create a temporary managed instance of the class to get the initial values + tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); + + if (!tmp_object) { + ERR_PRINT("Failed to allocate temporary MonoObject."); + return false; + } + + tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed) + + GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); + + ERR_FAIL_NULL_V_MSG(ctor, NULL, + "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); + + MonoException *ctor_exc = NULL; + ctor->invoke(tmp_object, NULL, &ctor_exc); + + tmp_native = GDMonoMarshal::unbox(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); + + if (ctor_exc) { + // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? + + MonoGCHandle::free_handle(tmp_pinned_gchandle); + tmp_object = NULL; + + ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); + GDMonoUtils::debug_print_unhandled_exception(ctor_exc); + return false; + } } +#endif GDMonoClass *top = script_class; @@ -2382,16 +2393,16 @@ bool CSharpScript::_update_exports() { if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) { StringName member_name = field->get_name(); - if (exported) { - member_info[member_name] = prop_info; + member_info[member_name] = prop_info; +#ifdef TOOLS_ENABLED + if (is_editor && exported) { exported_members_cache.push_front(prop_info); if (tmp_object) { exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); } - } else { - member_info[member_name] = prop_info; } +#endif } } @@ -2403,8 +2414,9 @@ bool CSharpScript::_update_exports() { if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) { StringName member_name = property->get_name(); - if (exported) { - member_info[member_name] = prop_info; + member_info[member_name] = prop_info; +#ifdef TOOLS_ENABLED + if (is_editor && exported) { exported_members_cache.push_front(prop_info); if (tmp_object) { @@ -2417,57 +2429,62 @@ bool CSharpScript::_update_exports() { exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret); } } - } else { - member_info[member_name] = prop_info; } +#endif } } top = top->get_parent_class(); } - // Need to check this here, before disposal - bool base_ref = Object::cast_to(tmp_native) != NULL; +#ifdef TOOLS_ENABLED + if (is_editor) { + // Need to check this here, before disposal + bool base_ref = Object::cast_to(tmp_native) != NULL; - // Dispose the temporary managed instance + // Dispose the temporary managed instance - MonoException *exc = NULL; - GDMonoUtils::dispose(tmp_object, &exc); + MonoException *exc = NULL; + GDMonoUtils::dispose(tmp_object, &exc); - if (exc) { - ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(exc); + if (exc) { + ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); + GDMonoUtils::debug_print_unhandled_exception(exc); + } + + MonoGCHandle::free_handle(tmp_pinned_gchandle); + tmp_object = NULL; + + if (tmp_native && !base_ref) { + Node *node = Object::cast_to(tmp_native); + if (node && node->is_inside_tree()) { + ERR_PRINTS("Temporary instance was added to the scene tree."); + } else { + memdelete(tmp_native); + } + } } +#endif + } - MonoGCHandle::free_handle(tmp_pinned_gchandle); - tmp_object = NULL; +#ifdef TOOLS_ENABLED + if (is_editor) { + placeholder_fallback_enabled = false; - if (tmp_native && !base_ref) { - Node *node = Object::cast_to(tmp_native); - if (node && node->is_inside_tree()) { - ERR_PRINTS("Temporary instance was added to the scene tree."); - } else { - memdelete(tmp_native); + if (placeholders.size()) { + // Update placeholders if any + Map values; + List propnames; + _update_exports_values(values, propnames); + + for (Set::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->update(propnames, values); } } } - - placeholder_fallback_enabled = false; - - if (placeholders.size()) { - // Update placeholders if any - Map values; - List propnames; - _update_exports_values(values, propnames); - - for (Set::Element *E = placeholders.front(); E; E = E->next()) { - E->get()->update(propnames, values); - } - } +#endif return changed; -#endif - return false; } void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class) { @@ -2538,7 +2555,6 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve return false; } -#ifdef TOOLS_ENABLED /** * Returns false if there was an error, otherwise true. * If there was an error, r_prop_info and r_exported are not assigned any value. @@ -2552,8 +2568,10 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect (m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name()) if (p_member->is_static()) { +#ifdef TOOLS_ENABLED if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) ERR_PRINTS("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } @@ -2575,13 +2593,17 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) { GDMonoProperty *property = static_cast(p_member); if (!property->has_getter()) { +#ifdef TOOLS_ENABLED if (exported) ERR_PRINTS("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } if (!property->has_setter()) { +#ifdef TOOLS_ENABLED if (exported) ERR_PRINTS("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } } @@ -2600,10 +2622,13 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect String hint_string; if (variant_type == Variant::NIL) { +#ifdef TOOLS_ENABLED ERR_PRINTS("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); +#endif return false; } +#ifdef TOOLS_ENABLED int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string); ERR_FAIL_COND_V_MSG(hint_res == -1, false, @@ -2614,6 +2639,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); } +#endif r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE); r_exported = true; @@ -2623,6 +2649,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect #undef MEMBER_FULL_QUALIFIED_NAME } +#ifdef TOOLS_ENABLED int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) { GD_MONO_ASSERT_THREAD_ATTACHED; diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index f244bc41193..1d7de13f471 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -133,8 +133,8 @@ class CSharpScript : public Script { bool _get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector ¶ms); bool _update_exports(); -#ifdef TOOLS_ENABLED bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported); +#ifdef TOOLS_ENABLED static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string); #endif