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