diff --git a/OpenRa.Game/Sync.cs b/OpenRa.Game/Sync.cs index 10261f506f..607dc5a509 100755 --- a/OpenRa.Game/Sync.cs +++ b/OpenRa.Game/Sync.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; +using System.Reflection.Emit; +using IjwFramework.Collections; namespace OpenRa.Game { @@ -10,48 +12,96 @@ namespace OpenRa.Game static class Sync { + static Cache> hashFuncCache = new Cache>( t => GenerateHashFunc( t ) ); + public static int CalculateSyncHash( object obj ) { - int hash = 0; // TODO: start with a more interesting initial value. + return hashFuncCache[ obj.GetType() ]( obj ); + } + + public static Func GenerateHashFunc( Type t ) + { + var d = new DynamicMethod( "hash_{0}".F( t.Name ), typeof( int ), new Type[] { typeof( object ) }, t ); + var il = d.GetILGenerator(); + var this_ = il.DeclareLocal( t ).LocalIndex; + il.Emit( OpCodes.Ldarg_0 ); + il.Emit( OpCodes.Castclass, t ); + il.Emit( OpCodes.Stloc, this_ ); + il.Emit( OpCodes.Ldc_I4_0 ); - // TODO: cache the Syncable fields; maybe use DynamicMethod to make this fast? - // FIXME: does GetFields even give fields in a well-defined order? const BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - foreach( var field in obj.GetType().GetFields( bf ).Where( x => x.GetCustomAttributes( typeof( SyncAttribute ), true ).Length != 0 ) ) + foreach( var field in t.GetFields( bf ).Where( x => x.GetCustomAttributes( typeof( SyncAttribute ), true ).Length != 0 ) ) { - if( field.FieldType == typeof( int ) ) - hash ^= (int)field.GetValue( obj ); + il.Emit( OpCodes.Ldloc, this_ ); + il.Emit( OpCodes.Ldfld, field ); - else if( field.FieldType == typeof( Actor ) ) + if( field.FieldType == typeof( int ) ) { - var a = (Actor)field.GetValue( obj ); - if( a != null ) - hash ^= (int)( a.ActorID << 16 ); + il.Emit( OpCodes.Xor ); + } + else if( field.FieldType == typeof( bool ) ) + { + var l = il.DefineLabel(); + il.Emit( OpCodes.Ldc_I4, 0xaaa ); + il.Emit( OpCodes.Brtrue, l ); + il.Emit( OpCodes.Pop ); + il.Emit( OpCodes.Ldc_I4, 0x555 ); + il.MarkLabel( l ); + il.Emit( OpCodes.Xor ); + } + else if( field.FieldType == typeof( int2 ) ) + { + il.EmitCall( OpCodes.Call, ( (Func)hash_int2 ).Method, null ); + il.Emit( OpCodes.Xor ); } else if( field.FieldType == typeof( TypeDictionary ) ) { - foreach( var o in (TypeDictionary)field.GetValue( obj ) ) - hash += CalculateSyncHash( o ); + il.EmitCall( OpCodes.Call, ( (Func)hash_tdict ).Method, null ); + il.Emit( OpCodes.Xor ); } - else if( field.FieldType == typeof( bool ) ) - hash ^= (bool)field.GetValue( obj ) ? 0xaaa : 0x555; - - else if( field.FieldType == typeof( int2 ) ) + else if( field.FieldType == typeof( Actor ) ) { - var i2 = (int2)field.GetValue( obj ); - hash ^= ( ( i2.X * 5 ) ^ ( i2.Y * 3 ) ) / 4; + il.EmitCall( OpCodes.Call, ( (Func)hash_actor ).Method, null ); + il.Emit( OpCodes.Xor ); } else if( field.FieldType == typeof( Player ) ) { - var p = (Player)field.GetValue( obj ); - if( p != null ) - hash ^= p.Index * 0x567; + il.EmitCall( OpCodes.Call, ( (Func)hash_player ).Method, null ); + il.Emit( OpCodes.Xor ); } else throw new NotImplementedException( "SyncAttribute on unhashable field" ); } - return hash; + il.Emit( OpCodes.Ret ); + return (Func)d.CreateDelegate( typeof( Func ) ); + } + + internal static int hash_int2( int2 i2 ) + { + return ( ( i2.X * 5 ) ^ ( i2.Y * 3 ) ) / 4; + } + + internal static int hash_tdict( TypeDictionary d ) + { + int ret = 0; + foreach( var o in d ) + ret += CalculateSyncHash( o ); + return ret; + } + + internal static int hash_actor( Actor a ) + { + if( a != null ) + return (int)( a.ActorID << 16 ); + return 0; + } + + internal static int hash_player( Player p ) + { + if( p != null ) + return p.Index * 0x567; + return 0; } } }