PageRenderTime 27ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/OpenRA.Game/Network/SyncReport.cs

http://github.com/OpenRA/OpenRA
C# | 323 lines | 256 code | 41 blank | 26 comment | 29 complexity | 02bc3e8e0ddd746eb152496114291862 MD5 | raw file
Possible License(s): GPL-3.0
  1. #region Copyright & License Information
  2. /*
  3. * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
  4. * This file is part of OpenRA, which is free software. It is made
  5. * available to you under the terms of the GNU General Public License
  6. * as published by the Free Software Foundation, either version 3 of
  7. * the License, or (at your option) any later version. For more
  8. * information, see COPYING.
  9. */
  10. #endregion
  11. using System;
  12. using System.Collections.Generic;
  13. using System.Globalization;
  14. using System.Linq;
  15. using System.Linq.Expressions;
  16. using System.Reflection;
  17. using OpenRA.Primitives;
  18. namespace OpenRA.Network
  19. {
  20. class SyncReport
  21. {
  22. const int NumSyncReports = 5;
  23. static Cache<Type, TypeInfo> typeInfoCache = new Cache<Type, TypeInfo>(t => new TypeInfo(t));
  24. readonly OrderManager orderManager;
  25. readonly Report[] syncReports = new Report[NumSyncReports];
  26. int curIndex = 0;
  27. static Pair<string[], Values> DumpSyncTrait(ISync sync)
  28. {
  29. var type = sync.GetType();
  30. TypeInfo typeInfo;
  31. lock (typeInfoCache)
  32. typeInfo = typeInfoCache[type];
  33. var values = new Values(typeInfo.Names.Length);
  34. var index = 0;
  35. foreach (var func in typeInfo.SerializableCopyOfMemberFunctions)
  36. values[index++] = func(sync);
  37. return Pair.New(typeInfo.Names, values);
  38. }
  39. public SyncReport(OrderManager orderManager)
  40. {
  41. this.orderManager = orderManager;
  42. for (var i = 0; i < NumSyncReports; i++)
  43. syncReports[i] = new Report();
  44. }
  45. internal void UpdateSyncReport()
  46. {
  47. GenerateSyncReport(syncReports[curIndex]);
  48. curIndex = ++curIndex % NumSyncReports;
  49. }
  50. void GenerateSyncReport(Report report)
  51. {
  52. report.Frame = orderManager.NetFrameNumber;
  53. report.SyncedRandom = orderManager.World.SharedRandom.Last;
  54. report.TotalCount = orderManager.World.SharedRandom.TotalCount;
  55. report.Traits.Clear();
  56. report.Effects.Clear();
  57. foreach (var actor in orderManager.World.ActorsHavingTrait<ISync>())
  58. {
  59. foreach (var syncHash in actor.SyncHashes)
  60. {
  61. var hash = syncHash.Hash();
  62. if (hash != 0)
  63. {
  64. report.Traits.Add(new TraitReport()
  65. {
  66. ActorID = actor.ActorID,
  67. Type = actor.Info.Name,
  68. Owner = (actor.Owner == null) ? "null" : actor.Owner.PlayerName,
  69. Trait = syncHash.Trait.GetType().Name,
  70. Hash = hash,
  71. NamesValues = DumpSyncTrait(syncHash.Trait)
  72. });
  73. }
  74. }
  75. }
  76. foreach (var sync in orderManager.World.SyncedEffects)
  77. {
  78. var hash = Sync.Hash(sync);
  79. if (hash != 0)
  80. {
  81. report.Effects.Add(new EffectReport()
  82. {
  83. Name = sync.GetType().Name,
  84. Hash = hash,
  85. NamesValues = DumpSyncTrait(sync)
  86. });
  87. }
  88. }
  89. }
  90. internal void DumpSyncReport(int frame, IEnumerable<FrameData.ClientOrder> orders)
  91. {
  92. var reportName = "syncreport-" + DateTime.UtcNow.ToString("yyyy-MM-ddTHHmmssZ", CultureInfo.InvariantCulture) + ".log";
  93. Log.AddChannel("sync", reportName);
  94. foreach (var r in syncReports)
  95. {
  96. if (r.Frame == frame)
  97. {
  98. var mod = Game.ModData.Manifest.Metadata;
  99. Log.Write("sync", "Player: {0} ({1} {2} {3})", Game.Settings.Player.Name, Platform.CurrentPlatform, Environment.OSVersion, Platform.RuntimeVersion);
  100. Log.Write("sync", "Game ID: {0} (Mod: {1} at Version {2})", orderManager.LobbyInfo.GlobalSettings.GameUid, mod.Title, mod.Version);
  101. Log.Write("sync", "Sync for net frame {0} -------------", r.Frame);
  102. Log.Write("sync", "SharedRandom: {0} (#{1})", r.SyncedRandom, r.TotalCount);
  103. Log.Write("sync", "Synced Traits:");
  104. foreach (var a in r.Traits)
  105. {
  106. Log.Write("sync", "\t {0} {1} {2} {3} ({4})".F(a.ActorID, a.Type, a.Owner, a.Trait, a.Hash));
  107. var nvp = a.NamesValues;
  108. for (int i = 0; i < nvp.First.Length; i++)
  109. if (nvp.Second[i] != null)
  110. Log.Write("sync", "\t\t {0}: {1}".F(nvp.First[i], nvp.Second[i]));
  111. }
  112. Log.Write("sync", "Synced Effects:");
  113. foreach (var e in r.Effects)
  114. {
  115. Log.Write("sync", "\t {0} ({1})", e.Name, e.Hash);
  116. var nvp = e.NamesValues;
  117. for (int i = 0; i < nvp.First.Length; i++)
  118. if (nvp.Second[i] != null)
  119. Log.Write("sync", "\t\t {0}: {1}".F(nvp.First[i], nvp.Second[i]));
  120. }
  121. Log.Write("sync", "Orders Issued:");
  122. foreach (var o in orders)
  123. Log.Write("sync", "\t {0}", o.ToString());
  124. return;
  125. }
  126. }
  127. Log.Write("sync", "No sync report available!");
  128. }
  129. class Report
  130. {
  131. public int Frame;
  132. public int SyncedRandom;
  133. public int TotalCount;
  134. public List<TraitReport> Traits = new List<TraitReport>();
  135. public List<EffectReport> Effects = new List<EffectReport>();
  136. }
  137. struct TraitReport
  138. {
  139. public uint ActorID;
  140. public string Type;
  141. public string Owner;
  142. public string Trait;
  143. public int Hash;
  144. public Pair<string[], Values> NamesValues;
  145. }
  146. struct EffectReport
  147. {
  148. public string Name;
  149. public int Hash;
  150. public Pair<string[], Values> NamesValues;
  151. }
  152. struct TypeInfo
  153. {
  154. static readonly ParameterExpression SyncParam = Expression.Parameter(typeof(ISync), "sync");
  155. static readonly ConstantExpression NullString = Expression.Constant(null, typeof(string));
  156. static readonly ConstantExpression TrueString = Expression.Constant(bool.TrueString, typeof(string));
  157. static readonly ConstantExpression FalseString = Expression.Constant(bool.FalseString, typeof(string));
  158. public readonly Func<ISync, object>[] SerializableCopyOfMemberFunctions;
  159. public readonly string[] Names;
  160. public TypeInfo(Type type)
  161. {
  162. const BindingFlags Flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
  163. var fields = type.GetFields(Flags).Where(fi => !fi.IsLiteral && !fi.IsStatic && fi.HasAttribute<SyncAttribute>());
  164. var properties = type.GetProperties(Flags).Where(pi => pi.HasAttribute<SyncAttribute>());
  165. foreach (var prop in properties)
  166. if (!prop.CanRead || prop.GetIndexParameters().Any())
  167. throw new InvalidOperationException(
  168. "Properties using the Sync attribute must be readable and must not use index parameters.\n" +
  169. "Invalid Property: " + prop.DeclaringType.FullName + "." + prop.Name);
  170. var sync = Expression.Convert(SyncParam, type);
  171. SerializableCopyOfMemberFunctions = fields
  172. .Select(fi => SerializableCopyOfMember(Expression.Field(sync, fi), fi.FieldType, fi.Name))
  173. .Concat(properties.Select(pi => SerializableCopyOfMember(Expression.Property(sync, pi), pi.PropertyType, pi.Name)))
  174. .ToArray();
  175. Names = fields.Select(fi => fi.Name).Concat(properties.Select(pi => pi.Name)).ToArray();
  176. }
  177. static Func<ISync, object> SerializableCopyOfMember(MemberExpression getMember, Type memberType, string name)
  178. {
  179. // We need to serialize a copy of the current value so if the sync report is generated, the values can
  180. // be dumped as strings.
  181. if (memberType.IsValueType)
  182. {
  183. // PERF: For value types we can avoid the overhead of calling ToString immediately. We can instead
  184. // just box a copy of the current value into an object. This is faster than calling ToString. We
  185. // can call ToString later when we generate the report. Most of the time, the sync report is never
  186. // generated so we successfully avoid the overhead to calling ToString.
  187. if (memberType == typeof(bool))
  188. {
  189. // PERF: If the member is a Boolean, we can also avoid the allocation caused by boxing it.
  190. // Instead, we can just return the resulting strings directly.
  191. var getBoolString = Expression.Condition(getMember, TrueString, FalseString);
  192. return Expression.Lambda<Func<ISync, string>>(getBoolString, name, new[] { SyncParam }).Compile();
  193. }
  194. var boxedCopy = Expression.Convert(getMember, typeof(object));
  195. return Expression.Lambda<Func<ISync, object>>(boxedCopy, name, new[] { SyncParam }).Compile();
  196. }
  197. // For reference types, we have to call ToString right away to get a snapshot of the value. We cannot
  198. // delay, as calling ToString later may produce different results.
  199. return MemberToString(getMember, memberType, name);
  200. }
  201. static Func<ISync, string> MemberToString(MemberExpression getMember, Type memberType, string name)
  202. {
  203. // The lambda generated is shown below.
  204. // TSync is actual type of the ISync object. Foo is a field or property with the Sync attribute applied.
  205. var toString = memberType.GetMethod("ToString", Type.EmptyTypes);
  206. Expression getString;
  207. if (memberType.IsValueType)
  208. {
  209. // (ISync sync) => { return ((TSync)sync).Foo.ToString(); }
  210. getString = Expression.Call(getMember, toString);
  211. }
  212. else
  213. {
  214. // (ISync sync) => { var foo = ((TSync)sync).Foo; return foo == null ? null : foo.ToString(); }
  215. var memberVariable = Expression.Variable(memberType, getMember.Member.Name);
  216. var assignMemberVariable = Expression.Assign(memberVariable, getMember);
  217. var member = Expression.Block(new[] { memberVariable }, assignMemberVariable);
  218. getString = Expression.Call(member, toString);
  219. var nullMember = Expression.Constant(null, memberType);
  220. getString = Expression.Condition(Expression.Equal(member, nullMember), NullString, getString);
  221. }
  222. return Expression.Lambda<Func<ISync, string>>(getString, name, new[] { SyncParam }).Compile();
  223. }
  224. }
  225. /// <summary>
  226. /// Holds up to 4 objects directly, or else allocates an array to hold the items. This allows us to record
  227. /// trait values for traits with up to 4 sync members inline without having to allocate extra memory.
  228. /// </summary>
  229. struct Values
  230. {
  231. static readonly object Sentinel = new object();
  232. object item1OrArray;
  233. object item2OrSentinel;
  234. object item3;
  235. object item4;
  236. public Values(int size)
  237. {
  238. item1OrArray = null;
  239. item2OrSentinel = null;
  240. item3 = null;
  241. item4 = null;
  242. if (size > 4)
  243. {
  244. item1OrArray = new object[size];
  245. item2OrSentinel = Sentinel;
  246. }
  247. }
  248. public object this[int index]
  249. {
  250. get
  251. {
  252. if (item2OrSentinel == Sentinel)
  253. return ((object[])item1OrArray)[index];
  254. switch (index)
  255. {
  256. case 0: return item1OrArray;
  257. case 1: return item2OrSentinel;
  258. case 2: return item3;
  259. case 3: return item4;
  260. default: throw new ArgumentOutOfRangeException("index");
  261. }
  262. }
  263. set
  264. {
  265. if (item2OrSentinel == Sentinel)
  266. {
  267. ((object[])item1OrArray)[index] = value;
  268. return;
  269. }
  270. switch (index)
  271. {
  272. case 0: item1OrArray = value; break;
  273. case 1: item2OrSentinel = value; break;
  274. case 2: item3 = value; break;
  275. case 3: item4 = value; break;
  276. default: throw new ArgumentOutOfRangeException("index");
  277. }
  278. }
  279. }
  280. }
  281. }
  282. }