Transcript

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]