diff options
Diffstat (limited to 'modules/mono')
23 files changed, 455 insertions, 78 deletions
diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp index c1d6f4dccf..b04e53bd81 100644 --- a/modules/mono/class_db_api_json.cpp +++ b/modules/mono/class_db_api_json.cpp @@ -71,6 +71,13 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) { while ((k = t->method_map.next(k))) { + String name = k->operator String(); + + ERR_CONTINUE(name.empty()); + + if (name[0] == '_') + continue; // Ignore non-virtual methods that start with an underscore + snames.push_back(*k); } diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index d82e78d080..9e19e5f8c4 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -106,7 +106,7 @@ Error CSharpLanguage::execute_file(const String &p_path) { void CSharpLanguage::init() { #ifdef DEBUG_METHODS_ENABLED - if (OS::get_singleton()->get_cmdline_args().find("--class_db_to_json")) { + if (OS::get_singleton()->get_cmdline_args().find("--class-db-json")) { class_db_api_to_json("user://class_db_api.json", ClassDB::API_CORE); #ifdef TOOLS_ENABLED class_db_api_to_json("user://class_db_api_editor.json", ClassDB::API_EDITOR); @@ -159,6 +159,19 @@ void CSharpLanguage::finish() { // Clear here, after finalizing all domains to make sure there is nothing else referencing the elements. script_bindings.clear(); +#ifdef DEBUG_ENABLED + for (Map<ObjectID, int>::Element *E = unsafe_object_references.front(); E; E = E->next()) { + const ObjectID &id = E->get(); + Object *obj = ObjectDB::get_instance(id); + + if (obj) { + ERR_PRINTS("Leaked unsafe reference to object: " + obj->get_class() + ":" + itos(id)); + } else { + ERR_PRINTS("Leaked unsafe reference to deleted object: " + itos(id)); + } + } +#endif + finalizing = false; } @@ -546,6 +559,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() #ifdef DEBUG_ENABLED _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); + GD_MONO_SCOPE_THREAD_ATTACH; if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated) return Vector<StackInfo>(); @@ -570,6 +584,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObject *p_stack_trace) { _TLS_RECURSION_GUARD_V_(Vector<StackInfo>()); + GD_MONO_SCOPE_THREAD_ATTACH; MonoException *exc = NULL; @@ -615,6 +630,25 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec } #endif +void CSharpLanguage::post_unsafe_reference(Object *p_obj) { +#ifdef DEBUG_ENABLED + SCOPED_MUTEX_LOCK(unsafe_object_references_lock); + ObjectID id = p_obj->get_instance_id(); + unsafe_object_references[id]++; +#endif +} + +void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) { +#ifdef DEBUG_ENABLED + SCOPED_MUTEX_LOCK(unsafe_object_references_lock); + ObjectID id = p_obj->get_instance_id(); + Map<ObjectID, int>::Element *elem = unsafe_object_references.find(id); + ERR_FAIL_NULL(elem); + if (--elem->value() == 0) + unsafe_object_references.erase(elem); +#endif +} + void CSharpLanguage::frame() { if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) { @@ -659,6 +693,7 @@ void CSharpLanguage::reload_all_scripts() { #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { + GD_MONO_SCOPE_THREAD_ATTACH; reload_assemblies(false); } #endif @@ -676,6 +711,7 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { + GD_MONO_SCOPE_THREAD_ATTACH; reload_assemblies(p_soft_reload); } #endif @@ -1212,6 +1248,14 @@ CSharpLanguage::CSharpLanguage() { language_bind_mutex = Mutex::create(); #endif +#ifdef DEBUG_ENABLED +#ifdef NO_THREADS + unsafe_object_references_lock = NULL; +#else + unsafe_object_references_lock = Mutex::create(); +#endif +#endif + lang_idx = -1; scripts_metadata_invalidated = true; @@ -1240,6 +1284,13 @@ CSharpLanguage::~CSharpLanguage() { script_gchandle_release_mutex = NULL; } +#ifdef DEBUG_ENABLED + if (unsafe_object_references_lock) { + memdelete(unsafe_object_references_lock); + unsafe_object_references_lock = NULL; + } +#endif + singleton = NULL; } @@ -1286,6 +1337,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) ref->reference(); + CSharpLanguage::get_singleton()->post_unsafe_reference(ref); } return true; @@ -1325,6 +1377,8 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) { if (finalizing) return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there + GD_MONO_ASSERT_THREAD_ATTACHED; + { SCOPED_MUTEX_LOCK(language_bind_mutex); @@ -1351,6 +1405,7 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { #ifdef DEBUG_ENABLED CRASH_COND(!ref_owner); + CRASH_COND(!p_object->has_script_instance_binding(get_language_index())); #endif void *data = p_object->get_script_instance_binding(get_language_index()); @@ -1363,6 +1418,8 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { return; if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + GD_MONO_SCOPE_THREAD_ATTACH; + // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, // so the owner must hold the managed side alive again to avoid it from being GCed. @@ -1384,6 +1441,7 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { #ifdef DEBUG_ENABLED CRASH_COND(!ref_owner); + CRASH_COND(!p_object->has_script_instance_binding(get_language_index())); #endif void *data = p_object->get_script_instance_binding(get_language_index()); @@ -1398,6 +1456,8 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { return refcount == 0; if (refcount == 1 && gchandle.is_valid() && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + GD_MONO_SCOPE_THREAD_ATTACH; + // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. @@ -1449,6 +1509,8 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND_V(!script.is_valid(), false); + GD_MONO_SCOPE_THREAD_ATTACH; + MonoObject *mono_object = get_mono_object(); ERR_FAIL_NULL_V(mono_object, false); @@ -1501,6 +1563,8 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { ERR_FAIL_COND_V(!script.is_valid(), false); + GD_MONO_SCOPE_THREAD_ATTACH; + MonoObject *mono_object = get_mono_object(); ERR_FAIL_NULL_V(mono_object, false); @@ -1594,6 +1658,8 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { ERR_FAIL_COND(!script.is_valid()); + GD_MONO_SCOPE_THREAD_ATTACH; + MonoObject *mono_object = get_mono_object(); ERR_FAIL_NULL(mono_object); @@ -1638,6 +1704,8 @@ bool CSharpInstance::has_method(const StringName &p_method) const { if (!script.is_valid()) return false; + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = script->script_class; while (top && top != script->native) { @@ -1653,6 +1721,11 @@ bool CSharpInstance::has_method(const StringName &p_method) const { Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { + if (!script.is_valid()) + ERR_FAIL_V(Variant()); + + GD_MONO_SCOPE_THREAD_ATTACH; + MonoObject *mono_object = get_mono_object(); if (!mono_object) { @@ -1660,9 +1733,6 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, ERR_FAIL_V(Variant()); } - if (!script.is_valid()) - ERR_FAIL_V(Variant()); - GDMonoClass *top = script->script_class; while (top && top != script->native) { @@ -1690,6 +1760,8 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, void CSharpInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) { + GD_MONO_SCOPE_THREAD_ATTACH; + if (script.is_valid()) { MonoObject *mono_object = get_mono_object(); @@ -1701,6 +1773,8 @@ void CSharpInstance::call_multilevel(const StringName &p_method, const Variant * void CSharpInstance::_call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount) { + GD_MONO_ASSERT_THREAD_ATTACHED; + GDMonoClass *top = script->script_class; while (top && top != script->native) { @@ -1736,9 +1810,12 @@ bool CSharpInstance::_reference_owner_unsafe() { // See: _unreference_owner_unsafe() // May not me referenced yet, so we must use init_ref() instead of reference() - bool success = Object::cast_to<Reference>(owner)->init_ref(); - unsafe_referenced = success; - return success; + if (static_cast<Reference *>(owner)->init_ref()) { + CSharpLanguage::get_singleton()->post_unsafe_reference(owner); + unsafe_referenced = true; + } + + return unsafe_referenced; } bool CSharpInstance::_unreference_owner_unsafe() { @@ -1759,6 +1836,7 @@ bool CSharpInstance::_unreference_owner_unsafe() { // See: _reference_owner_unsafe() // Destroying the owner here means self destructing, so we defer the owner destruction to the caller. + CSharpLanguage::get_singleton()->pre_unsafe_unreference(owner); return static_cast<Reference *>(owner)->unreference(); } @@ -1859,6 +1937,8 @@ void CSharpInstance::refcount_incremented() { Reference *ref_owner = Object::cast_to<Reference>(owner); if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + GD_MONO_SCOPE_THREAD_ATTACH; + // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, // so the owner must hold the managed side alive again to avoid it from being GCed. @@ -1882,6 +1962,8 @@ bool CSharpInstance::refcount_decremented() { int refcount = ref_owner->reference_get_count(); if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + GD_MONO_SCOPE_THREAD_ATTACH; + // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. @@ -1922,6 +2004,8 @@ MultiplayerAPI::RPCMode CSharpInstance::_member_get_rpc_mode(IMonoClassMember *p MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const { + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = script->script_class; while (top && top != script->native) { @@ -1938,6 +2022,8 @@ MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variable) const { + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = script->script_class; while (top && top != script->native) { @@ -1959,6 +2045,8 @@ MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab void CSharpInstance::notification(int p_notification) { + GD_MONO_SCOPE_THREAD_ATTACH; + if (p_notification == Object::NOTIFICATION_PREDELETE) { // When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose(). // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed @@ -1996,6 +2084,8 @@ void CSharpInstance::notification(int p_notification) { void CSharpInstance::_call_notification(int p_notification) { + GD_MONO_ASSERT_THREAD_ATTACHED; + MonoObject *mono_object = get_mono_object(); ERR_FAIL_NULL(mono_object); @@ -2020,6 +2110,8 @@ void CSharpInstance::_call_notification(int p_notification) { } String CSharpInstance::to_string(bool *r_valid) { + GD_MONO_SCOPE_THREAD_ATTACH; + MonoObject *mono_object = get_mono_object(); if (mono_object == NULL) { @@ -2068,6 +2160,8 @@ CSharpInstance::CSharpInstance() : CSharpInstance::~CSharpInstance() { + GD_MONO_SCOPE_THREAD_ATTACH; + destructing_script_instance = true; if (gchandle.is_valid()) { @@ -2099,6 +2193,17 @@ CSharpInstance::~CSharpInstance() { // Transfer ownership to an "instance binding" + Reference *ref_owner = static_cast<Reference *>(owner); + + // We will unreference the owner before referencing it again, so we need to keep it alive + Ref<Reference> scope_keep_owner_alive(ref_owner); + (void)scope_keep_owner_alive; + + // Unreference the owner here, before the new "instance binding" references it. + // Otherwise, the unsafe reference debug checks will incorrectly detect a bug. + bool die = _unreference_owner_unsafe(); + CRASH_COND(die == true); // `owner_keep_alive` holds a reference, so it can't die + void *data = owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); CRASH_COND(data == NULL); @@ -2114,8 +2219,10 @@ CSharpInstance::~CSharpInstance() { } } - bool die = _unreference_owner_unsafe(); - CRASH_COND(die == true); // The "instance binding" should be holding a reference +#ifdef DEBUG_ENABLED + // The "instance binding" holds a reference so the refcount should be at least 2 before `scope_keep_owner_alive` goes out of scope + CRASH_COND(ref_owner->reference_get_count() <= 1); +#endif } if (script.is_valid() && owner) { @@ -2158,6 +2265,8 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List void CSharpScript::_update_member_info_no_exports() { if (exports_invalidated) { + GD_MONO_ASSERT_THREAD_ATTACHED; + exports_invalidated = false; member_info.clear(); @@ -2216,6 +2325,8 @@ bool CSharpScript::_update_exports() { bool changed = false; if (exports_invalidated) { + GD_MONO_SCOPE_THREAD_ATTACH; + exports_invalidated = false; changed = true; @@ -2243,7 +2354,11 @@ bool CSharpScript::_update_exports() { MonoException *ctor_exc = NULL; ctor->invoke(tmp_object, NULL, &ctor_exc); + Object *tmp_native = GDMonoMarshal::unbox<Object *>(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; @@ -2310,6 +2425,9 @@ bool CSharpScript::_update_exports() { top = top->get_parent_class(); } + // Need to check this here, before disposal + bool base_ref = Object::cast_to<Reference>(tmp_native) != NULL; + // Dispose the temporary managed instance MonoException *exc = NULL; @@ -2322,6 +2440,15 @@ bool CSharpScript::_update_exports() { MonoGCHandle::free_handle(tmp_pinned_gchandle); tmp_object = NULL; + + if (tmp_native && !base_ref) { + Node *node = Object::cast_to<Node>(tmp_native); + if (node && node->is_inside_tree()) { + ERR_PRINTS("Temporary instance was added to the scene tree."); + } else { + memdelete(tmp_native); + } + } } placeholder_fallback_enabled = false; @@ -2352,6 +2479,8 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati // make sure this classes signals are empty when loading for the first time _signals.clear(); + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = p_class; while (top && top != p_native_class) { const Vector<GDMonoClass *> &delegates = top->get_all_delegates(); @@ -2372,6 +2501,8 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati } bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> ¶ms) { + GD_MONO_ASSERT_THREAD_ATTACHED; + if (p_delegate->has_attribute(CACHED_CLASS(SignalAttribute))) { MonoType *raw_type = p_delegate->get_mono_type(); @@ -2413,6 +2544,8 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve */ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) { + GD_MONO_ASSERT_THREAD_ATTACHED; + // Goddammit, C++. All I wanted was some nested functions. #define MEMBER_FULL_QUALIFIED_NAME(m_member) \ (m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name()) @@ -2491,6 +2624,8 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect 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; + if (p_variant_type == Variant::INT && p_type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(p_type.type_class->get_mono_ptr())) { r_hint = PROPERTY_HINT_ENUM; @@ -2600,6 +2735,8 @@ Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, i return Variant(); } + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = script_class; while (top && top != native) { @@ -2792,6 +2929,8 @@ StringName CSharpScript::get_instance_base_type() const { CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error) { + GD_MONO_ASSERT_THREAD_ATTACHED; + /* STEP 1, CREATE */ // Search the constructor first, to fail with an error if it's not found before allocating anything else. @@ -2886,12 +3025,14 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::Call } r_error.error = Variant::CallError::CALL_OK; - REF ref; ERR_FAIL_NULL_V(native, Variant()); + GD_MONO_SCOPE_THREAD_ATTACH; + Object *owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native)); + REF ref; Reference *r = Object::cast_to<Reference>(owner); if (r) { ref = REF(r); @@ -2929,6 +3070,8 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { } } + GD_MONO_SCOPE_THREAD_ATTACH; + Variant::CallError unchecked_error; return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error); } @@ -2976,6 +3119,8 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { if (!script_class) return; + GD_MONO_SCOPE_THREAD_ATTACH; + // TODO: Filter out things unsuitable for explicit calls, like constructors. const Vector<GDMonoMethod *> &methods = script_class->get_all_methods(); for (int i = 0; i < methods.size(); ++i) { @@ -2988,6 +3133,8 @@ bool CSharpScript::has_method(const StringName &p_method) const { if (!script_class) return false; + GD_MONO_SCOPE_THREAD_ATTACH; + return script_class->has_fetched_method_unknown_params(p_method); } @@ -2996,6 +3143,8 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { if (!script_class) return MethodInfo(); + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoClass *top = script_class; while (top && top != native) { @@ -3020,6 +3169,8 @@ Error CSharpScript::reload(bool p_keep_state) { ERR_FAIL_COND_V(!p_keep_state && has_instances, ERR_ALREADY_IN_USE); + GD_MONO_SCOPE_THREAD_ATTACH; + GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly(); if (project_assembly) { @@ -3247,39 +3398,7 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p script->set_path(p_original_path); -#ifndef TOOLS_ENABLED - -#ifdef DEBUG_ENABLED - // User is responsible for thread attach/detach - CRASH_COND_MSG(mono_domain_get() == NULL, "Thread is not attached."); -#endif - -#endif - -#ifdef TOOLS_ENABLED - MonoDomain *domain = mono_domain_get(); - if (Engine::get_singleton()->is_editor_hint() && domain == NULL) { - - CRASH_COND(Thread::get_caller_id() == Thread::get_main_id()); - - // Thread is not attached, but we will make an exception in this case - // because this may be called by one of the editor's worker threads. - // Attach this thread temporarily to reload the script. - - if (domain) { - MonoThread *mono_thread = mono_thread_attach(domain); - CRASH_COND(mono_thread == NULL); - script->reload(); - mono_thread_detach(mono_thread); - } - - } else { // just reload it normally -#endif - script->reload(); - -#ifdef TOOLS_ENABLED - } -#endif + script->reload(); if (r_error) *r_error = OK; diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 027f429125..f244bc4119 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -307,6 +307,12 @@ class CSharpLanguage : public ScriptLanguage { Map<Object *, CSharpScriptBinding> script_bindings; +#ifdef DEBUG_ENABLED + // List of unsafe object references + Map<ObjectID, int> unsafe_object_references; + Mutex *unsafe_object_references_lock; +#endif + struct StringNameCache { StringName _signal_callback; @@ -458,6 +464,9 @@ public: Vector<StackInfo> stack_trace_get_info(MonoObject *p_stack_trace); #endif + void post_unsafe_reference(Object *p_obj); + void pre_unsafe_unreference(Object *p_obj); + CSharpLanguage(); ~CSharpLanguage(); }; diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj index 1eaa36c1aa..8fdd485209 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -9,7 +9,7 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>GodotTools.BuildLogger</RootNamespace> <AssemblyName>GodotTools.BuildLogger</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <LangVersion>7</LangVersion> </PropertyGroup> @@ -50,11 +50,11 @@ </ProjectReference> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> </Target> <Target Name="AfterBuild"> </Target> --> -</Project>
\ No newline at end of file +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj index 1974220f2f..2c35ef540a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -7,7 +7,7 @@ <OutputType>Library</OutputType> <RootNamespace>GodotTools.Core</RootNamespace> <AssemblyName>GodotTools.Core</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> <LangVersion>7</LangVersion> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> @@ -36,4 +36,4 @@ <Compile Include="StringExtensions.cs" /> </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> -</Project>
\ No newline at end of file +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj index 427a26508f..8454535fba 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj @@ -9,7 +9,7 @@ <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>GodotTools.IdeConnection</RootNamespace> <AssemblyName>GodotTools.IdeConnection</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <LangVersion>7</LangVersion> </PropertyGroup> @@ -50,4 +50,4 @@ <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> -</Project>
\ No newline at end of file +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index b6bb0aac34..b60e501beb 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -7,7 +7,7 @@ <OutputType>Library</OutputType> <RootNamespace>GodotTools.ProjectEditor</RootNamespace> <AssemblyName>GodotTools.ProjectEditor</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath> <LangVersion>7</LangVersion> </PropertyGroup> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index 82627de01a..28b7832f90 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -100,7 +100,7 @@ namespace GodotTools.ProjectEditor mainGroup.AddProperty("OutputPath", Path.Combine("bin", "$(Configuration)")); mainGroup.AddProperty("RootNamespace", IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true)); mainGroup.AddProperty("AssemblyName", name); - mainGroup.AddProperty("TargetFrameworkVersion", "v4.5"); + mainGroup.AddProperty("TargetFrameworkVersion", "v4.7"); mainGroup.AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString()); var debugGroup = root.AddPropertyGroup(); diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index 174509dc5b..9abfda4538 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -81,7 +81,12 @@ namespace GodotTools } } - ScriptClassParser.ParseFileOrThrow(projectIncludeFile, out var classes); + Error parseError = ScriptClassParser.ParseFile(projectIncludeFile, out var classes, out string errorStr); + if (parseError != Error.Ok) + { + GD.PushError($"Failed to determine namespace and class for script: {projectIncludeFile}. Parse error: {errorStr ?? parseError.ToString()}"); + continue; + } string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile); diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 96cafba87f..3e2a8c22a9 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -17,6 +17,43 @@ namespace GodotTools.Export { public class ExportPlugin : EditorExportPlugin { + [Flags] + enum I18NCodesets + { + None = 0, + CJK = 1, + MidEast = 2, + Other = 4, + Rare = 8, + West = 16, + All = CJK | MidEast | Other | Rare | West + } + + private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string platform) + { + var codesets = (I18NCodesets) ProjectSettings.GetSetting("mono/export/i18n_codesets"); + + if (codesets == I18NCodesets.None) + return; + + string bclDir = DeterminePlatformBclDir(platform) ?? typeof(object).Assembly.Location.GetBaseDir(); + + void AddI18NAssembly(string name) => assemblies.Add(name, Path.Combine(bclDir, $"{name}.dll")); + + AddI18NAssembly("I18N"); + + if ((codesets & I18NCodesets.CJK) != 0) + AddI18NAssembly("I18N.CJK"); + if ((codesets & I18NCodesets.MidEast) != 0) + AddI18NAssembly("I18N.MidEast"); + if ((codesets & I18NCodesets.Other) != 0) + AddI18NAssembly("I18N.Other"); + if ((codesets & I18NCodesets.Rare) != 0) + AddI18NAssembly("I18N.Rare"); + if ((codesets & I18NCodesets.West) != 0) + AddI18NAssembly("I18N.West"); + } + public void RegisterExportSettings() { // TODO: These would be better as export preset options, but that doesn't seem to be supported yet @@ -24,6 +61,16 @@ namespace GodotTools.Export GlobalDef("mono/export/include_scripts_content", false); GlobalDef("mono/export/export_assemblies_inside_pck", true); + GlobalDef("mono/export/i18n_codesets", I18NCodesets.All); + + ProjectSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = Variant.Type.Int, + ["name"] = "mono/export/i18n_codesets", + ["hint"] = PropertyHint.Flags, + ["hint_string"] = "CJK,MidEast,Other,Rare,West" + }); + GlobalDef("mono/export/aot/enabled", false); GlobalDef("mono/export/aot/full_aot", false); @@ -145,6 +192,8 @@ namespace GodotTools.Export var initialDependencies = dependencies.Duplicate(); internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, DeterminePlatformBclDir(platform), dependencies); + AddI18NAssemblies(dependencies, platform); + string outputDataDir = null; if (PlatformHasTemplateDir(platform)) diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index 618527f916..379dfd9f7d 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -7,7 +7,7 @@ <OutputType>Library</OutputType> <RootNamespace>GodotTools</RootNamespace> <AssemblyName>GodotTools</AssemblyName> - <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <TargetFrameworkVersion>v4.7</TargetFrameworkVersion> <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> <DataDirToolsOutputPath>$(GodotSourceRootPath)/bin/GodotSharp/Tools</DataDirToolsOutputPath> <GodotApiConfiguration>Debug</GodotApiConfiguration> diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs index 80e45b3a3c..7fb087467f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs @@ -25,14 +25,17 @@ namespace GodotTools.Internals } [MethodImpl(MethodImplOptions.InternalCall)] - private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes); + private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes, out string errorStr); - public static void ParseFileOrThrow(string filePath, out IEnumerable<ClassDecl> classes) + public static Error ParseFile(string filePath, out IEnumerable<ClassDecl> classes, out string errorStr) { var classesArray = new Array<Dictionary>(); - var error = internal_ParseFile(filePath, classesArray); + var error = internal_ParseFile(filePath, classesArray, out errorStr); if (error != Error.Ok) - throw new Exception($"Failed to determine namespace and class for script: {filePath}. Parse error: {error}"); + { + classes = null; + return error; + } var classesList = new List<ClassDecl>(); @@ -47,6 +50,8 @@ namespace GodotTools.Internals } classes = classesList; + + return Error.Ok; } } } diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index cfc869cd39..c8d20e80be 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -201,7 +201,9 @@ uint32_t godot_icall_BindingsGenerator_CsGlueVersion() { return CS_GLUE_VERSION; } -int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes) { +int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes, MonoString **r_error_str) { + *r_error_str = NULL; + String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath); ScriptClassParser scp; @@ -220,6 +222,11 @@ int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObje classDeclDict["base_count"] = classDecl.base.size(); classes.push_back(classDeclDict); } + } else { + String error_str = scp.get_error(); + if (!error_str.empty()) { + *r_error_str = GDMonoMarshal::mono_string_from_godot(error_str); + } } return err; } diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp index c400479b89..bece23c9a6 100644 --- a/modules/mono/editor/script_class_parser.cpp +++ b/modules/mono/editor/script_class_parser.cpp @@ -302,8 +302,10 @@ Error ScriptClassParser::_skip_generic_type_params() { Error err = _skip_generic_type_params(); if (err) return err; - continue; - } else if (tk == TK_OP_GREATER) { + tk = get_token(); + } + + if (tk == TK_OP_GREATER) { return OK; } else if (tk != TK_COMMA) { error_str = "Unexpected token: " + get_token_name(tk); @@ -629,6 +631,84 @@ Error ScriptClassParser::parse(const String &p_code) { return OK; } +static String get_preprocessor_directive(const String &p_line, int p_from) { + CRASH_COND(p_line[p_from] != '#'); + p_from++; + int i = p_from; + while (i < p_line.length() && (p_line[i] == '_' || (p_line[i] >= 'A' && p_line[i] <= 'Z') || + (p_line[i] >= 'a' && p_line[i] <= 'z') || p_line[i] > 127)) { + i++; + } + return p_line.substr(p_from, i - p_from); +} + +static void run_dummy_preprocessor(String &r_source, const String &p_filepath) { + + Vector<String> lines = r_source.split("\n", /* p_allow_empty: */ true); + + bool *include_lines = memnew_arr(bool, lines.size()); + + int if_level = -1; + Vector<bool> is_branch_being_compiled; + + for (int i = 0; i < lines.size(); i++) { + const String &line = lines[i]; + + const int line_len = line.length(); + + int j; + for (j = 0; j < line_len; j++) { + if (line[j] != ' ' && line[j] != '\t') { + if (line[j] == '#') { + // First non-whitespace char of the line is '#' + include_lines[i] = false; + + String directive = get_preprocessor_directive(line, j); + + if (directive == "if") { + if_level++; + is_branch_being_compiled.push_back(if_level == 0 || is_branch_being_compiled[if_level - 1]); + } else if (directive == "elif") { + ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#elif' directive. File: '" + p_filepath + "'."); + is_branch_being_compiled.write[if_level] = false; + } else if (directive == "else") { + ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#else' directive. File: '" + p_filepath + "'."); + is_branch_being_compiled.write[if_level] = false; + } else if (directive == "endif") { + ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#endif' directive. File: '" + p_filepath + "'."); + is_branch_being_compiled.remove(if_level); + if_level--; + } + + break; + } else { + // First non-whitespace char of the line is not '#' + include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level]; + break; + } + } + } + + if (j == line_len) { + // Loop ended without finding a non-whitespace character. + // Either the line was empty or it only contained whitespaces. + include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level]; + } + } + + r_source.clear(); + + // Custom join ignoring lines removed by the preprocessor + for (int i = 0; i < lines.size(); i++) { + if (i > 0 && include_lines[i - 1]) + r_source += '\n'; + + if (include_lines[i]) { + r_source += lines[i]; + } + } +} + Error ScriptClassParser::parse_file(const String &p_filepath) { String source; @@ -641,6 +721,8 @@ Error ScriptClassParser::parse_file(const String &p_filepath) { " Please ensure that scripts are saved in valid UTF-8 unicode." : "Failed to read file: '" + p_filepath + "'."); + run_dummy_preprocessor(source, p_filepath); + return parse(source); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index c5e62b77c8..d38589013e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -387,6 +387,19 @@ namespace Godot return b; } + public Basis Slerp(Basis target, real_t t) + { + var from = new Quat(this); + var to = new Quat(target); + + var b = new Basis(from.Slerp(to, t)); + b.Row0 *= Mathf.Lerp(Row0.Length(), target.Row0.Length(), t); + b.Row1 *= Mathf.Lerp(Row1.Length(), target.Row1.Length(), t); + b.Row2 *= Mathf.Lerp(Row2.Length(), target.Row2.Length(), t); + + return b; + } + public real_t Tdotx(Vector3 with) { return this.Row0[0] * with[0] + this.Row1[0] * with[1] + this.Row2[0] * with[2]; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs index 5023725f17..5d16260f5d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/NodeExtensions.cs @@ -9,7 +9,7 @@ namespace Godot public T GetNodeOrNull<T>(NodePath path) where T : class { - return GetNode(path) as T; + return GetNodeOrNull(path) as T; } public T GetChild<T>(int idx) where T : class diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs index 8f60867ac3..6702634c51 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs @@ -82,12 +82,20 @@ namespace Godot public Vector3 GetEuler() { +#if DEBUG + if (!IsNormalized()) + throw new InvalidOperationException("Quat is not normalized"); +#endif var basis = new Basis(this); return basis.GetEuler(); } public Quat Inverse() { +#if DEBUG + if (!IsNormalized()) + throw new InvalidOperationException("Quat is not normalized"); +#endif return new Quat(-x, -y, -z, w); } @@ -125,6 +133,13 @@ namespace Godot public Quat Slerp(Quat b, real_t t) { +#if DEBUG + if (!IsNormalized()) + throw new InvalidOperationException("Quat is not normalized"); + if (!b.IsNormalized()) + throw new ArgumentException("Argument is not normalized", nameof(b)); +#endif + // Calculate cosine real_t cosom = x * b.x + y * b.y + z * b.z + w * b.w; @@ -200,9 +215,13 @@ namespace Godot public Vector3 Xform(Vector3 v) { - Quat q = this * v; - q *= Inverse(); - return new Vector3(q.x, q.y, q.z); +#if DEBUG + if (!IsNormalized()) + throw new InvalidOperationException("Quat is not normalized"); +#endif + var u = new Vector3(x, y, z); + Vector3 uv = u.Cross(v); + return v + ((uv * w) + u.Cross(uv)) * 2; } // Static Readonly Properties @@ -257,8 +276,12 @@ namespace Godot public Quat(Vector3 axis, real_t angle) { +#if DEBUG + if (!axis.IsNormalized()) + throw new ArgumentException("Argument is not normalized", nameof(axis)); +#endif + real_t d = axis.Length(); - real_t angle_t = angle; if (d == 0f) { @@ -269,12 +292,14 @@ namespace Godot } else { - real_t s = Mathf.Sin(angle_t * 0.5f) / d; + real_t sinAngle = Mathf.Sin(angle * 0.5f); + real_t cosAngle = Mathf.Cos(angle * 0.5f); + real_t s = sinAngle / d; x = axis.x * s; y = axis.y * s; z = axis.z * s; - w = Mathf.Cos(angle_t * 0.5f); + w = cosAngle; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 025b09199f..fded34002d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -255,7 +255,7 @@ namespace Godot { #if DEBUG if (!n.IsNormalized()) - throw new ArgumentException(String.Format("{0} is not normalized", n), nameof(n)); + throw new ArgumentException("Argument is not normalized", nameof(n)); #endif return 2.0f * n * Dot(n) - this; } @@ -296,6 +296,10 @@ namespace Godot public Vector3 Slerp(Vector3 b, real_t t) { +#if DEBUG + if (!IsNormalized()) + throw new InvalidOperationException("Vector3 is not normalized"); +#endif real_t theta = AngleTo(b); return Rotated(Cross(b), theta * t); } diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp index 04a489f1f9..02246b2f2f 100644 --- a/modules/mono/glue/base_object_glue.cpp +++ b/modules/mono/glue/base_object_glue.cpp @@ -108,6 +108,7 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolea // Unsafe refcount decrement. The managed instance also counts as a reference. // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) + CSharpLanguage::get_singleton()->pre_unsafe_unreference(ref); if (ref->unreference()) { memdelete(ref); } else { diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index 9d7ac5c5ea..6cf5377e2c 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -129,7 +129,7 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d (void)user_data; // UNUSED - String name = mono_assembly_name_get_name(aname); + String name = String::utf8(mono_assembly_name_get_name(aname)); bool has_extension = name.ends_with(".dll") || name.ends_with(".exe"); if (no_search) @@ -176,7 +176,7 @@ MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, vo no_search = true; in_preload = true; - String name = mono_assembly_name_get_name(aname); + String name = String::utf8(mono_assembly_name_get_name(aname)); bool has_extension = name.ends_with(".dll"); GDMonoAssembly *res = NULL; @@ -276,7 +276,7 @@ GDMonoAssembly *GDMonoAssembly::_load_assembly_from(const String &p_name, const } void GDMonoAssembly::_wrap_mono_assembly(MonoAssembly *assembly) { - String name = mono_assembly_name_get_name(mono_assembly_get_name(assembly)); + String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly))); MonoImage *image = mono_assembly_get_image(assembly); diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp index 8669182c4e..75aa77c7b0 100644 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -83,7 +83,9 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) // May not me referenced yet, so we must use init_ref() instead of reference() - ref->init_ref(); + if (ref->init_ref()) { + CSharpLanguage::get_singleton()->post_unsafe_reference(ref); + } } // The object was just created, no script instance binding should have been attached diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index 3c8aa0c727..4e7f590a69 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -115,6 +115,7 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) { // but the managed instance is alive, the refcount will be 1 instead of 0. // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) ref->reference(); + CSharpLanguage::get_singleton()->post_unsafe_reference(ref); } return mono_object; @@ -124,10 +125,12 @@ void set_main_thread(MonoThread *p_thread) { mono_thread_set_main(p_thread); } -void attach_current_thread() { - ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - MonoThread *mono_thread = mono_thread_attach(mono_get_root_domain()); - ERR_FAIL_NULL(mono_thread); +MonoThread *attach_current_thread() { + ERR_FAIL_COND_V(!GDMono::get_singleton()->is_runtime_initialized(), NULL); + MonoDomain *scripts_domain = GDMono::get_singleton()->get_scripts_domain(); + MonoThread *mono_thread = mono_thread_attach(scripts_domain ? scripts_domain : mono_get_root_domain()); + ERR_FAIL_NULL_V(mono_thread, NULL); + return mono_thread; } void detach_current_thread() { @@ -137,10 +140,20 @@ void detach_current_thread() { mono_thread_detach(mono_thread); } +void detach_current_thread(MonoThread *p_mono_thread) { + ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); + ERR_FAIL_NULL(p_mono_thread); + mono_thread_detach(p_mono_thread); +} + MonoThread *get_current_thread() { return mono_thread_current(); } +bool is_thread_attached() { + return mono_domain_get() != NULL; +} + void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc) { GDMonoMethod *ctor = p_class->get_method(".ctor", 0); ERR_FAIL_NULL(ctor); @@ -616,4 +629,19 @@ GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, Mon } // namespace Marshal +ScopeThreadAttach::ScopeThreadAttach() : + mono_thread(NULL) { + if (likely(GDMono::get_singleton()->is_runtime_initialized()) && unlikely(!mono_domain_get())) { + mono_thread = GDMonoUtils::attach_current_thread(); + } +} + +ScopeThreadAttach::~ScopeThreadAttach() { + if (unlikely(mono_thread)) { + GDMonoUtils::detach_current_thread(mono_thread); + } +} + +// namespace Marshal + } // namespace GDMonoUtils diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index d9eb912cd7..db9f99bfdc 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -83,9 +83,11 @@ _FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) MonoObject *unmanaged_get_managed(Object *unmanaged); void set_main_thread(MonoThread *p_thread); -void attach_current_thread(); +MonoThread *attach_current_thread(); void detach_current_thread(); +void detach_current_thread(MonoThread *p_mono_thread); MonoThread *get_current_thread(); +bool is_thread_attached(); _FORCE_INLINE_ bool is_main_thread() { return mono_domain_get() != NULL && mono_thread_get_main() == mono_thread_current(); @@ -142,6 +144,14 @@ uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool & void dispose(MonoObject *p_mono_object, MonoException **r_exc); +struct ScopeThreadAttach { + ScopeThreadAttach(); + ~ScopeThreadAttach(); + +private: + MonoThread *mono_thread; +}; + } // namespace GDMonoUtils #define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL))) @@ -153,4 +163,15 @@ void dispose(MonoObject *p_mono_object, MonoException **r_exc); #define GD_MONO_END_RUNTIME_INVOKE \ _runtime_invoke_count_ref -= 1; +#define GD_MONO_SCOPE_THREAD_ATTACH \ + GDMonoUtils::ScopeThreadAttach __gdmono__scope__thread__attach__; \ + (void)__gdmono__scope__thread__attach__; + +#ifdef DEBUG_ENABLED +#define GD_MONO_ASSERT_THREAD_ATTACHED \ + { CRASH_COND(!GDMonoUtils::is_thread_attached()); } +#else +#define GD_MONO_ASSERT_THREAD_ATTACHED +#endif + #endif // GD_MONOUTILS_H |