34
Using Reflections and Automatic Code Generation Ivan Dolgushin Game Developer Appturn

Using Reflections and Automatic Code Generation

Embed Size (px)

Citation preview

Using Reflections and Automatic Code Generation

Ivan Dolgushin

Game Developer

Appturn

It is Very Technical:

• A lot of code

• Only 5 pictures

• About not writing code

Cloning

namespace System

{

[ComVisible (true)]

public interface ICloneable

{

//

// Methods

//

object Clone ();

}

}

#region ICloneable implementation

public virtual object Clone()

{

return new BaseClass(this);

}

#endregion

Manual Implementation

#region ICloneable implementation

public override object Clone()

{

return new DerivedClass(this);

}

#endregion

Manual Implementation

#region ICloneable implementation

public virtual object Clone()

{

return new BaseClass(this);

}

#endregion

#region ICloneable implementation

public override object Clone()

{

return new DerivedClass(this);

}

#endregion

Manual Implementation

System.Activator

#region ICloneable implementation

public object Clone()

{

return Activator

.CreateInstance(

this.GetType(),

this

);

}

#endregion

Factory

public static BaseEffect CreateEffect(

Creature owner, Creature caster,

EEffectType effectType, byte effectlLevel)

{

var effectInfo = ObjectsConfig.Instance

.GetEffectInfo(effectType);

return (BaseEffect)Activator

.CreateInstance(

Type.GetType(effectInfo.TypeName),

owner, caster,

effectType, effectlLevel);

}

System.Reflection

#region ICloneable implementation

public object Clone()

{

return this

.GetType().GetConstructor(

new Type[] { this.GetType() }

).Invoke(new object[] { this });

}

#endregion

Field Value Comparison

Data Storage Classes

public class ObjectBaseLevelInfo

{

public int RequiredObjectLevel = 0;

public int LaboratoryUpgradeCost = 0;

public uint LaboratoryUpgradeDuration = 0;

public int UpgradeCost = 0;

public uint UpgradeDuration = 0;

public int Hp = 0;

public int XpAmount = 0;

public int VisualLevel = 1;

}

Data Storage Classes

public class MonsterLevelInfo

: ObjectBaseLevelInfo

{

public int Xp = 0;

public float MovementSpeed = 0f;

public byte MinLvlDrop = 0;

public byte MaxLvlDrop = 0;

public float DropChance = 0f;

}

Manual Comparator

public override bool Equals(object obj)

{

MonsterLevelInfo other

= obj as MonsterLevelInfo;

return null != obj

&& base.Equals(obj)

&& Xp == other.Xp

&& MovementSpeed == other.MovementSpeed

&& MinLvlDrop == other.MinLvlDrop

&& MaxLvlDrop == other.MaxLvlDrop

&& DropChance == other.DropChance;

}

System.Reflection

public static bool ValueEqual(this object a, object b)

{

if (object.ReferenceEquals(a, b)) return true;

Type typeA = a.GetType();

if (typeA != b.GetType()) return false;

foreach (FieldInfo fi in typeA.GetFields(

BindingFlags.Public |

BindingFlags.GetField |

BindingFlags.GetProperty |

BindingFlags.FlattenHierarchy))

{

if (fi.GetValue(a) != fi.GetValue(b))

return false;

}

return true;

}

Let’s Add More Control

[AttributeUsage(AttributeTargets.Field)]

public class DontCompareAttribute : Attribute {}

public static bool ValueEqual(this object a, object b)

{

...

foreach (FieldInfo fi in typeA.GetFields(...))

{

if (fi.GetCustomAttributes(

typeof(DontCompareAttribute),

true).Length == 0

&& fi.GetValue(a) != fi.GetValue(b))

return false;

}

return true;

}

Data Storage Class

public class MonsterLevelInfo

: ObjectBaseLevelInfo

{

public int Xp = 0;

public float MovementSpeed = 0f;

public byte MinLvlDrop = 0;

public byte MaxLvlDrop = 0;

[DontCompare]

public float DropChance = 0f;

}

Data Storage Class

[Serializable]

[XmlType("MonsterLevel")]

public class MonsterLevelInfo : ObjectBaseLevelInfo

{

[XmlAttribute]

public int Xp = 0;

[XmlAttribute]

public float MovementSpeed = 0f;

[XmlAttribute]

public byte MinLvlDrop = 0;

[XmlAttribute]

public byte MaxLvlDrop = 0;

[XmlAttribute]

[DontCompare]

public float DropChance = 0f;

}

System.CodeDom

public static void GenerateComparators()

{

CodeCompileUnit targetUnit;

CodeTypeDeclaration targetClass;

Assembly assembly = GetAssemblies().First(a => a.GetName().Name == "Assembly-CSharp");

foreach (Type type in assembly.GetTypes())

{

object[] genAttributes = type.GetCustomAttributes(typeof(GenerateComparatorAttribute), false);

if (genAttributes.Length > 0)

{

targetUnit = new CodeCompileUnit();

CodeNamespace ns = new CodeNamespace(type.Namespace);

ns.Imports.Add(new CodeNamespaceImport("System"));

targetClass = new CodeTypeDeclaration(type.Name);

targetClass.IsClass = true; targetClass.IsPartial = true;

targetClass.TypeAttributes = TypeAttributes.Public;

ns.Types.Add(targetClass);

targetUnit.Namespaces.Add(ns);

targetClass.Members.Add(GenerateComparator(type));

using (CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp")) {

CodeGeneratorOptions options = new CodeGeneratorOptions();

options.BracingStyle = "C";

string outputFileName = Path.Combine(outputDirName, type.FullName + ".Comparator.cs");

using (StreamWriter sourceWriter = new StreamWriter(outputFileName))

{

provider.GenerateCodeFromCompileUnit(targetUnit, sourceWriter, options);

}

}

}

}

}

