50b603c7dc
We're targeting .NET 5 for now to make development easier while .NET 6 is not yet released. TEMPORARY REGRESSIONS --------------------- Assembly unloading is not implemented yet. As such, many Godot resources are leaked at exit. This will be re-implemented later together with assembly hot-reloading.
382 lines
13 KiB
C#
382 lines
13 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using Godot.NativeInterop;
|
|
|
|
namespace Godot
|
|
{
|
|
public partial class Object : IDisposable
|
|
{
|
|
private bool _disposed = false;
|
|
private Type _cachedType = typeof(Object);
|
|
|
|
internal IntPtr NativePtr;
|
|
internal bool MemoryOwn;
|
|
|
|
public Object() : this(false)
|
|
{
|
|
if (NativePtr == IntPtr.Zero)
|
|
{
|
|
unsafe
|
|
{
|
|
NativePtr = NativeCtor();
|
|
}
|
|
|
|
InteropUtils.TieManagedToUnmanaged(this, NativePtr,
|
|
NativeName, refCounted: false, GetType(), _cachedType);
|
|
}
|
|
else
|
|
{
|
|
InteropUtils.TieManagedToUnmanagedWithPreSetup(this, NativePtr);
|
|
}
|
|
|
|
_InitializeGodotScriptInstanceInternals();
|
|
}
|
|
|
|
internal void _InitializeGodotScriptInstanceInternals()
|
|
{
|
|
// Performance is not critical here as this will be replaced with source generators.
|
|
Type top = GetType();
|
|
Type native = InternalGetClassNativeBase(top);
|
|
|
|
while (top != null && top != native)
|
|
{
|
|
foreach (var eventSignal in top.GetEvents(
|
|
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
|
BindingFlags.NonPublic | BindingFlags.Public)
|
|
.Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any()))
|
|
{
|
|
unsafe
|
|
{
|
|
using var eventSignalName = new StringName(eventSignal.Name);
|
|
godot_string_name eventSignalNameAux = eventSignalName.NativeValue;
|
|
NativeFuncs.godotsharp_internal_object_connect_event_signal(NativePtr, &eventSignalNameAux);
|
|
}
|
|
}
|
|
|
|
top = top.BaseType;
|
|
}
|
|
}
|
|
|
|
internal Object(bool memoryOwn)
|
|
{
|
|
MemoryOwn = memoryOwn;
|
|
}
|
|
|
|
public IntPtr NativeInstance => NativePtr;
|
|
|
|
internal static IntPtr GetPtr(Object instance)
|
|
{
|
|
if (instance == null)
|
|
return IntPtr.Zero;
|
|
|
|
if (instance._disposed)
|
|
throw new ObjectDisposedException(instance.GetType().FullName);
|
|
|
|
return instance.NativePtr;
|
|
}
|
|
|
|
~Object()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
|
|
if (NativePtr != IntPtr.Zero)
|
|
{
|
|
if (MemoryOwn)
|
|
{
|
|
MemoryOwn = false;
|
|
NativeFuncs.godotsharp_internal_refcounted_disposed(NativePtr, (!disposing).ToGodotBool());
|
|
}
|
|
else
|
|
{
|
|
NativeFuncs.godotsharp_internal_object_disposed(NativePtr);
|
|
}
|
|
|
|
NativePtr = IntPtr.Zero;
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
public override unsafe string ToString()
|
|
{
|
|
using godot_string str = default;
|
|
NativeFuncs.godotsharp_object_to_string(GetPtr(this), &str);
|
|
return Marshaling.mono_string_from_godot(str);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a new <see cref="Godot.SignalAwaiter"/> awaiter configured to complete when the instance
|
|
/// <paramref name="source"/> emits the signal specified by the <paramref name="signal"/> parameter.
|
|
/// </summary>
|
|
/// <param name="source">
|
|
/// The instance the awaiter will be listening to.
|
|
/// </param>
|
|
/// <param name="signal">
|
|
/// The signal the awaiter will be waiting for.
|
|
/// </param>
|
|
/// <example>
|
|
/// This sample prints a message once every frame up to 100 times.
|
|
/// <code>
|
|
/// public override void _Ready()
|
|
/// {
|
|
/// for (int i = 0; i < 100; i++)
|
|
/// {
|
|
/// await ToSignal(GetTree(), "idle_frame");
|
|
/// GD.Print($"Frame {i}");
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
public SignalAwaiter ToSignal(Object source, StringName signal)
|
|
{
|
|
return new SignalAwaiter(source, signal, this);
|
|
}
|
|
|
|
internal static Type InternalGetClassNativeBase(Type t)
|
|
{
|
|
do
|
|
{
|
|
var assemblyName = t.Assembly.GetName();
|
|
|
|
if (assemblyName.Name == "GodotSharp")
|
|
return t;
|
|
|
|
if (assemblyName.Name == "GodotSharpEditor")
|
|
return t;
|
|
} while ((t = t.BaseType) != null);
|
|
|
|
return null;
|
|
}
|
|
|
|
internal static bool InternalIsClassNativeBase(Type t)
|
|
{
|
|
var assemblyName = t.Assembly.GetName();
|
|
return assemblyName.Name == "GodotSharp" || assemblyName.Name == "GodotSharpEditor";
|
|
}
|
|
|
|
internal unsafe bool InternalGodotScriptCallViaReflection(string method, godot_variant** args, int argCount,
|
|
out godot_variant ret)
|
|
{
|
|
// Performance is not critical here as this will be replaced with source generators.
|
|
Type top = GetType();
|
|
Type native = InternalGetClassNativeBase(top);
|
|
|
|
while (top != null && top != native)
|
|
{
|
|
var methodInfo = top.GetMethod(method,
|
|
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
|
BindingFlags.NonPublic | BindingFlags.Public);
|
|
|
|
if (methodInfo != null)
|
|
{
|
|
var parameters = methodInfo.GetParameters();
|
|
int paramCount = parameters.Length;
|
|
|
|
if (argCount == paramCount)
|
|
{
|
|
object[] invokeParams = new object[paramCount];
|
|
|
|
for (int i = 0; i < paramCount; i++)
|
|
{
|
|
invokeParams[i] = Marshaling.variant_to_mono_object_of_type(
|
|
args[i], parameters[i].ParameterType);
|
|
}
|
|
|
|
object retObj = methodInfo.Invoke(this, invokeParams);
|
|
|
|
ret = Marshaling.mono_object_to_variant(retObj);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
top = top.BaseType;
|
|
}
|
|
|
|
ret = default;
|
|
return false;
|
|
}
|
|
|
|
internal unsafe bool InternalGodotScriptSetFieldOrPropViaReflection(string name, godot_variant* value)
|
|
{
|
|
// Performance is not critical here as this will be replaced with source generators.
|
|
Type top = GetType();
|
|
Type native = InternalGetClassNativeBase(top);
|
|
|
|
while (top != null && top != native)
|
|
{
|
|
var fieldInfo = top.GetField(name,
|
|
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
|
BindingFlags.NonPublic | BindingFlags.Public);
|
|
|
|
if (fieldInfo != null)
|
|
{
|
|
object valueManaged = Marshaling.variant_to_mono_object_of_type(value, fieldInfo.FieldType);
|
|
fieldInfo.SetValue(this, valueManaged);
|
|
|
|
return true;
|
|
}
|
|
|
|
var propertyInfo = top.GetProperty(name,
|
|
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
|
BindingFlags.NonPublic | BindingFlags.Public);
|
|
|
|
if (propertyInfo != null)
|
|
{
|
|
object valueManaged = Marshaling.variant_to_mono_object_of_type(value, propertyInfo.PropertyType);
|
|
propertyInfo.SetValue(this, valueManaged);
|
|
|
|
return true;
|
|
}
|
|
|
|
top = top.BaseType;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
internal bool InternalGodotScriptGetFieldOrPropViaReflection(string name, out godot_variant value)
|
|
{
|
|
// Performance is not critical here as this will be replaced with source generators.
|
|
Type top = GetType();
|
|
Type native = InternalGetClassNativeBase(top);
|
|
|
|
while (top != null && top != native)
|
|
{
|
|
var fieldInfo = top.GetField(name,
|
|
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
|
BindingFlags.NonPublic | BindingFlags.Public);
|
|
|
|
if (fieldInfo != null)
|
|
{
|
|
object valueManaged = fieldInfo.GetValue(this);
|
|
value = Marshaling.mono_object_to_variant(valueManaged);
|
|
return true;
|
|
}
|
|
|
|
var propertyInfo = top.GetProperty(name,
|
|
BindingFlags.DeclaredOnly | BindingFlags.Instance |
|
|
BindingFlags.NonPublic | BindingFlags.Public);
|
|
|
|
if (propertyInfo != null)
|
|
{
|
|
object valueManaged = propertyInfo.GetValue(this);
|
|
value = Marshaling.mono_object_to_variant(valueManaged);
|
|
return true;
|
|
}
|
|
|
|
top = top.BaseType;
|
|
}
|
|
|
|
value = default;
|
|
return false;
|
|
}
|
|
|
|
internal unsafe void InternalRaiseEventSignal(godot_string_name* eventSignalName, godot_variant** args,
|
|
int argc)
|
|
{
|
|
// Performance is not critical here as this will be replaced with source generators.
|
|
|
|
using var stringName = StringName.CreateTakingOwnershipOfDisposableValue(
|
|
NativeFuncs.godotsharp_string_name_new_copy(eventSignalName));
|
|
string eventSignalNameStr = stringName.ToString();
|
|
|
|
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);
|
|
|
|
var eventSignalField = fields
|
|
.Where(f => typeof(Delegate).IsAssignableFrom(f.FieldType))
|
|
.Where(f => foundEventSignals.Contains(f.Name))
|
|
.FirstOrDefault(f => f.Name == eventSignalNameStr);
|
|
|
|
if (eventSignalField != null)
|
|
{
|
|
var @delegate = (Delegate)eventSignalField.GetValue(this);
|
|
|
|
if (@delegate == null)
|
|
continue;
|
|
|
|
var delegateType = eventSignalField.FieldType;
|
|
|
|
var invokeMethod = delegateType.GetMethod("Invoke");
|
|
|
|
if (invokeMethod == null)
|
|
throw new MissingMethodException(delegateType.FullName, "Invoke");
|
|
|
|
var parameterInfos = invokeMethod.GetParameters();
|
|
var paramsLength = parameterInfos.Length;
|
|
|
|
if (argc != paramsLength)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"The event delegate expects {paramsLength} arguments, but received {argc}.");
|
|
}
|
|
|
|
var managedArgs = new object[argc];
|
|
|
|
for (uint i = 0; i < argc; i++)
|
|
{
|
|
managedArgs[i] = Marshaling.variant_to_mono_object_of_type(
|
|
args[i], parameterInfos[i].ParameterType);
|
|
}
|
|
|
|
invokeMethod.Invoke(@delegate, managedArgs);
|
|
return;
|
|
}
|
|
|
|
top = top.BaseType;
|
|
}
|
|
}
|
|
|
|
internal static unsafe IntPtr ClassDB_get_method(StringName type, string method)
|
|
{
|
|
IntPtr methodBind;
|
|
fixed (char* methodChars = method)
|
|
{
|
|
methodBind = NativeFuncs.godotsharp_method_bind_get_method(ref type.NativeValue, methodChars);
|
|
}
|
|
|
|
if (methodBind == IntPtr.Zero)
|
|
throw new NativeMethodBindNotFoundException(type + "." + method);
|
|
|
|
return methodBind;
|
|
}
|
|
|
|
internal static unsafe delegate* unmanaged<IntPtr> ClassDB_get_constructor(StringName type)
|
|
{
|
|
// for some reason the '??' operator doesn't support 'delegate*'
|
|
var nativeConstructor = NativeFuncs.godotsharp_get_class_constructor(ref type.NativeValue);
|
|
|
|
if (nativeConstructor == null)
|
|
throw new NativeConstructorNotFoundException(type);
|
|
|
|
return nativeConstructor;
|
|
}
|
|
}
|
|
}
|