293 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using Mono.Cecil;
 | 
						|
using Mono.Cecil.Cil;
 | 
						|
using Mono.Cecil.Rocks;
 | 
						|
using UnrealSharpWeaver.TypeProcessors;
 | 
						|
using UnrealSharpWeaver.Utilities;
 | 
						|
 | 
						|
namespace UnrealSharpWeaver.MetaData;
 | 
						|
 | 
						|
public class FunctionMetaData : BaseMetaData
 | 
						|
{ 
 | 
						|
    public PropertyMetaData[] Parameters { get; set; }
 | 
						|
    public PropertyMetaData? ReturnValue { get; set; }
 | 
						|
    public EFunctionFlags FunctionFlags { get; set; }
 | 
						|
    
 | 
						|
    // Non-serialized for JSON
 | 
						|
    public readonly MethodDefinition MethodDef;
 | 
						|
    public FunctionRewriteInfo RewriteInfo;
 | 
						|
    public FieldDefinition? FunctionPointerField;
 | 
						|
    public bool IsBlueprintEvent => FunctionFlags.HasAnyFlags(EFunctionFlags.BlueprintNativeEvent);
 | 
						|
    public bool HasParameters => Parameters.Length > 0 || HasReturnValue;
 | 
						|
    public bool HasReturnValue => ReturnValue != null;
 | 
						|
    public bool IsRpc => FunctionFlags.HasAnyFlags(Utilities.MethodUtilities.RpcFlags);
 | 
						|
    public bool HasOutParams => FunctionFlags.HasAnyFlags(EFunctionFlags.HasOutParms);
 | 
						|
    private bool _shouldBeRemoved;
 | 
						|
    // End non-serialized
 | 
						|
 | 
						|
    private const string CallInEditorName = "CallInEditor";
 | 
						|
 | 
						|
    public FunctionMetaData(MethodDefinition method, bool onlyCollectMetaData = false, EFunctionFlags functionFlags = EFunctionFlags.None) 
 | 
						|
        : base(method, Utilities.MethodUtilities.UFunctionAttribute)
 | 
						|
    {
 | 
						|
        MethodDef = method;
 | 
						|
        FunctionFlags = functionFlags;
 | 
						|
        
 | 
						|
        bool hasOutParams = false;
 | 
						|
        
 | 
						|
        if (!method.ReturnsVoid())
 | 
						|
        {
 | 
						|
            hasOutParams = true;
 | 
						|
            try
 | 
						|
            {
 | 
						|
                ReturnValue = PropertyMetaData.FromTypeReference(method.ReturnType, "ReturnValue", ParameterType.ReturnValue);
 | 
						|
            }
 | 
						|
            catch (InvalidPropertyException)
 | 
						|
            {
 | 
						|
                throw new InvalidUnrealFunctionException(method, $"'{method.ReturnType.FullName}' is invalid for unreal function return value.");
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (BaseAttribute != null)
 | 
						|
        {
 | 
						|
            CustomAttributeArgument? callInEditor = BaseAttribute.FindAttributeField(CallInEditorName);
 | 
						|
            if (callInEditor.HasValue)
 | 
						|
            {
 | 
						|
                TryAddMetaData(CallInEditorName, (bool) callInEditor.Value.Value);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        Parameters = new PropertyMetaData[method.Parameters.Count];
 | 
						|
        for (int i = 0; i < method.Parameters.Count; ++i)
 | 
						|
        {
 | 
						|
            ParameterDefinition param = method.Parameters[i];
 | 
						|
            ParameterType modifier = ParameterType.Value;
 | 
						|
            TypeReference paramType = param.ParameterType;
 | 
						|
            
 | 
						|
            if (param.IsOut)
 | 
						|
            {
 | 
						|
                hasOutParams = true;
 | 
						|
                modifier = ParameterType.Out;
 | 
						|
            }
 | 
						|
            else if (paramType.IsByReference)
 | 
						|
            {
 | 
						|
                hasOutParams = true;
 | 
						|
                modifier = ParameterType.Ref;
 | 
						|
            }
 | 
						|
 | 
						|
            Parameters[i] = PropertyMetaData.FromTypeReference(paramType, param.Name, modifier, param);
 | 
						|
 | 
						|
            if (param.HasConstant)
 | 
						|
            {
 | 
						|
                string? defaultValue = DefaultValueToString(param);
 | 
						|
                if (defaultValue != null)
 | 
						|
                {
 | 
						|
                    TryAddMetaData($"CPP_Default_{param.Name}", defaultValue);
 | 
						|
                    FunctionFlags |= EFunctionFlags.HasDefaults;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        FunctionFlags |= MethodDef.GetFunctionFlags();
 | 
						|
        
 | 
						|
        if (hasOutParams)
 | 
						|
        {
 | 
						|
            FunctionFlags |= EFunctionFlags.HasOutParms;
 | 
						|
        }
 | 
						|
        
 | 
						|
        if (onlyCollectMetaData)
 | 
						|
        {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        
 | 
						|
        RewriteFunction();
 | 
						|
    }
 | 
						|
    
 | 
						|
    public void RewriteFunction(bool forceOverwriteBody = false)
 | 
						|
    {
 | 
						|
        TypeDefinition baseType = MethodDef.GetOriginalBaseMethod().DeclaringType;
 | 
						|
        if (baseType == MethodDef.DeclaringType)
 | 
						|
        {
 | 
						|
            RewriteInfo = new FunctionRewriteInfo(this);
 | 
						|
            FunctionProcessor.PrepareFunctionForRewrite(this, MethodDef.DeclaringType, forceOverwriteBody);
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            EFunctionFlags flags = GetFunctionFlags(MethodDef.GetOriginalBaseMethod());
 | 
						|
            if (flags.HasAnyFlags(EFunctionFlags.BlueprintCallable) 
 | 
						|
                && !flags.HasAnyFlags(EFunctionFlags.BlueprintNativeEvent))
 | 
						|
            {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            
 | 
						|
            FunctionProcessor.MakeImplementationMethod(this);
 | 
						|
            
 | 
						|
            // We don't need the override anymore. It's copied into the Implementation method.
 | 
						|
            // But we can't remove it here because it would mess up for child classes during weaving.
 | 
						|
            _shouldBeRemoved = true;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    public void TryRemoveMethod()
 | 
						|
    {
 | 
						|
        if (!_shouldBeRemoved)
 | 
						|
        {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        
 | 
						|
        MethodDef.DeclaringType.Methods.Remove(MethodDef);
 | 
						|
    }
 | 
						|
    
 | 
						|
    public static bool IsAsyncUFunction(MethodDefinition method)
 | 
						|
    {
 | 
						|
        if (!method.HasCustomAttributes)
 | 
						|
        {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        CustomAttribute? functionAttribute = method.GetUFunction();
 | 
						|
        if (functionAttribute == null)
 | 
						|
        {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!functionAttribute.HasConstructorArguments)
 | 
						|
        {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        var flags = (EFunctionFlags) (ulong) functionAttribute.ConstructorArguments[0].Value;
 | 
						|
        return flags == EFunctionFlags.BlueprintCallable && method.ReturnType.FullName.StartsWith("System.Threading.Tasks.Task");
 | 
						|
    }
 | 
						|
 | 
						|
    public static bool IsBlueprintEventOverride(MethodDefinition method)
 | 
						|
    {
 | 
						|
        if (!method.IsVirtual)
 | 
						|
        {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
        
 | 
						|
        MethodDefinition baseMethod = method.GetOriginalBaseMethod();
 | 
						|
        if (baseMethod != method && baseMethod.HasCustomAttributes)
 | 
						|
        {
 | 
						|
            return baseMethod.IsUFunction();
 | 
						|
        }
 | 
						|
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
    
 | 
						|
    public static string? DefaultValueToString(ParameterDefinition value)
 | 
						|
    {
 | 
						|
        // Can be null if the value is set to = default/null
 | 
						|
        if (value.Constant == null)
 | 
						|
        {
 | 
						|
            return null;
 | 
						|
        }
 | 
						|
        
 | 
						|
        TypeDefinition typeDefinition = value.ParameterType.Resolve();
 | 
						|
        if (typeDefinition.IsEnum)
 | 
						|
        {
 | 
						|
            return typeDefinition.Fields[(byte) value.Constant].Name;
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Unreal doesn't support commas in default values
 | 
						|
        string defaultValue = value.Constant.ToString()!;
 | 
						|
        defaultValue = defaultValue.Replace(",", ".");
 | 
						|
        
 | 
						|
        return defaultValue;
 | 
						|
    }
 | 
						|
 | 
						|
    public static EFunctionFlags GetFunctionFlags(MethodDefinition method)
 | 
						|
    {
 | 
						|
        return (EFunctionFlags) GetFlags(method, "FunctionFlagsMapAttribute");
 | 
						|
    }
 | 
						|
 | 
						|
    public static bool IsInterfaceFunction(MethodDefinition method)
 | 
						|
    {
 | 
						|
        return TryGetInterfaceFunction(method) != null;
 | 
						|
    }
 | 
						|
    
 | 
						|
    public static MethodDefinition? TryGetInterfaceFunction(MethodDefinition method)
 | 
						|
    {
 | 
						|
        foreach (var typeInterface in method.DeclaringType.Interfaces)
 | 
						|
        {
 | 
						|
            var interfaceType = typeInterface.InterfaceType.Resolve();
 | 
						|
            
 | 
						|
            if (!interfaceType.IsUInterface())
 | 
						|
            {
 | 
						|
                continue; 
 | 
						|
            }
 | 
						|
 | 
						|
            foreach (MethodDefinition? interfaceMethod in interfaceType.Methods)
 | 
						|
            {
 | 
						|
                if (interfaceMethod.Name == method.Name)
 | 
						|
                {
 | 
						|
                    return interfaceMethod;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        return null;
 | 
						|
    }
 | 
						|
    
 | 
						|
    public static bool IsBlueprintCallable(MethodDefinition method)
 | 
						|
    {
 | 
						|
        EFunctionFlags flags = GetFunctionFlags(method);
 | 
						|
        return flags.HasAnyFlags(EFunctionFlags.BlueprintCallable);
 | 
						|
    }
 | 
						|
    
 | 
						|
    public void EmitFunctionPointers(ILProcessor processor, Instruction loadTypeField, Instruction setFunctionPointer)
 | 
						|
    {
 | 
						|
        processor.Append(loadTypeField);
 | 
						|
        processor.Emit(OpCodes.Ldstr, Name);
 | 
						|
        processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativeFunctionFromClassAndNameMethod);
 | 
						|
        processor.Append(setFunctionPointer);
 | 
						|
    }
 | 
						|
    
 | 
						|
    public void EmitFunctionParamOffsets(ILProcessor processor, Instruction loadFunctionPointer)
 | 
						|
    {
 | 
						|
        foreach (FunctionParamRewriteInfo paramRewriteInfo in RewriteInfo.FunctionParams)
 | 
						|
        {
 | 
						|
            FieldDefinition? offsetField = paramRewriteInfo.OffsetField;
 | 
						|
            if (offsetField == null)
 | 
						|
            {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            PropertyMetaData param = paramRewriteInfo.PropertyMetaData;
 | 
						|
                
 | 
						|
            processor.Append(loadFunctionPointer);
 | 
						|
            processor.Emit(OpCodes.Ldstr, param.Name);
 | 
						|
            processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetPropertyOffsetFromNameMethod);
 | 
						|
            processor.Emit(OpCodes.Stsfld, offsetField);
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    public void EmitFunctionParamSize(ILProcessor processor, Instruction loadFunctionPointer)
 | 
						|
    {
 | 
						|
        if (RewriteInfo.FunctionParamSizeField == null)
 | 
						|
        {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        processor.Append(loadFunctionPointer);
 | 
						|
        processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativeFunctionParamsSizeMethod);
 | 
						|
        processor.Emit(OpCodes.Stsfld, RewriteInfo.FunctionParamSizeField);
 | 
						|
    }
 | 
						|
    
 | 
						|
    public void EmitParamNativeProperty(ILProcessor processor, Instruction? loadFunctionPointer)
 | 
						|
    {
 | 
						|
        foreach (var paramRewriteInfo in RewriteInfo.FunctionParams)
 | 
						|
        {
 | 
						|
            FieldDefinition? nativePropertyField = paramRewriteInfo.NativePropertyField;
 | 
						|
            if (nativePropertyField == null)
 | 
						|
            {
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            processor.Append(loadFunctionPointer);
 | 
						|
            processor.Emit(OpCodes.Ldstr, paramRewriteInfo.PropertyMetaData.Name);
 | 
						|
            processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativePropertyFromNameMethod);
 | 
						|
            processor.Emit(OpCodes.Stsfld, nativePropertyField);
 | 
						|
        }
 | 
						|
    }
 | 
						|
} |