System.CodeDom

foreach (FieldInfo fi in type.GetFields(...))

{

var checkExpr = new CodeBinaryOperatorExpression(

new CodeFieldReferenceExpression(thisRef, fi.Name),

CodeBinaryOperatorType.ValueEquality,

new CodeFieldReferenceExpression(argRef, fi.Name));

var appendExpr = new CodeBinaryOperatorExpression(

retRef, CodeBinaryOperatorType.BooleanAnd, checkExpr);

CodeAssignStatement checkStatement =

new CodeAssignStatement(retRef, appendExpr);

compareMethod.Statements.Add(checkStatement);

}

Call It With Unity Menu

public static partial class CodeGeneration

{

[MenuItem("Tools/Code Generation/Comparators",

false, 1103)]

public static void GenerateComparators()

{

Comparer.GenerateComparators();

}

}

WYG

public partial class MonsterLevelInfo

{

public override bool Equals(object obj)

{

bool rv = true;

MonsterLevelInfo other=obj as MonsterLevelInfo;

if ((other == null)) return false;

rv = (rv && (this.Xp == other.Xp));

...

rv = (rv && (this.Hp == other.Hp));

rv = (rv && (this.XpAmount == other.XpAmount));

return rv ;

}

}

Enum Converter

Generated Convert Method

public static EResearch MonsterToResearchType(EObject v)

{

EResearchType rv = default(EResearchType);

switch (v) {

case EObject.Cobra: rv = EResearch.Cobra; break;

case EObject.Crab: rv = EResearch.Crab; break;

case EObject.Kobold: rv = EResearch.Kobold; break;

case EObject.Orc: rv = EResearch.Orc; break;

case EObject.Shaman: rv = EResearch.Shaman; break;

case EObject.Spider: rv = EResearch.Spider; break;

case EObject.Imp: rv = EResearch.Imp; break;

}

return rv;

}

System.CodeDom

System.CodeDom

StringBuilder switchBuilder = new StringBuilder();

switchBuilder.AppendLine("switch (v) {");

foreach(var val in Enum.GetNames(sourceEnum))

{

if (Enum.IsDefined(destEnum, val))

{

switchBuilder.AppendFormat(

"case {0}.{2}: retValue = {1}.{2}; ",

sourceEnum, destEnum, val);

switchBuilder.AppendLine("break;");

}

}

convertMethod.Statements.Add(

new CodeSnippetStatement(switchBuilder));

Xml Serialization

XML Serializer Generator Tool (Sgen.exe)

The XML Serializer Generator creates an XML serialization assembly for types in a specified assembly in order to improve the startup performance of a XmlSerializer when it serializes or deserializes objects of the specified types.

Sgen.exe

rm -R *.cssgen --assembly:Assembly-CSharp.dll \--reference:UnityEngine.dll \--verbose --force --keep \--type:PaperPath.ObjectBaseLevelInfo \--type:PaperPath.MonsterLevelInfo \...--type:PaperPath.CharacterLevelInfo \

result=$(find . -name *.cs)mv $result ../Assets/Classes/GeneratedSerializers.csrm Assembly-CSharp.XmlSerializers.dll

Do It with Unity

[MenuItem("Tools/Code Generation/Serializers”)]

public static void RegenerateSerializers()

{

System.IO.Directory.CreateDirectory(OutDir);

CompileAssemblies();

PrepareSGen();

GenerateSerializers();

System.IO.Directory.Delete(OutDir, true);

}

Compile

public static void CompileAssemblies()

{

HandleExecutable he = new HandleExecutable();

he.OutputHandler = Log;

he.ErrorHandler = LogError;

string Configuration = "Debug";

string Solution = "Client.sln";

he.CallExecutable("xbuild"

, string.Format("/property:OutDir={0}

/p:Configuration={1} {2}",

OutDir, Configuration, Solution)

, ””, true);

}

Prepare Script

public static string GetScript()

{

StringBuilder sgenScript = new StringBuilder();

sgenScript.AppendLine("rm -R *.cs");

sgenScript.AppendFormat("sgen --assembly:{0} --reference:{1}” +

” --force –keep”, AssemblyPath, DependencyPaths);

foreach (Type type in GetAssembly().GetTypes())

{

object[] genAttributes = type.GetCustomAttributes(

typeof(GenerateSerializersAttribute), false);

if (genAttributes.Length > 0)

sgenScript.AppendLine(" --type:" + type.FullName + " \\");

}

sgenScript.AppendLine();

sgenScript.AppendLine("result=$(find . -name *.cs)");

sgenScript.AppendLine("mv $result ../" + GeneratedSerializersPath);

sgenScript.AppendLine("rm Assembly-CSharp.XmlSerializers.dll");

return sgenScript;

}

Run Script and Postprocess

public static void GenerateSerializers()

{

HandleExecutable he = new HandleExecutable();

he.CallExecutable("sh”, "generate.sh”, OutDir, true);

string serializersCode = File.ReadAllText(GSPath);

serializersCode = serializersCode.Replace(

"(type.FullName)",

"(type.FullName.Replace(\"+\", \".\"))");

File.WriteAllText(

GeneratedSerializersPath,

serializersCode);

}

Thank You!

Questions?

Feel free to contact me

[email protected]