291 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			291 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System.Text.Json.Serialization; | |||
|  | using Mono.Cecil; | |||
|  | using Mono.Cecil.Cil; | |||
|  | using UnrealSharpWeaver.NativeTypes; | |||
|  | using UnrealSharpWeaver.Utilities; | |||
|  | 
 | |||
|  | namespace UnrealSharpWeaver.MetaData; | |||
|  | 
 | |||
|  | public class PropertyMetaData : BaseMetaData | |||
|  | { | |||
|  |     public PropertyFlags PropertyFlags { get; set; } = PropertyFlags.None; | |||
|  |     public NativeDataType PropertyDataType { get; set; } = null!; | |||
|  |     public string RepNotifyFunctionName { get; set; } = string.Empty; | |||
|  |     public LifetimeCondition LifetimeCondition { get; set; } = LifetimeCondition.None; | |||
|  |     public string BlueprintSetter { get; set; } = string.Empty; | |||
|  |     public string BlueprintGetter { get; set; } = string.Empty; | |||
|  |     public bool HasCustomAccessors { get; set; } = false; | |||
|  |     [JsonIgnore] | |||
|  |     public PropertyDefinition? GeneratedAccessorProperty { get; set; } = null; | |||
|  | 
 | |||
|  |     // Non-serialized for JSON | |||
|  |     public FieldDefinition? PropertyOffsetField; | |||
|  |     public FieldDefinition? NativePropertyField; | |||
|  |     public readonly MemberReference? MemberRef; | |||
|  |     public bool IsOutParameter => (PropertyFlags & PropertyFlags.OutParm) == PropertyFlags.OutParm; | |||
|  |     public bool IsReferenceParameter => (PropertyFlags & PropertyFlags.ReferenceParm) == PropertyFlags.ReferenceParm; | |||
|  |     public bool IsReturnParameter => (PropertyFlags & PropertyFlags.ReturnParm) == PropertyFlags.ReturnParm; | |||
|  |     public bool IsInstancedReference => (PropertyFlags & PropertyFlags.InstancedReference) == PropertyFlags.InstancedReference; | |||
|  |     // End non-serialized | |||
|  |      | |||
|  |     private PropertyMetaData(MemberReference memberRef) : base(memberRef, PropertyUtilities.UPropertyAttribute) | |||
|  |     { | |||
|  | 
 | |||
|  |     } | |||
|  | 
 | |||
|  |     private PropertyMetaData(TypeReference typeRef, string paramName, ParameterType modifier) : this(typeRef) | |||
|  |     { | |||
|  |         MemberRef = typeRef; | |||
|  |         Name = paramName; | |||
|  |         PropertyDataType = typeRef.GetDataType(paramName, null); | |||
|  |          | |||
|  |         PropertyFlags flags = PropertyFlags.None; | |||
|  |          | |||
|  |         if (modifier != ParameterType.None) | |||
|  |         { | |||
|  |             flags |= PropertyFlags.Parm; | |||
|  |         } | |||
|  |          | |||
|  |         switch (modifier) | |||
|  |         { | |||
|  |             case ParameterType.Out: | |||
|  |                 flags |= PropertyFlags.OutParm; | |||
|  |                 break; | |||
|  |             case ParameterType.Ref: | |||
|  |                 flags |= PropertyFlags.OutParm | PropertyFlags.ReferenceParm; | |||
|  |                 break; | |||
|  |             case ParameterType.ReturnValue: | |||
|  |                 flags |= PropertyFlags.ReturnParm | PropertyFlags.OutParm; | |||
|  |                 break; | |||
|  |         } | |||
|  | 
 | |||
|  |         PropertyFlags = flags; | |||
|  |     } | |||
|  | 
 | |||
|  |     public PropertyMetaData(PropertyDefinition property) : this((MemberReference) property) | |||
|  |     { | |||
|  |         MemberRef = property; | |||
|  |          | |||
|  |         MethodDefinition getter = property.GetMethod; | |||
|  |         MethodDefinition setter = property.SetMethod; | |||
|  | 
 | |||
|  |         if (getter == null) | |||
|  |         { | |||
|  |             throw new InvalidPropertyException(property, "Unreal properties must have a default get method"); | |||
|  |         } | |||
|  |          | |||
|  |         // Check if we have custom accessors | |||
|  |         bool hasCustomGetter = !getter.MethodIsCompilerGenerated(); | |||
|  |         bool hasCustomSetter = setter != null && !setter.MethodIsCompilerGenerated(); | |||
|  | 
 | |||
|  |         HasCustomAccessors = hasCustomGetter || hasCustomSetter; | |||
|  | 
 | |||
|  |         // Allow custom getter/setter implementations | |||
|  |         if (!HasCustomAccessors) | |||
|  |         { | |||
|  |             // Only throw exception if not a custom accessor | |||
|  |             if (!getter.MethodIsCompilerGenerated()) | |||
|  |             { | |||
|  |                 throw new InvalidPropertyException(property, "Getter can not have a body for Unreal properties unless it's a custom accessor"); | |||
|  |             } | |||
|  | 
 | |||
|  |             if (setter != null && !setter.MethodIsCompilerGenerated()) | |||
|  |             { | |||
|  |                 throw new InvalidPropertyException(property, "Setter can not have a body for Unreal properties unless it's a custom accessor"); | |||
|  |             } | |||
|  |         } | |||
|  |         else | |||
|  |         { | |||
|  |             // Register custom accessors as UFunctions if they have BlueprintGetter/Setter specified | |||
|  |             RegisterPropertyAccessorAsUFunction(property.GetMethod, true); | |||
|  |             RegisterPropertyAccessorAsUFunction(property.SetMethod, false); | |||
|  |         } | |||
|  |          | |||
|  |         if (getter.IsPrivate && PropertyFlags.HasFlag(PropertyFlags.BlueprintVisible)) | |||
|  |         { | |||
|  |             if(!GetBoolMetadata("AllowPrivateAccess")) | |||
|  |             { | |||
|  |                 throw new InvalidPropertyException(property, "Blueprint visible properties can not be set to private."); | |||
|  |             }  | |||
|  |         } | |||
|  |          | |||
|  |         Initialize(property, property.PropertyType); | |||
|  |     } | |||
|  | 
 | |||
|  |     public PropertyMetaData(FieldDefinition property) : this((MemberReference) property) | |||
|  |     { | |||
|  |         MemberRef = property; | |||
|  |         Initialize(property, property.FieldType); | |||
|  |     } | |||
|  |      | |||
|  |     private void Initialize(IMemberDefinition property, TypeReference propertyType) | |||
|  |     { | |||
|  |         Name = property.Name; | |||
|  |         PropertyDataType = propertyType.GetDataType(property.FullName, property.CustomAttributes); | |||
|  |         PropertyFlags flags = (PropertyFlags) GetFlags(property, "PropertyFlagsMapAttribute"); | |||
|  |          | |||
|  |         CustomAttribute? upropertyAttribute = property.GetUProperty(); | |||
|  |         if (upropertyAttribute == null) | |||
|  |         { | |||
|  |             return; | |||
|  |         } | |||
|  | 
 | |||
|  |         CustomAttributeArgument? blueprintSetterArgument = upropertyAttribute.FindAttributeField("BlueprintSetter"); | |||
|  |         if (blueprintSetterArgument.HasValue) | |||
|  |         { | |||
|  |             BlueprintSetter = (string) blueprintSetterArgument.Value.Value; | |||
|  |         } | |||
|  |          | |||
|  |         CustomAttributeArgument? blueprintGetterArgument = upropertyAttribute.FindAttributeField("BlueprintGetter"); | |||
|  |         if (blueprintGetterArgument.HasValue) | |||
|  |         { | |||
|  |             BlueprintGetter = (string) blueprintGetterArgument.Value.Value; | |||
|  |         } | |||
|  |          | |||
|  |         CustomAttributeArgument? lifetimeConditionField = upropertyAttribute.FindAttributeField("LifetimeCondition"); | |||
|  |         if (lifetimeConditionField.HasValue) | |||
|  |         { | |||
|  |             LifetimeCondition = (LifetimeCondition) lifetimeConditionField.Value.Value; | |||
|  |         } | |||
|  |          | |||
|  |         CustomAttributeArgument? notifyMethodArgument = upropertyAttribute.FindAttributeField("ReplicatedUsing"); | |||
|  |         if (notifyMethodArgument.HasValue) | |||
|  |         { | |||
|  |             string notifyMethodName = (string) notifyMethodArgument.Value.Value; | |||
|  |             MethodReference? notifyMethod = property.DeclaringType.FindMethod(notifyMethodName); | |||
|  |              | |||
|  |             if (notifyMethod == null) | |||
|  |             { | |||
|  |                 throw new InvalidPropertyException(property, $"RepNotify method '{notifyMethodName}' not found on {property.DeclaringType.Name}"); | |||
|  |             } | |||
|  |              | |||
|  |             if (!notifyMethod.Resolve().IsUFunction()) | |||
|  |             { | |||
|  |                 throw new InvalidPropertyException(property, $"RepNotify method '{notifyMethodName}' needs to be declared as a UFunction."); | |||
|  |             } | |||
|  | 
 | |||
|  |             if (!notifyMethod.ReturnsVoid()) | |||
|  |             { | |||
|  |                 throw new InvalidPropertyException(property, $"RepNotify method '{notifyMethodName}' must return void"); | |||
|  |             } | |||
|  | 
 | |||
|  |             if (notifyMethod.Parameters.Count > 0) | |||
|  |             { | |||
|  |                 if (notifyMethod.Parameters[0].ParameterType != propertyType) | |||
|  |                 { | |||
|  |                     throw new InvalidPropertyException(property, $"RepNotify can only have matching parameters to the property it is notifying. '{notifyMethodName}' takes a '{notifyMethod.Parameters[0].ParameterType.FullName}' but the property is a '{propertyType.FullName}'"); | |||
|  |                 } | |||
|  |                  | |||
|  |                 if (notifyMethod.Parameters.Count > 1) | |||
|  |                 { | |||
|  |                     throw new InvalidPropertyException(property, $"RepNotify method '{notifyMethodName}' must take a single argument"); | |||
|  |                 } | |||
|  |             } | |||
|  | 
 | |||
|  |             // Just a quality of life, if the property is set to ReplicatedUsing, it should be replicating | |||
|  |             flags |= PropertyFlags.Net | PropertyFlags.RepNotify; | |||
|  |             RepNotifyFunctionName = notifyMethodName; | |||
|  |         } | |||
|  |          | |||
|  |         if (flags.HasFlag(PropertyFlags.Net) && !PropertyDataType.IsNetworkSupported) | |||
|  |         { | |||
|  |             throw new InvalidPropertyException(property, $"{Name} is marked as replicated but the {PropertyDataType.CSharpType} is not supported for replication"); | |||
|  |         } | |||
|  |          | |||
|  |         bool isDefaultComponent = NativeDataDefaultComponent.IsDefaultComponent(property.CustomAttributes); | |||
|  |         bool isPersistentInstance = (flags & PropertyFlags.PersistentInstance) != 0; | |||
|  | 
 | |||
|  |         const PropertyFlags instancedFlags = PropertyFlags.InstancedReference | PropertyFlags.ExportObject; | |||
|  |          | |||
|  |         if ((flags & PropertyFlags.InstancedReference) != 0 || isPersistentInstance) | |||
|  |         { | |||
|  |             flags |= instancedFlags; | |||
|  |         } | |||
|  |          | |||
|  |         if (isDefaultComponent) | |||
|  |         { | |||
|  |             flags = PropertyFlags.BlueprintVisible | PropertyFlags.NonTransactional | PropertyFlags.InstancedReference; | |||
|  |         } | |||
|  | 
 | |||
|  |         if (isPersistentInstance || isDefaultComponent) | |||
|  |         { | |||
|  |             TryAddMetaData("EditInline", "true"); | |||
|  |         } | |||
|  |          | |||
|  |         PropertyFlags = flags; | |||
|  |     } | |||
|  |      | |||
|  |     public void InitializePropertyPointers(ILProcessor processor, Instruction loadNativeType, Instruction setPropertyPointer) | |||
|  |     { | |||
|  |         processor.Append(loadNativeType); | |||
|  |         processor.Emit(OpCodes.Ldstr, Name); | |||
|  |         processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativePropertyFromNameMethod); | |||
|  |         processor.Append(setPropertyPointer); | |||
|  |     } | |||
|  |      | |||
|  |     public void InitializePropertyOffsets(ILProcessor processor, Instruction loadNativeType) | |||
|  |     { | |||
|  |         processor.Append(loadNativeType); | |||
|  |         processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetPropertyOffset); | |||
|  |         processor.Emit(OpCodes.Stsfld, PropertyOffsetField); | |||
|  |     } | |||
|  |      | |||
|  |     public static PropertyMetaData FromTypeReference(TypeReference typeRef, string paramName, ParameterType modifier = ParameterType.None, ParameterDefinition? parameterDefinition = null) | |||
|  |     { | |||
|  |         var metadata = new PropertyMetaData(typeRef, paramName, modifier); | |||
|  |         if (parameterDefinition is null) return metadata; | |||
|  |         metadata.AddMetaData(parameterDefinition); | |||
|  |         metadata.AddMetaTagsNamespace(parameterDefinition); | |||
|  |         return metadata; | |||
|  |     } | |||
|  |      | |||
|  |     private void RegisterPropertyAccessorAsUFunction(MethodDefinition accessorMethod, bool isGetter) | |||
|  |     { | |||
|  |         if (accessorMethod == null) | |||
|  |         { | |||
|  |             return; | |||
|  |         } | |||
|  | 
 | |||
|  |         // Set the appropriate blueprint accessor name based on getter or setter | |||
|  |         if (isGetter) | |||
|  |         { | |||
|  |             BlueprintGetter = accessorMethod.Name; | |||
|  |         } | |||
|  |         else | |||
|  |         { | |||
|  |             BlueprintSetter = accessorMethod.Name; | |||
|  |         } | |||
|  | 
 | |||
|  |         // Add UFunction attribute if not already present | |||
|  |         if (!accessorMethod.IsUFunction()) | |||
|  |         { | |||
|  |             var ufunctionCtor = WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference( | |||
|  |                 WeaverImporter.Instance.UFunctionAttributeConstructor); | |||
|  | 
 | |||
|  |             // Create constructor arguments array | |||
|  |             var ctorArgs = new[] | |||
|  |             { | |||
|  |                 // First argument - FunctionFlags (combine BlueprintCallable with BlueprintPure for getters) | |||
|  |                 new CustomAttributeArgument( | |||
|  |                     WeaverImporter.Instance.UInt64TypeRef, | |||
|  |                     (ulong)(isGetter  | |||
|  |                         ? EFunctionFlags.BlueprintCallable | EFunctionFlags.BlueprintPure  | |||
|  |                         : EFunctionFlags.BlueprintCallable)) | |||
|  |             }; | |||
|  | 
 | |||
|  |             var ufunctionAttribute = new CustomAttribute(ufunctionCtor) | |||
|  |             { | |||
|  |                 ConstructorArguments = { ctorArgs[0] } | |||
|  |             }; | |||
|  | 
 | |||
|  |             accessorMethod.CustomAttributes.Add(ufunctionAttribute); | |||
|  |              | |||
|  |             var blueprintInternalUseOnlyCtor = WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference( | |||
|  |                 WeaverImporter.Instance.BlueprintInternalUseAttributeConstructor); | |||
|  |             accessorMethod.CustomAttributes.Add(new CustomAttribute(blueprintInternalUseOnlyCtor)); | |||
|  |         } | |||
|  | 
 | |||
|  |         // Make the method public to be accessible from Blueprint | |||
|  |         accessorMethod.Attributes = (accessorMethod.Attributes & ~MethodAttributes.MemberAccessMask) | MethodAttributes.Public; | |||
|  |     } | |||
|  | } |