Create a separate FrameCache for caching sprite frames.

We split the caching SpriteLoader into a SpriteCache and FrameCache. SpriteLoader instead becomes a holder for static loading methods.

Only a few classes loaded sprite frames, and they all use it with a transient cache. By moving this method into a new class, we can lose the now redundant frame cache, saving on memory significantly since the frame data array can be reclaimed by the GC. This saves ~58 MiB on frames and ~4 MiB on the caching dictionary in simple tests.
This commit is contained in:
RoosterDragon
2014-10-14 20:04:56 +01:00
parent 2a15c44d91
commit d671e1de01
9 changed files with 45 additions and 40 deletions

View File

@@ -45,10 +45,10 @@ namespace OpenRA.Graphics
foreach (var p in nodesDict["Palettes"].Nodes)
palette.AddPalette(p.Key, new ImmutablePalette(GlobalFileSystem.Open(p.Value.Value), shadowIndex), false);
var spriteLoader = new SpriteLoader(modData.SpriteLoaders, new string[0], new SheetBuilder(SheetType.Indexed));
var spriteCache = new SpriteCache(modData.SpriteLoaders, new string[0], new SheetBuilder(SheetType.Indexed));
foreach (var s in nodesDict["Cursors"].Nodes)
LoadSequencesForCursor(spriteLoader, s.Key, s.Value);
spriteLoader.SheetBuilder.Current.ReleaseBuffer();
LoadSequencesForCursor(spriteCache, s.Key, s.Value);
spriteCache.SheetBuilder.Current.ReleaseBuffer();
palette.Initialize();
}
@@ -59,10 +59,10 @@ namespace OpenRA.Graphics
return new PaletteReference(name, palette.GetPaletteIndex(name), pal);
}
void LoadSequencesForCursor(SpriteLoader loader, string cursorSrc, MiniYaml cursor)
void LoadSequencesForCursor(SpriteCache cache, string cursorSrc, MiniYaml cursor)
{
foreach (var sequence in cursor.Nodes)
cursors.Add(sequence.Key, new CursorSequence(loader, cursorSrc, cursor.Value, sequence.Value));
cursors.Add(sequence.Key, new CursorSequence(cache, cursorSrc, cursor.Value, sequence.Value));
}
public bool HasCursorSequence(string cursor)

View File

