#region Copyright & License Information /* * Copyright 2007-2016 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.Collections.Generic; using System.Linq; using OpenRA.FileSystem; namespace OpenRA.Graphics { using Sequences = IReadOnlyDictionary>>; using UnitSequences = Lazy>; public interface ISpriteSequence { string Name { get; } int Start { get; } int Length { get; } int Stride { get; } int Facings { get; } int Tick { get; } int ZOffset { get; } int ShadowStart { get; } int ShadowZOffset { get; } int[] Frames { get; } Sprite GetSprite(int frame); Sprite GetSprite(int frame, int facing); Sprite GetShadow(int frame, int facing); } public interface ISpriteSequenceLoader { Action OnMissingSpriteError { get; set; } IReadOnlyDictionary ParseSequences(ModData modData, TileSet tileSet, SpriteCache cache, MiniYamlNode node); } public class SequenceProvider : IDisposable { readonly ModData modData; readonly TileSet tileSet; readonly Lazy sequences; readonly Lazy spriteCache; public SpriteCache SpriteCache { get { return spriteCache.Value; } } readonly Dictionary sequenceCache = new Dictionary(); public SequenceProvider(IReadOnlyFileSystem fileSystem, ModData modData, TileSet tileSet, Map map) { this.modData = modData; this.tileSet = tileSet; sequences = Exts.Lazy(() => LoadSequences(fileSystem, map)); spriteCache = Exts.Lazy(() => new SpriteCache(fileSystem, modData.SpriteLoaders, new SheetBuilder(SheetType.Indexed))); } public ISpriteSequence GetSequence(string unitName, string sequenceName) { UnitSequences unitSeq; if (!sequences.Value.TryGetValue(unitName, out unitSeq)) throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName)); ISpriteSequence seq; if (!unitSeq.Value.TryGetValue(sequenceName, out seq)) throw new InvalidOperationException("Unit `{0}` does not have a sequence named `{1}`".F(unitName, sequenceName)); return seq; } public bool HasSequence(string unitName) { return sequences.Value.ContainsKey(unitName); } public bool HasSequence(string unitName, string sequenceName) { UnitSequences unitSeq; if (!sequences.Value.TryGetValue(unitName, out unitSeq)) throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName)); return unitSeq.Value.ContainsKey(sequenceName); } public IEnumerable Sequences(string unitName) { UnitSequences unitSeq; if (!sequences.Value.TryGetValue(unitName, out unitSeq)) throw new InvalidOperationException("Unit `{0}` does not have any sequences defined.".F(unitName)); return unitSeq.Value.Keys; } public Sequences LoadSequences(IReadOnlyFileSystem fileSystem, Map map) { using (new Support.PerfTimer("LoadSequences")) return Load(fileSystem, map != null ? map.SequenceDefinitions : new List()); } Sequences Load(IReadOnlyFileSystem fileSystem, List sequenceNodes) { var nodes = MiniYaml.Merge(modData.Manifest.Sequences .Select(s => MiniYaml.FromStream(fileSystem.Open(s))) .Append(sequenceNodes)); var items = new Dictionary(); foreach (var n in nodes) { // Work around the loop closure issue in older versions of C# var node = n; var key = node.Value.ToLines(node.Key).JoinWith("|"); UnitSequences t; if (sequenceCache.TryGetValue(key, out t)) items.Add(node.Key, t); else { t = Exts.Lazy(() => modData.SpriteSequenceLoader.ParseSequences(modData, tileSet, SpriteCache, node)); sequenceCache.Add(key, t); items.Add(node.Key, t); } } return new ReadOnlyDictionary(items); } public void Preload() { SpriteCache.SheetBuilder.Current.CreateBuffer(); foreach (var unitSeq in sequences.Value.Values) foreach (var seq in unitSeq.Value.Values) { } SpriteCache.SheetBuilder.Current.ReleaseBuffer(); } public void Dispose() { if (spriteCache.IsValueCreated) spriteCache.Value.SheetBuilder.Dispose(); } } }