Skip to content

Commit ede7035

Browse files
authored
automatically generate components accessor attributes on Tick and Reset methods of nodes. (#148)
* automatically generate components accessor attributes on `Tick` and `Reset` methods of nodes. * test case of set attributes manually on node methods. * use auto-gen attributes on the node methods. * update dependency of sample package. * remove rw attribute from nodes.
1 parent 12ed3fd commit ede7035

File tree

66 files changed

+1264
-45
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1264
-45
lines changed

Packages/codegen.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Packages/codegen/Editor.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using EntitiesBT.Core;
4+
using Mono.Cecil;
5+
using Mono.Cecil.Rocks;
6+
7+
namespace EntitiesBT.CodeGen.Editor
8+
{
9+
public class AccessorAttributeILPostProcessor : IILCodeGenerator
10+
{
11+
public int Order { get; }
12+
public ILogger Logger { get; set; }
13+
14+
public bool Generate(AssemblyDefinition assemblyDefinition)
15+
{
16+
Logger.Debug($"generate accessor attributes for {assemblyDefinition.Name}");
17+
var methods = assemblyDefinition.MainModule.GetAllTypes()
18+
.Where(type => typeof(INodeData).IsAssignableFrom(type) && type.IsClass && !type.IsAbstract)
19+
.SelectMany(FetchNodeDataMethods)
20+
.Where(method => !method.CustomAttributes.FindAccessorAttributes().Any())
21+
;
22+
var modified = false;
23+
foreach (var method in methods)
24+
{
25+
var attributes = method.GenerateAccessorAttributes();
26+
if (!attributes.Any()) continue;
27+
28+
Logger.Debug($"generate accessor attributes for {method.FullName}");
29+
modified = true;
30+
method.CustomAttributes.AddRange(attributes);
31+
}
32+
return modified;
33+
}
34+
35+
IEnumerable<MethodDefinition> FetchNodeDataMethods(TypeDefinition type)
36+
{
37+
yield return type.GetMethod(nameof(INodeData.Tick));
38+
yield return type.GetMethod(nameof(INodeData.Reset));
39+
}
40+
}
41+
}

Packages/codegen/Editor/AccessorAttributeILPostProcessor.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using EntitiesBT.Core;
5+
using Mono.Cecil;
6+
using Mono.Cecil.Cil;
7+
8+
namespace EntitiesBT.CodeGen.Editor
9+
{
10+
internal static class AccessorAttributesGenerator
11+
{
12+
public static IEnumerable<CustomAttribute> GenerateAccessorAttributes(this MethodDefinition methodDefinition)
13+
{
14+
var module = methodDefinition.Module;
15+
return methodDefinition.HasBody
16+
? methodDefinition.Body.Instructions.SelectMany(GenerateAccessorAttributes)
17+
: Enumerable.Empty<CustomAttribute>()
18+
;
19+
20+
IEnumerable<CustomAttribute> GenerateAccessorAttributes(Instruction instruction)
21+
{
22+
switch (instruction.OpCode.Code)
23+
{
24+
case Code.Calli:
25+
throw new NotSupportedException("Cannot generate attributes from `calli`");
26+
case Code.Call:
27+
case Code.Callvirt:
28+
return GenerateFromOperand(instruction);
29+
default:
30+
return Enumerable.Empty<CustomAttribute>();
31+
}
32+
}
33+
34+
IEnumerable<CustomAttribute> GenerateFromOperand(Instruction instruction)
35+
{
36+
switch (instruction.Operand)
37+
{
38+
case GenericInstanceMethod method:
39+
{
40+
var methodDefinition = method.GetElementMethod().Resolve();
41+
var genericAttributes = CreateAccessorAttributes(methodDefinition.GenericParameters, method.GenericArguments);
42+
var methodAttributes = FindAccessorAttributes(methodDefinition.CustomAttributes);
43+
var parameterAttributes = CreateAccessorAttributesOnParameters(methodDefinition, instruction);
44+
return genericAttributes.Concat(methodAttributes).Concat(parameterAttributes);
45+
}
46+
case MethodReference method:
47+
{
48+
var methodDefinition = method.Resolve();
49+
var methodAttributes = FindAccessorAttributes(methodDefinition.CustomAttributes);
50+
var parameterAttributes = CreateAccessorAttributesOnParameters(methodDefinition, instruction);
51+
return methodAttributes.Concat(parameterAttributes);
52+
}
53+
default:
54+
{
55+
return Enumerable.Empty<CustomAttribute>();
56+
}
57+
}
58+
}
59+
60+
IEnumerable<CustomAttribute> CreateAccessorAttributes(IEnumerable<ICustomAttributeProvider> attributeProviders, IEnumerable<TypeReference> types)
61+
{
62+
return attributeProviders.Zip(types, (param, type) => (attributes: FindAccessorAttributes(param.CustomAttributes), type))
63+
.SelectMany(t => t.attributes.Select(attribute => (attribute, t.type)))
64+
.Select(t => CreateAccessorAttribute(t.attribute.Constructor, t.type.Yield()))
65+
;
66+
}
67+
68+
IEnumerable<CustomAttribute> CreateAccessorAttributesOnParameters(MethodDefinition method, Instruction instruction)
69+
{
70+
var attributes = FindAccessorAttributes(method.Parameters.SelectMany(param => param.CustomAttributes));
71+
if (!attributes.Any()) yield break;
72+
73+
if (method.Parameters.Count > 1)
74+
throw new NotSupportedException($"Cannot generate accessor attribute on method({method.FullName}) with multiple parameters. must manually set accessor attributes instead.");
75+
var parameter = method.Parameters[0];
76+
foreach (var attr in attributes)
77+
{
78+
var arguments = (CustomAttributeArgument[]) attr.ConstructorArguments[0].Value;
79+
if (arguments == null || !arguments.Any())
80+
{
81+
var type = parameter.ParameterType.FullName == "System.Type"
82+
? GuessParameterType()
83+
: parameter.ParameterType
84+
;
85+
yield return CreateAccessorAttribute(attr.Constructor, type.Yield());
86+
}
87+
else
88+
{
89+
yield return CreateAccessorAttribute(attr.Constructor, arguments.Select(arg => (TypeReference)arg.Value));
90+
}
91+
}
92+
93+
TypeReference GuessParameterType()
94+
{
95+
var errorMessage = $"Cannot generate accessor attribute on method[{method.FullName}] with unsupported instruction ";
96+
instruction = instruction.Previous; // call typeof
97+
while (instruction.OpCode.Code == Code.Constrained) instruction = instruction.Previous; // skip constrained
98+
if (instruction.OpCode.Code != Code.Call) throw new NotSupportedException(errorMessage + instruction.OpCode.Code + " [-1]");
99+
var typeofMethod = instruction.Operand as MethodReference;
100+
if (typeofMethod == null) throw new NotSupportedException(errorMessage + instruction.Operand.GetType() + " [-1]");
101+
if (typeofMethod.Name != nameof(Type.GetTypeFromHandle)) throw new NotSupportedException(errorMessage + typeofMethod.Name + " [-1]");
102+
103+
instruction = instruction.Previous; // ldtoken type
104+
if (instruction.OpCode.Code != Code.Ldtoken) throw new NotSupportedException(errorMessage + instruction.OpCode.Code + " [-2]");
105+
var type = instruction.Operand as TypeReference;
106+
if (type == null) throw new NotSupportedException(errorMessage + instruction.Operand.GetType() + " [-2]");
107+
return type;
108+
}
109+
}
110+
111+
CustomAttribute CreateAccessorAttribute(MethodReference constructor, IEnumerable<TypeReference> arguments)
112+
{
113+
var attribute = new CustomAttribute(module.ImportReference(constructor));
114+
var systemTypeReference = module.ImportReference(typeof(Type));
115+
var argumentType = module.ImportReference(typeof(Type[]));
116+
var argumentValue = arguments.Select(argType => new CustomAttributeArgument(systemTypeReference, module.ImportReference(argType))).ToArray();
117+
var argument = new CustomAttributeArgument(argumentType, argumentValue);
118+
attribute.ConstructorArguments.Add(argument);
119+
return attribute;
120+
}
121+
}
122+
123+
public static IEnumerable<CustomAttribute> FindAccessorAttributes(this IEnumerable<CustomAttribute> attributes)
124+
{
125+
var attributeType = typeof(ComponentAccessorAttribute);
126+
return attributes.Where(attribute => attributeType.IsAssignableFrom(attribute.AttributeType));
127+
}
128+
}
129+
}

Packages/codegen/Editor/AccessorAttributesGenerator.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("EntitiesBT.CodeGen.Tests")]

