From e63f330717811d665f948c72e8b6e4020996985b Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Wed, 28 May 2014 21:29:29 +0100 Subject: [PATCH] Improved efficiency of startup methods. - ShpReader will copy the input stream into memory just once rather than for every header. - ShpReader.CopyImageData switched to use Array.Copy since that uses some unsafe magic for speed. - In ActorInfo, cache a GetType call and prevent needless materialization in PrerequisitesOf. - In ObjectCreator, cache type and ctor lookups since these are expensive and often repeated. - Implement IReadOnlyDictionary on Cache to provide some supplementary functions. - In TechTree.GatherOwnedPrerequisites, rearrange a Boolean 'and' expression to evaluate expensive functions later in the chain, and use ContainsKey to speed up name check. --- OpenRA.Game/FileFormats/Format40.cs | 32 +++++++++--------- OpenRA.Game/FileFormats/Format80.cs | 9 ++--- OpenRA.Game/FileFormats/ShpReader.cs | 29 +++++++--------- OpenRA.Game/GameRules/ActorInfo.cs | 13 +++++--- OpenRA.Game/ObjectCreator.cs | 35 ++++++++++++++------ OpenRA.Game/Primitives/Cache.cs | 28 ++++++++-------- OpenRA.Game/Primitives/ReadOnlyDictionary.cs | 8 ++--- OpenRA.Mods.RA/Player/TechTree.cs | 16 +++++---- 8 files changed, 93 insertions(+), 77 deletions(-) diff --git a/OpenRA.Game/FileFormats/Format40.cs b/OpenRA.Game/FileFormats/Format40.cs index 7ffbadadd2..8f4d3cfcda 100644 --- a/OpenRA.Game/FileFormats/Format40.cs +++ b/OpenRA.Game/FileFormats/Format40.cs @@ -12,58 +12,58 @@ namespace OpenRA.FileFormats { public static class Format40 { - public static int DecodeInto( byte[] src, byte[] dest ) + public static int DecodeInto(byte[] src, byte[] dest, int srcOffset) { - var ctx = new FastByteReader(src); + var ctx = new FastByteReader(src, srcOffset); int destIndex = 0; - while( true ) + while (true) { byte i = ctx.ReadByte(); - if( ( i & 0x80 ) == 0 ) + if ((i & 0x80) == 0) { int count = i & 0x7F; - if( count == 0 ) + if (count == 0) { // case 6 count = ctx.ReadByte(); byte value = ctx.ReadByte(); - for( int end = destIndex + count ; destIndex < end ; destIndex++ ) - dest[ destIndex ] ^= value; + for (int end = destIndex + count; destIndex < end; destIndex++) + dest[destIndex] ^= value; } else { // case 5 - for( int end = destIndex + count ; destIndex < end ; destIndex++ ) + for (int end = destIndex + count; destIndex < end; destIndex++) dest[destIndex] ^= ctx.ReadByte(); } } else { int count = i & 0x7F; - if( count == 0 ) + if (count == 0) { count = ctx.ReadWord(); - if( count == 0 ) + if (count == 0) return destIndex; - if( ( count & 0x8000 ) == 0 ) + if ((count & 0x8000) == 0) { // case 2 - destIndex += ( count & 0x7FFF ); + destIndex += (count & 0x7FFF); } - else if( ( count & 0x4000 ) == 0 ) + else if ((count & 0x4000) == 0) { // case 3 - for( int end = destIndex + ( count & 0x3FFF ) ; destIndex < end ; destIndex++ ) + for (int end = destIndex + (count & 0x3FFF); destIndex < end; destIndex++) dest[destIndex] ^= ctx.ReadByte(); } else { // case 4 byte value = ctx.ReadByte(); - for( int end = destIndex + ( count & 0x3FFF ) ; destIndex < end ; destIndex++ ) - dest[ destIndex ] ^= value; + for (int end = destIndex + (count & 0x3FFF); destIndex < end; destIndex++) + dest[destIndex] ^= value; } } else diff --git a/OpenRA.Game/FileFormats/Format80.cs b/OpenRA.Game/FileFormats/Format80.cs index 4780ec8f69..37e719fff4 100644 --- a/OpenRA.Game/FileFormats/Format80.cs +++ b/OpenRA.Game/FileFormats/Format80.cs @@ -16,11 +16,12 @@ namespace OpenRA.FileFormats class FastByteReader { readonly byte[] src; - int offset = 0; + int offset; - public FastByteReader(byte[] src) + public FastByteReader(byte[] src, int offset = 0) { this.src = src; + this.offset = offset; } public bool Done() { return offset >= src.Length; } @@ -59,9 +60,9 @@ namespace OpenRA.FileFormats } } - public static int DecodeInto(byte[] src, byte[] dest) + public static int DecodeInto(byte[] src, byte[] dest, int srcOffset = 0) { - var ctx = new FastByteReader(src); + var ctx = new FastByteReader(src, srcOffset); var destIndex = 0; while (true) diff --git a/OpenRA.Game/FileFormats/ShpReader.cs b/OpenRA.Game/FileFormats/ShpReader.cs index 1c80d9092b..0fb933acdb 100644 --- a/OpenRA.Game/FileFormats/ShpReader.cs +++ b/OpenRA.Game/FileFormats/ShpReader.cs @@ -67,6 +67,9 @@ namespace OpenRA.FileFormats int recurseDepth = 0; readonly int imageCount; + readonly long shpBytesFileOffset; + readonly byte[] shpBytes; + public ShpReader(Stream stream) { imageCount = stream.ReadUInt16(); @@ -93,22 +96,16 @@ namespace OpenRA.FileFormats throw new InvalidDataException("Reference doesnt point to image data {0}->{1}".F(h.FileOffset, h.RefOffset)); } + shpBytesFileOffset = stream.Position; + shpBytes = stream.ReadBytes((int)(stream.Length - stream.Position)); + foreach (var h in headers) - Decompress(stream, h); + Decompress(h); spriteFrames = Exts.Lazy(() => headers.Cast()); } - static byte[] ReadCompressedData(Stream stream, ImageHeader h) - { - stream.Position = h.FileOffset; - - // Actually, far too big. There's no length field with the correct length though :( - var compressedLength = (int)(stream.Length - stream.Position); - return stream.ReadBytes(compressedLength); - } - - void Decompress(Stream stream, ImageHeader h) + void Decompress(ImageHeader h) { // No extra work is required for empty frames if (h.Size.Width == 0 || h.Size.Height == 0) @@ -125,19 +122,19 @@ namespace OpenRA.FileFormats if (h.RefImage.Data == null) { ++recurseDepth; - Decompress(stream, h.RefImage); + Decompress(h.RefImage); --recurseDepth; } h.Data = CopyImageData(h.RefImage.Data); - Format40.DecodeInto(ReadCompressedData(stream, h), h.Data); + Format40.DecodeInto(shpBytes, h.Data, (int)(h.FileOffset - shpBytesFileOffset)); break; } case Format.Format80: { var imageBytes = new byte[Size.Width * Size.Height]; - Format80.DecodeInto(ReadCompressedData(stream, h), imageBytes); + Format80.DecodeInto(shpBytes, imageBytes, (int)(h.FileOffset - shpBytesFileOffset)); h.Data = imageBytes; break; } @@ -150,9 +147,7 @@ namespace OpenRA.FileFormats byte[] CopyImageData(byte[] baseImage) { var imageData = new byte[Size.Width * Size.Height]; - for (var i = 0; i < Size.Width * Size.Height; i++) - imageData[i] = baseImage[i]; - + Array.Copy(baseImage, imageData, imageData.Length); return imageData; } diff --git a/OpenRA.Game/GameRules/ActorInfo.cs b/OpenRA.Game/GameRules/ActorInfo.cs index 1151b7c18c..ec8d5f196b 100644 --- a/OpenRA.Game/GameRules/ActorInfo.cs +++ b/OpenRA.Game/GameRules/ActorInfo.cs @@ -94,7 +94,11 @@ namespace OpenRA while (t.Count != 0) { var prereqs = PrerequisitesOf(t[index]); - var unsatisfied = prereqs.Where(n => !ret.Any(x => x.GetType() == n || n.IsAssignableFrom(x.GetType()))); + var unsatisfied = prereqs.Where(n => !ret.Any(x => + { + var type = x.GetType(); + return type == n || n.IsAssignableFrom(type); + })); if (!unsatisfied.Any()) { ret.Add(t[index]); @@ -111,14 +115,13 @@ namespace OpenRA return ret; } - static List PrerequisitesOf(ITraitInfo info) + static IEnumerable PrerequisitesOf(ITraitInfo info) { return info .GetType() .GetInterfaces() - .Where( t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof( Requires<> ) ) - .Select( t => t.GetGenericArguments()[ 0 ] ) - .ToList(); + .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Requires<>)) + .Select(t => t.GetGenericArguments()[0]); } public IEnumerable> GetInitKeys() diff --git a/OpenRA.Game/ObjectCreator.cs b/OpenRA.Game/ObjectCreator.cs index b25aecb3db..db14e3f0ec 100755 --- a/OpenRA.Game/ObjectCreator.cs +++ b/OpenRA.Game/ObjectCreator.cs @@ -19,10 +19,15 @@ namespace OpenRA { public class ObjectCreator { - Pair[] assemblies; + readonly Cache typeCache; + readonly Cache ctorCache; + readonly Pair[] assemblies; public ObjectCreator(Manifest manifest) { + typeCache = new Cache(FindType); + ctorCache = new Cache(GetCtor); + // All the core namespaces var asms = typeof(Game).Assembly.GetNamespaces() // Game .Select(c => Pair.New(typeof(Game).Assembly, c)) @@ -48,23 +53,18 @@ namespace OpenRA public T CreateObject(string className, Dictionary args) { - var type = FindType(className); + var type = typeCache[className]; if (type == null) { MissingTypeAction(className); return default(T); } - var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; - var ctors = type.GetConstructors(flags) - .Where(x => x.HasAttribute()).ToList(); - - if (ctors.Count == 0) + var ctor = ctorCache[type]; + if (ctor == null) return (T)CreateBasic(type); - else if (ctors.Count == 1) - return (T)CreateUsingArgs(ctors[0], args); else - throw new InvalidOperationException("ObjectCreator: UseCtor on multiple constructors; invalid."); + return (T)CreateUsingArgs(ctor, args); } public Type FindType(string className) @@ -74,6 +74,21 @@ namespace OpenRA .FirstOrDefault(t => t != null); } + public ConstructorInfo GetCtor(Type type) + { + var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; + var ctors = type.GetConstructors(flags).Where(x => x.HasAttribute()); + using (var e = ctors.GetEnumerator()) + { + if (!e.MoveNext()) + return null; + var ctor = e.Current; + if (!e.MoveNext()) + return ctor; + } + throw new InvalidOperationException("ObjectCreator: UseCtor on multiple constructors; invalid."); + } + public object CreateBasic(Type type) { return type.GetConstructor(new Type[0]).Invoke(new object[0]); diff --git a/OpenRA.Game/Primitives/Cache.cs b/OpenRA.Game/Primitives/Cache.cs index cc36c6bc86..87bdd196aa 100644 --- a/OpenRA.Game/Primitives/Cache.cs +++ b/OpenRA.Game/Primitives/Cache.cs @@ -14,18 +14,17 @@ using System.Collections.Generic; namespace OpenRA.Primitives { - public class Cache : IEnumerable> + public class Cache : IReadOnlyDictionary { - Dictionary hax; - Func loader; + readonly Dictionary cache; + readonly Func loader; - public Cache(Func loader, IEqualityComparer c) + public Cache(Func loader, IEqualityComparer c) { - hax = new Dictionary(c); if (loader == null) throw new ArgumentNullException("loader"); - this.loader = loader; + cache = new Dictionary(c); } public Cache(Func loader) @@ -36,18 +35,17 @@ namespace OpenRA.Primitives get { U result; - if (!hax.TryGetValue(key, out result)) - hax.Add(key, result = loader(key)); - + if (!cache.TryGetValue(key, out result)) + cache.Add(key, result = loader(key)); return result; } } - - public IEnumerator> GetEnumerator() { return hax.GetEnumerator(); } - + public bool ContainsKey(T key) { return cache.ContainsKey(key); } + public bool TryGetValue(T key, out U value) { return cache.TryGetValue(key, out value); } + public int Count { get { return cache.Count; } } + public ICollection Keys { get { return cache.Keys; } } + public ICollection Values { get { return cache.Values; } } + public IEnumerator> GetEnumerator() { return cache.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } - - public IEnumerable Keys { get { return hax.Keys; } } - public IEnumerable Values { get { return hax.Values; } } } } diff --git a/OpenRA.Game/Primitives/ReadOnlyDictionary.cs b/OpenRA.Game/Primitives/ReadOnlyDictionary.cs index 870ba4c270..f4cb20e686 100644 --- a/OpenRA.Game/Primitives/ReadOnlyDictionary.cs +++ b/OpenRA.Game/Primitives/ReadOnlyDictionary.cs @@ -25,8 +25,8 @@ namespace OpenRA { int Count { get; } TValue this[TKey key] { get; } - IEnumerable Keys { get; } - IEnumerable Values { get; } + ICollection Keys { get; } + ICollection Values { get; } bool ContainsKey(TKey key); bool TryGetValue(TKey key, out TValue value); @@ -68,9 +68,9 @@ namespace OpenRA public TValue this[TKey key] { get { return dict[key]; } } - public IEnumerable Keys { get { return dict.Keys; } } + public ICollection Keys { get { return dict.Keys; } } - public IEnumerable Values { get { return dict.Values; } } + public ICollection Values { get { return dict.Values; } } #endregion #region IEnumerable implementation diff --git a/OpenRA.Mods.RA/Player/TechTree.cs b/OpenRA.Mods.RA/Player/TechTree.cs index 8bbd3f04be..131818239f 100755 --- a/OpenRA.Mods.RA/Player/TechTree.cs +++ b/OpenRA.Mods.RA/Player/TechTree.cs @@ -80,9 +80,13 @@ namespace OpenRA.Mods.RA // Add buildables that have a build limit set and are not already in the list player.World.ActorsWithTrait() - .Where(a => a.Actor.Info.Traits.Get().BuildLimit > 0 && !a.Actor.IsDead() && a.Actor.IsInWorld && a.Actor.Owner == player && ret.Keys.All(k => k != a.Actor.Info.Name)) - .ToList() - .ForEach(b => ret[b.Actor.Info.Name].Add(b.Actor)); + .Where(a => + a.Actor.Owner == player && + a.Actor.IsInWorld && + !a.Actor.IsDead() && + !ret.ContainsKey(a.Actor.Info.Name) && + a.Actor.Info.Traits.Get().BuildLimit > 0) + .Do(b => ret[b.Actor.Info.Name].Add(b.Actor)); return ret; } @@ -111,17 +115,17 @@ namespace OpenRA.Mods.RA bool HasPrerequisites(Cache> ownedPrerequisites) { - return prerequisites.All(p => !(p.Replace("~", "").StartsWith("!") ^ !ownedPrerequisites.Keys.Contains(p.Replace("!", "").Replace("~", "")))); + return prerequisites.All(p => !(p.Replace("~", "").StartsWith("!") ^ !ownedPrerequisites.ContainsKey(p.Replace("!", "").Replace("~", "")))); } bool IsHidden(Cache> ownedPrerequisites) { - return prerequisites.Any(prereq => prereq.StartsWith("~") && (prereq.Replace("~", "").StartsWith("!") ^ !ownedPrerequisites.Keys.Contains(prereq.Replace("~", "").Replace("!", "")))); + return prerequisites.Any(prereq => prereq.StartsWith("~") && (prereq.Replace("~", "").StartsWith("!") ^ !ownedPrerequisites.ContainsKey(prereq.Replace("~", "").Replace("!", "")))); } public void Update(Cache> ownedPrerequisites) { - var hasReachedLimit = limit > 0 && ownedPrerequisites.Keys.Contains(Key) && ownedPrerequisites[Key].Count >= limit; + var hasReachedLimit = limit > 0 && ownedPrerequisites.ContainsKey(Key) && ownedPrerequisites[Key].Count >= limit; // The '!' annotation inverts prerequisites: "I'm buildable if this prerequisite *isn't* met" var nowHasPrerequisites = HasPrerequisites(ownedPrerequisites) && !hasReachedLimit; var nowHidden = IsHidden(ownedPrerequisites);