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

View File

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

View File

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