Lua向C#逻辑迁移 一期 #13

将整个插件代码上传
This commit is contained in:
2025-10-26 21:48:39 +08:00
parent 56994b3927
commit 648386cd73
785 changed files with 53683 additions and 2 deletions

View File

@ -0,0 +1,22 @@
namespace UnrealSharpWeaver.MetaData;
public class ApiMetaData
{
public ApiMetaData(string assemblyName)
{
AssemblyName = assemblyName;
ClassMetaData = new List<ClassMetaData>();
StructMetaData = new List<StructMetaData>();
EnumMetaData = new List<EnumMetaData>();
InterfacesMetaData = new List<InterfaceMetaData>();
DelegateMetaData = new List<DelegateMetaData>();
}
public List<ClassMetaData> ClassMetaData { get; set; }
public List<StructMetaData> StructMetaData { get; set; }
public List<EnumMetaData> EnumMetaData { get; set; }
public List<InterfaceMetaData> InterfacesMetaData { get; set; }
public List<DelegateMetaData> DelegateMetaData { get; set; }
public string AssemblyName { get; set; }
}

View File

@ -0,0 +1,282 @@
using System.Globalization;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
using UnrealSharpWeaver.Utilities;
namespace UnrealSharpWeaver.MetaData;
public class BaseMetaData
{
public string Name { get; set; }
public Dictionary<string, string> MetaData { get; set; }
// Non-serialized for JSON
public readonly string AttributeName;
public readonly IMemberDefinition MemberDefinition;
public readonly CustomAttribute? BaseAttribute;
public readonly string SourceName;
// End non-serialized
public BaseMetaData(MemberReference member, string attributeName)
{
MemberDefinition = member.Resolve();
SourceName = MemberDefinition.Name;
Name = MemberDefinition.GetEngineName();
AttributeName = attributeName;
MetaData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
BaseAttribute = MemberDefinition.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, AttributeName)!;
AddMetaData(); // Add any [UMetaData("key", "value")] attributes (general metadata attribute to allow support of any engine tag)
AddMetaTagsNamespace(); // Add all named attributes in the UnrealSharp.Attributes.MetaTags namespace
AddBaseAttributes(); // Add fields from base attribute e.g. [UClass | UFunction | UEnum | UProperty | UStruct]
AddDefaultCategory(); // Add Category="Default" if no category yet added
AddBlueprintAccess(); // Add default Blueprint access if not already added
}
public void TryAddMetaData(string key, string value = "")
{
if (MetaData.TryAdd(key, value))
{
return;
}
MetaData[key] = value;
}
public void TryAddMetaData(string key, bool value)
{
TryAddMetaData(key, value ? "true" : "false");
}
public void TryAddMetaData(string key, int value)
{
TryAddMetaData(key, value.ToString());
}
public void TryAddMetaData(string key, ulong value)
{
TryAddMetaData(key, value.ToString());
}
public void TryAddMetaData(string key, float value)
{
TryAddMetaData(key, value.ToString());
}
public void TryAddMetaData(string key, double value)
{
TryAddMetaData(key, value.ToString());
}
public void TryAddMetaData(string key, object value)
{
TryAddMetaData(key, value?.ToString() ?? "");
}
public void AddDefaultCategory()
{
if (!MetaData.ContainsKey("Category"))
{
TryAddMetaData("Category", "Default");
}
}
public void AddBlueprintAccess()
{
if (MetaData.ContainsKey("NotBlueprintType"))
{
return;
}
TryAddMetaData("BlueprintType", "true");
TryAddMetaData("IsBlueprintBase", "true");
}
public static ulong GetFlags(IEnumerable<CustomAttribute> customAttributes, string flagsAttributeName)
{
CustomAttribute? flagsAttribute = customAttributes.FindAttributeByType(WeaverImporter.UnrealSharpNamespace + ".Attributes", flagsAttributeName);
return flagsAttribute == null ? 0 : GetFlags(flagsAttribute);
}
public static ulong GetFlags(CustomAttribute flagsAttribute)
{
return (ulong) flagsAttribute.ConstructorArguments[0].Value;
}
public static ulong ExtractBoolAsFlags(TypeDefinition attributeType, CustomAttributeNamedArgument namedArg, string flagsAttributeName)
{
var arg = namedArg.Argument;
if (!(bool)arg.Value)
{
return 0;
}
// Find the property definition for this argument to resolve the true value to the desired flags map.
var properties = (from prop in attributeType.Properties where prop.Name == namedArg.Name select prop).ToArray();
TypeProcessors.ConstructorBuilder.VerifySingleResult(properties, attributeType, "attribute property " + namedArg.Name);
return GetFlags(properties[0].CustomAttributes, flagsAttributeName);
}
public static ulong ExtractStringAsFlags(TypeDefinition attributeType, CustomAttributeNamedArgument namedArg, string flagsAttributeName)
{
var arg = namedArg.Argument;
var argValue = (string) arg.Value;
if (argValue is not { Length: > 0 })
{
return 0;
}
PropertyDefinition? foundProperty = attributeType.FindPropertyByName(namedArg.Name);
if (foundProperty == null)
{
return 0;
}
TypeProcessors.ConstructorBuilder.VerifySingleResult([foundProperty], attributeType, "attribute property " + namedArg.Name);
return GetFlags(foundProperty.CustomAttributes, flagsAttributeName);
}
public static ulong GetFlags(IMemberDefinition member, string flagsAttributeName)
{
SequencePoint? sequencePoint = ErrorEmitter.GetSequencePointFromMemberDefinition(member);
Collection<CustomAttribute>? customAttributes = member.CustomAttributes;
ulong flags = 0;
foreach (CustomAttribute attribute in customAttributes)
{
TypeDefinition? attributeClass = attribute.AttributeType.Resolve();
if (attributeClass == null)
{
continue;
}
CustomAttribute? flagsMap = attributeClass.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, flagsAttributeName);
if (flagsMap == null)
{
continue;
}
flags |= GetFlags(flagsMap);
if (attribute.HasConstructorArguments)
{
foreach (CustomAttributeArgument arg in attribute.ConstructorArguments)
{
flags |= Convert.ToUInt64(arg.Value);
}
}
if (!attribute.HasProperties)
{
continue;
}
foreach (CustomAttributeNamedArgument arg in attribute.Properties)
{
TypeDefinition argType = arg.Argument.Type.Resolve();
if (argType.IsValueType && argType.Namespace == "System" && argType.Name == "Boolean")
{
flags |= ExtractBoolAsFlags(attributeClass, arg, flagsAttributeName);
}
else if (argType.Namespace == "System" && argType.Name == "String")
{
flags |= ExtractStringAsFlags(attributeClass, arg, flagsAttributeName);
}
else
{
throw new InvalidAttributeException(attributeClass, sequencePoint, $"{argType.FullName} is not supported as an attribute property type.");
}
}
}
return flags;
}
private void AddMetaData()
{
AddMetaData(MemberDefinition);
}
protected void AddMetaData(ICustomAttributeProvider provider)
{
//[UMetaData("key","value")]
List<CustomAttribute> metaDataAttributes = provider.CustomAttributes.FindMetaDataAttributes();
foreach (var attrib in metaDataAttributes)
{
switch (attrib.ConstructorArguments.Count)
{
case < 1:
continue;
case 1:
TryAddMetaData((string)attrib.ConstructorArguments[0].Value);
break;
default:
TryAddMetaData((string)attrib.ConstructorArguments[0].Value, (string)attrib.ConstructorArguments[1].Value);
break;
}
}
}
private void AddMetaTagsNamespace()
{
AddMetaTagsNamespace(MemberDefinition);
}
protected void AddMetaTagsNamespace(ICustomAttributeProvider provider)
{
//Specific MetaData Tags - all attributes in the UnrealSharp.Attributes.MetaTags Namespace
List<CustomAttribute> metaDataAttributes = provider.CustomAttributes.FindMetaDataAttributesByNamespace();
foreach (var attrib in metaDataAttributes)
{
var key = attrib.AttributeType.Name.Replace("Attribute", "");
if (attrib.HasConstructorArguments)
{
TryAddMetaData(key, attrib.ConstructorArguments[0].Value);
}
else
{
TryAddMetaData(key, "true");
}
}
}
private void AddBaseAttributes()
{
if (BaseAttribute == null)
{
return;
}
CustomAttributeArgument? displayNameArgument = BaseAttribute.FindAttributeField("DisplayName");
if (displayNameArgument.HasValue)
{
TryAddMetaData("DisplayName", (string) displayNameArgument.Value.Value);
}
CustomAttributeArgument? categoryArgument = BaseAttribute.FindAttributeField("Category");
if (categoryArgument.HasValue)
{
TryAddMetaData("Category", (string) categoryArgument.Value.Value);
}
}
protected bool GetBoolMetadata(string key)
{
if (!MetaData.TryGetValue(key, out var val))
{
return false;
}
return 0 == StringComparer.OrdinalIgnoreCase.Compare(val, "true");
}
}

