Forráskód Böngészése

Merge pull request #15232 from neikeq/issue-15138-and-more

Mono fixes
Rémi Verschelde 7 éve
szülő
commit
defdb5761d

+ 69 - 23
modules/mono/csharp_script.cpp

@@ -122,6 +122,16 @@ void CSharpLanguage::init() {
 
 void CSharpLanguage::finish() {
 
+	finalizing = true;
+
+#ifdef TOOLS_ENABLED
+	// Must be here, to avoid StringName leaks
+	if (BindingsGenerator::singleton) {
+		memdelete(BindingsGenerator::singleton);
+		BindingsGenerator::singleton = NULL;
+	}
+#endif
+
 	// Release gchandle bindings before finalizing mono runtime
 	gchandle_bindings.clear();
 
@@ -129,6 +139,8 @@ void CSharpLanguage::finish() {
 		memdelete(gdmono);
 		gdmono = NULL;
 	}
+
+	finalizing = false;
 }
 
 void CSharpLanguage::get_reserved_words(List<String> *p_words) const {
@@ -742,6 +754,8 @@ CSharpLanguage::CSharpLanguage() {
 	ERR_FAIL_COND(singleton);
 	singleton = this;
 
+	finalizing = false;
+
 	gdmono = NULL;
 
 #ifdef NO_THREADS
@@ -798,12 +812,9 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
 	ERR_FAIL_NULL_V(mono_object, NULL);
 
 	// Tie managed to unmanaged
-	bool strong_handle = true;
 	Reference *ref = Object::cast_to<Reference>(p_object);
 
 	if (ref) {
-		strong_handle = false;
-
 		// Unsafe refcount increment. The managed instance also counts as a reference.
 		// This way if the unmanaged world has no references to our owner
 		// but the managed instance is alive, the refcount will be 1 instead of 0.
@@ -812,8 +823,7 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
 		ref->reference();
 	}
 
-	Ref<MonoGCHandle> gchandle = strong_handle ? MonoGCHandle::create_strong(mono_object) :
-												 MonoGCHandle::create_weak(mono_object);
+	Ref<MonoGCHandle> gchandle = MonoGCHandle::create_strong(mono_object);
 
 #ifndef NO_THREADS
 	script_bind_lock->lock();
@@ -838,27 +848,38 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
 		return;
 	}
 
+	if (finalizing)
+		return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there
+
 #ifndef NO_THREADS
 	script_bind_lock->lock();
 #endif
 
-	gchandle_bindings.erase((Map<Object *, Ref<MonoGCHandle> >::Element *)p_data);
+	Map<Object *, Ref<MonoGCHandle> >::Element *data = (Map<Object *, Ref<MonoGCHandle> >::Element *)p_data;
+
+	// Set the native instance field to IntPtr.Zero, if not yet garbage collected
+	MonoObject *mono_object = data->value()->get_target();
+	if (mono_object) {
+		CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
+	}
+
+	gchandle_bindings.erase(data);
 
 #ifndef NO_THREADS
 	script_bind_lock->unlock();
 #endif
 }
 
-void CSharpInstance::_ml_call_reversed(GDMonoClass *klass, const StringName &p_method, const Variant **p_args, int p_argcount) {
+void CSharpInstance::_ml_call_reversed(MonoObject *p_mono_object, GDMonoClass *p_klass, const StringName &p_method, const Variant **p_args, int p_argcount) {
 
-	GDMonoClass *base = klass->get_parent_class();
+	GDMonoClass *base = p_klass->get_parent_class();
 	if (base && base != script->native)
-		_ml_call_reversed(base, p_method, p_args, p_argcount);
+		_ml_call_reversed(p_mono_object, base, p_method, p_args, p_argcount);
 
-	GDMonoMethod *method = klass->get_method(p_method, p_argcount);
+	GDMonoMethod *method = p_klass->get_method(p_method, p_argcount);
 
 	if (method) {
-		method->invoke(get_mono_object(), p_args);
+		method->invoke(p_mono_object, p_args);
 	}
 }
 
@@ -1020,7 +1041,6 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args,
 
 	MonoObject *mono_object = get_mono_object();
 
-	ERR_EXPLAIN("Reference has been garbage collected?");
 	ERR_FAIL_NULL_V(mono_object, Variant());
 
 	if (!script.is_valid())
@@ -1054,23 +1074,34 @@ void CSharpInstance::call_multilevel(const StringName &p_method, const Variant *
 	if (script.is_valid()) {
 		MonoObject *mono_object = get_mono_object();
 
-		GDMonoClass *top = script->script_class;
+		ERR_FAIL_NULL(mono_object);
 
-		while (top && top != script->native) {
-			GDMonoMethod *method = top->get_method(p_method, p_argcount);
+		_call_multilevel(mono_object, p_method, p_args, p_argcount);
+	}
+}
 
-			if (method)
-				method->invoke(mono_object, p_args);
+void CSharpInstance::_call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount) {
 
-			top = top->get_parent_class();
-		}
+	GDMonoClass *top = script->script_class;
+
+	while (top && top != script->native) {
+		GDMonoMethod *method = top->get_method(p_method, p_argcount);
+
+		if (method)
+			method->invoke(p_mono_object, p_args);
+
+		top = top->get_parent_class();
 	}
 }
 
 void CSharpInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) {
 
 	if (script.is_valid()) {
-		_ml_call_reversed(script->script_class, p_method, p_args, p_argcount);
+		MonoObject *mono_object = get_mono_object();
+
+		ERR_FAIL_NULL(mono_object);
+
+		_ml_call_reversed(mono_object, script->script_class, p_method, p_args, p_argcount);
 	}
 }
 
