using System;
using System.Collections.Generic;
using System.Linq;
namespace Unity.VisualScripting
{
///
/// Invokes a method or a constructor via reflection.
///
public sealed class InvokeMember : MemberUnit
{
public InvokeMember() : base() { }
public InvokeMember(Member member) : base(member) { }
private bool useExpandedParameters;
///
/// Whether the target should be output to allow for chaining.
///
[Serialize]
[InspectableIf(nameof(supportsChaining))]
public bool chainable { get; set; }
[DoNotSerialize]
public bool supportsChaining => member.requiresTarget;
[DoNotSerialize]
[MemberFilter(Methods = true, Constructors = true)]
public Member invocation
{
get { return member; }
set { member = value; }
}
[DoNotSerialize]
[PortLabelHidden]
public ControlInput enter { get; private set; }
[DoNotSerialize]
public Dictionary inputParameters { get; private set; }
///
/// The target object used when setting the value.
///
[DoNotSerialize]
[PortLabel("Target")]
[PortLabelHidden]
public ValueOutput targetOutput { get; private set; }
[DoNotSerialize]
[PortLabelHidden]
public ValueOutput result { get; private set; }
[DoNotSerialize]
public Dictionary outputParameters { get; private set; }
[DoNotSerialize]
[PortLabelHidden]
public ControlOutput exit { get; private set; }
[DoNotSerialize]
private int parameterCount;
protected override void Definition()
{
base.Definition();
inputParameters = new Dictionary();
outputParameters = new Dictionary();
useExpandedParameters = true;
enter = ControlInput(nameof(enter), Enter);
exit = ControlOutput(nameof(exit));
Succession(enter, exit);
if (member.requiresTarget)
{
Requirement(target, enter);
}
if (supportsChaining && chainable)
{
targetOutput = ValueOutput(member.targetType, nameof(targetOutput));
Assignment(enter, targetOutput);
}
if (member.isGettable)
{
result = ValueOutput(member.type, nameof(result), Result);
if (member.requiresTarget)
{
Requirement(target, result);
}
}
var parameterInfos = member.GetParameterInfos().ToArray();
parameterCount = parameterInfos.Length;
for (int parameterIndex = 0; parameterIndex < parameterCount; parameterIndex++)
{
var parameterInfo = parameterInfos[parameterIndex];
var parameterType = parameterInfo.UnderlyingParameterType();
if (!parameterInfo.HasOutModifier())
{
var inputParameterKey = "%" + parameterInfo.Name;
var inputParameter = ValueInput(parameterType, inputParameterKey);
inputParameters.Add(parameterIndex, inputParameter);
inputParameter.SetDefaultValue(parameterInfo.PseudoDefaultValue());
if (parameterInfo.AllowsNull())
{
inputParameter.AllowsNull();
}
Requirement(inputParameter, enter);
if (member.isGettable)
{
Requirement(inputParameter, result);
}
}
if (parameterInfo.ParameterType.IsByRef || parameterInfo.IsOut)
{
var outputParameterKey = "&" + parameterInfo.Name;
var outputParameter = ValueOutput(parameterType, outputParameterKey);
outputParameters.Add(parameterIndex, outputParameter);
Assignment(enter, outputParameter);
useExpandedParameters = false;
}
}
if (inputParameters.Count > 5)
{
useExpandedParameters = false;
}
}
protected override bool IsMemberValid(Member member)
{
return member.isInvocable;
}
private object Invoke(object target, Flow flow)
{
if (useExpandedParameters)
{
switch (inputParameters.Count)
{
case 0:
return member.Invoke(target);
case 1:
return member.Invoke(target,
flow.GetConvertedValue(inputParameters[0]));
case 2:
return member.Invoke(target,
flow.GetConvertedValue(inputParameters[0]),
flow.GetConvertedValue(inputParameters[1]));
case 3:
return member.Invoke(target,
flow.GetConvertedValue(inputParameters[0]),
flow.GetConvertedValue(inputParameters[1]),
flow.GetConvertedValue(inputParameters[2]));
case 4:
return member.Invoke(target,
flow.GetConvertedValue(inputParameters[0]),
flow.GetConvertedValue(inputParameters[1]),
flow.GetConvertedValue(inputParameters[2]),
flow.GetConvertedValue(inputParameters[3]));
case 5:
return member.Invoke(target,
flow.GetConvertedValue(inputParameters[0]),
flow.GetConvertedValue(inputParameters[1]),
flow.GetConvertedValue(inputParameters[2]),
flow.GetConvertedValue(inputParameters[3]),
flow.GetConvertedValue(inputParameters[4]));
default:
throw new NotSupportedException();
}
}
else
{
var arguments = new object[parameterCount];
for (int parameterIndex = 0; parameterIndex < parameterCount; parameterIndex++)
{
if (inputParameters.TryGetValue(parameterIndex, out var inputParameter))
{
arguments[parameterIndex] = flow.GetConvertedValue(inputParameter);
}
}
var result = member.Invoke(target, arguments);
for (int parameterIndex = 0; parameterIndex < parameterCount; parameterIndex++)
{
if (outputParameters.TryGetValue(parameterIndex, out var outputParameter))
{
flow.SetValue(outputParameter, arguments[parameterIndex]);
}
}
return result;
}
}
private object GetAndChainTarget(Flow flow)
{
if (member.requiresTarget)
{
var target = flow.GetValue(this.target, member.targetType);
if (supportsChaining && chainable)
{
flow.SetValue(targetOutput, target);
}
return target;
}
return null;
}
private object Result(Flow flow)
{
var target = GetAndChainTarget(flow);
return Invoke(target, flow);
}
private ControlOutput Enter(Flow flow)
{
var target = GetAndChainTarget(flow);
var result = Invoke(target, flow);
if (this.result != null)
{
flow.SetValue(this.result, result);
}
return exit;
}
#region Analytics
public override AnalyticsIdentifier GetAnalyticsIdentifier()
{
const int maxNumParameters = 5;
var s = $"{member.targetType.FullName}.{member.name}";
if (member.parameterTypes != null)
{
s += "(";
for (var i = 0; i < member.parameterTypes.Length; ++i)
{
if (i >= maxNumParameters)
{
s += $"->{i}";
break;
}
s += member.parameterTypes[i].FullName;
if (i < member.parameterTypes.Length - 1)
s += ", ";
}
s += ")";
}
var aid = new AnalyticsIdentifier
{
Identifier = s,
Namespace = member.targetType.Namespace
};
aid.Hashcode = aid.Identifier.GetHashCode();
return aid;
}
#endregion
}
}