diff --git a/OpenRA.Game/Actor.cs b/OpenRA.Game/Actor.cs index bf211ac472..869cc6ab65 100644 --- a/OpenRA.Game/Actor.cs +++ b/OpenRA.Game/Actor.cs @@ -24,6 +24,13 @@ namespace OpenRA { public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable, IDisposable { + internal struct SyncHash + { + public readonly ISync Trait; + public readonly int Hash; + public SyncHash(ISync trait, int hash) { Trait = trait; Hash = hash; } + } + public readonly ActorInfo Info; public readonly World World; @@ -62,6 +69,8 @@ namespace OpenRA } } + internal IEnumerable SyncHashes { get; private set; } + readonly IFacing facing; readonly IHealth health; readonly IRenderModifier[] renderModifiers; @@ -112,6 +121,12 @@ namespace OpenRA visibilityModifiers = TraitsImplementing().ToArray(); defaultVisibility = Trait(); Targetables = TraitsImplementing().ToArray(); + + SyncHashes = + TraitsImplementing() + .Select(sync => Pair.New(sync, Sync.GetHashFunction(sync))) + .ToArray() + .Select(pair => new SyncHash(pair.First, pair.Second(pair.First))); } Rectangle DetermineBounds() diff --git a/OpenRA.Game/Network/SyncReport.cs b/OpenRA.Game/Network/SyncReport.cs index c3a2f0fbd3..2917ba9b78 100644 --- a/OpenRA.Game/Network/SyncReport.cs +++ b/OpenRA.Game/Network/SyncReport.cs @@ -63,31 +63,29 @@ namespace OpenRA.Network report.SyncedRandom = orderManager.World.SharedRandom.Last; report.TotalCount = orderManager.World.SharedRandom.TotalCount; report.Traits.Clear(); - foreach (var a in orderManager.World.ActorsWithTrait()) - { - var sync = Sync.CalculateSyncHash(a.Trait); - if (sync != 0) - 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, - NamesValues = DumpSyncTrait(a.Trait) - }); - } + foreach (var actor in orderManager.World.ActorsHavingTrait()) + foreach (var syncHash in actor.SyncHashes) + if (syncHash.Hash != 0) + report.Traits.Add(new TraitReport() + { + ActorID = actor.ActorID, + Type = actor.Info.Name, + Owner = (actor.Owner == null) ? "null" : actor.Owner.PlayerName, + Trait = syncHash.Trait.GetType().Name, + Hash = syncHash.Hash, + NamesValues = DumpSyncTrait(syncHash.Trait) + }); foreach (var e in orderManager.World.Effects) { var sync = e as ISync; if (sync != null) { - var hash = Sync.CalculateSyncHash(sync); + var hash = Sync.Hash(sync); if (hash != 0) report.Effects.Add(new EffectReport() { - Name = sync.ToString().Split('.').Last(), + Name = sync.GetType().Name, Hash = hash, NamesValues = DumpSyncTrait(sync) }); diff --git a/OpenRA.Game/Sync.cs b/OpenRA.Game/Sync.cs index 9492016424..733be00eae 100644 --- a/OpenRA.Game/Sync.cs +++ b/OpenRA.Game/Sync.cs @@ -26,24 +26,29 @@ namespace OpenRA public static class Sync { - static Cache> hashFuncCache = new Cache>(t => GenerateHashFunc(t)); + static readonly ConcurrentCache> HashFunctions = + new ConcurrentCache>(GenerateHashFunc); - public static int CalculateSyncHash(object obj) + internal static Func GetHashFunction(ISync sync) { - return hashFuncCache[obj.GetType()](obj); + return HashFunctions[sync.GetType()]; } - static Dictionary hashFunctions = new Dictionary() + internal static int Hash(ISync sync) + { + return GetHashFunction(sync)(sync); + } + + static readonly Dictionary CustomHashFunctions = new Dictionary() { { typeof(int2), ((Func)HashInt2).Method }, { typeof(CPos), ((Func)HashCPos).Method }, { typeof(CVec), ((Func)HashCVec).Method }, - { typeof(WDist), ((Func)Hash).Method }, - { typeof(WPos), ((Func)Hash).Method }, - { typeof(WVec), ((Func)Hash).Method }, - { typeof(WAngle), ((Func)Hash).Method }, - { typeof(WRot), ((Func)Hash).Method }, - { typeof(TypeDictionary), ((Func)HashTDict).Method }, + { typeof(WDist), ((Func)HashUsingHashCode).Method }, + { typeof(WPos), ((Func)HashUsingHashCode).Method }, + { typeof(WVec), ((Func)HashUsingHashCode).Method }, + { typeof(WAngle), ((Func)HashUsingHashCode).Method }, + { typeof(WRot), ((Func)HashUsingHashCode).Method }, { typeof(Actor), ((Func)HashActor).Method }, { typeof(Player), ((Func)HashPlayer).Method }, { typeof(Target), ((Func)HashTarget).Method }, @@ -51,8 +56,8 @@ namespace OpenRA static void EmitSyncOpcodes(Type type, ILGenerator il) { - if (hashFunctions.ContainsKey(type)) - il.EmitCall(OpCodes.Call, hashFunctions[type], null); + if (CustomHashFunctions.ContainsKey(type)) + il.EmitCall(OpCodes.Call, CustomHashFunctions[type], null); else if (type == typeof(bool)) { var l = il.DefineLabel(); @@ -62,15 +67,13 @@ namespace OpenRA il.Emit(OpCodes.Ldc_I4, 0x555); il.MarkLabel(l); } - else if (type.HasAttribute()) - il.EmitCall(OpCodes.Call, ((Func)CalculateSyncHash).Method, null); else if (type != typeof(int)) throw new NotImplementedException("SyncAttribute on member of unhashable type: {0}".F(type.FullName)); il.Emit(OpCodes.Xor); } - public static Func GenerateHashFunc(Type t) + 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(); @@ -116,14 +119,6 @@ namespace OpenRA return ((i2.X * 5) ^ (i2.Y * 3)) / 4; } - public static int HashTDict(TypeDictionary d) - { - var ret = 0; - foreach (var o in d) - ret += CalculateSyncHash(o); - return ret; - } - public static int HashActor(Actor a) { if (a != null) @@ -152,7 +147,7 @@ namespace OpenRA return (int)(t.FrozenActor.Actor.ActorID << 16) * 0x567; case TargetType.Terrain: - return Hash(t.CenterPosition); + return HashUsingHashCode(t.CenterPosition); default: case TargetType.Invalid: @@ -160,7 +155,7 @@ namespace OpenRA } } - public static int Hash(T t) + public static int HashUsingHashCode(T t) { return t.GetHashCode(); } diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 1a9149346d..b37f5c4448 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -341,18 +341,19 @@ namespace OpenRA // hash all the actors foreach (var a in Actors) - ret += n++ * (int)(1 + a.ActorID) * Sync.CalculateSyncHash(a); + ret += n++ * (int)(1 + a.ActorID) * Sync.HashActor(a); // hash all the traits that tick - foreach (var x in ActorsWithTrait()) - ret += n++ * (int)(1 + x.Actor.ActorID) * Sync.CalculateSyncHash(x.Trait); + foreach (var actor in ActorsHavingTrait()) + foreach (var syncHash in actor.SyncHashes) + ret += n++ * (int)(1 + actor.ActorID) * syncHash.Hash; // TODO: don't go over all effects foreach (var e in Effects) { var sync = e as ISync; if (sync != null) - ret += n++ * Sync.CalculateSyncHash(sync); + ret += n++ * Sync.Hash(sync); } // Hash the shared rng diff --git a/OpenRA.Mods.Common/Traits/Player/MissionObjectives.cs b/OpenRA.Mods.Common/Traits/Player/MissionObjectives.cs index f50eb657cc..54176d24e7 100644 --- a/OpenRA.Mods.Common/Traits/Player/MissionObjectives.cs +++ b/OpenRA.Mods.Common/Traits/Player/MissionObjectives.cs @@ -63,7 +63,7 @@ namespace OpenRA.Mods.Common.Traits { var hash = 0; foreach (var objective in objectives) - hash ^= Sync.Hash(objective.State); + hash ^= Sync.HashUsingHashCode(objective.State); return hash; } }