diff --git a/OpenRA.Game/Network/SyncReport.cs b/OpenRA.Game/Network/SyncReport.cs index 8f29189870..cdd364c160 100755 --- a/OpenRA.Game/Network/SyncReport.cs +++ b/OpenRA.Game/Network/SyncReport.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, @@ -11,21 +11,22 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reflection; -using System.Reflection.Emit; using OpenRA.Primitives; namespace OpenRA.Network { + using NamesValuesPair = Pair; + class SyncReport { const int NumSyncReports = 5; - - static Cache>> dumpFuncCache = new Cache>>(t => GenerateDumpFunc(t)); + static Cache typeInfoCache = new Cache(t => new TypeInfo(t)); readonly OrderManager orderManager; - Report[] syncReports = new Report[NumSyncReports]; + readonly Report[] syncReports = new Report[NumSyncReports]; int curIndex = 0; public SyncReport(OrderManager orderManager) @@ -41,91 +42,6 @@ namespace OpenRA.Network curIndex = ++curIndex % NumSyncReports; } - public static Dictionary DumpSyncTrait(object obj) - { - return dumpFuncCache[obj.GetType()](obj); - } - - public static Func> GenerateDumpFunc(Type t) - { - var dictType = typeof(Dictionary); - - var d = new DynamicMethod("dump_{0}".F(t.Name), dictType, new Type[] { typeof(object) }, t); - var il = d.GetILGenerator(); - - var this_ = il.DeclareLocal(t).LocalIndex; - var dict_ = il.DeclareLocal(dictType).LocalIndex; - var obj_ = il.DeclareLocal(typeof(object)).LocalIndex; - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Castclass, t); - il.Emit(OpCodes.Stloc, this_); - - var dictAdd_ = dictType.GetMethod("Add", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string), typeof(string) }, null); - var dictCtor_ = dictType.GetConstructor(Type.EmptyTypes); - var objToString_ = typeof(object).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); - - il.Emit(OpCodes.Newobj, dictCtor_); - il.Emit(OpCodes.Stloc, dict_); - - const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - foreach (var field in t.GetFields(Flags).Where(x => x.HasAttribute())) - { - if (field.IsLiteral || field.IsStatic) continue; - - var lblNull = il.DefineLabel(); - - il.Emit(OpCodes.Ldloc, this_); - il.Emit(OpCodes.Ldfld, field); - - if (field.FieldType.IsValueType) - il.Emit(OpCodes.Box, field.FieldType); - - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, obj_); - - il.Emit(OpCodes.Brfalse, lblNull); - - il.Emit(OpCodes.Ldloc, dict_); - il.Emit(OpCodes.Ldstr, field.Name); - - il.Emit(OpCodes.Ldloc, obj_); - il.Emit(OpCodes.Callvirt, objToString_); - il.Emit(OpCodes.Callvirt, dictAdd_); - - il.MarkLabel(lblNull); - } - - foreach (var prop in t.GetProperties(Flags).Where(x => x.HasAttribute())) - { - var lblNull = il.DefineLabel(); - - il.Emit(OpCodes.Ldloc, this_); - il.EmitCall(OpCodes.Call, prop.GetGetMethod(), null); - - if (prop.PropertyType.IsValueType) - il.Emit(OpCodes.Box, prop.PropertyType); - - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, obj_); - - il.Emit(OpCodes.Brfalse, lblNull); - - il.Emit(OpCodes.Ldloc, dict_); - il.Emit(OpCodes.Ldstr, prop.Name); - - il.Emit(OpCodes.Ldloc, obj_); - il.Emit(OpCodes.Callvirt, objToString_); - il.Emit(OpCodes.Callvirt, dictAdd_); - - il.MarkLabel(lblNull); - } - - il.Emit(OpCodes.Ldloc, dict_); - il.Emit(OpCodes.Ret); - return (Func>)d.CreateDelegate(typeof(Func>)); - } - void GenerateSyncReport(Report report) { report.Frame = orderManager.NetFrameNumber; @@ -136,19 +52,15 @@ namespace OpenRA.Network { var sync = Sync.CalculateSyncHash(a.Trait); if (sync != 0) - { - var tr = new TraitReport() + report.Traits.Add(new TraitReport() { ActorID = a.Actor.ActorID, Type = a.Actor.Info.Name, Owner = (a.Actor.Owner == null) ? "null" : a.Actor.Owner.PlayerName, Trait = a.Trait.GetType().Name, - Hash = sync - }; - - tr.Fields = DumpSyncTrait(a.Trait); - report.Traits.Add(tr); - } + Hash = sync, + NamesValues = DumpSyncTrait(a.Trait) + }); } foreach (var e in orderManager.world.Effects) @@ -158,20 +70,31 @@ namespace OpenRA.Network { var hash = Sync.CalculateSyncHash(sync); if (hash != 0) - { - var er = new EffectReport() + report.Effects.Add(new EffectReport() { Name = sync.ToString().Split('.').Last(), - Hash = hash - }; - - er.Fields = DumpSyncTrait(sync); - report.Effects.Add(er); - } + Hash = hash, + NamesValues = DumpSyncTrait(sync) + }); } } } + static NamesValuesPair DumpSyncTrait(ISync sync) + { + var type = sync.GetType(); + TypeInfo typeInfo; + lock (typeInfoCache) + typeInfo = typeInfoCache[type]; + var values = new string[typeInfo.Names.Length]; + var index = 0; + + foreach (var func in typeInfo.MemberToStringFunctions) + values[index++] = func(sync); + + return Pair.New(typeInfo.Names, values); + } + internal void DumpSyncReport(int frame) { foreach (var r in syncReports) @@ -188,16 +111,21 @@ namespace OpenRA.Network { Log.Write("sync", "\t {0} {1} {2} {3} ({4})".F(a.ActorID, a.Type, a.Owner, a.Trait, a.Hash)); - foreach (var f in a.Fields) - Log.Write("sync", "\t\t {0}: {1}".F(f.Key, f.Value)); + var nvp = a.NamesValues; + for (int i = 0; i < nvp.First.Length; i++) + if (nvp.Second[i] != null) + Log.Write("sync", "\t\t {0}: {1}".F(nvp.First[i], nvp.Second[i])); } Log.Write("sync", "Synced Effects:"); foreach (var e in r.Effects) { Log.Write("sync", "\t {0} ({1})", e.Name, e.Hash); - foreach (var f in e.Fields) - Log.Write("sync", "\t\t {0}: {1}".F(f.Key, f.Value)); + + var nvp = e.NamesValues; + for (int i = 0; i < nvp.First.Length; i++) + if (nvp.Second[i] != null) + Log.Write("sync", "\t\t {0}: {1}".F(nvp.First[i], nvp.Second[i])); } return; @@ -223,14 +151,67 @@ namespace OpenRA.Network public string Owner; public string Trait; public int Hash; - public Dictionary Fields; + public NamesValuesPair NamesValues; } struct EffectReport { public string Name; public int Hash; - public Dictionary Fields; + public NamesValuesPair NamesValues; + } + + struct TypeInfo + { + static ParameterExpression syncParam = Expression.Parameter(typeof(ISync), "sync"); + static ConstantExpression nullString = Expression.Constant(null, typeof(string)); + + public readonly Func[] MemberToStringFunctions; + public readonly string[] Names; + + public TypeInfo(Type type) + { + const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + var fields = type.GetFields(Flags).Where(fi => !fi.IsLiteral && !fi.IsStatic && fi.HasAttribute()); + var properties = type.GetProperties(Flags).Where(pi => pi.HasAttribute()); + + foreach (var prop in properties) + if (!prop.CanRead || prop.GetIndexParameters().Any()) + throw new InvalidOperationException( + "Properties using the Sync attribute must be readable and must not use index parameters.\n" + + "Invalid Property: " + prop.DeclaringType.FullName + "." + prop.Name); + + var sync = Expression.Convert(syncParam, type); + MemberToStringFunctions = fields.Select( + fi => MemberToString(Expression.Field(sync, fi), fi.FieldType, fi.Name)) + .Concat(properties.Select( + pi => MemberToString(Expression.Property(sync, pi), pi.PropertyType, pi.Name)) + ).ToArray(); + + Names = fields.Select(fi => fi.Name).Concat(properties.Select(pi => pi.Name)).ToArray(); + } + + static Func MemberToString(MemberExpression getMember, Type memberType, string name) + { + // The lambda generated is shown below. + // TSync is actual type of the ISync object. Foo is a field or property with the Sync attribute applied. + var toString = memberType.GetMethod("ToString", Type.EmptyTypes); + Expression getString; + if (memberType.IsValueType) + // (ISync sync) => ((TSync)sync).Foo.ToString() + getString = Expression.Call(getMember, toString); + else + { + // (ISync sync) => { var foo = ((TSync)sync).Foo; return foo == null ? null : foo.ToString()); } + var memberVariable = Expression.Variable(memberType, getMember.Member.Name); + var assignMemberVariable = Expression.Assign(memberVariable, getMember); + var member = Expression.Block(new[] { memberVariable }, assignMemberVariable); + getString = Expression.Call(member, toString); + var nullMember = Expression.Constant(null, memberType); + getString = Expression.Condition(Expression.Equal(member, nullMember), nullString, getString); + } + return Expression.Lambda>(getString, name, new[] { syncParam }).Compile(); + } } } }