Packages/codegen/Editor/AssemblyInfo.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using JetBrains.Annotations;
6+
using Mono.Cecil;
7+
using Mono.Collections.Generic;
8+
using TypeAttributes = Mono.Cecil.TypeAttributes;
9+
10+
namespace EntitiesBT.CodeGen.Editor
11+
{
12+
internal static class CecilExtension
13+
{
14+
[Pure]
15+
public static bool HasAttribute<T>([NotNull] this TypeDefinition typeDefinition) where T : Attribute
16+
{
17+
return HasAttribute(typeDefinition, typeof(T));
18+
}
19+
20+
[Pure]
21+
public static bool HasAttribute([NotNull] this TypeDefinition typeDefinition, [NotNull] Type attributeType)
22+
{
23+
return typeDefinition.CustomAttributes.HasAttribute(attributeType);
24+
}
25+
26+
[Pure]
27+
public static bool HasAttribute([NotNull] this IEnumerable<CustomAttribute> attributes, [NotNull] Type attributeType)
28+
{
29+
return attributes.Any(attribute => attributeType.IsAssignableFrom(attribute.AttributeType));
30+
}
31+
32+
[Pure]
33+
public static bool HasAttribute<T>([NotNull] this IEnumerable<CustomAttribute> attributes) where T : Attribute
34+
{
35+
return HasAttribute(attributes, typeof(T));
36+
}
37+
38+
[Pure]
39+
public static bool IsAssignableFrom([NotNull] this Type type, TypeReference typeReference)
40+
{
41+
return type.IsAssignableFrom(typeReference.Resolve().ToReflectionType());
42+
}
43+
44+
[Pure]
45+
public static T GetAttribute<T>([NotNull] this TypeDefinition typeDefinition) where T : Attribute
46+
{
47+
var customAttribute = typeDefinition
48+
.CustomAttributes
49+
.Single(attribute => attribute.AttributeType.Resolve().ToReflectionType() == typeof(T))
50+
;
51+
return customAttribute.ToAttribute<T>();
52+
}
53+
54+
[Pure]
55+
public static T ToAttribute<T>([NotNull] this CustomAttribute attribute) where T : Attribute
56+
{
57+
var attributeType = typeof(T);
58+
var constructorParameters = attributeType.GetConstructors().Single().GetParameters();
59+
if (constructorParameters.Length != attribute.ConstructorArguments.Count)
60+
throw new ArgumentException($"Invalid constructor of type {attributeType}");
61+
var arguments = new object[constructorParameters.Length];
62+
for (var i = 0; i < constructorParameters.Length; i++)
63+
{
64+
var argumentType = constructorParameters[i].ParameterType;
65+
var value = attribute.ConstructorArguments[i].Value;
66+
arguments[i] = argumentType.IsEnum ? Enum.ToObject(argumentType, value) : value;
67+
}
68+
return (T) Activator.CreateInstance(attributeType, arguments);
69+
}
70+
71+
[Pure]
72+
public static Type ToReflectionType([NotNull] this TypeDefinition typeDefinition)
73+
{
74+
var fullName = Assembly.CreateQualifiedName(typeDefinition.Module.Assembly.FullName, typeDefinition.FullName);
75+
var type = Type.GetType(fullName);
76+
if (type == null) throw new ArgumentException($"Cannot get type of {fullName}");
77+
return type;
78+
}
79+
80+
// [Pure]
81+
// public static Type ToReflectionType([NotNull] this TypeReference typeReference)
82+
// {
83+
// // force resolve the reference before `GetType`
84+
// typeReference.Resolve();
85+
// var fullName = Assembly.CreateQualifiedName(typeReference.Module.Assembly.FullName, typeReference.FullName);
86+
// return Type.GetType(fullName);
87+
// }
88+
89+
[Pure]
90+
public static TypeDefinition FindTypeDefinition<T>([NotNull] this AssemblyDefinition assembly)
91+
{
92+
return FindTypeDefinition(assembly, typeof(T));
93+
}
94+
95+
[Pure]
96+
public static TypeDefinition FindTypeDefinition([NotNull] this AssemblyDefinition assemblyDefinition, [NotNull] Type reflectionType)
97+
{
98+
var stack = new Stack<string>();
99+
var currentType = reflectionType;
100+
while (currentType != null) {
101+
stack.Push((currentType.DeclaringType == null ? currentType.Namespace + "." : "") + currentType.Name);
102+
currentType = currentType.DeclaringType;
103+
}
104+
105+
var typeDefinition = assemblyDefinition.MainModule.GetType(stack.Pop());
106+
if (typeDefinition == null)
107+
return null;
108+
109+
while (stack.Count > 0) {
110+
var name = stack.Pop();
111+
typeDefinition = typeDefinition.NestedTypes.Single(t => t.Name == name);
112+
}
113+
114+
return typeDefinition;
115+
}
116+
117+
[Pure]
118+
public static MethodDefinition GetMethod([NotNull] this TypeDefinition self, [NotNull] string name)
119+
{
120+
return self.Methods.First(m => m.Name == name);
121+
}
122+
123+
[Pure]
124+
public static void AddRange<T>([NotNull] this Collection<T> self, [NotNull, ItemNotNull] IEnumerable<T> items)
125+
{
126+
foreach (var item in items) self.Add(item);
127+
}
128+
129+
[Pure]
130+
public static FieldDefinition GetField([NotNull] this TypeDefinition self, [NotNull] string name)
131+
{
132+
return self.Fields.First(f => f.Name == name);
133+
}
134+
135+
public static TypeDefinition CreateStructTypeDefinition(
136+
[NotNull] this ModuleDefinition moduleDefinition
137+
, [NotNull] string @namespace
138+
, [NotNull] string name
139+
, TypeAttributes additionalAttributes = TypeAttributes.Public
140+
)
141+
{
142+
//.class public sealed sequential ansi beforefieldinit
143+
// StructName
144+
// extends [netstandard]System.ValueType
145+
//{
146+
//} // end of class StructName
147+
var typeAttributes = TypeAttributes.Class
148+
| TypeAttributes.Sealed
149+
| TypeAttributes.AnsiClass
150+
| TypeAttributes.BeforeFieldInit
151+
| additionalAttributes
152+
;
153+
var isExplicitLayout = (typeAttributes & TypeAttributes.ExplicitLayout) == TypeAttributes.ExplicitLayout;
154+
if (!isExplicitLayout) typeAttributes |= TypeAttributes.SequentialLayout;
155+
return new TypeDefinition(@namespace, name, typeAttributes)
156+
{
157+
BaseType = moduleDefinition.ImportReference(typeof(ValueType))
158+
};
159+
}
160+
161+
private const string _AUTO_PROPERTY_SUFFIX = ">k__BackingField";
162+
private const string _AUTO_PROPERTY_PREFIX = "<";
163+
164+
public static bool IsAutoPropertyField(this FieldDefinition fieldDefinition)
165+
{
166+
return fieldDefinition.Name.StartsWith(_AUTO_PROPERTY_PREFIX);
167+
}
168+
169+
public static string GetCodeName(this FieldDefinition fieldDefinition)
170+
{
171+
var name = fieldDefinition.Name;
172+
if (fieldDefinition.IsAutoPropertyField())
173+
{
174+
var prefixLength = _AUTO_PROPERTY_PREFIX.Length;
175+
var suffixLength = _AUTO_PROPERTY_SUFFIX.Length;
176+
name = name.Substring(prefixLength, name.Length - prefixLength - suffixLength);
177+
}
178+
return name;
179+
}
180+
}
181+
}

Packages/codegen/Editor/CecilExtension.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)