Skip to content

Commit

Permalink
Merge pull request godotengine#27497 from neikeq/dynamicmetaobject
Browse files Browse the repository at this point in the history
C#: Add DynamicGodotObject class
  • Loading branch information
neikeq authored Mar 29, 2019
2 parents 472c8a7 + bb6814a commit 805eec7
Show file tree
Hide file tree
Showing 11 changed files with 407 additions and 34 deletions.
21 changes: 7 additions & 14 deletions modules/mono/editor/bindings_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type
#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array"

#define BINDINGS_GENERATOR_VERSION UINT32_C(7)
#define BINDINGS_GENERATOR_VERSION UINT32_C(8)

const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN = "\t%0 %1_in = %1;\n";
Expand Down Expand Up @@ -1912,20 +1912,13 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
String vararg_arg = "arg" + argc_str;
String real_argc_str = itos(p_imethod.arguments.size() - 1); // Arguments count without vararg

p_output.push_back("\tVector<Variant> varargs;\n"
"\tint vararg_length = mono_array_length(");
p_output.push_back("\tint vararg_length = mono_array_length(");
p_output.push_back(vararg_arg);
p_output.push_back(");\n\tint total_length = ");
p_output.push_back(real_argc_str);
p_output.push_back(" + vararg_length;\n\t");
p_output.push_back(err_fail_macro);
p_output.push_back("(varargs.resize(vararg_length) != OK");
p_output.push_back(fail_ret);
p_output.push_back(");\n\tVector<Variant*> " C_LOCAL_PTRCALL_ARGS ";\n\t");
p_output.push_back(err_fail_macro);
p_output.push_back("(call_args.resize(total_length) != OK");
p_output.push_back(fail_ret);
p_output.push_back(");\n");
p_output.push_back(" + vararg_length;\n"
"\tArgumentsVector<Variant> varargs(vararg_length);\n"
"\tArgumentsVector<const Variant *> " C_LOCAL_PTRCALL_ARGS "(total_length);\n");
p_output.push_back(c_in_statements);
p_output.push_back("\tfor (int i = 0; i < vararg_length; i++) " OPEN_BLOCK
"\t\tMonoObject* elem = mono_array_get(");
Expand All @@ -1934,7 +1927,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
"\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n"
"\t\t" C_LOCAL_PTRCALL_ARGS ".set(");
p_output.push_back(real_argc_str);
p_output.push_back(" + i, &varargs.write[i]);\n\t" CLOSE_BLOCK);
p_output.push_back(" + i, &varargs.get(i));\n\t" CLOSE_BLOCK);
} else {
p_output.push_back(c_in_statements);
p_output.push_back("\tconst void* " C_LOCAL_PTRCALL_ARGS "[");
Expand All @@ -1956,7 +1949,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
}

p_output.push_back(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", ");
p_output.push_back(p_imethod.arguments.size() ? "(const Variant**)" C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL");
p_output.push_back(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL");
p_output.push_back(", total_length, vcall_error);\n");

// See the comment on the C_LOCAL_VARARG_RET declaration
Expand Down
213 changes: 213 additions & 0 deletions modules/mono/glue/Managed/Files/DynamicObject.cs
Original file line number Diff line number Diff line change
@@ -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
}
}
28 changes: 28 additions & 0 deletions modules/mono/glue/Managed/Files/Object.base.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,39 @@ protected virtual void Dispose(bool disposing)
disposed = true;
}

/// <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 < 100; i++)
/// {
/// await ToSignal(GetTree(), "idle_frame");
/// GD.Print($"Frame {i}");
/// }
/// }
/// </code>
/// </example>
public SignalAwaiter ToSignal(Object source, string signal)
{
return new SignalAwaiter(source, signal, this);
}

/// <summary>
/// Gets a new <see cref="Godot.DynamicGodotObject"/> associated with this instance.
/// </summary>
public dynamic DynamicObject => new DynamicGodotObject(this);

[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static IntPtr godot_icall_Object_Ctor(Object obj);

Expand Down
60 changes: 60 additions & 0 deletions modules/mono/glue/arguments_vector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*************************************************************************/
/* arguments_vector.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/

#ifndef ARGUMENTS_VECTOR_H
#define ARGUMENTS_VECTOR_H

#include "core/os/memory.h"

template <typename T, int POOL_SIZE = 5>
struct ArgumentsVector {

private:
T pool[POOL_SIZE];
T *_ptr;

ArgumentsVector();
ArgumentsVector(const ArgumentsVector &);

public:
T *ptr() { return _ptr; }
T &get(int p_idx) { return _ptr[p_idx]; }
void set(int p_idx, const T &p_value) { _ptr[p_idx] = p_value; }

explicit ArgumentsVector(int size) {
if (size <= POOL_SIZE) {
_ptr = pool;
} else {
_ptr = memnew_arr(T, size);
}
}
};

#endif // ARGUMENTS_VECTOR_H
Loading

0 comments on commit 805eec7

Please sign in to comment.