Files
BusyRabbit/Plugins/UnrealSharp/Managed/UnrealSharpPrograms/UnrealSharpWeaver/MetaData/PropertyMetaData.cs
wyatt 648386cd73 Lua向C#逻辑迁移 一期 #13
将整个插件代码上传
2025-10-26 21:48:39 +08:00

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