@ -0,0 +1,291 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user