#region Copyright & License Information /* * Copyright 2007-2011 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, * see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using OpenRA.Traits; using OpenRA.Primitives; namespace OpenRA { [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class SyncAttribute : Attribute { } public interface ISync { } /* marker interface */ public static class Sync { static Cache> hashFuncCache = new Cache>(t => GenerateHashFunc(t)); public static int CalculateSyncHash(object obj) { return hashFuncCache[obj.GetType()](obj); } static Dictionary hashFunctions = new Dictionary() { {typeof(int2), ((Func)hash_int2).Method}, {typeof(CPos), ((Func)hash_CPos).Method}, {typeof(CVec), ((Func)hash_CVec).Method}, {typeof(WRange), ((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)hash_tdict).Method}, {typeof(Actor), ((Func)hash_actor).Method}, {typeof(Player), ((Func)hash_player).Method}, {typeof(Target), ((Func)hash_target).Method}, }; static void EmitSyncOpcodes(Type type, ILGenerator il) { if (hashFunctions.ContainsKey(type)) il.EmitCall(OpCodes.Call, hashFunctions[type], null); else if (type == 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); } 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) { 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); const BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; foreach (var field in t.GetFields(bf).Where(x => x.HasAttribute())) { il.Emit(OpCodes.Ldloc, this_); il.Emit(OpCodes.Ldfld, field); EmitSyncOpcodes(field.FieldType, il); } foreach (var prop in t.GetProperties(bf).Where(x => x.HasAttribute())) { il.Emit(OpCodes.Ldloc, this_); il.EmitCall(OpCodes.Call, prop.GetGetMethod(), null); EmitSyncOpcodes(prop.PropertyType, il); } il.Emit(OpCodes.Ret); return (Func)d.CreateDelegate(typeof(Func)); } public static int hash_int2(int2 i2) { return ((i2.X * 5) ^ (i2.Y * 3)) / 4; } public static int hash_CPos(CPos i2) { return ((i2.X * 5) ^ (i2.Y * 3)) / 4; } public static int hash_CVec(CVec i2) { return ((i2.X * 5) ^ (i2.Y * 3)) / 4; } public static int hash_tdict(TypeDictionary d) { int ret = 0; foreach (var o in d) ret += CalculateSyncHash(o); return ret; } public static int hash_actor(Actor a) { if (a != null) return (int)(a.ActorID << 16); return 0; } public static int hash_player(Player p) { if (p != null) return (int)(p.PlayerActor.ActorID << 16) * 0x567; return 0; } public static int hash_target(Target t) { switch (t.Type) { case TargetType.Actor: return (int)(t.Actor.ActorID << 16) * 0x567; case TargetType.FrozenActor: return (int)(t.FrozenActor.Actor.ActorID << 16) * 0x567; case TargetType.Terrain: return hash(t.CenterPosition); default: case TargetType.Invalid: return 0; } } public static int hash(T t) { return t.GetHashCode(); } public static void CheckSyncUnchanged(World world, Action fn) { CheckSyncUnchanged(world, () => { fn(); return true; }); } static bool inUnsyncedCode = false; public static T CheckSyncUnchanged(World world, Func fn) { if (world == null) return fn(); var shouldCheckSync = Game.Settings.Debug.SanityCheckUnsyncedCode; int sync = shouldCheckSync ? world.SyncHash() : 0; bool prevInUnsyncedCode = inUnsyncedCode; inUnsyncedCode = true; try { return fn(); } finally { inUnsyncedCode = prevInUnsyncedCode; if (shouldCheckSync && sync != world.SyncHash()) throw new InvalidOperationException("CheckSyncUnchanged: sync-changing code may not run here"); } } public static void AssertUnsynced(string message) { if (!inUnsyncedCode) throw new InvalidOperationException(message); } } }