DynamicObject.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Dynamic;
  4. using System.Linq.Expressions;
  5. using System.Runtime.CompilerServices;
  6. namespace Godot
  7. {
  8. /// <summary>
  9. /// Represents an <see cref="Godot.Object"/> whose members can be dynamically accessed at runtime through the Variant API.
  10. /// </summary>
  11. /// <remarks>
  12. /// <para>
  13. /// The <see cref="Godot.DynamicGodotObject"/> class enables access to the Variant
  14. /// members of a <see cref="Godot.Object"/> instance at runtime.
  15. /// </para>
  16. /// <para>
  17. /// This allows accessing the class members using their original names in the engine as well as the members from the
  18. /// script attached to the <see cref="Godot.Object"/>, regardless of the scripting language it was written in.
  19. /// </para>
  20. /// </remarks>
  21. /// <example>
  22. /// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Godot.Object"/>.
  23. /// <code>
  24. /// dynamic sprite = GetNode("Sprite").DynamicGodotObject;
  25. /// sprite.add_child(this);
  26. ///
  27. /// if ((sprite.hframes * sprite.vframes) &gt; 0)
  28. /// sprite.frame = 0;
  29. /// </code>
  30. /// </example>
  31. /// <example>
  32. /// 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"/>.
  33. /// <code>
  34. /// dynamic childNode = GetNode("ChildNode").DynamicGodotObject;
  35. ///
  36. /// if (childNode.print_allowed)
  37. /// {
  38. /// childNode.message = "Hello from C#";
  39. /// childNode.print_message(3);
  40. /// }
  41. /// </code>
  42. /// The <c>ChildNode</c> node has the following GDScript script attached:
  43. /// <code>
  44. /// // # ChildNode.gd
  45. /// // var print_allowed = true
  46. /// // var message = ""
  47. /// //
  48. /// // func print_message(times):
  49. /// // for i in times:
  50. /// // print(message)
  51. /// </code>
  52. /// </example>
  53. public class DynamicGodotObject : DynamicObject
  54. {
  55. /// <summary>
  56. /// Gets the <see cref="Godot.Object"/> associated with this <see cref="Godot.DynamicGodotObject"/>.
  57. /// </summary>
  58. public Object Value { get; }
  59. /// <summary>
  60. /// Initializes a new instance of the <see cref="Godot.DynamicGodotObject"/> class.
  61. /// </summary>
  62. /// <param name="godotObject">
  63. /// The <see cref="Godot.Object"/> that will be associated with this <see cref="Godot.DynamicGodotObject"/>.
  64. /// </param>
  65. /// <exception cref="System.ArgumentNullException">
  66. /// Thrown when the <paramref name="godotObject"/> parameter is null.
  67. /// </exception>
  68. public DynamicGodotObject(Object godotObject)
  69. {
  70. if (godotObject == null)
  71. throw new ArgumentNullException(nameof(godotObject));
  72. this.Value = godotObject;
  73. }
  74. public override IEnumerable<string> GetDynamicMemberNames()
  75. {
  76. return godot_icall_DynamicGodotObject_SetMemberList(Object.GetPtr(Value));
  77. }
  78. public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)
  79. {
  80. switch (binder.Operation)
  81. {
  82. case ExpressionType.Equal:
  83. case ExpressionType.NotEqual:
  84. if (binder.ReturnType == typeof(bool) || binder.ReturnType.IsAssignableFrom(typeof(bool)))
  85. {
  86. if (arg == null)
  87. {
  88. bool boolResult = Object.IsInstanceValid(Value);
  89. if (binder.Operation == ExpressionType.Equal)
  90. boolResult = !boolResult;
  91. result = boolResult;
  92. return true;
  93. }
  94. if (arg is Object other)
  95. {
  96. bool boolResult = (Value == other);
  97. if (binder.Operation == ExpressionType.NotEqual)
  98. boolResult = !boolResult;
  99. result = boolResult;
  100. return true;
  101. }
  102. }
  103. break;
  104. default:
  105. // We're not implementing operators <, <=, >, and >= (LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual).
  106. // These are used on the actual pointers in variant_op.cpp. It's better to let the user do that explicitly.
  107. break;
  108. }
  109. return base.TryBinaryOperation(binder, arg, out result);
  110. }
  111. public override bool TryConvert(ConvertBinder binder, out object result)
  112. {
  113. if (binder.Type == typeof(Object))
  114. {
  115. result = Value;
  116. return true;
  117. }
  118. if (typeof(Object).IsAssignableFrom(binder.Type))
  119. {
  120. // Throws InvalidCastException when the cast fails
  121. result = Convert.ChangeType(Value, binder.Type);
  122. return true;
  123. }
  124. return base.TryConvert(binder, out result);
  125. }
  126. public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
  127. {
  128. if (indexes.Length == 1)
  129. {
  130. if (indexes[0] is string name)
  131. {
  132. return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), name, out result);
  133. }
  134. }
  135. return base.TryGetIndex(binder, indexes, out result);
  136. }
  137. public override bool TryGetMember(GetMemberBinder binder, out object result)
  138. {
  139. return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), binder.Name, out result);
  140. }
  141. public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
  142. {
  143. return godot_icall_DynamicGodotObject_InvokeMember(Object.GetPtr(Value), binder.Name, args, out result);
  144. }
  145. public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
  146. {
  147. if (indexes.Length == 1)
  148. {
  149. if (indexes[0] is string name)
  150. {
  151. return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), name, value);
  152. }
  153. }
  154. return base.TrySetIndex(binder, indexes, value);
  155. }
  156. public override bool TrySetMember(SetMemberBinder binder, object value)
  157. {
  158. return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), binder.Name, value);
  159. }
  160. [MethodImpl(MethodImplOptions.InternalCall)]
  161. internal extern static string[] godot_icall_DynamicGodotObject_SetMemberList(IntPtr godotObject);
  162. [MethodImpl(MethodImplOptions.InternalCall)]
  163. internal extern static bool godot_icall_DynamicGodotObject_InvokeMember(IntPtr godotObject, string name, object[] args, out object result);
  164. [MethodImpl(MethodImplOptions.InternalCall)]
  165. internal extern static bool godot_icall_DynamicGodotObject_GetMember(IntPtr godotObject, string name, out object result);
  166. [MethodImpl(MethodImplOptions.InternalCall)]
  167. internal extern static bool godot_icall_DynamicGodotObject_SetMember(IntPtr godotObject, string name, object value);
  168. #region We don't override these methods
  169. // Looks like this is not usable from C#
  170. //public override bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result);
  171. // Object members cannot be deleted
  172. //public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes);
  173. //public override bool TryDeleteMember(DeleteMemberBinder binder);
  174. // Invokation on the object itself, e.g.: obj(param)
  175. //public override bool TryInvoke(InvokeBinder binder, object[] args, out object result);
  176. // No unnary operations to handle
  177. //public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result);
  178. #endregion
  179. }
  180. }