diff --git a/OpenRA.Game/GameRules/Rules.cs b/OpenRA.Game/GameRules/Rules.cs index 235375e57d..43988885e0 100755 --- a/OpenRA.Game/GameRules/Rules.cs +++ b/OpenRA.Game/GameRules/Rules.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 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, @@ -13,44 +13,130 @@ using System.Collections.Generic; using System.Linq; using OpenRA.FileFormats; using OpenRA.GameRules; +using OpenRA.Support; namespace OpenRA { public static class Rules { - public static Dictionary Info; - public static Dictionary Weapons; - public static Dictionary Voices; - public static Dictionary Notifications; - public static Dictionary Music; - public static Dictionary Movies; - public static Dictionary TileSets; + public static Dictionary Info { get { return Game.modData.Rules.Actors; } } + public static Dictionary Weapons { get { return Game.modData.Rules.Weapons; } } + public static Dictionary Voices { get { return Game.modData.Rules.Voices; } } + public static Dictionary Notifications { get { return Game.modData.Rules.Notifications; } } + public static Dictionary Music { get { return Game.modData.Rules.Music; } } + public static Dictionary Movies { get { return Game.modData.Rules.Movies; } } + public static Dictionary TileSets { get { return Game.modData.Rules.TileSets; } } public static void LoadRules(Manifest m, Map map) { - // Added support to extend the list of rules (add it to m.LocalRules) - Info = LoadYamlRules(m.Rules, map.Rules, (k, y) => new ActorInfo(k.Key.ToLowerInvariant(), k.Value, y)); - Weapons = LoadYamlRules(m.Weapons, map.Weapons, (k, _) => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value)); - Voices = LoadYamlRules(m.Voices, map.Voices, (k, _) => new SoundInfo(k.Value)); - Notifications = LoadYamlRules(m.Notifications, map.Notifications, (k, _) => new SoundInfo(k.Value)); - Music = LoadYamlRules(m.Music, new List(), (k, _) => new MusicInfo(k.Key, k.Value)); - Movies = LoadYamlRules(m.Movies, new List(), (k, v) => k.Value.Value); - - TileSets = new Dictionary(); - foreach (var file in m.TileSets) - { - var t = new TileSet(file); - TileSets.Add(t.Id,t); - } - } - - static Dictionary LoadYamlRules(string[] files, List dict, Func, T> f) - { - var y = files.Select(MiniYaml.FromFile).Aggregate(dict, MiniYaml.MergeLiberal); - var yy = y.ToDictionary(x => x.Key, x => x.Value); - return y.ToDictionaryWithConflictLog(kv => kv.Key.ToLowerInvariant(), kv => f(kv, yy), "LoadYamlRules", null, null); + // HACK: Fallback for code that hasn't been updated yet + Game.modData.Rules = new ModRules(Game.modData); + Game.modData.Rules.ActivateMap(map); } public static IEnumerable> InstalledMusic { get { return Music.Where(m => m.Value.Exists); } } } + + + public class ModRules + { + readonly ModData modData; + + // + // These contain all unique instances created from each mod/map combination + // + readonly Dictionary actorCache = new Dictionary(); + readonly Dictionary weaponCache = new Dictionary(); + readonly Dictionary voiceCache = new Dictionary(); + readonly Dictionary notificationCache = new Dictionary(); + readonly Dictionary musicCache = new Dictionary(); + readonly Dictionary movieCache = new Dictionary(); + readonly Dictionary tileSetCache = new Dictionary(); + + // + // These are the instances needed for the current map + // + public Dictionary Actors { get; private set; } + public Dictionary Weapons { get; private set; } + public Dictionary Voices { get; private set; } + public Dictionary Notifications { get; private set; } + public Dictionary Music { get; private set; } + public Dictionary Movies { get; private set; } + public Dictionary TileSets { get; private set; } + + + public ModRules(ModData modData) + { + this.modData = modData; + } + + public void ActivateMap(Map map) + { + var m = modData.Manifest; + using (new PerfTimer("Actors")) + Actors = LoadYamlRules(actorCache, m.Rules, map.Rules, (k, y) => new ActorInfo(k.Key.ToLowerInvariant(), k.Value, y)); + using (new PerfTimer("Weapons")) + Weapons = LoadYamlRules(weaponCache, m.Weapons, map.Weapons, (k, _) => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value)); + using (new PerfTimer("Voices")) + Voices = LoadYamlRules(voiceCache, m.Voices, map.Voices, (k, _) => new SoundInfo(k.Value)); + using (new PerfTimer("Notifications")) + Notifications = LoadYamlRules(notificationCache, m.Notifications, map.Notifications, (k, _) => new SoundInfo(k.Value)); + using (new PerfTimer("Music")) + Music = LoadYamlRules(musicCache, m.Music, new List(), (k, _) => new MusicInfo(k.Key, k.Value)); + using (new PerfTimer("Movies")) + Movies = LoadYamlRules(movieCache, m.Movies, new List(), (k, v) => k.Value.Value); + using (new PerfTimer("TileSets")) + TileSets = LoadTileSets(tileSetCache, m.TileSets); + } + + Dictionary LoadYamlRules( + Dictionary itemCache, + string[] files, List nodes, + Func, T> f) + { + var mergedNodes = files + .Select(s => MiniYaml.FromFile(s)) + .Aggregate(nodes, MiniYaml.MergeLiberal); + + Func, T> wrap = (wkv, wyy) => + { + var key = wkv.Value.ToLines(wkv.Key).JoinWith("|"); + T t; + if (itemCache.TryGetValue(key, out t)) + return t; + + t = f(wkv, wyy); + itemCache.Add(key, t); + return t; + }; + + var yy = mergedNodes.ToDictionary(x => x.Key, x => x.Value); + var itemSet = mergedNodes.ToDictionaryWithConflictLog(kv => kv.Key.ToLowerInvariant(), kv => wrap(kv, yy), "LoadYamlRules", null, null); + + return itemSet; + } + + Dictionary LoadTileSets(Dictionary itemCache, string[] files) + { + var items = new Dictionary(); + + foreach (var file in files) + { + TileSet t; + if (itemCache.TryGetValue(file, out t)) + { + items.Add(t.Id, t); + } + else + { + t = new TileSet(file); + itemCache.Add(file, t); + + items.Add(t.Id, t); + } + } + + return items; + } + } } diff --git a/OpenRA.Game/Graphics/SequenceProvider.cs b/OpenRA.Game/Graphics/SequenceProvider.cs index 02e2fe8cd5..d85a7cc484 100644 --- a/OpenRA.Game/Graphics/SequenceProvider.cs +++ b/OpenRA.Game/Graphics/SequenceProvider.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 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, @@ -13,45 +13,79 @@ using System.Collections.Generic; using System.IO; using System.Linq; using OpenRA.FileFormats; +using OpenRA.Primitives; namespace OpenRA.Graphics { public static class SequenceProvider { - static Dictionary> units; - - public static void Initialize(string[] sequenceFiles, List sequenceNodes) + public static Sequence GetSequence(string unitName, string sequenceName) { - units = new Dictionary>(); + return Game.modData.SequenceProvider.GetSequence(unitName, sequenceName); + } - var sequences = sequenceFiles + public static bool HasSequence(string unitName, string sequenceName) + { + return Game.modData.SequenceProvider.HasSequence(unitName, sequenceName); + } + + public static IEnumerable Sequences(string unitName) + { + return Game.modData.SequenceProvider.Sequences(unitName); + } + } + + public class ModSequenceProvider + { + readonly ModData modData; + + readonly Dictionary>> sequenceCache = new Dictionary>>(); + Dictionary>> sequences; + + public ModSequenceProvider(ModData modData) + { + this.modData = modData; + } + + public void ActivateMap(Map map) + { + sequences = Load(modData.Manifest.Sequences, map.Sequences); + } + + public Dictionary>> Load(string[] sequenceFiles, List sequenceNodes) + { + Game.modData.LoadScreen.Display(); + + var nodes = sequenceFiles .Select(s => MiniYaml.FromFile(s)) .Aggregate(sequenceNodes, MiniYaml.MergeLiberal); - foreach (var s in sequences) - LoadSequencesForUnit(s.Key, s.Value); - } - - static void LoadSequencesForUnit(string unit, MiniYaml sequences) - { - Game.modData.LoadScreen.Display(); - try + var items = new Dictionary>>(); + foreach (var node in nodes) { - var seq = sequences.NodesDict.ToDictionary(x => x.Key, x => new Sequence(unit,x.Key,x.Value)); - units.Add(unit, seq); + var key = node.Value.ToLines(node.Key).JoinWith("|"); + Lazy> t; + if (sequenceCache.TryGetValue(key, out t)) + { + items.Add(node.Key, t); + } + else + { + t = Exts.Lazy(() => node.Value.NodesDict.ToDictionary(x => x.Key, x => new Sequence(node.Key, x.Key, x.Value))); + sequenceCache.Add(key, t); + items.Add(node.Key, t); + } } - catch (FileNotFoundException) - { - // Do nothing; we can crash later if we actually wanted art - } + + return items; } - public static Sequence GetSequence(string unitName, string sequenceName) + public Sequence GetSequence(string unitName, string sequenceName) { - try { return units[unitName][sequenceName]; } + try { return sequences[unitName].Value[sequenceName]; } catch (KeyNotFoundException) { - if (units.ContainsKey(unitName)) + if (sequences.ContainsKey(unitName)) throw new InvalidOperationException( "Unit `{0}` does not have a sequence `{1}`".F(unitName, sequenceName)); else @@ -60,22 +94,22 @@ namespace OpenRA.Graphics } } - public static bool HasSequence(string unit, string seq) + public bool HasSequence(string unitName, string sequenceName) { - if (!units.ContainsKey(unit)) + if (!sequences.ContainsKey(unitName)) throw new InvalidOperationException( - "Unit `{0}` does not have sequence `{1}` defined.".F(unit, seq)); + "Unit `{0}` does not have sequence `{1}` defined.".F(unitName, sequenceName)); - return units[unit].ContainsKey(seq); + return sequences[unitName].Value.ContainsKey(sequenceName); } - public static IEnumerable Sequences(string unit) + public IEnumerable Sequences(string unitName) { - if (!units.ContainsKey(unit)) + if (!sequences.ContainsKey(unitName)) throw new InvalidOperationException( - "Unit `{0}` does not have all sequences defined.".F(unit)); + "Unit `{0}` does not have all sequences defined.".F(unitName)); - return units[unit].Keys; + return sequences[unitName].Value.Keys; } } } diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 6bc9db8e54..f781c731b5 100755 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -28,6 +28,8 @@ namespace OpenRA public SheetBuilder SheetBuilder; public SpriteLoader SpriteLoader; public VoxelLoader VoxelLoader; + public ModSequenceProvider SequenceProvider; + public ModRules Rules; public ModData(string mod) { @@ -39,6 +41,8 @@ namespace OpenRA LoadScreen.Display(); WidgetLoader = new WidgetLoader(this); MapCache = new MapCache(Manifest); + SequenceProvider = new ModSequenceProvider(this); + Rules = new ModRules(this); // HACK: Mount only local folders so we have a half-working environment for the asset installer GlobalFileSystem.UnmountAll(); @@ -118,15 +122,13 @@ namespace OpenRA // Mount map package so custom assets can be used. TODO: check priority. GlobalFileSystem.Mount(GlobalFileSystem.OpenPackage(map.Path, null, int.MaxValue)); - using (new Support.PerfTimer("LoadRules")) - Rules.LoadRules(Manifest, map); + using (new Support.PerfTimer("Rules.ActivateMap")) + Rules.ActivateMap(map); SpriteLoader = new SpriteLoader(Rules.TileSets[map.Tileset].Extensions, SheetBuilder); - using (new Support.PerfTimer("SequenceProvider.Initialize")) - { - // TODO: Don't load the sequences for assets that are not used in this tileset. Maybe use the existing EditorTilesetFilters. - SequenceProvider.Initialize(Manifest.Sequences, map.Sequences); - } + using (new Support.PerfTimer("SequenceProvider.ActivateMap")) + SequenceProvider.ActivateMap(map); + VoxelProvider.Initialize(Manifest.VoxelSequences, map.VoxelSequences); return map; }