|
@@ -0,0 +1,213 @@
|
|
|
+
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Dynamic;
|
|
|
+using System.Linq.Expressions;
|
|
|
+using System.Runtime.CompilerServices;
|
|
|
+
|
|
|
+namespace Godot
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// Represents an <see cref="Godot.Object"/> whose members can be dynamically accessed at runtime through the Variant API.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// <para>
|
|
|
+ /// The <see cref="Godot.DynamicGodotObject"/> class enables access to the Variant
|
|
|
+ /// members of a <see cref="Godot.Object"/> instance at runtime.
|
|
|
+ /// </para>
|
|
|
+ /// <para>
|
|
|
+ /// This allows accessing the class members using their original names in the engine as well as the members from the
|
|
|
+ /// script attached to the <see cref="Godot.Object"/>, regardless of the scripting language it was written in.
|
|
|
+ /// </para>
|
|
|
+ /// </remarks>
|
|
|
+ /// <example>
|
|
|
+ /// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Godot.Object"/>.
|
|
|
+ /// <code>
|
|
|
+ /// dynamic sprite = GetNode("Sprite").DynamicGodotObject;
|
|
|
+ /// sprite.add_child(this);
|
|
|
+ ///
|
|
|
+ /// if ((sprite.hframes * sprite.vframes) > 0)
|
|
|
+ /// sprite.frame = 0;
|
|
|
+ /// </code>
|
|
|
+ /// </example>
|
|
|
+ /// <example>
|
|
|
+ /// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the members of the script attached to a <see cref="Godot.Object"/>.
|
|
|
+ /// <code>
|
|
|
+ /// dynamic childNode = GetNode("ChildNode").DynamicGodotObject;
|
|
|
+ ///
|
|
|
+ /// if (childNode.print_allowed)
|
|
|
+ /// {
|
|
|
+ /// childNode.message = "Hello from C#";
|
|
|
+ /// childNode.print_message(3);
|
|
|
+ /// }
|
|
|
+ /// </code>
|
|
|
+ /// The <c>ChildNode</c> node has the following GDScript script attached:
|
|
|
+ /// <code>
|
|
|
+ /// // # ChildNode.gd
|
|
|
+ /// // var print_allowed = true
|
|
|
+ /// // var message = ""
|
|
|
+ /// //
|
|
|
+ /// // func print_message(times):
|
|
|
+ /// // for i in times:
|
|
|
+ /// // print(message)
|
|
|
+ /// </code>
|
|
|
+ /// </example>
|
|
|
+ public class DynamicGodotObject : DynamicObject
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// Gets the <see cref="Godot.Object"/> associated with this <see cref="Godot.DynamicGodotObject"/>.
|
|
|
+ /// </summary>
|
|
|
+ public Object Value { get; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new instance of the <see cref="Godot.DynamicGodotObject"/> class.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="godotObject">
|
|
|
+ /// The <see cref="Godot.Object"/> that will be associated with this <see cref="Godot.DynamicGodotObject"/>.
|
|
|
+ /// </param>
|
|
|
+ /// <exception cref="System.ArgumentNullException">
|
|
|
+ /// Thrown when the <paramref name="godotObject"/> parameter is null.
|
|
|
+ /// </exception>
|
|
|
+ public DynamicGodotObject(Object godotObject)
|
|
|
+ {
|
|
|
+ if (godotObject == null)
|
|
|
+ throw new ArgumentNullException(nameof(godotObject));
|
|
|
+
|
|
|
+ this.Value = godotObject;
|
|
|
+ }
|
|
|
+
|
|
|
+ public override IEnumerable<string> GetDynamicMemberNames()
|
|
|
+ {
|
|
|
+ return godot_icall_DynamicGodotObject_SetMemberList(Object.GetPtr(Value));
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)
|
|
|
+ {
|
|
|
+ switch (binder.Operation)
|
|
|
+ {
|
|
|
+ case ExpressionType.Equal:
|
|
|
+ case ExpressionType.NotEqual:
|
|
|
+ if (binder.ReturnType == typeof(bool) || binder.ReturnType.IsAssignableFrom(typeof(bool)))
|
|
|
+ {
|
|
|
+ if (arg == null)
|
|
|
+ {
|
|
|
+ bool boolResult = Object.IsInstanceValid(Value);
|
|
|
+
|
|
|
+ if (binder.Operation == ExpressionType.Equal)
|
|
|
+ boolResult = !boolResult;
|
|
|
+
|
|
|
+ result = boolResult;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (arg is Object other)
|
|
|
+ {
|
|
|
+ bool boolResult = (Value == other);
|
|
|
+
|
|
|
+ if (binder.Operation == ExpressionType.NotEqual)
|
|
|
+ boolResult = !boolResult;
|
|
|
+
|
|
|
+ result = boolResult;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // We're not implementing operators <, <=, >, and >= (LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual).
|
|
|
+ // These are used on the actual pointers in variant_op.cpp. It's better to let the user do that explicitly.
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return base.TryBinaryOperation(binder, arg, out result);
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool TryConvert(ConvertBinder binder, out object result)
|
|
|
+ {
|
|
|
+ if (binder.Type == typeof(Object))
|
|
|
+ {
|
|
|
+ result = Value;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof(Object).IsAssignableFrom(binder.Type))
|
|
|
+ {
|
|
|
+ // Throws InvalidCastException when the cast fails
|
|
|
+ result = Convert.ChangeType(Value, binder.Type);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return base.TryConvert(binder, out result);
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
|
|
|
+ {
|
|
|
+ if (indexes.Length == 1)
|
|
|
+ {
|
|
|
+ if (indexes[0] is string name)
|
|
|
+ {
|
|
|
+ return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), name, out result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return base.TryGetIndex(binder, indexes, out result);
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool TryGetMember(GetMemberBinder binder, out object result)
|
|
|
+ {
|
|
|
+ return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), binder.Name, out result);
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
|
|
|
+ {
|
|
|
+ return godot_icall_DynamicGodotObject_InvokeMember(Object.GetPtr(Value), binder.Name, args, out result);
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
|
|
|
+ {
|
|
|
+ if (indexes.Length == 1)
|
|
|
+ {
|
|
|
+ if (indexes[0] is string name)
|
|
|
+ {
|
|
|
+ return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), name, value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return base.TrySetIndex(binder, indexes, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public override bool TrySetMember(SetMemberBinder binder, object value)
|
|
|
+ {
|
|
|
+ return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), binder.Name, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ [MethodImpl(MethodImplOptions.InternalCall)]
|
|
|
+ internal extern static string[] godot_icall_DynamicGodotObject_SetMemberList(IntPtr godotObject);
|
|
|
+
|
|
|
+ [MethodImpl(MethodImplOptions.InternalCall)]
|
|
|
+ internal extern static bool godot_icall_DynamicGodotObject_InvokeMember(IntPtr godotObject, string name, object[] args, out object result);
|
|
|
+
|
|
|
+ [MethodImpl(MethodImplOptions.InternalCall)]
|
|
|
+ internal extern static bool godot_icall_DynamicGodotObject_GetMember(IntPtr godotObject, string name, out object result);
|
|
|
+
|
|
|
+ [MethodImpl(MethodImplOptions.InternalCall)]
|
|
|
+ internal extern static bool godot_icall_DynamicGodotObject_SetMember(IntPtr godotObject, string name, object value);
|
|
|
+
|
|
|
+ #region We don't override these methods
|
|
|
+
|
|
|
+ // Looks like this is not usable from C#
|
|
|
+ //public override bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result);
|
|
|
+
|
|
|
+ // Object members cannot be deleted
|
|
|
+ //public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes);
|
|
|
+ //public override bool TryDeleteMember(DeleteMemberBinder binder);
|
|
|
+
|
|
|
+ // Invokation on the object itself, e.g.: obj(param)
|
|
|
+ //public override bool TryInvoke(InvokeBinder binder, object[] args, out object result);
|
|
|
+
|
|
|
+ // No unnary operations to handle
|
|
|
+ //public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result);
|
|
|
+
|
|
|
+ #endregion
|
|
|
+ }
|
|
|
+}
|