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

@@ -50,10 +50,10 @@ namespace OpenRA.Editor
this.TileSize = Math.Min(tileSize.Width, tileSize.Height); this.TileSize = Math.Min(tileSize.Width, tileSize.Height);
templates = new Dictionary<ushort, byte[][]>(); templates = new Dictionary<ushort, byte[][]>();
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) 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; var frames = t.Value.Frames != null ? t.Value.Frames.Select(f => allFrames[f]).ToArray() : allFrames;
templates.Add(t.Value.Id, frames.Select(f => ExtractSquareTile(f)).ToArray()); templates.Add(t.Value.Id, frames.Select(f => ExtractSquareTile(f)).ToArray());
} }

View File

@@ -45,10 +45,10 @@ namespace OpenRA.Graphics
foreach (var p in nodesDict["Palettes"].Nodes) foreach (var p in nodesDict["Palettes"].Nodes)
palette.AddPalette(p.Key, new ImmutablePalette(GlobalFileSystem.Open(p.Value.Value), shadowIndex), false); 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) foreach (var s in nodesDict["Cursors"].Nodes)
LoadSequencesForCursor(spriteLoader, s.Key, s.Value); LoadSequencesForCursor(spriteCache, s.Key, s.Value);
spriteLoader.SheetBuilder.Current.ReleaseBuffer(); spriteCache.SheetBuilder.Current.ReleaseBuffer();
palette.Initialize(); palette.Initialize();
} }
@@ -59,10 +59,10 @@ namespace OpenRA.Graphics
return new PaletteReference(name, palette.GetPaletteIndex(name), pal); 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) 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) public bool HasCursorSequence(string cursor)

View File

@@ -23,9 +23,9 @@ namespace OpenRA.Graphics
Sprite[] sprites; 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(); var d = info.ToDictionary();
start = Exts.ParseIntegerInvariant(d["start"].Value); start = Exts.ParseIntegerInvariant(d["start"].Value);

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Graphics
public readonly int ShadowZOffset; public readonly int ShadowZOffset;
public readonly int[] Frames; 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; var srcOverride = info.Value;
Name = name; Name = name;
@@ -50,7 +50,7 @@ namespace OpenRA.Graphics
// Apply offset to each sprite in the sequence // Apply offset to each sprite in the sequence
// Different sequences may apply different offsets to the same frame // 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(); s => new Sprite(s.sheet, s.bounds, s.offset + offset, s.channel, blendMode)).ToArray();
if (!d.ContainsKey("Length")) if (!d.ContainsKey("Length"))

View File

@@ -21,12 +21,12 @@ namespace OpenRA.Graphics
public class SequenceProvider public class SequenceProvider
{ {
readonly Lazy<Sequences> sequences; readonly Lazy<Sequences> sequences;
public readonly SpriteLoader SpriteLoader; public readonly SpriteCache SpriteCache;
public SequenceProvider(SequenceCache cache, Map map) public SequenceProvider(SequenceCache cache, Map map)
{ {
this.sequences = Exts.Lazy(() => cache.LoadSequences(map)); this.sequences = Exts.Lazy(() => cache.LoadSequences(map));
this.SpriteLoader = cache.SpriteLoader; this.SpriteCache = cache.SpriteCache;
} }
public Sequence GetSequence(string unitName, string sequenceName) public Sequence GetSequence(string unitName, string sequenceName)
@@ -70,8 +70,8 @@ namespace OpenRA.Graphics
public class SequenceCache public class SequenceCache
{ {
readonly ModData modData; readonly ModData modData;
readonly Lazy<SpriteLoader> spriteLoader; readonly Lazy<SpriteCache> spriteCache;
public SpriteLoader SpriteLoader { get { return spriteLoader.Value; } } public SpriteCache SpriteCache { get { return spriteCache.Value; } }
readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>(); readonly Dictionary<string, UnitSequences> sequenceCache = new Dictionary<string, UnitSequences>();
@@ -79,7 +79,7 @@ namespace OpenRA.Graphics
{ {
this.modData = modData; 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) public Sequences LoadSequences(Map map)
@@ -128,7 +128,7 @@ namespace OpenRA.Graphics
{ {
try 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) catch (FileNotFoundException ex)
{ {

View File

@@ -30,32 +30,42 @@ namespace OpenRA.Graphics
bool DisableExportPadding { get; } bool DisableExportPadding { get; }
} }
public class SpriteLoader public class SpriteCache
{ {
public readonly SheetBuilder SheetBuilder; public readonly SheetBuilder SheetBuilder;
readonly ISpriteLoader[] loaders;
readonly Cache<string, Sprite[]> sprites; 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; SheetBuilder = sheetBuilder;
// Include extension-less version // Include extension-less version
this.exts = exts.Append("").ToArray(); exts = exts.Append("").ToArray();
sprites = new Cache<string, Sprite[]>(CacheSprites); sprites = new Cache<string, Sprite[]>(filename => SpriteLoader.GetSprites(filename, exts, loaders, sheetBuilder));
frames = new Cache<string, ISpriteFrame[]>(CacheFrames);
} }
Sprite[] CacheSprites(string filename) public Sprite[] this[string filename] { get { return sprites[filename]; } }
}
public class FrameCache
{ {
return frames[filename].Select(a => SheetBuilder.Add(a)) readonly Cache<string, ISpriteFrame[]> frames;
.ToArray();
public FrameCache(ISpriteLoader[] loaders, string[] exts)
{
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)) using (var stream = GlobalFileSystem.OpenWithExts(filename, exts))
{ {
@@ -67,8 +77,5 @@ namespace OpenRA.Graphics
throw new InvalidDataException(filename + " is not a valid sprite file"); 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); sheetBuilder = new SheetBuilder(SheetType.Indexed, allocate);
templates = new Dictionary<ushort, Sprite[]>(); templates = new Dictionary<ushort, Sprite[]>();
// We manage the SheetBuilder ourselves, to avoid loading all of the tileset images var frameCache = new FrameCache(Game.modData.SpriteLoaders, tileset.Extensions);
var spriteLoader = new SpriteLoader(Game.modData.SpriteLoaders, tileset.Extensions, null);
foreach (var t in tileset.Templates) 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; 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()); templates.Add(t.Value.Id, frames.Select(f => sheetBuilder.Add(f)).ToArray());
} }

View File

@@ -41,8 +41,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
var palette = new ImmutablePalette(args[2], shadowIndex); var palette = new ImmutablePalette(args[2], shadowIndex);
var frames = new SpriteLoader(modData.SpriteLoaders, new string[0], null) var frames = SpriteLoader.GetFrames(src, new string[0], modData.SpriteLoaders);
.LoadAllFrames(src);
var usePadding = !args.Contains("--nopadding"); var usePadding = !args.Contains("--nopadding");
var count = 0; var count = 0;

View File

@@ -308,7 +308,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
else else
{ {
currentFilename = filename; currentFilename = filename;
currentSprites = world.Map.SequenceProvider.SpriteLoader.LoadAllSprites(filename); currentSprites = world.Map.SequenceProvider.SpriteCache[filename];
currentFrame = 0; currentFrame = 0;
frameSlider.MaximumValue = (float)currentSprites.Length - 1; frameSlider.MaximumValue = (float)currentSprites.Length - 1;
frameSlider.Ticks = currentSprites.Length; frameSlider.Ticks = currentSprites.Length;