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); | |||
|  |         } | |||
|  |     } | |||
|  | } |