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:
Pavlos Touboulidis
2014-05-02 01:35:38 +03:00
parent 77d0199384
commit 2b3d5f1544
3 changed files with 189 additions and 67 deletions

View File

@@ -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<string, ActorInfo> Info;
public static Dictionary<string, WeaponInfo> Weapons;
public static Dictionary<string, SoundInfo> Voices;
public static Dictionary<string, SoundInfo> Notifications;
public static Dictionary<string, MusicInfo> Music;
public static Dictionary<string, string> Movies;
public static Dictionary<string, TileSet> TileSets;
public static Dictionary<string, ActorInfo> Info { get { return Game.modData.Rules.Actors; } }
public static Dictionary<string, WeaponInfo> Weapons { get { return Game.modData.Rules.Weapons; } }
public static Dictionary<string, SoundInfo> Voices { get { return Game.modData.Rules.Voices; } }
public static Dictionary<string, SoundInfo> Notifications { get { return Game.modData.Rules.Notifications; } }
public static Dictionary<string, MusicInfo> Music { get { return Game.modData.Rules.Music; } }
public static Dictionary<string, string> Movies { get { return Game.modData.Rules.Movies; } }
public static Dictionary<string, TileSet> 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<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);
// 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<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;
}
}
}

View File

@@ -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<string, Dictionary<string, Sequence>> units;
public static void Initialize(string[] sequenceFiles, List<MiniYamlNode> sequenceNodes)
public static Sequence GetSequence(string unitName, string sequenceName)
{
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))
.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<string, Lazy<Dictionary<string, Sequence>>>();
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<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)
{
// 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<string> Sequences(string unit)
public IEnumerable<string> 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;
}
}
}

View File

@@ -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;
}