123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- 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
- }
- }
|