View File

@ -0,0 +1,200 @@
using Mono.Cecil;
using Mono.Cecil.Rocks;
using UnrealSharpWeaver.TypeProcessors;
using UnrealSharpWeaver.Utilities;
namespace UnrealSharpWeaver.MetaData;
public class ClassMetaData : TypeReferenceMetadata
{
public TypeReferenceMetadata ParentClass { get; set; }
public List<PropertyMetaData> Properties { get; set; }
public List<FunctionMetaData> Functions { get; set; }
public List<FunctionMetaData> VirtualFunctions { get; set; }
public List<TypeReferenceMetadata> Interfaces { get; set; }
public string ConfigCategory { get; set; }
public ClassFlags ClassFlags { get; set; }
// Non-serialized for JSON
public bool HasProperties => Properties.Count > 0;
private readonly TypeDefinition _classDefinition;
// End non-serialized
public ClassMetaData(TypeDefinition type) : base(type, TypeDefinitionUtilities.UClassAttribute)
{
_classDefinition = type;
Properties = [];
Functions = [];
VirtualFunctions = [];
ConfigCategory = string.Empty;
Interfaces = [];
PopulateInterfaces();
PopulateProperties();
PopulateFunctions();
AddConfigCategory();
ParentClass = new TypeReferenceMetadata(type.BaseType.Resolve());
ClassFlags |= GetClassFlags(type, AttributeName) | ClassFlags.CompiledFromBlueprint;
// Force DefaultConfig if Config is set and no other config flag is set
if (ClassFlags.HasFlag(ClassFlags.Config) &&
!ClassFlags.HasFlag(ClassFlags.GlobalUserConfig | ClassFlags.DefaultConfig | ClassFlags.ProjectUserConfig))
{
ClassFlags |= ClassFlags.DefaultConfig;
}
if (type.IsChildOf(WeaverImporter.Instance.UActorComponentDefinition))
{
TryAddMetaData("BlueprintSpawnableComponent", true);
}
}
private void AddConfigCategory()
{
CustomAttribute uClassAttribute = _classDefinition.GetUClass()!;
CustomAttributeArgument? configCategoryProperty = uClassAttribute.FindAttributeField(nameof(ConfigCategory));
if (configCategoryProperty != null)
{
ConfigCategory = (string) configCategoryProperty.Value.Value;
}
}
private void PopulateProperties()
{
if (_classDefinition.Properties.Count == 0)
{
return;
}
Properties = [];
foreach (PropertyDefinition property in _classDefinition.Properties)
{
CustomAttribute? uPropertyAttribute = property.GetUProperty();
if (uPropertyAttribute == null)
{
continue;
}
PropertyMetaData propertyMetaData = new PropertyMetaData(property);
Properties.Add(propertyMetaData);
if (propertyMetaData.IsInstancedReference)
{
ClassFlags |= ClassFlags.HasInstancedReference;
}
}
}
void PopulateFunctions()
{
if (_classDefinition.Methods.Count == 0)
{
return;
}
Functions = [];
VirtualFunctions = [];
for (var i = _classDefinition.Methods.Count - 1; i >= 0; i--)
{
MethodDefinition method = _classDefinition.Methods[i];
if (method.HasParameters)
{
var paramNameSet = new HashSet<string>();
var uniqueNum = 0;
foreach (var param in method.Parameters)
{
if (!paramNameSet.Add(param.Name))
{
param.Name = $"{param.Name}_{uniqueNum++}";
}
}
}
if (FunctionMetaData.IsAsyncUFunction(method))
{
method.CustomAttributes.Clear();
continue;
}
bool isBlueprintOverride = FunctionMetaData.IsBlueprintEventOverride(method);
bool isInterfaceFunction = FunctionMetaData.IsInterfaceFunction(method);
if (method.IsUFunction() && !isInterfaceFunction)
{
if (isBlueprintOverride)
{
throw new Exception($"{method.FullName} is a Blueprint override and cannot be marked as a UFunction again.");
}
FunctionMetaData functionMetaData = new FunctionMetaData(method);
if (isInterfaceFunction && functionMetaData.FunctionFlags.HasFlag(EFunctionFlags.BlueprintNativeEvent))
{
throw new Exception("Interface functions cannot be marked as BlueprintEvent. Mark base declaration as BlueprintEvent instead.");
}
Functions.Add(functionMetaData);
}
if (isBlueprintOverride || isInterfaceFunction && method.GetBaseMethod().DeclaringType == _classDefinition)
{
EFunctionFlags functionFlags = EFunctionFlags.None;
if (isInterfaceFunction)
{
MethodDefinition interfaceFunction = FunctionMetaData.TryGetInterfaceFunction(method)!;
functionFlags = interfaceFunction.GetFunctionFlags();
}
VirtualFunctions.Add(new FunctionMetaData(method, false, functionFlags));
}
}
}
private static ClassFlags GetClassFlags(TypeReference classReference, string flagsAttributeName)
{
return (ClassFlags) GetFlags(classReference.Resolve().CustomAttributes, flagsAttributeName);
}
void PopulateInterfaces()
{
if (_classDefinition.Interfaces.Count == 0)
{
return;
}
Interfaces = [];
foreach (InterfaceImplementation? typeInterface in _classDefinition.Interfaces)
{
TypeDefinition interfaceType = typeInterface.InterfaceType.Resolve();
if (interfaceType == WeaverImporter.Instance.IInterfaceType || !interfaceType.IsUInterface())
{
continue;
}
Interfaces.Add(new TypeReferenceMetadata(interfaceType));
}
}
public void PostWeaveCleanup()
{
foreach (FunctionMetaData function in Functions)
{
function.TryRemoveMethod();
}
foreach (FunctionMetaData virtualFunction in VirtualFunctions)
{
virtualFunction.TryRemoveMethod();
}
}
}

