diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index bcf50f1fd1..b01405c989 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -246,11 +246,17 @@ namespace OpenRA { BeforeGameStart(); - var map = modData.PrepareMap(mapUID); - orderManager.world = new World(modData.Manifest, map, orderManager, isShellmap); - orderManager.world.Timestep = Timestep; + Map map; + using (new PerfTimer("PrepareMap")) + map = modData.PrepareMap(mapUID); + using (new PerfTimer("NewWorld")) + { + orderManager.world = new World(modData.Manifest, map, orderManager, isShellmap); + orderManager.world.Timestep = Timestep; + } worldRenderer = new WorldRenderer(orderManager.world); - orderManager.world.LoadComplete(worldRenderer); + using (new PerfTimer("LoadComplete")) + orderManager.world.LoadComplete(worldRenderer); if (orderManager.GameStarted) return; @@ -385,7 +391,8 @@ namespace OpenRA modData = new ModData(mod); Renderer.InitializeFonts(modData.Manifest); modData.InitializeLoaders(); - modData.MapCache.LoadMaps(); + using (new PerfTimer("LoadMaps")) + modData.MapCache.LoadMaps(); PerfHistory.items["render"].hasNormalTick = false; PerfHistory.items["batches"].hasNormalTick = false; @@ -436,7 +443,10 @@ namespace OpenRA public static void LoadShellMap() { - StartGame(ChooseShellmap(), true); + var shellmap = ChooseShellmap(); + + using (new PerfTimer("StartGame")) + StartGame(shellmap, true); } static string ChooseShellmap() @@ -478,7 +488,7 @@ namespace OpenRA Tick(orderManager); - var waitTime = Math.Min(idealFrameTime - sw.ElapsedTime(), 1); + var waitTime = Math.Min(idealFrameTime - sw.Elapsed.TotalSeconds, 1); if (waitTime > 0) System.Threading.Thread.Sleep(TimeSpan.FromSeconds(waitTime)); } diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 2c878bbeca..8e8bf27b4b 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -431,13 +431,20 @@ namespace OpenRA string ComputeHash() { // UID is calculated by taking an SHA1 of the yaml and binary data - // Read the relevant data into a buffer - var data = Container.GetContent("map.yaml").ReadAllBytes() - .Concat(Container.GetContent("map.bin").ReadAllBytes()).ToArray(); - // Take the SHA1 - using (var csp = SHA1.Create()) - return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray()); + using (var ms = new MemoryStream()) + { + // Read the relevant data into the buffer + using (var s = Container.GetContent("map.yaml")) + s.CopyTo(ms); + using (var s = Container.GetContent("map.bin")) + s.CopyTo(ms); + + // Take the SHA1 + ms.Seek(0, SeekOrigin.Begin); + using (var csp = SHA1.Create()) + return new string(csp.ComputeHash(ms).SelectMany(a => a.ToString("x2")).ToArray()); + } } public void MakeDefaultPlayers() diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index d745bf39e3..135ab6ce10 100755 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -47,9 +47,12 @@ namespace OpenRA { try { - var map = new Map(path, manifest.Mod.Id); - if (manifest.MapCompatibility.Contains(map.RequiresMod)) - previews[map.Uid].UpdateFromMap(map); + using (new Support.PerfTimer(path)) + { + var map = new Map(path, manifest.Mod.Id); + if (manifest.MapCompatibility.Contains(map.RequiresMod)) + previews[map.Uid].UpdateFromMap(map); + } } catch (Exception e) { diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index b65d8ed0d3..6bc9db8e54 100755 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -118,11 +118,15 @@ namespace OpenRA // Mount map package so custom assets can be used. TODO: check priority. GlobalFileSystem.Mount(GlobalFileSystem.OpenPackage(map.Path, null, int.MaxValue)); - Rules.LoadRules(Manifest, map); + using (new Support.PerfTimer("LoadRules")) + Rules.LoadRules(Manifest, map); SpriteLoader = new SpriteLoader(Rules.TileSets[map.Tileset].Extensions, SheetBuilder); - // TODO: Don't load the sequences for assets that are not used in this tileset. Maybe use the existing EditorTilesetFilters. - SequenceProvider.Initialize(Manifest.Sequences, map.Sequences); + using (new Support.PerfTimer("SequenceProvider.Initialize")) + { + // TODO: Don't load the sequences for assets that are not used in this tileset. Maybe use the existing EditorTilesetFilters. + SequenceProvider.Initialize(Manifest.Sequences, map.Sequences); + } VoxelProvider.Initialize(Manifest.VoxelSequences, map.VoxelSequences); return map; } diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index 4d5b9a6e57..3447fecd9c 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -293,7 +293,7 @@ - + diff --git a/OpenRA.Game/Settings.cs b/OpenRA.Game/Settings.cs index ae463b48f4..813325a9bb 100644 --- a/OpenRA.Game/Settings.cs +++ b/OpenRA.Game/Settings.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2013 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, @@ -71,7 +71,7 @@ namespace OpenRA public bool BotDebug = false; public bool PerfText = false; public bool PerfGraph = false; - public float LongTickThreshold = 0.001f; + public TimeSpan LongTickThreshold = TimeSpan.FromMilliseconds(1d); public bool SanityCheckUnsyncedCode = false; public int Samples = 25; public bool IgnoreVersionMismatch = false; diff --git a/OpenRA.Game/Support/Log.cs b/OpenRA.Game/Support/Log.cs index 19c62e07eb..b837815a96 100755 --- a/OpenRA.Game/Support/Log.cs +++ b/OpenRA.Game/Support/Log.cs @@ -46,6 +46,12 @@ namespace OpenRA { if (Channels.ContainsKey(channelName)) return; + if (string.IsNullOrEmpty(baseFilename)) + { + Channels.Add(channelName, new ChannelInfo()); + return; + } + foreach (var filename in FilenamesForChannel(channelName, baseFilename)) try { @@ -70,6 +76,9 @@ namespace OpenRA if (!Channels.TryGetValue(channel, out info)) throw new Exception("Tried logging to non-existant channel " + channel); + if (info.Writer == null) + return; + info.Writer.WriteLine(format, args); } } diff --git a/OpenRA.Game/Support/PerfHistory.cs b/OpenRA.Game/Support/PerfHistory.cs index 51d54dbfcc..a226904483 100644 --- a/OpenRA.Game/Support/PerfHistory.cs +++ b/OpenRA.Game/Support/PerfHistory.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, @@ -116,7 +116,7 @@ namespace OpenRA.Support public void Dispose() { - PerfHistory.Increment(Item, sw.ElapsedTime() * 1000); + PerfHistory.Increment(Item, sw.Elapsed.TotalMilliseconds); } } } diff --git a/OpenRA.Game/Support/PerfTimer.cs b/OpenRA.Game/Support/PerfTimer.cs new file mode 100755 index 0000000000..8fdb4ea5a7 --- /dev/null +++ b/OpenRA.Game/Support/PerfTimer.cs @@ -0,0 +1,117 @@ +#region Copyright & License Information +/* + * 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, + * see COPYING. + */ +#endregion + +using System; +using System.Linq; +using System.Threading; + +namespace OpenRA.Support +{ + public class PerfTimer : IDisposable + { + readonly Stopwatch sw = new Stopwatch(); + readonly string Name; + + // + // Hacks to give the output a tree-like structure + // + static ThreadLocal depth = new ThreadLocal(); + static ThreadLocal prevHeader = new ThreadLocal(); + const int MaxWidth = 60, Digits = 6; + const int MaxIndentedLabel = MaxWidth - Digits; + const string IndentationString = "| "; + readonly string FormatString = "{0," + MaxIndentedLabel + "} {1," + Digits + "} ms"; + + public PerfTimer(string name) + { + if (prevHeader.Value != null) + { + Log.Write("perf", prevHeader.Value); + prevHeader.Value = null; + } + + this.Name = name; + + prevHeader.Value = GetHeader(Indentation, this.Name); + depth.Value++; + } + + public void Dispose() + { + depth.Value--; + + string s; + + if (prevHeader.Value == null) + { + s = GetFooter(Indentation); + } + else + { + s = GetOneLiner(Indentation, this.Name); + prevHeader.Value = null; + } + + Log.Write("perf", FormatString, s, Math.Round(this.sw.Elapsed.TotalMilliseconds)); + } + + static string GetHeader(string indentation, string label) + { + return string.Concat(indentation, LimitLength(label, MaxIndentedLabel - indentation.Length)); + } + + static string GetOneLiner(string indentation, string label) + { + return string.Concat(indentation, SetLength(label, MaxIndentedLabel - indentation.Length)); + } + + static string GetFooter(string indentation) + { + return string.Concat(indentation, new string('-', MaxIndentedLabel - indentation.Length)); + } + + static string LimitLength(string s, int length, int minLength = 8) + { + length = Math.Max(length, minLength); + + if (s == null || s.Length <= length) + return s; + + return s.Substring(0, length); + } + + static string SetLength(string s, int length, int minLength = 8) + { + length = Math.Max(length, minLength); + + if (s == null || s.Length == length) + return s; + + if (s.Length < length) + return s.PadRight(length); + + return s.Substring(0, length); + } + + static string Indentation + { + get + { + var d = depth.Value; + if (d == 1) + return IndentationString; + else if (d <= 0) + return string.Empty; + else + return string.Concat(Enumerable.Repeat(IndentationString, depth.Value)); + } + } + } +} diff --git a/OpenRA.Game/Support/Stopwatch.cs b/OpenRA.Game/Support/Stopwatch.cs index 22790e830f..76d2e5b3ae 100755 --- a/OpenRA.Game/Support/Stopwatch.cs +++ b/OpenRA.Game/Support/Stopwatch.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, @@ -13,14 +13,15 @@ namespace OpenRA.Support public class Stopwatch { System.Diagnostics.Stopwatch sw; - public Stopwatch () + + public Stopwatch() { Reset(); } - public double ElapsedTime() + public System.TimeSpan Elapsed { - return sw.Elapsed.TotalMilliseconds / 1000.0; + get { return this.sw.Elapsed; } } public void Reset() diff --git a/OpenRA.Game/Support/Timer.cs b/OpenRA.Game/Support/Timer.cs deleted file mode 100755 index 6463f7a1fe..0000000000 --- a/OpenRA.Game/Support/Timer.cs +++ /dev/null @@ -1,27 +0,0 @@ -#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 - -namespace OpenRA.Support -{ - public static class Timer - { - static Stopwatch sw = new Stopwatch(); - static double lastTime = 0; - - public static void Time( string message ) - { - var time = sw.ElapsedTime(); - var dt = time - lastTime; - if( dt > 0.0001 ) - Log.Write("perf", message, dt ); - lastTime = time; - } - } -} diff --git a/OpenRA.Game/Traits/Util.cs b/OpenRA.Game/Traits/Util.cs index 6573964eca..0b347d4e67 100755 --- a/OpenRA.Game/Traits/Util.cs +++ b/OpenRA.Game/Traits/Util.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2013 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, @@ -83,9 +83,9 @@ namespace OpenRA.Traits var sw = new Stopwatch(); act = act.Tick(self); - var dt = sw.ElapsedTime(); + var dt = sw.Elapsed; if (dt > Game.Settings.Debug.LongTickThreshold) - Log.Write("perf", "[{2}] Activity: {0} ({1:0.000} ms)", prev, dt * 1000, Game.LocalTick); + Log.Write("perf", "[{2}] Activity: {0} ({1:0.000} ms)", prev, dt.TotalMilliseconds, Game.LocalTick); if (prev == act) break; diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 30e9ae7de2..1d1d12213a 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -148,7 +148,10 @@ namespace OpenRA public void LoadComplete(WorldRenderer wr) { foreach (var wlh in WorldActor.TraitsImplementing()) - wlh.WorldLoaded(this, wr); + { + using (new Support.PerfTimer(wlh.GetType().Name + ".WorldLoaded")) + wlh.WorldLoaded(this, wr); + } } public Actor CreateActor(string name, TypeDictionary initDict) diff --git a/OpenRA.Game/WorldUtils.cs b/OpenRA.Game/WorldUtils.cs index 7949f2f64f..23141fef10 100755 --- a/OpenRA.Game/WorldUtils.cs +++ b/OpenRA.Game/WorldUtils.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, @@ -171,17 +171,17 @@ namespace OpenRA } } - public static void DoTimed(this IEnumerable e, Action a, string text, double time) + public static void DoTimed(this IEnumerable e, Action a, string text, TimeSpan time) { var sw = new Stopwatch(); e.Do(x => { - var t = sw.ElapsedTime(); + var t = sw.Elapsed; a(x); - var dt = sw.ElapsedTime() - t; + var dt = sw.Elapsed - t; if (dt > time) - Log.Write("perf", text, x, dt * 1000, Game.LocalTick); + Log.Write("perf", text, x, dt.TotalMilliseconds, Game.LocalTick); }); } diff --git a/OpenRA.Lint/YamlChecker.cs b/OpenRA.Lint/YamlChecker.cs index 61ded227c4..2dfa8d1c1d 100644 --- a/OpenRA.Lint/YamlChecker.cs +++ b/OpenRA.Lint/YamlChecker.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.IO; using OpenRA.FileSystem; using OpenRA.Traits; @@ -41,6 +42,8 @@ namespace OpenRA.Lint try { + Log.AddChannel("perf", null); + var options = args.Where(a => a.StartsWith("-")); var mod = args.Where(a => !options.Contains(a)).First(); var map = args.Where(a => !options.Contains(a)).Skip(1).FirstOrDefault(); diff --git a/OpenRA.Mods.Cnc/CncLoadScreen.cs b/OpenRA.Mods.Cnc/CncLoadScreen.cs index 06685d47dc..069768d494 100644 --- a/OpenRA.Mods.Cnc/CncLoadScreen.cs +++ b/OpenRA.Mods.Cnc/CncLoadScreen.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, @@ -76,7 +76,7 @@ namespace OpenRA.Mods.Cnc public void Display() { - if (r == null || loadTimer.ElapsedTime() < 0.25) + if (r == null || loadTimer.Elapsed.TotalSeconds < 0.25) return; loadTimer.Reset(); diff --git a/OpenRA.Mods.RA/DefaultLoadScreen.cs b/OpenRA.Mods.RA/DefaultLoadScreen.cs index 5d664e2988..ef9427ff1a 100644 --- a/OpenRA.Mods.RA/DefaultLoadScreen.cs +++ b/OpenRA.Mods.RA/DefaultLoadScreen.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2013 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, @@ -55,7 +55,7 @@ namespace OpenRA.Mods.RA return; // Update text at most every 0.5 seconds - if (lastUpdate.ElapsedTime() < 0.5) + if (lastUpdate.Elapsed.TotalSeconds < 0.5) return; if (r.Fonts == null) diff --git a/OpenRA.Mods.RA/World/DomainIndex.cs b/OpenRA.Mods.RA/World/DomainIndex.cs index dae2a6ca05..1b9dfec858 100644 --- a/OpenRA.Mods.RA/World/DomainIndex.cs +++ b/OpenRA.Mods.RA/World/DomainIndex.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2013 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, @@ -241,7 +241,7 @@ namespace OpenRA.Mods.RA domain += 1; } - Log.Write("debug", "{0}: Found {1} domains. Took {2} s", map.Title, domain-1, timer.ElapsedTime()); + Log.Write("debug", "{0}: Found {1} domains. Took {2} s", map.Title, domain-1, timer.Elapsed.TotalSeconds); } } }