@@ -23,9 +23,9 @@ namespace OpenRA.Graphics
Sprite[] sprites;
public CursorSequence(SpriteLoader loader, string cursorSrc, string palette, MiniYaml info)
public CursorSequence(SpriteCache cache, string cursorSrc, string palette, MiniYaml info)
{
sprites = loader.LoadAllSprites(cursorSrc);
sprites = cache[cursorSrc];
var d = info.ToDictionary();
start = Exts.ParseIntegerInvariant(d["start"].Value);

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Graphics
public readonly int ShadowZOffset;
public readonly int[] Frames;
public Sequence(SpriteLoader loader, string unit, string name, MiniYaml info)
public Sequence(SpriteCache cache, string unit, string name, MiniYaml info)
{
var srcOverride = info.Value;
Name = name;
@@ -50,7 +50,7 @@ namespace OpenRA.Graphics
// Apply offset to each sprite in the sequence
// Different sequences may apply different offsets to the same frame
sprites = loader.LoadAllSprites(srcOverride ?? unit).Select(
sprites = cache[srcOverride ?? unit].Select(
s => new Sprite(s.sheet, s.bounds, s.offset + offset, s.channel, blendMode)).ToArray();
if (!d.ContainsKey("Length"))

View File

@@ -21,12 +21,12 @@ namespace OpenRA.Graphics
public class SequenceProvider
{
readonly Lazy<Sequences> sequences;
public readonly SpriteLoader SpriteLoader;
public readonly SpriteCache SpriteCache;
public SequenceProvider(SequenceCache cache, Map map)
{
this.sequences = Exts.Lazy(() => cache.LoadSequences(map));
this.SpriteLoader = cache.SpriteLoader;
this.SpriteCache = cache.SpriteCache;
}
public Sequence GetSequence(string unitName, string sequenceName)
@@ -70,8 +70,8 @@ namespace OpenRA.Graphics
public class SequenceCache
{
readonly ModData modData;
readonly Lazy<SpriteLoader> spriteLoader;
public SpriteLoader SpriteLoader { get { return spriteLoader.Value; } }
readonly Lazy<SpriteCache> spriteCache;
public SpriteCache SpriteCache { get { return spriteCache.Value; } }
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
@@ -79,7 +79,7 @@ namespace OpenRA.Graphics
{
this.modData = modData;
spriteLoader = Exts.Lazy(() => new SpriteLoader(modData.SpriteLoaders, tileSet.Extensions, new SheetBuilder(SheetType.Indexed)));
spriteCache = Exts.Lazy(() => new SpriteCache(modData.SpriteLoaders, tileSet.Extensions, new SheetBuilder(SheetType.Indexed)));
}
public Sequences LoadSequences(Map map)
@@ -128,7 +128,7 @@ namespace OpenRA.Graphics
{
try
{
unitSequences.Add(kvp.Key, new Sequence(spriteLoader.Value, node.Key, kvp.Key, kvp.Value));
unitSequences.Add(kvp.Key, new Sequence(spriteCache.Value, node.Key, kvp.Key, kvp.Value));
}
catch (FileNotFoundException ex)
{

View File

@@ -30,32 +30,42 @@ namespace OpenRA.Graphics
bool DisableExportPadding { get; }
}
public class SpriteLoader
public class SpriteCache
{
public readonly SheetBuilder SheetBuilder;
readonly ISpriteLoader[] loaders;
readonly Cache<string, Sprite[]> sprites;
readonly Cache<string, ISpriteFrame[]> frames;
readonly string[] exts;
public SpriteLoader(ISpriteLoader[] loaders, string[] exts, SheetBuilder sheetBuilder)
public SpriteCache(ISpriteLoader[] loaders, string[] exts, SheetBuilder sheetBuilder)
{
this.loaders = loaders;
SheetBuilder = sheetBuilder;
// Include extension-less version
this.exts = exts.Append("").ToArray();
sprites = new Cache<string, Sprite[]>(CacheSprites);
frames = new Cache<string, ISpriteFrame[]>(CacheFrames);
exts = exts.Append("").ToArray();
sprites = new Cache<string, Sprite[]>(filename => SpriteLoader.GetSprites(filename, exts, loaders, sheetBuilder));
}
Sprite[] CacheSprites(string filename)
public Sprite[] this[string filename] { get { return sprites[filename]; } }
}
public class FrameCache
{
readonly Cache<string, ISpriteFrame[]> frames;
public FrameCache(ISpriteLoader[] loaders, string[] exts)
{
return frames[filename].Select(a => SheetBuilder.Add(a))
.ToArray();
frames = new Cache<string, ISpriteFrame[]>(filename => SpriteLoader.GetFrames(filename, exts, loaders));
}
ISpriteFrame[] CacheFrames(string filename)
public ISpriteFrame[] this[string filename] { get { return frames[filename]; } }
}
public static class SpriteLoader
{
public static Sprite[] GetSprites(string filename, string[] exts, ISpriteLoader[] loaders, SheetBuilder sheetBuilder)
{
return GetFrames(filename, exts, loaders).Select(a => sheetBuilder.Add(a)).ToArray();
}
public static ISpriteFrame[] GetFrames(string filename, string[] exts, ISpriteLoader[] loaders)
{
using (var stream = GlobalFileSystem.OpenWithExts(filename, exts))
{
@@ -67,8 +77,5 @@ namespace OpenRA.Graphics
throw new InvalidDataException(filename + " is not a valid sprite file");
}
}
public Sprite[] LoadAllSprites(string filename) { return sprites[filename]; }
public ISpriteFrame[] LoadAllFrames(string filename) { return frames[filename]; }
}
}

View File

@@ -37,11 +37,10 @@ namespace OpenRA.Graphics
sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate);
templates = new Dictionary<ushort, Sprite[]>();
// We manage the SheetBuilder ourselves, to avoid loading all of the tileset images
var spriteLoader = new SpriteLoader(Game.modData.SpriteLoaders, tileset.Extensions, null);
var frameCache = new FrameCache(Game.modData.SpriteLoaders, tileset.Extensions);
foreach (var t in tileset.Templates)
{
var allFrames = spriteLoader.LoadAllFrames(t.Value.Image);
var allFrames = frameCache[t.Value.Image];
var frames = t.Value.Frames != null ? t.Value.Frames.Select(f => allFrames[f]).ToArray() : allFrames;
templates.Add(t.Value.Id, frames.Select(f => sheetBuilder.Add(f)).ToArray());
}