View File

@ -0,0 +1,17 @@
using Mono.Cecil;
using UnrealSharpWeaver.Utilities;
namespace UnrealSharpWeaver.MetaData;
public class DelegateMetaData : TypeReferenceMetadata
{
public FunctionMetaData Signature { get; set; }
public DelegateMetaData(FunctionMetaData signature, TypeReference member, string attributeName = "", EFunctionFlags functionFlags = EFunctionFlags.None) : base(member, attributeName)
{
Name = DelegateUtilities.GetUnrealDelegateName(member);
Signature = signature;
Signature.FunctionFlags |= functionFlags;
}
}

View File

@ -0,0 +1,24 @@
using Mono.Cecil;
using UnrealSharpWeaver.Utilities;
namespace UnrealSharpWeaver.MetaData;
public class EnumMetaData : TypeReferenceMetadata
{
public List<string> Items { get; set; }
public EnumMetaData(TypeDefinition enumType) : base(enumType, TypeDefinitionUtilities.UEnumAttribute)
{
Items = new List<string>();
foreach (var field in enumType.Fields)
{
if (!field.IsStatic && field.Name == "value__")
{
continue;
}
Items.Add(field.Name);
}
}
}

View File

@ -0,0 +1,293 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using UnrealSharpWeaver.TypeProcessors;
using UnrealSharpWeaver.Utilities;
namespace UnrealSharpWeaver.MetaData;
public class FunctionMetaData : BaseMetaData
{
public PropertyMetaData[] Parameters { get; set; }
public PropertyMetaData? ReturnValue { get; set; }
public EFunctionFlags FunctionFlags { get; set; }
// Non-serialized for JSON
public readonly MethodDefinition MethodDef;
public FunctionRewriteInfo RewriteInfo;
public FieldDefinition? FunctionPointerField;
public bool IsBlueprintEvent => FunctionFlags.HasAnyFlags(EFunctionFlags.BlueprintNativeEvent);
public bool HasParameters => Parameters.Length > 0 || HasReturnValue;
public bool HasReturnValue => ReturnValue != null;
public bool IsRpc => FunctionFlags.HasAnyFlags(Utilities.MethodUtilities.RpcFlags);
public bool HasOutParams => FunctionFlags.HasAnyFlags(EFunctionFlags.HasOutParms);
private bool _shouldBeRemoved;
// End non-serialized
private const string CallInEditorName = "CallInEditor";
public FunctionMetaData(MethodDefinition method, bool onlyCollectMetaData = false, EFunctionFlags functionFlags = EFunctionFlags.None)
: base(method, Utilities.MethodUtilities.UFunctionAttribute)
{
MethodDef = method;
FunctionFlags = functionFlags;
bool hasOutParams = false;
if (!method.ReturnsVoid())
{
hasOutParams = true;
try
{
ReturnValue = PropertyMetaData.FromTypeReference(method.ReturnType, "ReturnValue", ParameterType.ReturnValue);
}
catch (InvalidPropertyException)
{
throw new InvalidUnrealFunctionException(method, $"'{method.ReturnType.FullName}' is invalid for unreal function return value.");
}
}
if (BaseAttribute != null)
{
CustomAttributeArgument? callInEditor = BaseAttribute.FindAttributeField(CallInEditorName);
if (callInEditor.HasValue)
{
TryAddMetaData(CallInEditorName, (bool) callInEditor.Value.Value);
}
}
Parameters = new PropertyMetaData[method.Parameters.Count];
for (int i = 0; i < method.Parameters.Count; ++i)
{
ParameterDefinition param = method.Parameters[i];
ParameterType modifier = ParameterType.Value;
TypeReference paramType = param.ParameterType;
if (param.IsOut)
{
hasOutParams = true;
modifier = ParameterType.Out;
}
else if (paramType.IsByReference)
{
hasOutParams = true;
modifier = ParameterType.Ref;
}
Parameters[i] = PropertyMetaData.FromTypeReference(paramType, param.Name, modifier, param);
if (param.HasConstant)
{
string? defaultValue = DefaultValueToString(param);
if (defaultValue != null)
{
TryAddMetaData($"CPP_Default_{param.Name}", defaultValue);
FunctionFlags |= EFunctionFlags.HasDefaults;
}
}
}
FunctionFlags |= MethodDef.GetFunctionFlags();
if (hasOutParams)
{
FunctionFlags |= EFunctionFlags.HasOutParms;
}
if (onlyCollectMetaData)
{
return;
}
RewriteFunction();
}
public void RewriteFunction(bool forceOverwriteBody = false)
{
TypeDefinition baseType = MethodDef.GetOriginalBaseMethod().DeclaringType;
if (baseType == MethodDef.DeclaringType)
{
RewriteInfo = new FunctionRewriteInfo(this);
FunctionProcessor.PrepareFunctionForRewrite(this, MethodDef.DeclaringType, forceOverwriteBody);
}
else
{
EFunctionFlags flags = GetFunctionFlags(MethodDef.GetOriginalBaseMethod());
if (flags.HasAnyFlags(EFunctionFlags.BlueprintCallable)
&& !flags.HasAnyFlags(EFunctionFlags.BlueprintNativeEvent))
{
return;
}
FunctionProcessor.MakeImplementationMethod(this);
// We don't need the override anymore. It's copied into the Implementation method.
// But we can't remove it here because it would mess up for child classes during weaving.
_shouldBeRemoved = true;
}
}
public void TryRemoveMethod()
{
if (!_shouldBeRemoved)
{
return;
}
MethodDef.DeclaringType.Methods.Remove(MethodDef);
}
public static bool IsAsyncUFunction(MethodDefinition method)
{
if (!method.HasCustomAttributes)
{
return false;
}
CustomAttribute? functionAttribute = method.GetUFunction();
if (functionAttribute == null)
{
return false;
}
if (!functionAttribute.HasConstructorArguments)
{
return false;
}
var flags = (EFunctionFlags) (ulong) functionAttribute.ConstructorArguments[0].Value;
return flags == EFunctionFlags.BlueprintCallable && method.ReturnType.FullName.StartsWith("System.Threading.Tasks.Task");
}
public static bool IsBlueprintEventOverride(MethodDefinition method)
{
if (!method.IsVirtual)
{
return false;
}
MethodDefinition baseMethod = method.GetOriginalBaseMethod();
if (baseMethod != method && baseMethod.HasCustomAttributes)
{
return baseMethod.IsUFunction();
}
return false;
}
public static string? DefaultValueToString(ParameterDefinition value)
{
// Can be null if the value is set to = default/null
if (value.Constant == null)
{
return null;
}
TypeDefinition typeDefinition = value.ParameterType.Resolve();
if (typeDefinition.IsEnum)
{
return typeDefinition.Fields[(byte) value.Constant].Name;
}
// Unreal doesn't support commas in default values
string defaultValue = value.Constant.ToString()!;
defaultValue = defaultValue.Replace(",", ".");
return defaultValue;
}
public static EFunctionFlags GetFunctionFlags(MethodDefinition method)
{
return (EFunctionFlags) GetFlags(method, "FunctionFlagsMapAttribute");
}
public static bool IsInterfaceFunction(MethodDefinition method)
{
return TryGetInterfaceFunction(method) != null;
}
public static MethodDefinition? TryGetInterfaceFunction(MethodDefinition method)
{
foreach (var typeInterface in method.DeclaringType.Interfaces)
{
var interfaceType = typeInterface.InterfaceType.Resolve();
if (!interfaceType.IsUInterface())
{
continue;
}
foreach (MethodDefinition? interfaceMethod in interfaceType.Methods)
{
if (interfaceMethod.Name == method.Name)
{
return interfaceMethod;
}
}
}
return null;
}
public static bool IsBlueprintCallable(MethodDefinition method)
{
EFunctionFlags flags = GetFunctionFlags(method);
return flags.HasAnyFlags(EFunctionFlags.BlueprintCallable);
}
public void EmitFunctionPointers(ILProcessor processor, Instruction loadTypeField, Instruction setFunctionPointer)
{
processor.Append(loadTypeField);
processor.Emit(OpCodes.Ldstr, Name);
processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativeFunctionFromClassAndNameMethod);
processor.Append(setFunctionPointer);
}
public void EmitFunctionParamOffsets(ILProcessor processor, Instruction loadFunctionPointer)
{
foreach (FunctionParamRewriteInfo paramRewriteInfo in RewriteInfo.FunctionParams)
{
FieldDefinition? offsetField = paramRewriteInfo.OffsetField;
if (offsetField == null)
{
continue;
}
PropertyMetaData param = paramRewriteInfo.PropertyMetaData;
processor.Append(loadFunctionPointer);
processor.Emit(OpCodes.Ldstr, param.Name);
processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetPropertyOffsetFromNameMethod);
processor.Emit(OpCodes.Stsfld, offsetField);
}
}
public void EmitFunctionParamSize(ILProcessor processor, Instruction loadFunctionPointer)
{
if (RewriteInfo.FunctionParamSizeField == null)
{
return;
}
processor.Append(loadFunctionPointer);
processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativeFunctionParamsSizeMethod);
processor.Emit(OpCodes.Stsfld, RewriteInfo.FunctionParamSizeField);
}
public void EmitParamNativeProperty(ILProcessor processor, Instruction? loadFunctionPointer)
{
foreach (var paramRewriteInfo in RewriteInfo.FunctionParams)
{
FieldDefinition? nativePropertyField = paramRewriteInfo.NativePropertyField;
if (nativePropertyField == null)
{
continue;
}
processor.Append(loadFunctionPointer);
processor.Emit(OpCodes.Ldstr, paramRewriteInfo.PropertyMetaData.Name);
processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativePropertyFromNameMethod);
processor.Emit(OpCodes.Stsfld, nativePropertyField);
}
}
}

