Merge pull request #10909 from pchote/remove-ruleset-cache

Remove ruleset cache
This commit is contained in:
abcdefg30
2016-03-12 20:41:03 +01:00
22 changed files with 194 additions and 226 deletions

View File

@@ -361,7 +361,7 @@ namespace OpenRA
public static Dictionary<TKey, TElement> ToDictionaryWithConflictLog<TSource, TKey, TElement>(
this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector,
string debugName, Func<TKey, string> logKey, Func<TElement, string> logValue)
string debugName, Func<TKey, string> logKey = null, Func<TElement, string> logValue = null)
{
// Fall back on ToString() if null functions are provided:
logKey = logKey ?? (s => s.ToString());

View File

@@ -9,8 +9,11 @@
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using OpenRA.FileSystem;
using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.Traits;
@@ -24,25 +27,25 @@ namespace OpenRA
public readonly IReadOnlyDictionary<string, SoundInfo> Voices;
public readonly IReadOnlyDictionary<string, SoundInfo> Notifications;
public readonly IReadOnlyDictionary<string, MusicInfo> Music;
public readonly IReadOnlyDictionary<string, TileSet> TileSets;
public readonly IReadOnlyDictionary<string, SequenceProvider> Sequences;
public readonly TileSet TileSet;
public readonly SequenceProvider Sequences;
public Ruleset(
IDictionary<string, ActorInfo> actors,
IDictionary<string, WeaponInfo> weapons,
IDictionary<string, SoundInfo> voices,
IDictionary<string, SoundInfo> notifications,
IDictionary<string, MusicInfo> music,
IDictionary<string, TileSet> tileSets,
IDictionary<string, SequenceProvider> sequences)
IReadOnlyDictionary<string, ActorInfo> actors,
IReadOnlyDictionary<string, WeaponInfo> weapons,
IReadOnlyDictionary<string, SoundInfo> voices,
IReadOnlyDictionary<string, SoundInfo> notifications,
IReadOnlyDictionary<string, MusicInfo> music,
TileSet tileSet,
SequenceProvider sequences)
{
Actors = new ReadOnlyDictionary<string, ActorInfo>(actors);
Weapons = new ReadOnlyDictionary<string, WeaponInfo>(weapons);
Voices = new ReadOnlyDictionary<string, SoundInfo>(voices);
Notifications = new ReadOnlyDictionary<string, SoundInfo>(notifications);
Music = new ReadOnlyDictionary<string, MusicInfo>(music);
TileSets = new ReadOnlyDictionary<string, TileSet>(tileSets);
Sequences = new ReadOnlyDictionary<string, SequenceProvider>(sequences);
Actors = actors;
Weapons = weapons;
Voices = voices;
Notifications = notifications;
Music = music;
TileSet = tileSet;
Sequences = sequences;
foreach (var a in Actors.Values)
{
@@ -80,5 +83,120 @@ namespace OpenRA
}
public IEnumerable<KeyValuePair<string, MusicInfo>> InstalledMusic { get { return Music.Where(m => m.Value.Exists); } }
static IReadOnlyDictionary<string, T> MergeOrDefault<T>(string name, IReadOnlyFileSystem fileSystem, IEnumerable<string> files, MiniYaml additional,
IReadOnlyDictionary<string, T> defaults, Func<MiniYamlNode, T> makeObject)
{
if (additional == null && defaults != null)
return defaults;
var result = MiniYaml.Load(fileSystem, files, additional)
.ToDictionaryWithConflictLog(k => k.Key.ToLowerInvariant(), makeObject, "LoadFromManifest<" + name + ">");
return new ReadOnlyDictionary<string, T>(result);
}
public static Ruleset LoadDefaults(ModData modData)
{
var m = modData.Manifest;
var fs = modData.DefaultFileSystem;
Ruleset ruleset = null;
Action f = () =>
{
var actors = MergeOrDefault("Manifest,Rules", fs, m.Rules, null, null,
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value));
var weapons = MergeOrDefault("Manifest,Weapons", fs, m.Weapons, null, null,
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
var voices = MergeOrDefault("Manifest,Voices", fs, m.Voices, null, null,
k => new SoundInfo(k.Value));
var notifications = MergeOrDefault("Manifest,Notifications", fs, m.Notifications, null, null,
k => new SoundInfo(k.Value));
var music = MergeOrDefault("Manifest,Music", fs, m.Music, null, null,
k => new MusicInfo(k.Key, k.Value));
// The default ruleset does not include a preferred tileset or sequence set
ruleset = new Ruleset(actors, weapons, voices, notifications, music, null, null);
};
if (modData.IsOnMainThread)
{
modData.HandleLoadingProgress();
var loader = new Task(f);
loader.Start();
// Animate the loadscreen while we wait
while (!loader.Wait(40))
modData.HandleLoadingProgress();
}
else
f();
return ruleset;
}
public static Ruleset LoadDefaultsForTileSet(ModData modData, string tileSet)
{
var dr = modData.DefaultRules;
var ts = modData.DefaultTileSets[tileSet];
var sequences = modData.DefaultSequences[tileSet];
return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences);
}
public static Ruleset LoadFromMap(ModData modData, Map map)
{
var m = modData.Manifest;
var dr = modData.DefaultRules;
Ruleset ruleset = null;
Action f = () =>
{
var actors = MergeOrDefault("Manifest,Rules", map, m.Rules, map.RuleDefinitions, dr.Actors,
k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value));
var weapons = MergeOrDefault("Manifest,Weapons", map, m.Weapons, map.WeaponDefinitions, dr.Weapons,
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
var voices = MergeOrDefault("Manifest,Voices", map, m.Voices, map.VoiceDefinitions, dr.Voices,
k => new SoundInfo(k.Value));
var notifications = MergeOrDefault("Manifest,Notifications", map, m.Notifications, map.NotificationDefinitions, dr.Notifications,
k => new SoundInfo(k.Value));
var music = MergeOrDefault("Manifest,Music", map, m.Music, map.NotificationDefinitions, dr.Music,
k => new MusicInfo(k.Key, k.Value));
// TODO: Add support for merging custom tileset modifications
var ts = modData.DefaultTileSets[map.Tileset];
// TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object
var sequences = map.SequenceDefinitions == null ? modData.DefaultSequences[map.Tileset] :
new SequenceProvider(map, modData, ts, map.SequenceDefinitions);
// TODO: Add support for custom voxel sequences
ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences);
};
if (modData.IsOnMainThread)
{
modData.HandleLoadingProgress();
var loader = new Task(f);
loader.Start();
// Animate the loadscreen while we wait
while (!loader.Wait(40))
modData.HandleLoadingProgress();
}
else
f();
return ruleset;
}
}
}