@@ -1118,7 +1149,7 @@ void CSharpInstance::refcount_incremented() {
 
 	Reference *ref_owner = Object::cast_to<Reference>(owner);
 
-	if (ref_owner->reference_get_count() > 1) { // Remember the managed side holds a reference, hence 1 instead of 0 here
+	if (ref_owner->reference_get_count() > 1) { // The managed side also holds a reference, hence 1 instead of 0
 		// 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.
@@ -1138,7 +1169,7 @@ bool CSharpInstance::refcount_decremented() {
 
 	int refcount = ref_owner->reference_get_count();
 
-	if (refcount == 1) { // Remember the managed side holds a reference, hence 1 instead of 0 here
+	if (refcount == 1) { // The managed side also holds a reference, hence 1 instead of 0
 		// If owner owner is no longer referenced by the unmanaged side,
 		// the managed instance takes responsibility of deleting the owner when GCed.
 
@@ -1207,10 +1238,25 @@ ScriptInstance::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab
 
 void CSharpInstance::notification(int p_notification) {
 
+	MonoObject *mono_object = get_mono_object();
+
+	if (p_notification == Object::NOTIFICATION_PREDELETE) {
+		if (mono_object != NULL) { // otherwise it was collected, and the finalizer already called NOTIFICATION_PREDELETE
+			call_notification_no_check(mono_object, p_notification);
+			// Set the native instance field to IntPtr.Zero
+			CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
+		}
+		return;
+	}
+
+	call_notification_no_check(mono_object, p_notification);
+}
+
+void CSharpInstance::call_notification_no_check(MonoObject *p_mono_object, int p_notification) {
 	Variant value = p_notification;
 	const Variant *args[1] = { &value };
 
-	call_multilevel(CACHED_STRING_NAME(_notification), args, 1);
+	_call_multilevel(p_mono_object, CACHED_STRING_NAME(_notification), args, 1);
 }
 
 Ref<Script> CSharpInstance::get_script() const {

+ 10 - 5
modules/mono/csharp_script.h

@@ -167,7 +167,7 @@ class CSharpInstance : public ScriptInstance {
 	bool base_ref;
 	bool ref_dying;
 
-	void _ml_call_reversed(GDMonoClass *klass, const StringName &p_method, const Variant **p_args, int p_argcount);
+	void _ml_call_reversed(MonoObject *p_mono_object, GDMonoClass *klass, const StringName &p_method, const Variant **p_args, int p_argcount);
 
 	void _reference_owner_unsafe();
 	void _unreference_owner_unsafe();
@@ -176,6 +176,8 @@ class CSharpInstance : public ScriptInstance {
 	friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
 	static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle);
 
+	void _call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount);
+
 public:
 	MonoObject *get_mono_object() const;
 
@@ -192,13 +194,14 @@ public:
 
 	void mono_object_disposed();
 
-	void refcount_incremented();
-	bool refcount_decremented();
+	virtual void refcount_incremented();
+	virtual bool refcount_decremented();
 
-	RPCMode get_rpc_mode(const StringName &p_method) const;
-	RPCMode get_rset_mode(const StringName &p_variable) const;
+	virtual RPCMode get_rpc_mode(const StringName &p_method) const;
+	virtual RPCMode get_rset_mode(const StringName &p_variable) const;
 
 	virtual void notification(int p_notification);
+	void call_notification_no_check(MonoObject *p_mono_object, int p_notification);
 
 	virtual Ref<Script> get_script() const;
 
@@ -215,6 +218,8 @@ class CSharpLanguage : public ScriptLanguage {
 
 	static CSharpLanguage *singleton;
 
+	bool finalizing;
+
 	GDMono *gdmono;
 	SelfList<CSharpScript>::List script_list;
 

+ 9 - 8
modules/mono/editor/bindings_generator.cpp

@@ -108,6 +108,8 @@ const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN = "\t%0 %1_in
 
 bool BindingsGenerator::verbose_output = false;
 
+BindingsGenerator *BindingsGenerator::singleton = NULL;
+
 static String snake_to_pascal_case(const String &p_identifier, bool p_input_is_upper = false) {
 
 	String ret;
@@ -200,7 +202,7 @@ void BindingsGenerator::_generate_header_icalls() {
 	core_custom_icalls.clear();
 
 	core_custom_icalls.push_back(InternalCall(ICALL_GET_METHODBIND, "IntPtr", "string type, string method"));
-	core_custom_icalls.push_back(InternalCall(ICALL_OBJECT_DTOR, "void", "IntPtr ptr"));
+	core_custom_icalls.push_back(InternalCall(ICALL_OBJECT_DTOR, "void", "object obj, IntPtr ptr"));
 
 	core_custom_icalls.push_back(InternalCall(ICALL_CONNECT_SIGNAL_AWAITER, "Error",
 			"IntPtr source, string signal, IntPtr target, " CS_CLASS_SIGNALAWAITER " awaiter"));
@@ -931,8 +933,8 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
 										  "if (" BINDINGS_PTR_FIELD " != IntPtr.Zero)\n" OPEN_BLOCK_L3
 										  "if (" CS_FIELD_MEMORYOWN ")\n" OPEN_BLOCK_L4 CS_FIELD_MEMORYOWN
 										  " = false;\n" INDENT5 CS_CLASS_NATIVECALLS "." ICALL_OBJECT_DTOR
-										  "(" BINDINGS_PTR_FIELD ");\n" INDENT5 BINDINGS_PTR_FIELD
-										  " = IntPtr.Zero;\n" CLOSE_BLOCK_L4 CLOSE_BLOCK_L3 INDENT3
+										  "(this, " BINDINGS_PTR_FIELD ");\n" CLOSE_BLOCK_L4 CLOSE_BLOCK_L3 INDENT3
+										  "this." BINDINGS_PTR_FIELD " = IntPtr.Zero;\n" INDENT3
 										  "GC.SuppressFinalize(this);\n" INDENT3 "disposed = true;\n" CLOSE_BLOCK_L2);
 
 			Map<StringName, TypeInterface>::Element *array_itype = builtin_types.find(name_cache.type_Array);
@@ -2466,8 +2468,7 @@ void BindingsGenerator::_populate_global_constants() {
 	}
 }
 
-BindingsGenerator::BindingsGenerator() :
-		name_cache(NameCache::get_singleton()) {
+void BindingsGenerator::initialize() {
 
 	EditorHelp::generate_doc();
 
@@ -2509,7 +2510,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
 			const List<String>::Element *path_elem = elem->next();
 
 			if (path_elem) {
-				if (get_singleton().generate_glue(path_elem->get()) != OK)
+				if (get_singleton()->generate_glue(path_elem->get()) != OK)
 					ERR_PRINT("Mono glue generation failed");
 				elem = elem->next();
 			} else {
@@ -2523,7 +2524,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
 			const List<String>::Element *path_elem = elem->next();
 
 			if (path_elem) {
-				if (get_singleton().generate_cs_core_project(path_elem->get()) != OK)
+				if (get_singleton()->generate_cs_core_project(path_elem->get()) != OK)
 					ERR_PRINT("Generation of solution and C# project for the Core API failed");
 				elem = elem->next();
 			} else {
@@ -2538,7 +2539,7 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
 
 			if (path_elem) {
 				if (path_elem->next()) {
-					if (get_singleton().generate_cs_editor_project(path_elem->get(), path_elem->next()->get()) != OK)
+					if (get_singleton()->generate_cs_editor_project(path_elem->get(), path_elem->next()->get()) != OK)
 						ERR_PRINT("Generation of solution and C# project for the Editor API failed");
 					elem = path_elem->next();
 				} else {

+ 13 - 10
modules/mono/editor/bindings_generator.h

@@ -145,7 +145,7 @@ class BindingsGenerator {
 		}
 
 		MethodInterface() {
-			return_type = NameCache::get_singleton().type_void;
+			return_type = BindingsGenerator::get_singleton()->name_cache.type_void;
 			is_vararg = false;
 			is_virtual = false;
 			requires_object_call = false;
@@ -469,16 +469,11 @@ class BindingsGenerator {
 			enum_Error = StaticCString::create("Error");
 		}
 
-		static NameCache &get_singleton() {
-			static NameCache singleton;
-			return singleton;
-		}
-
 		NameCache(const NameCache &);
 		NameCache &operator=(const NameCache &);
 	};
 
-	const NameCache &name_cache;
+	NameCache name_cache;
 
 	const List<InternalCall>::Element *find_icall_by_name(const String &p_name, const List<InternalCall> &p_list) {
 		const List<InternalCall>::Element *it = p_list.front();
@@ -525,18 +520,26 @@ class BindingsGenerator {
 
 	Error _save_file(const String &path, const List<String> &content);
 
-	BindingsGenerator();
+	BindingsGenerator() {}
 
 	BindingsGenerator(const BindingsGenerator &);
 	BindingsGenerator &operator=(const BindingsGenerator &);
 
+	friend class CSharpLanguage;
+	static BindingsGenerator *singleton;
+
 public:
 	Error generate_cs_core_project(const String &p_output_dir, bool p_verbose_output = true);
 	Error generate_cs_editor_project(const String &p_output_dir, const String &p_core_dll_path, bool p_verbose_output = true);
 	Error generate_glue(const String &p_output_dir);
 
-	static BindingsGenerator &get_singleton() {
-		static BindingsGenerator singleton;
+	void initialize();
+
+	_FORCE_INLINE_ static BindingsGenerator *get_singleton() {
+		if (!singleton) {
+			singleton = memnew(BindingsGenerator);
+			singleton->initialize();
+		}
 		return singleton;
 	}
 

+ 3 - 3
modules/mono/editor/godotsharp_builds.cpp

@@ -238,12 +238,12 @@ bool GodotSharpBuilds::make_api_sln(GodotSharpBuilds::APIType p_api_type) {
 #error "How am I supposed to generate the bindings?"
 #endif
 
-		BindingsGenerator &gen = BindingsGenerator::get_singleton();
+		BindingsGenerator *gen = BindingsGenerator::get_singleton();
 		bool gen_verbose = OS::get_singleton()->is_stdout_verbose();
 
 		Error err = p_api_type == API_CORE ?
-							gen.generate_cs_core_project(api_sln_dir, gen_verbose) :
-							gen.generate_cs_editor_project(api_sln_dir, core_api_assembly, gen_verbose);
+							gen->generate_cs_core_project(api_sln_dir, gen_verbose) :
+							gen->generate_cs_editor_project(api_sln_dir, core_api_assembly, gen_verbose);
 
 		if (err != OK) {
 			show_build_error_dialog("Failed to generate " + api_name + " solution. Error: " + itos(err));

+ 5 - 3
modules/mono/glue/glue_header.h

@@ -53,9 +53,11 @@
 	}                                                  \
 	Object *m_instance = ci->creation_func();
 
-void godot_icall_Object_Dtor(Object *ptr) {
-	ERR_FAIL_NULL(ptr);
-	_GodotSharp::get_singleton()->queue_dispose(ptr);
+void godot_icall_Object_Dtor(MonoObject *obj, Object *ptr) {
+#ifdef DEBUG_ENABLED
+	CRASH_COND(ptr == NULL);
+#endif
+	_GodotSharp::get_singleton()->queue_dispose(obj, ptr);
 }
 
 // -- ClassDB --

+ 1 - 4
modules/mono/mono_gc_handle.cpp

@@ -41,10 +41,7 @@ uint32_t MonoGCHandle::make_strong_handle(MonoObject *p_object) {
 
 uint32_t MonoGCHandle::make_weak_handle(MonoObject *p_object) {
 
-	return mono_gchandle_new_weakref(
-			p_object,
-			true /* track_resurrection: allows us to invoke _notification(NOTIFICATION_PREDELETE) while disposing */
-	);
+	return mono_gchandle_new_weakref(p_object, false);
 }
 
 Ref<MonoGCHandle> MonoGCHandle::create_strong(MonoObject *p_object) {

+ 8 - 1
modules/mono/mono_gd/gd_mono.cpp

@@ -703,7 +703,7 @@ bool _GodotSharp::is_domain_loaded() {
 		call_deferred("_dispose_callback");   \
 	}
 
-void _GodotSharp::queue_dispose(Object *p_object) {
+void _GodotSharp::queue_dispose(MonoObject *p_mono_object, Object *p_object) {
 
 	if (GDMonoUtils::is_main_thread() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) {
 		_dispose_object(p_object);
@@ -712,6 +712,13 @@ void _GodotSharp::queue_dispose(Object *p_object) {
 		queue_mutex->lock();
 #endif
 
+		// This is our last chance to invoke notification predelete (this is being called from the finalizer)
+		// We must use the MonoObject* passed by the finalizer, because the weak GC handle target returns NULL at this point
+		CSharpInstance *si = CAST_CSHARP_INSTANCE(p_object->get_script_instance());
+		if (si) {
+			si->call_notification_no_check(p_mono_object, Object::NOTIFICATION_PREDELETE);
+		}
+
 		ENQUEUE_FOR_DISPOSAL(obj_delete_queue, p_object);
 
 #ifndef NO_THREADS

+ 1 - 1
modules/mono/mono_gd/gd_mono.h

@@ -215,7 +215,7 @@ public:
 	bool is_finalizing_domain();
 	bool is_domain_loaded();
 
-	void queue_dispose(Object *p_object);
+	void queue_dispose(MonoObject *p_mono_object, Object *p_object);
 	void queue_dispose(NodePath *p_node_path);
 	void queue_dispose(RID *p_rid);