View File

@ -0,0 +1,49 @@
using Mono.Cecil;
using UnrealSharpWeaver.Utilities;
namespace UnrealSharpWeaver.MetaData;
public class InterfaceMetaData : TypeReferenceMetadata
{
public TypeReferenceMetadata ParentInterface { get; set; }
public List<FunctionMetaData> Functions { get; set; }
// Non-serialized for JSON
const string CannotImplementInterfaceInBlueprint = "CannotImplementInterfaceInBlueprint";
// End non-serialized
public InterfaceMetaData(TypeDefinition typeDefinition) : base(typeDefinition, TypeDefinitionUtilities.UInterfaceAttribute)
{
Functions = [];
if (typeDefinition.HasInterfaces)
{
foreach (InterfaceImplementation? interfaceType in typeDefinition.Interfaces)
{
TypeDefinition interfaceDef = interfaceType.InterfaceType.Resolve();
if (!interfaceDef.IsUInterface())
{
continue;
}
ParentInterface = new TypeReferenceMetadata(interfaceDef);
break;
}
}
foreach (var method in typeDefinition.Methods)
{
if (method.IsAbstract && method.IsUFunction())
{
Functions.Add(new FunctionMetaData(method, onlyCollectMetaData: true));
}
}
CustomAttributeArgument? nonBpInterface = BaseAttribute!.FindAttributeField(CannotImplementInterfaceInBlueprint);
if (nonBpInterface != null)
{
TryAddMetaData(CannotImplementInterfaceInBlueprint, (bool) nonBpInterface.Value.Value);
}
}
}

