summaryrefslogtreecommitdiff
path: root/modules/mono/glue
diff options
context:
space:
mode:
authorIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-05-28 04:56:46 +0200
committerIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-08-22 03:36:51 +0200
commite235cef09f71d0cd752ba4931640be24dcb551ab (patch)
treebb347c5defc17beb54490d48a91edef9da2b0d1d /modules/mono/glue
parentd78e0a842638df9c98a8f7637b125d36e488a367 (diff)
C#: Re-implement assembly reloading with ALCs
Diffstat (limited to 'modules/mono/glue')
-rw-r--r--modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml5
-rw-r--r--modules/mono/glue/GodotSharp/GodotPlugins/Main.cs147
-rw-r--r--modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs5
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings1
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs18
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs104
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs67
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs12
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs315
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs92
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs98
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs176
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs18
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs24
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs8
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs28
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs5
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs75
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs9
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj5
-rw-r--r--modules/mono/glue/runtime_interop.cpp14
24 files changed, 1063 insertions, 183 deletions
diff --git a/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml b/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml
new file mode 100644
index 0000000000..2dc350d4f2
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml
@@ -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>
diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
index 2a2e147eaa..395cc9bf66 100644
--- a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
+++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
@@ -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);
+
+ string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath;
+ *outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath);
- ScriptManagerBridge.LookupScriptsInAssembly(_projectAssembly);
+ 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;
+ }
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
index 982549fff7..dcd572c65e 100644
--- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
+++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
@@ -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");
diff --git a/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings b/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings
index 3103fa78c7..ba65b61e95 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings
+++ b/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings
@@ -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>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs
new file mode 100644
index 0000000000..ac2e2fae3c
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs
@@ -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;
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
index db53a508ef..9ede67b285 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
@@ -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);
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs
index bd11811c7d..456a118b90 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs
@@ -11,7 +11,7 @@ namespace Godot.Bridge
{
try
{
- GCHandle.FromIntPtr(gcHandlePtr).Free();
+ CustomGCHandle.Free(GCHandle.FromIntPtr(gcHandlePtr));
}
catch (Exception e)
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs
new file mode 100644
index 0000000000..26fbed8cac
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs
@@ -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;
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index 9f9d740659..a6e5f6da1a 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -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,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 61987c6466..40f1235e7e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -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();
+
+ [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 readonly object ScriptBridgeLock = new();
- private static System.Collections.Generic.Dictionary<IntPtr, Type> _scriptTypeMap = new();
- private static System.Collections.Generic.Dictionary<Type, IntPtr> _typeScriptMap = 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;
+ // This path is slower, but it's only executed for the first instantiation of the type
+ CreateScriptBridgeForType(scriptType, outScript);
+ }
+ }
- _scriptTypeMap.Add(scriptPtr, scriptType);
- _typeScriptMap.Add(scriptType, scriptPtr);
+ 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;
+ }
- NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr);
+ // 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)
@@ -531,13 +640,74 @@ 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)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
new file mode 100644
index 0000000000..a58f6849ad
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
@@ -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);
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs
new file mode 100644
index 0000000000..42f19ace1a
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs
@@ -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();
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
index fb5e3c6dda..48eec66182 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
@@ -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;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index 2bea2f3b4f..2523728c8b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -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>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs
index 4e15b37e44..75793ea446 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs
@@ -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)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
index 7a2f205632..5a0ea2ba13 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
@@ -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);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs
index f6f0186016..82f1c04d40 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs
@@ -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);
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
index fc11f56680..1a0d9946d2 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
@@ -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
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index d7c57fa260..1ab2b4c0bf 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
@@ -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);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
index 71a620716f..8d683c6d1e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
@@ -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);
+ }
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs
new file mode 100644
index 0000000000..ee605f8d8f
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs
@@ -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);
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs
index fb72d706c7..8ba3c403fa 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs
@@ -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;
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 2e121bb789..d0897fe85e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -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" />
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index df72413fcc..637db00706 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -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,