View File

@@ -1,159 +0,0 @@
#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, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.FileSystem;
using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.Support;
namespace OpenRA
{
public sealed class RulesetCache
{
readonly ModData modData;
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, TileSet> tileSetCache = new Dictionary<string, TileSet>();
public event EventHandler LoadingProgress;
void RaiseProgress()
{
if (LoadingProgress != null)
LoadingProgress(this, new EventArgs());
}
public RulesetCache(ModData modData)
{
this.modData = modData;
}
public Ruleset Load(IReadOnlyFileSystem fileSystem,
MiniYaml additionalRules,
MiniYaml additionalWeapons,
MiniYaml additionalVoices,
MiniYaml additionalNotifications,
MiniYaml additionalMusic,
MiniYaml additionalSequences)
{
var m = modData.Manifest;
Dictionary<string, ActorInfo> actors;
Dictionary<string, WeaponInfo> weapons;
Dictionary<string, SoundInfo> voices;
Dictionary<string, SoundInfo> notifications;
Dictionary<string, MusicInfo> music;
Dictionary<string, TileSet> tileSets;
using (new PerfTimer("Actors"))
actors = LoadYamlRules(fileSystem, actorCache, m.Rules, additionalRules,
k => new ActorInfo(Game.ModData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value));
using (new PerfTimer("Weapons"))
weapons = LoadYamlRules(fileSystem, weaponCache, m.Weapons, additionalWeapons,
k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value));
using (new PerfTimer("Voices"))
voices = LoadYamlRules(fileSystem, voiceCache, m.Voices, additionalVoices,
k => new SoundInfo(k.Value));
using (new PerfTimer("Notifications"))
notifications = LoadYamlRules(fileSystem, notificationCache, m.Notifications, additionalNotifications,
k => new SoundInfo(k.Value));
using (new PerfTimer("Music"))
music = LoadYamlRules(fileSystem, musicCache, m.Music, additionalMusic,
k => new MusicInfo(k.Key, k.Value));
using (new PerfTimer("TileSets"))
tileSets = LoadTileSets(fileSystem, tileSetCache, m.TileSets);
// TODO: only initialize, and then cache, the provider for the given map
var sequences = tileSets.ToDictionary(t => t.Key, t => new SequenceProvider(fileSystem, modData, t.Value, additionalSequences));
return new Ruleset(actors, weapons, voices, notifications, music, tileSets, sequences);
}
/// <summary>
/// Cache and return the Ruleset for a given map.
/// If a map isn't specified then return the default mod Ruleset.
/// </summary>
public Ruleset Load(IReadOnlyFileSystem fileSystem, Map map = null)
{
return map != null ? Load(fileSystem, map.RuleDefinitions, map.WeaponDefinitions,
map.VoiceDefinitions, map.NotificationDefinitions, map.MusicDefinitions,
map.SequenceDefinitions) : Load(fileSystem, null, null, null, null, null, null);
}
Dictionary<string, T> LoadYamlRules<T>(IReadOnlyFileSystem fileSystem,
Dictionary<string, T> itemCache,
IEnumerable<string> files, MiniYaml mapRules,
Func<MiniYamlNode, T> f)
{
RaiseProgress();
if (mapRules != null && mapRules.Value != null)
files = files.Append(FieldLoader.GetValue<string[]>("value", mapRules.Value));
var inputKey = string.Join("|", files);
if (mapRules != null && mapRules.Nodes.Any())
inputKey += "|" + mapRules.Nodes.WriteToString();
Func<MiniYamlNode, T> wrap = wkv =>
{
var key = inputKey + wkv.Value.ToLines(wkv.Key).JoinWith("|");
T t;
if (itemCache.TryGetValue(key, out t))
return t;
t = f(wkv);
itemCache.Add(key, t);
RaiseProgress();
return t;
};
var tree = MiniYaml.Load(fileSystem, files, mapRules).ToDictionaryWithConflictLog(
n => n.Key, n => n.Value, "LoadYamlRules", null, null);
RaiseProgress();
var itemSet = tree.ToDictionary(kv => kv.Key.ToLowerInvariant(), kv => wrap(new MiniYamlNode(kv.Key, kv.Value)));
RaiseProgress();
return itemSet;
}
Dictionary<string, TileSet> LoadTileSets(IReadOnlyFileSystem fileSystem, 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(fileSystem, file);
itemCache.Add(file, t);
items.Add(t.Id, t);
}
}
return items;
}
}
}

