From 300281695ab88756fe5f6d3674c2882b7f547455 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Tue, 9 May 2023 19:36:58 +0100 Subject: [PATCH] Deserialize mod rules only once when loading all maps. This avoids loading, parsing and merging YAML rules for the mod during loading of each individual map. This saves significant time resolving custom rules on each map loaded. --- OpenRA.Game/Map/MapCache.cs | 11 +++++++++-- OpenRA.Game/Map/MapPreview.cs | 19 +++++++++++-------- OpenRA.Game/ModData.cs | 5 +++++ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index b70207ba27..22e9f344c1 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -123,10 +123,12 @@ namespace OpenRA mapDirectoryTrackers.Add(new MapDirectoryTracker(mapGrid, package, classification)); } + // PERF: Load the mod YAML once outside the loop, and reuse it when resolving each maps custom YAML. + var modDataRules = modData.GetRulesYaml(); foreach (var kv in MapLocations) { foreach (var map in kv.Key.Contents) - LoadMap(map, kv.Key, kv.Value, mapGrid, null); + LoadMapInternal(map, kv.Key, kv.Value, mapGrid, null, modDataRules); } // We only want to track maps in runtime, not at loadtime @@ -134,6 +136,11 @@ namespace OpenRA } public void LoadMap(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap) + { + LoadMapInternal(map, package, classification, mapGrid, oldMap, null); + } + + void LoadMapInternal(string map, IReadOnlyPackage package, MapClassification classification, MapGrid mapGrid, string oldMap, IEnumerable> modDataRules) { IReadOnlyPackage mapPackage = null; try @@ -144,7 +151,7 @@ namespace OpenRA if (mapPackage != null) { var uid = Map.ComputeUID(mapPackage); - previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type); + previews[uid].UpdateFromMap(mapPackage, package, classification, modData.Manifest.MapCompatibility, mapGrid.Type, modDataRules); if (oldMap != uid) { diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index 3c9b0609d1..23a367ff67 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -111,7 +111,7 @@ namespace OpenRA return key == "world" || key == "player"; } - public void SetCustomRules(ModData modData, IReadOnlyFileSystem fileSystem, Dictionary yaml) + public void SetCustomRules(ModData modData, IReadOnlyFileSystem fileSystem, Dictionary yaml, IEnumerable> modDataRules) { RuleDefinitions = LoadRuleSection(yaml, "Rules"); WeaponDefinitions = LoadRuleSection(yaml, "Weapons"); @@ -131,14 +131,17 @@ namespace OpenRA // This assumes/enforces that these actor types can only inherit abstract definitions (starting with ^) if (RuleDefinitions != null) { - var files = modData.Manifest.Rules.AsEnumerable(); + modDataRules ??= modData.GetRulesYaml(); + var files = Enumerable.Empty(); if (RuleDefinitions.Value != null) { var mapFiles = FieldLoader.GetValue("value", RuleDefinitions.Value); files = files.Append(mapFiles); } - var sources = files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s).Where(IsLoadableRuleDefinition).ToList()); + var sources = + modDataRules.Select(x => x.Where(IsLoadableRuleDefinition).ToList()) + .Concat(files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s).Where(IsLoadableRuleDefinition).ToList())); if (RuleDefinitions.Nodes.Count > 0) sources = sources.Append(RuleDefinitions.Nodes.Where(IsLoadableRuleDefinition).ToList()); @@ -319,10 +322,10 @@ namespace OpenRA { "Notifications", map.NotificationDefinitions }, { "Sequences", map.SequenceDefinitions }, { "ModelSequences", map.ModelSequenceDefinitions } - }); + }, null); } - public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification, string[] mapCompatibility, MapGridType gridType) + public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification, string[] mapCompatibility, MapGridType gridType, IEnumerable> modDataRules) { Dictionary yaml; using (var yamlStream = p.GetStream("map.yaml")) @@ -412,7 +415,7 @@ namespace OpenRA newData.Status = MapStatus.Unavailable; } - newData.SetCustomRules(modData, this, yaml); + newData.SetCustomRules(modData, this, yaml, modDataRules); if (cache.LoadPreviewImages && p.Contains("map.png")) using (var dataStream = p.GetStream("map.png")) @@ -475,7 +478,7 @@ namespace OpenRA var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules)); var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString)).ToDictionary(); - newData.SetCustomRules(modData, this, rulesYaml); + newData.SetCustomRules(modData, this, rulesYaml, null); } catch (Exception e) { @@ -554,7 +557,7 @@ namespace OpenRA innerData.Status = MapStatus.DownloadError; else { - UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType); + UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType, null); Game.RunAfterTick(onSuccess); } } diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index e6d485330f..f22a4daf43 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -166,6 +166,11 @@ namespace OpenRA return map; } + public List[] GetRulesYaml() + { + return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s)).ToArray(); + } + public void Dispose() { LoadScreen?.Dispose();