godot/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
Ignacio Roldán Etcheverry 50b603c7dc C#: Begin move to .NET Core
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.
2021-09-22 08:27:12 +02:00

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 &lt; 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;
}
}
}