using System; using System.Collections.Generic; using System.Dynamic; using System.Linq.Expressions; using System.Runtime.CompilerServices; namespace Godot { /// /// Represents an whose members can be dynamically accessed at runtime through the Variant API. /// /// /// /// The class enables access to the Variant /// members of a instance at runtime. /// /// /// This allows accessing the class members using their original names in the engine as well as the members from the /// script attached to the , regardless of the scripting language it was written in. /// /// /// /// This sample shows how to use to dynamically access the engine members of a . /// /// dynamic sprite = GetNode("Sprite2D").DynamicGodotObject; /// sprite.add_child(this); /// /// if ((sprite.hframes * sprite.vframes) > 0) /// sprite.frame = 0; /// /// /// /// This sample shows how to use to dynamically access the members of the script attached to a . /// /// dynamic childNode = GetNode("ChildNode").DynamicGodotObject; /// /// if (childNode.print_allowed) /// { /// childNode.message = "Hello from C#"; /// childNode.print_message(3); /// } /// /// The ChildNode node has the following GDScript script attached: /// /// // # ChildNode.gd /// // var print_allowed = true /// // var message = "" /// // /// // func print_message(times): /// // for i in times: /// // print(message) /// /// public class DynamicGodotObject : DynamicObject { /// /// Gets the associated with this . /// public Object Value { get; } /// /// Initializes a new instance of the class. /// /// /// The that will be associated with this . /// /// /// Thrown when the parameter is null. /// public DynamicGodotObject(Object godotObject) { if (godotObject == null) throw new ArgumentNullException(nameof(godotObject)); this.Value = godotObject; } public override IEnumerable 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); // Invocation 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 } }