View File

@@ -169,10 +169,9 @@ namespace OpenRA
[FieldLoader.Ignore] CellLayer<PPos[]> cellProjection;
[FieldLoader.Ignore] CellLayer<List<MPos>> inverseCellProjection;
[FieldLoader.Ignore] Lazy<TileSet> cachedTileSet;
[FieldLoader.Ignore] Lazy<Ruleset> rules;
public Ruleset Rules { get { return rules != null ? rules.Value : null; } }
public SequenceProvider SequenceProvider { get { return Rules.Sequences[Tileset]; } }
public SequenceProvider SequenceProvider { get { return Rules.Sequences; } }
[FieldLoader.Ignore] public ProjectedCellRegion ProjectedCellBounds;
[FieldLoader.Ignore] public CellRegion AllCells;
@@ -297,7 +296,7 @@ namespace OpenRA
{
try
{
return modData.RulesetCache.Load(this, this);
return Ruleset.LoadFromMap(modData, this);
}
catch (Exception e)
{
@@ -305,11 +304,9 @@ namespace OpenRA
Log.Write("debug", "Failed to load rules for {0} with error {1}", Title, e.Message);
}
return modData.DefaultRules;
return Ruleset.LoadDefaultsForTileSet(modData, Tileset);
});
cachedTileSet = Exts.Lazy(() => Rules.TileSets[Tileset]);
var tl = new MPos(0, 0).ToCPos(this);
var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this);
AllCells = new CellRegion(Grid.Type, tl, br);
@@ -391,7 +388,7 @@ namespace OpenRA
// Odd-height ramps get bumped up a level to the next even height layer
if ((height & 1) == 1)
{
var ti = cachedTileSet.Value.GetTileInfo(MapTiles.Value[uv]);
var ti = Rules.TileSet.GetTileInfo(MapTiles.Value[uv]);
if (ti != null && ti.RampType != 0)
height += 1;
}
@@ -627,7 +624,7 @@ namespace OpenRA
public byte[] SavePreview()
{
var tileset = Rules.TileSets[Tileset];
var tileset = Rules.TileSet;
var resources = Rules.Actors["world"].TraitInfos<ResourceTypeInfo>()
.ToDictionary(r => r.ResourceType, r => r.TerrainType);
@@ -878,7 +875,7 @@ namespace OpenRA
public void FixOpenAreas()
{
var r = new Random();
var tileset = Rules.TileSets[Tileset];
var tileset = Rules.TileSet;
for (var j = Bounds.Top; j < Bounds.Bottom; j++)
{
@@ -926,7 +923,7 @@ namespace OpenRA
{
var custom = CustomTerrain[uv];
terrainIndex = cachedTerrainIndexes[uv] =
custom != byte.MaxValue ? custom : cachedTileSet.Value.GetTerrainIndex(MapTiles.Value[uv]);
custom != byte.MaxValue ? custom : Rules.TileSet.GetTerrainIndex(MapTiles.Value[uv]);
}
return (byte)terrainIndex;
@@ -934,7 +931,7 @@ namespace OpenRA
public TerrainTypeInfo GetTerrainInfo(CPos cell)
{
return cachedTileSet.Value[GetTerrainIndex(cell)];
return Rules.TileSet[GetTerrainIndex(cell)];
}
public CPos Clamp(CPos cell)

View File

@@ -30,15 +30,20 @@ namespace OpenRA
public readonly ISoundLoader[] SoundLoaders;
public readonly ISpriteLoader[] SpriteLoaders;
public readonly ISpriteSequenceLoader SpriteSequenceLoader;
public readonly RulesetCache RulesetCache;
public ILoadScreen LoadScreen { get; private set; }
public VoxelLoader VoxelLoader { get; private set; }
public CursorProvider CursorProvider { get; private set; }
public FS ModFiles = new FS();
public IReadOnlyFileSystem DefaultFileSystem { get { return ModFiles; } }
readonly Lazy<Ruleset> defaultRules;
public Ruleset DefaultRules { get { return defaultRules.Value; } }
public IReadOnlyFileSystem DefaultFileSystem { get { return ModFiles; } }
readonly Lazy<IReadOnlyDictionary<string, TileSet>> defaultTileSets;
public IReadOnlyDictionary<string, TileSet> DefaultTileSets { get { return defaultTileSets.Value; } }
readonly Lazy<IReadOnlyDictionary<string, SequenceProvider>> defaultSequences;
public IReadOnlyDictionary<string, SequenceProvider> DefaultSequences { get { return defaultSequences.Value; } }
public ModData(string mod, bool useLoadScreen = false)
{
@@ -57,8 +62,6 @@ namespace OpenRA
}
WidgetLoader = new WidgetLoader(this);
RulesetCache = new RulesetCache(this);
RulesetCache.LoadingProgress += HandleLoadingProgress;
MapCache = new MapCache(this);
SoundLoaders = GetLoaders<ISoundLoader>(Manifest.SoundFormats, "sound");
@@ -73,19 +76,39 @@ namespace OpenRA
SpriteSequenceLoader = (ISpriteSequenceLoader)ctor.Invoke(new[] { this });
SpriteSequenceLoader.OnMissingSpriteError = s => Log.Write("debug", s);
defaultRules = Exts.Lazy(() => RulesetCache.Load(DefaultFileSystem));
defaultRules = Exts.Lazy(() => Ruleset.LoadDefaults(this));
defaultTileSets = Exts.Lazy(() =>
{
var items = new Dictionary<string, TileSet>();
foreach (var file in Manifest.TileSets)
{
var t = new TileSet(DefaultFileSystem, file);
items.Add(t.Id, t);
}
return (IReadOnlyDictionary<string, TileSet>)(new ReadOnlyDictionary<string, TileSet>(items));
});
defaultSequences = Exts.Lazy(() =>
{
var items = DefaultTileSets.ToDictionary(t => t.Key, t => new SequenceProvider(DefaultFileSystem, this, t.Value, null));
return (IReadOnlyDictionary<string, SequenceProvider>)(new ReadOnlyDictionary<string, SequenceProvider>(items));
});
initialThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
}
// HACK: Only update the loading screen if we're in the main thread.
int initialThreadId;
void HandleLoadingProgress(object sender, EventArgs e)
internal void HandleLoadingProgress()
{
if (LoadScreen != null && System.Threading.Thread.CurrentThread.ManagedThreadId == initialThreadId)
if (LoadScreen != null && IsOnMainThread)
LoadScreen.Display();
}
internal bool IsOnMainThread { get { return System.Threading.Thread.CurrentThread.ManagedThreadId == initialThreadId; } }
public void InitializeLoaders(IReadOnlyFileSystem fileSystem)
{
// all this manipulation of static crap here is nasty and breaks

View File

@@ -215,7 +215,6 @@
<Compile Include="Primitives\ReadOnlyList.cs" />
<Compile Include="ModMetadata.cs" />
<Compile Include="GameRules\Ruleset.cs" />
<Compile Include="GameRules\RulesetCache.cs" />
<Compile Include="Support\MersenneTwister.cs" />
<Compile Include="GameInformation.cs" />
<Compile Include="Map\CellLayer.cs" />

View File

@@ -160,7 +160,7 @@ namespace OpenRA
Map = map;
Timestep = orderManager.LobbyInfo.GlobalSettings.Timestep;
TileSet = map.Rules.TileSets[Map.Tileset];
TileSet = map.Rules.TileSet;
SharedRandom = new MersenneTwister(orderManager.LobbyInfo.GlobalSettings.RandomSeed);
var worldActorType = type == WorldType.Editor ? "EditorWorld" : "World";