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<T, U> on Cache<T, U> 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.
This commit is contained in:
RoosterDragon
2014-05-28 21:29:29 +01:00
parent 334a210231
commit e63f330717
8 changed files with 93 additions and 77 deletions

View File

@@ -12,9 +12,9 @@ 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)

View File

@@ -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)

View File

@@ -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<ISpriteFrame>());
}
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;
}

View File

@@ -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<Type> PrerequisitesOf(ITraitInfo info)
static IEnumerable<Type> PrerequisitesOf(ITraitInfo info)
{
return info
.GetType()
.GetInterfaces()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Requires<>))
.Select( t => t.GetGenericArguments()[ 0 ] )
.ToList();
.Select(t => t.GetGenericArguments()[0]);
}
public IEnumerable<Pair<string, Type>> GetInitKeys()

View File

@@ -19,10 +19,15 @@ namespace OpenRA
{
public class ObjectCreator
{
Pair<Assembly, string>[] assemblies;
readonly Cache<string, Type> typeCache;
readonly Cache<Type, ConstructorInfo> ctorCache;
readonly Pair<Assembly, string>[] assemblies;
public ObjectCreator(Manifest manifest)
{
typeCache = new Cache<string, Type>(FindType);
ctorCache = new Cache<Type, ConstructorInfo>(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<T>(string className, Dictionary<string, object> 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<UseCtorAttribute>()).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<UseCtorAttribute>());
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]);

View File

@@ -14,18 +14,17 @@ using System.Collections.Generic;
namespace OpenRA.Primitives
{
public class Cache<T, U> : IEnumerable<KeyValuePair<T, U>>
public class Cache<T, U> : IReadOnlyDictionary<T, U>
{
Dictionary<T, U> hax;
Func<T,U> loader;
readonly Dictionary<T, U> cache;
readonly Func<T, U> loader;
public Cache(Func<T, U> loader, IEqualityComparer<T> c)
{
hax = new Dictionary<T, U>(c);
if (loader == null)
throw new ArgumentNullException("loader");
this.loader = loader;
cache = new Dictionary<T, U>(c);
}
public Cache(Func<T, U> 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<KeyValuePair<T, U>> 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<T> Keys { get { return cache.Keys; } }
public ICollection<U> Values { get { return cache.Values; } }
public IEnumerator<KeyValuePair<T, U>> GetEnumerator() { return cache.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public IEnumerable<T> Keys { get { return hax.Keys; } }
public IEnumerable<U> Values { get { return hax.Values; } }
}
}

View File

@@ -25,8 +25,8 @@ namespace OpenRA
{
int Count { get; }
TValue this[TKey key] { get; }
IEnumerable<TKey> Keys { get; }
IEnumerable<TValue> Values { get; }
ICollection<TKey> Keys { get; }
ICollection<TValue> 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<TKey> Keys { get { return dict.Keys; } }
public ICollection<TKey> Keys { get { return dict.Keys; } }
public IEnumerable<TValue> Values { get { return dict.Values; } }
public ICollection<TValue> Values { get { return dict.Values; } }
#endregion
#region IEnumerable implementation

View File

@@ -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<Buildable>()
.Where(a => a.Actor.Info.Traits.Get<BuildableInfo>().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<BuildableInfo>().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<string, List<Actor>> 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<string, List<Actor>> 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<string, List<Actor>> 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);