diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 80e127bbc2c..52b0e82c6e7 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -1581,6 +1581,7 @@ void CSharpLanguage::tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gc
CSharpInstance *instance = CAST_CSHARP_INSTANCE(p_unmanaged->get_script_instance());
if (!instance) {
+ // Native bindings don't need post-setup
return;
}
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 89265d8f2b7..fdc50e22f34 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -75,6 +75,10 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
#define CLOSE_BLOCK_L3 INDENT3 CLOSE_BLOCK
#define CLOSE_BLOCK_L4 INDENT4 CLOSE_BLOCK
+#define BINDINGS_GLOBAL_SCOPE_CLASS "GD"
+#define BINDINGS_PTR_FIELD "NativePtr"
+#define BINDINGS_NATIVE_NAME_FIELD "NativeName"
+
#define CS_PARAM_MEMORYOWN "memoryOwn"
#define CS_PARAM_METHODBIND "method"
#define CS_PARAM_INSTANCE "ptr"
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index 9fa221a0cca..cd6655b857c 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -13,16 +13,21 @@ namespace Godot.Collections
/// interfacing with the engine. Otherwise prefer .NET collections
/// such as or .
///
- public sealed class Array : IList, IDisposable
+ public sealed class Array :
+ IList,
+ IDisposable
{
internal godot_array.movable NativeValue;
+ private WeakReference _weakReferenceToSelf;
+
///
/// Constructs a new empty .
///
public Array()
{
NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new();
+ _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
///
@@ -51,6 +56,8 @@ namespace Godot.Collections
throw new ArgumentNullException(nameof(array));
NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new();
+ _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
+
int length = array.Length;
Resize(length);
@@ -64,6 +71,7 @@ namespace Godot.Collections
NativeValue = (godot_array.movable)(nativeValueToOwn.IsAllocated ?
nativeValueToOwn :
NativeFuncs.godotsharp_array_new());
+ _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
// Explicit name to make it very clear
@@ -88,6 +96,7 @@ namespace Godot.Collections
{
// Always dispose `NativeValue` even if disposing is true
NativeValue.DangerousSelfRef.Dispose();
+ DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
}
///
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index 1d19376cdda..bd939ef27d0 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -33,6 +33,7 @@ namespace Godot.Bridge
public delegate* unmanaged GCHandleBridge_FreeGCHandle;
public delegate* unmanaged DebuggingUtils_InstallTraceListener;
public delegate* unmanaged Dispatcher_InitializeDefaultGodotTaskScheduler;
+ public delegate* unmanaged DisposablesTracker_OnGodotShuttingDown;
// @formatter:on
public static ManagedCallbacks Create()
@@ -65,6 +66,7 @@ namespace Godot.Bridge
GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle,
DebuggingUtils_InstallTraceListener = &DebuggingUtils.InstallTraceListener,
Dispatcher_InitializeDefaultGodotTaskScheduler = &Dispatcher.InitializeDefaultGodotTaskScheduler,
+ DisposablesTracker_OnGodotShuttingDown = &DisposablesTracker.OnGodotShuttingDown,
// @formatter:on
};
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 689d6cddbb5..e87b7f4d4bf 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -44,16 +44,19 @@ namespace Godot.Bridge
internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName,
IntPtr godotObject)
{
+ // TODO: Optimize with source generators and delegate pointers
+
try
{
Type nativeType = TypeGetProxyClass(nativeTypeName);
var obj = (Object)FormatterServices.GetUninitializedObject(nativeType);
- obj.NativePtr = godotObject;
-
var ctor = nativeType.GetConstructor(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
null, Type.EmptyTypes, null);
+
+ obj.NativePtr = godotObject;
+
_ = ctor!.Invoke(obj, null);
return GCHandle.ToIntPtr(GCHandle.Alloc(obj));
@@ -70,14 +73,14 @@ namespace Godot.Bridge
IntPtr godotObject,
godot_variant** args, int argCount)
{
+ // TODO: Optimize with source generators and delegate pointers
+
try
{
// Performance is not critical here as this will be replaced with source generators.
Type scriptType = _scriptBridgeMap[scriptPtr];
var obj = (Object)FormatterServices.GetUninitializedObject(scriptType);
- obj.NativePtr = godotObject;
-
var ctor = scriptType
.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(c => c.GetParameters().Length == argCount)
@@ -108,7 +111,11 @@ namespace Godot.Bridge
*args[i], parameters[i].ParameterType);
}
- ctor.Invoke(obj, invokeParams);
+ obj.NativePtr = godotObject;
+
+ _ = ctor.Invoke(obj, invokeParams);
+
+
return true.ToGodotBool();
}
catch (Exception e)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index 89fc2210b81..4a99872e7b4 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -18,12 +18,15 @@ namespace Godot.Collections
{
internal godot_dictionary.movable NativeValue;
+ private WeakReference _weakReferenceToSelf;
+
///
/// Constructs a new empty .
///
public Dictionary()
{
NativeValue = (godot_dictionary.movable)NativeFuncs.godotsharp_dictionary_new();
+ _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
///
@@ -45,6 +48,7 @@ namespace Godot.Collections
NativeValue = (godot_dictionary.movable)(nativeValueToOwn.IsAllocated ?
nativeValueToOwn :
NativeFuncs.godotsharp_dictionary_new());
+ _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
// Explicit name to make it very clear
@@ -69,6 +73,7 @@ namespace Godot.Collections
{
// Always dispose `NativeValue` even if disposing is true
NativeValue.DangerousSelfRef.Dispose();
+ DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
}
///
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs
new file mode 100644
index 00000000000..bf3b3b083a5
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.InteropServices;
+using System.Runtime.Loader;
+using Godot.NativeInterop;
+
+#nullable enable
+
+namespace Godot
+{
+ internal static class DisposablesTracker
+ {
+ static DisposablesTracker()
+ {
+ AssemblyLoadContext.Default.Unloading += _ => OnUnloading();
+ }
+
+ [UnmanagedCallersOnly]
+ internal static void OnGodotShuttingDown()
+ {
+ try
+ {
+ OnUnloading();
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.DebugUnhandledException(e);
+ }
+ }
+
+ private static void OnUnloading()
+ {
+ bool isStdoutVerbose;
+
+ try
+ {
+ isStdoutVerbose = OS.IsStdoutVerbose();
+ }
+ catch (ObjectDisposedException)
+ {
+ // OS singleton already disposed. Maybe OnUnloading was called twice.
+ isStdoutVerbose = false;
+ }
+
+ if (isStdoutVerbose)
+ GD.Print("Unloading: Disposing tracked instances...");
+
+ // Dispose Godot Objects first, and only then dispose other disposables
+ // like StringName, NodePath, Godot.Collections.Array/Dictionary, etc.
+ // The Godot Object Dispose() method may need any of the later instances.
+
+ foreach (WeakReference