View File

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

View File

@ -0,0 +1,92 @@
using System.Text.RegularExpressions;
using Mono.Cecil;
using UnrealSharpWeaver.Utilities;
using PropertyUtilities = UnrealSharpWeaver.Utilities.PropertyUtilities;
namespace UnrealSharpWeaver.MetaData;
public partial class StructMetaData : TypeReferenceMetadata
{
public List<PropertyMetaData> Fields { get; set; }
public StructFlags StructFlags { get; set; }
// Non-serialized for JSON
public readonly bool IsBlittableStruct;
// End non-serialized
public StructMetaData(TypeDefinition structDefinition) : base(structDefinition, TypeDefinitionUtilities.UStructAttribute)
{
Fields = new List<PropertyMetaData>();
IsBlittableStruct = true;
var backingFieldRegex = BackingFieldRegex();
foreach (var field in structDefinition.Fields)
{
if (field.IsStatic)
{
continue;
}
if (!field.IsUProperty())
{
// Struct is not blittable if it has non-UProperty fields
IsBlittableStruct = false;
}
PropertyMetaData property = new PropertyMetaData(field);
// If we match against a backing property field use the property name instead.
var backingFieldMatch = backingFieldRegex.Match(field.Name);
if (backingFieldMatch.Success)
{
string propertyName = backingFieldMatch.Groups[1].Value;
property.Name = propertyName;
}
if (property.IsInstancedReference)
{
StructFlags |= StructFlags.HasInstancedReference;
}
Fields.Add(property);
}
bool isPlainOldData = true;
foreach (var prop in Fields)
{
if (!prop.PropertyDataType.IsBlittable)
{
IsBlittableStruct = false;
}
if (!prop.PropertyDataType.IsPlainOldData)
{
isPlainOldData = false;
}
}
StructFlags |= (StructFlags) GetFlags(structDefinition, "StructFlagsMapAttribute");
if (isPlainOldData)
{
StructFlags |= StructFlags.IsPlainOldData;
StructFlags |= StructFlags.NoDestructor;
StructFlags |= StructFlags.ZeroConstructor;
}
if (!IsBlittableStruct)
{
return;
}
CustomAttribute structFlagsAttribute = new CustomAttribute(WeaverImporter.Instance.BlittableTypeConstructor);
structDefinition.CustomAttributes.Add(structFlagsAttribute);
TryAddMetaData("BlueprintType", true);
}
[GeneratedRegex("<([a-zA-Z$_][a-zA-Z0-9$_]*)>k__BackingField")]
private static partial Regex BackingFieldRegex();
}

View File

@ -0,0 +1,20 @@
using Mono.Cecil;
namespace UnrealSharpWeaver.MetaData;
public class TypeReferenceMetadata : BaseMetaData
{
public string AssemblyName { get; set; }
public string Namespace { get; set; }
// Non-serialized for JSON
public readonly TypeReference TypeRef;
// End non-serialized
public TypeReferenceMetadata(TypeReference member, string attributeName = "") : base(member, attributeName)
{
AssemblyName = member.Module.Assembly.Name.Name;
Namespace = member.Namespace;
TypeRef = member;
}
}

View File

@ -0,0 +1,6 @@
namespace UnrealSharpWeaver.MetaData;
public class UnrealSharpMetadata
{
public ICollection<string> AssemblyLoadingOrder { get; set; } = [];
}