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