Better caching for Rules and Sequences
Refactored the Rules and SequenceProvider classes to be parts of ModData and maintain a cache of the instances used in the mod. The caching reduced the load times a lot, especially after the first load. Some lazy loading in sequences also helped lower the startup time.. Note: The static classes were left behind to redirect the existing code's calls.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
#region Copyright & License Information
|
#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
|
* 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
|
* available to you under the terms of the GNU General Public License
|
||||||
* as published by the Free Software Foundation. For more information,
|
* as published by the Free Software Foundation. For more information,
|
||||||
@@ -13,44 +13,130 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.FileFormats;
|
using OpenRA.FileFormats;
|
||||||
using OpenRA.GameRules;
|
using OpenRA.GameRules;
|
||||||
|
using OpenRA.Support;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
public static class Rules
|
public static class Rules
|
||||||
{
|
{
|
||||||
public static Dictionary<string, ActorInfo> Info;
|
public static Dictionary<string, ActorInfo> Info { get { return Game.modData.Rules.Actors; } }
|
||||||
public static Dictionary<string, WeaponInfo> Weapons;
|
public static Dictionary<string, WeaponInfo> Weapons { get { return Game.modData.Rules.Weapons; } }
|
||||||
public static Dictionary<string, SoundInfo> Voices;
|
public static Dictionary<string, SoundInfo> Voices { get { return Game.modData.Rules.Voices; } }
|
||||||
public static Dictionary<string, SoundInfo> Notifications;
|
public static Dictionary<string, SoundInfo> Notifications { get { return Game.modData.Rules.Notifications; } }
|
||||||
public static Dictionary<string, MusicInfo> Music;
|
public static Dictionary<string, MusicInfo> Music { get { return Game.modData.Rules.Music; } }
|
||||||
public static Dictionary<string, string> Movies;
|
public static Dictionary<string, string> Movies { get { return Game.modData.Rules.Movies; } }
|
||||||
public static Dictionary<string, TileSet> TileSets;
|
public static Dictionary<string, TileSet> TileSets { get { return Game.modData.Rules.TileSets; } }
|
||||||
|
|
||||||
public static void LoadRules(Manifest m, Map map)
|
public static void LoadRules(Manifest m, Map map)
|
||||||
{
|
{
|
||||||
// Added support to extend the list of rules (add it to m.LocalRules)
|
// HACK: Fallback for code that hasn't been updated yet
|
||||||
Info = LoadYamlRules(m.Rules, map.Rules, (k, y) => new ActorInfo(k.Key.ToLowerInvariant(), k.Value, y));
|
Game.modData.Rules = new ModRules(Game.modData);
|
||||||
Weapons = LoadYamlRules(m.Weapons, map.Weapons, (k, _) => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
|
Game.modData.Rules.ActivateMap(map);
|
||||||
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<MiniYamlNode>(), (k, _) => new MusicInfo(k.Key, k.Value));
|
|
||||||
Movies = LoadYamlRules(m.Movies, new List<MiniYamlNode>(), (k, v) => k.Value.Value);
|
|
||||||
|
|
||||||
TileSets = new Dictionary<string, TileSet>();
|
|
||||||
foreach (var file in m.TileSets)
|
|
||||||
{
|
|
||||||
var t = new TileSet(file);
|
|
||||||
TileSets.Add(t.Id,t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Dictionary<string, T> LoadYamlRules<T>(string[] files, List<MiniYamlNode> dict, Func<MiniYamlNode, Dictionary<string, MiniYaml>, 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<KeyValuePair<string, MusicInfo>> InstalledMusic { get { return Music.Where(m => m.Value.Exists); } }
|
public static IEnumerable<KeyValuePair<string, MusicInfo>> 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<string, ActorInfo> actorCache = new Dictionary<string, ActorInfo>();
|
||||||
|
readonly Dictionary<string, WeaponInfo> weaponCache = new Dictionary<string, WeaponInfo>();
|
||||||
|
readonly Dictionary<string, SoundInfo> voiceCache = new Dictionary<string, SoundInfo>();
|
||||||
|
readonly Dictionary<string, SoundInfo> notificationCache = new Dictionary<string, SoundInfo>();
|
||||||
|
readonly Dictionary<string, MusicInfo> musicCache = new Dictionary<string, MusicInfo>();
|
||||||
|
readonly Dictionary<string, string> movieCache = new Dictionary<string, string>();
|
||||||
|
readonly Dictionary<string, TileSet> tileSetCache = new Dictionary<string, TileSet>();
|
||||||
|
|
||||||
|
//
|
||||||
|
// These are the instances needed for the current map
|
||||||
|
//
|
||||||
|
public Dictionary<string, ActorInfo> Actors { get; private set; }
|
||||||
|
public Dictionary<string, WeaponInfo> Weapons { get; private set; }
|
||||||
|
public Dictionary<string, SoundInfo> Voices { get; private set; }
|
||||||
|
public Dictionary<string, SoundInfo> Notifications { get; private set; }
|
||||||
|
public Dictionary<string, MusicInfo> Music { get; private set; }
|
||||||
|
public Dictionary<string, string> Movies { get; private set; }
|
||||||
|
public Dictionary<string, TileSet> 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<MiniYamlNode>(), (k, _) => new MusicInfo(k.Key, k.Value));
|
||||||
|
using (new PerfTimer("Movies"))
|
||||||
|
Movies = LoadYamlRules(movieCache, m.Movies, new List<MiniYamlNode>(), (k, v) => k.Value.Value);
|
||||||
|
using (new PerfTimer("TileSets"))
|
||||||
|
TileSets = LoadTileSets(tileSetCache, m.TileSets);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<string, T> LoadYamlRules<T>(
|
||||||
|
Dictionary<string, T> itemCache,
|
||||||
|
string[] files, List<MiniYamlNode> nodes,
|
||||||
|
Func<MiniYamlNode, Dictionary<string, MiniYaml>, T> f)
|
||||||
|
{
|
||||||
|
var mergedNodes = files
|
||||||
|
.Select(s => MiniYaml.FromFile(s))
|
||||||
|
.Aggregate(nodes, MiniYaml.MergeLiberal);
|
||||||
|
|
||||||
|
Func<MiniYamlNode, Dictionary<string, MiniYaml>, 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<string, TileSet> LoadTileSets(Dictionary<string, TileSet> itemCache, string[] files)
|
||||||
|
{
|
||||||
|
var items = new Dictionary<string, TileSet>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#region Copyright & License Information
|
#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
|
* 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
|
* available to you under the terms of the GNU General Public License
|
||||||
* as published by the Free Software Foundation. For more information,
|
* as published by the Free Software Foundation. For more information,
|
||||||
@@ -13,45 +13,79 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.FileFormats;
|
using OpenRA.FileFormats;
|
||||||
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.Graphics
|
namespace OpenRA.Graphics
|
||||||
{
|
{
|
||||||
public static class SequenceProvider
|
public static class SequenceProvider
|
||||||
{
|
{
|
||||||
static Dictionary<string, Dictionary<string, Sequence>> units;
|
public static Sequence GetSequence(string unitName, string sequenceName)
|
||||||
|
|
||||||
public static void Initialize(string[] sequenceFiles, List<MiniYamlNode> sequenceNodes)
|
|
||||||
{
|
{
|
||||||
units = new Dictionary<string, Dictionary<string, Sequence>>();
|
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<string> Sequences(string unitName)
|
||||||
|
{
|
||||||
|
return Game.modData.SequenceProvider.Sequences(unitName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ModSequenceProvider
|
||||||
|
{
|
||||||
|
readonly ModData modData;
|
||||||
|
|
||||||
|
readonly Dictionary<string, Lazy<Dictionary<string, Sequence>>> sequenceCache = new Dictionary<string, Lazy<Dictionary<string, Sequence>>>();
|
||||||
|
Dictionary<string, Lazy<Dictionary<string, Sequence>>> sequences;
|
||||||
|
|
||||||
|
public ModSequenceProvider(ModData modData)
|
||||||
|
{
|
||||||
|
this.modData = modData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ActivateMap(Map map)
|
||||||
|
{
|
||||||
|
sequences = Load(modData.Manifest.Sequences, map.Sequences);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, Lazy<Dictionary<string, Sequence>>> Load(string[] sequenceFiles, List<MiniYamlNode> sequenceNodes)
|
||||||
|
{
|
||||||
|
Game.modData.LoadScreen.Display();
|
||||||
|
|
||||||
|
var nodes = sequenceFiles
|
||||||
.Select(s => MiniYaml.FromFile(s))
|
.Select(s => MiniYaml.FromFile(s))
|
||||||
.Aggregate(sequenceNodes, MiniYaml.MergeLiberal);
|
.Aggregate(sequenceNodes, MiniYaml.MergeLiberal);
|
||||||
|
|
||||||
foreach (var s in sequences)
|
var items = new Dictionary<string, Lazy<Dictionary<string, Sequence>>>();
|
||||||
LoadSequencesForUnit(s.Key, s.Value);
|
foreach (var node in nodes)
|
||||||
}
|
|
||||||
|
|
||||||
static void LoadSequencesForUnit(string unit, MiniYaml sequences)
|
|
||||||
{
|
|
||||||
Game.modData.LoadScreen.Display();
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var seq = sequences.NodesDict.ToDictionary(x => x.Key, x => new Sequence(unit,x.Key,x.Value));
|
var key = node.Value.ToLines(node.Key).JoinWith("|");
|
||||||
units.Add(unit, seq);
|
Lazy<Dictionary<string, Sequence>> 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)
|
|
||||||
{
|
return items;
|
||||||
// Do nothing; we can crash later if we actually wanted art
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
catch (KeyNotFoundException)
|
||||||
{
|
{
|
||||||
if (units.ContainsKey(unitName))
|
if (sequences.ContainsKey(unitName))
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
"Unit `{0}` does not have a sequence `{1}`".F(unitName, sequenceName));
|
"Unit `{0}` does not have a sequence `{1}`".F(unitName, sequenceName));
|
||||||
else
|
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(
|
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<string> Sequences(string unit)
|
public IEnumerable<string> Sequences(string unitName)
|
||||||
{
|
{
|
||||||
if (!units.ContainsKey(unit))
|
if (!sequences.ContainsKey(unitName))
|
||||||
throw new InvalidOperationException(
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ namespace OpenRA
|
|||||||
public SheetBuilder SheetBuilder;
|
public SheetBuilder SheetBuilder;
|
||||||
public SpriteLoader SpriteLoader;
|
public SpriteLoader SpriteLoader;
|
||||||
public VoxelLoader VoxelLoader;
|
public VoxelLoader VoxelLoader;
|
||||||
|
public ModSequenceProvider SequenceProvider;
|
||||||
|
public ModRules Rules;
|
||||||
|
|
||||||
public ModData(string mod)
|
public ModData(string mod)
|
||||||
{
|
{
|
||||||
@@ -39,6 +41,8 @@ namespace OpenRA
|
|||||||
LoadScreen.Display();
|
LoadScreen.Display();
|
||||||
WidgetLoader = new WidgetLoader(this);
|
WidgetLoader = new WidgetLoader(this);
|
||||||
MapCache = new MapCache(Manifest);
|
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
|
// HACK: Mount only local folders so we have a half-working environment for the asset installer
|
||||||
GlobalFileSystem.UnmountAll();
|
GlobalFileSystem.UnmountAll();
|
||||||
@@ -118,15 +122,13 @@ namespace OpenRA
|
|||||||
// Mount map package so custom assets can be used. TODO: check priority.
|
// Mount map package so custom assets can be used. TODO: check priority.
|
||||||
GlobalFileSystem.Mount(GlobalFileSystem.OpenPackage(map.Path, null, int.MaxValue));
|
GlobalFileSystem.Mount(GlobalFileSystem.OpenPackage(map.Path, null, int.MaxValue));
|
||||||
|
|
||||||
using (new Support.PerfTimer("LoadRules"))
|
using (new Support.PerfTimer("Rules.ActivateMap"))
|
||||||
Rules.LoadRules(Manifest, map);
|
Rules.ActivateMap(map);
|
||||||
SpriteLoader = new SpriteLoader(Rules.TileSets[map.Tileset].Extensions, SheetBuilder);
|
SpriteLoader = new SpriteLoader(Rules.TileSets[map.Tileset].Extensions, SheetBuilder);
|
||||||
|
|
||||||
using (new Support.PerfTimer("SequenceProvider.Initialize"))
|
using (new Support.PerfTimer("SequenceProvider.ActivateMap"))
|
||||||
{
|
SequenceProvider.ActivateMap(map);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
VoxelProvider.Initialize(Manifest.VoxelSequences, map.VoxelSequences);
|
VoxelProvider.Initialize(Manifest.VoxelSequences, map.VoxelSequences);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user