MiniYaml becomes an immutable data structure.

This changeset is motivated by a simple concept - get rid of the MiniYaml.Clone and MiniYamlNode.Clone methods to avoid deep copying yaml trees during merging. MiniYaml becoming immutable allows the merge function to reuse existing yaml trees rather than cloning them, saving on memory and improving merge performance. On initial loading the YAML for all maps is processed, so this provides a small reduction in initial loading time.

The rest of the changeset is dealing with the change in the exposed API surface. Some With* helper methods are introduced to allow creating new YAML from existing YAML. Areas of code that generated small amounts of YAML are able to transition directly to the immutable model without too much ceremony. Some use cases are far less ergonomic even with these helper methods and so a MiniYamlBuilder is introduced to retain mutable creation functionality. This allows those areas to continue to use the old mutable structures. The main users are the update rules and linting capabilities.
This commit is contained in:
RoosterDragon
2023-05-06 10:39:15 +01:00
committed by Gustas
parent b6a5d19871
commit 949ba589c0
104 changed files with 722 additions and 393 deletions

View File

@@ -17,7 +17,7 @@ using OpenRA.FileSystem;
namespace OpenRA.Mods.Common.UpdateRules
{
using YamlFileSet = List<(IReadWritePackage, string, List<MiniYamlNode>)>;
using YamlFileSet = List<(IReadWritePackage, string, List<MiniYamlNodeBuilder>)>;
public static class UpdateUtils
{
@@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.UpdateRules
continue;
}
yaml.Add(((IReadWritePackage)package, name, MiniYaml.FromStream(package.GetStream(name), name, false)));
yaml.Add(((IReadWritePackage)package, name, MiniYaml.FromStream(package.GetStream(name), name, false).Select(n => new MiniYamlNodeBuilder(n)).ToList()));
}
return yaml;
@@ -44,7 +44,7 @@ namespace OpenRA.Mods.Common.UpdateRules
/// <summary>
/// Loads a YamlFileSet containing any external yaml definitions referenced by a map yaml block.
/// </summary>
static YamlFileSet LoadExternalMapYaml(ModData modData, MiniYaml yaml, HashSet<string> externalFilenames)
static YamlFileSet LoadExternalMapYaml(ModData modData, MiniYamlBuilder yaml, HashSet<string> externalFilenames)
{
return FieldLoader.GetValue<string[]>("value", yaml.Value)
.Where(f => f.Contains('|'))
@@ -56,7 +56,7 @@ namespace OpenRA.Mods.Common.UpdateRules
/// Loads a YamlFileSet containing any internal definitions yaml referenced by a map yaml block.
/// External references or internal references to missing files are ignored.
/// </summary>
static YamlFileSet LoadInternalMapYaml(ModData modData, IReadWritePackage mapPackage, MiniYaml yaml, HashSet<string> externalFilenames)
static YamlFileSet LoadInternalMapYaml(ModData modData, IReadWritePackage mapPackage, MiniYamlBuilder yaml, HashSet<string> externalFilenames)
{
var fileSet = new YamlFileSet()
{
@@ -68,7 +68,7 @@ namespace OpenRA.Mods.Common.UpdateRules
{
// Ignore any files that aren't in the map bundle
if (!filename.Contains('|') && mapPackage.Contains(filename))
fileSet.Add((mapPackage, filename, MiniYaml.FromStream(mapPackage.GetStream(filename), filename, false)));
fileSet.Add((mapPackage, filename, MiniYaml.FromStream(mapPackage.GetStream(filename), filename, false).Select(n => new MiniYamlNodeBuilder(n)).ToList()));
else if (modData.ModFiles.Exists(filename))
externalFilenames.Add(filename);
}
@@ -94,7 +94,7 @@ namespace OpenRA.Mods.Common.UpdateRules
return manualSteps;
}
var yaml = new MiniYaml(null, MiniYaml.FromStream(mapStream, mapPackage.Name, false));
var yaml = new MiniYamlBuilder(null, MiniYaml.FromStream(mapStream, mapPackage.Name, false));
files = new YamlFileSet() { (mapPackage, "map.yaml", yaml.Nodes) };
manualSteps.AddRange(rule.BeforeUpdate(modData));
@@ -159,7 +159,7 @@ namespace OpenRA.Mods.Common.UpdateRules
return manualSteps;
}
public static List<MiniYamlNode> LoadMapYaml(IReadOnlyFileSystem fileSystem, IReadOnlyPackage mapPackage, IEnumerable<string> files, MiniYaml mapNode)
public static List<MiniYamlNodeBuilder> LoadMapYaml(IReadOnlyFileSystem fileSystem, IReadOnlyPackage mapPackage, IEnumerable<string> files, MiniYamlBuilder mapNode)
{
var yaml = files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)).ToList();
@@ -177,9 +177,9 @@ namespace OpenRA.Mods.Common.UpdateRules
}
if (mapNode != null && mapNode.Nodes.Count > 0)
yaml.Add(mapNode.Nodes);
yaml.Add(mapNode.Nodes.Select(n => n.Build()).ToList());
return MiniYaml.Merge(yaml);
return MiniYaml.Merge(yaml).Select(n => new MiniYamlNodeBuilder(n)).ToList();
}
static IEnumerable<string> FilterExternalModFiles(ModData modData, IEnumerable<string> files, HashSet<string> externalFilenames)
@@ -215,7 +215,7 @@ namespace OpenRA.Mods.Common.UpdateRules
if (mapStream == null)
continue;
var yaml = new MiniYaml(null, MiniYaml.FromStream(mapStream, package.Name, false));
var yaml = new MiniYamlBuilder(new MiniYaml(null, MiniYaml.FromStream(mapStream, package.Name, false)));
var mapRulesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Rules");
if (mapRulesNode != null)
foreach (var f in LoadExternalMapYaml(modData, mapRulesNode.Value, externalFilenames))
@@ -240,7 +240,8 @@ namespace OpenRA.Mods.Common.UpdateRules
if (rule is IBeforeUpdateActors beforeActors)
{
var resolvedActors = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Rules, null);
var resolvedActors = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Rules, null)
.Select(n => new MiniYamlNodeBuilder(n)).ToList();
manualSteps.AddRange(beforeActors.BeforeUpdateActors(modData, resolvedActors));
}
@@ -248,7 +249,8 @@ namespace OpenRA.Mods.Common.UpdateRules
if (rule is IBeforeUpdateWeapons beforeWeapons)
{
var resolvedWeapons = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Weapons, null);
var resolvedWeapons = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Weapons, null)
.Select(n => new MiniYamlNodeBuilder(n)).ToList();
manualSteps.AddRange(beforeWeapons.BeforeUpdateWeapons(modData, resolvedWeapons));
}
@@ -256,7 +258,8 @@ namespace OpenRA.Mods.Common.UpdateRules
if (rule is IBeforeUpdateSequences beforeSequences)
{
var resolvedImages = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Sequences, null);
var resolvedImages = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Sequences, null)
.Select(n => new MiniYamlNodeBuilder(n)).ToList();
manualSteps.AddRange(beforeSequences.BeforeUpdateSequences(modData, resolvedImages));
}
@@ -277,7 +280,7 @@ namespace OpenRA.Mods.Common.UpdateRules
return manualSteps;
}
static IEnumerable<string> ApplyChromeTransformInner(ModData modData, MiniYamlNode current, UpdateRule.ChromeNodeTransform transform)
static IEnumerable<string> ApplyChromeTransformInner(ModData modData, MiniYamlNodeBuilder current, UpdateRule.ChromeNodeTransform transform)
{
foreach (var manualStep in transform(modData, current))
yield return manualStep;
@@ -337,13 +340,13 @@ namespace OpenRA.Mods.Common.UpdateRules
}
/// <summary>Checks if node is a removal (has '-' prefix).</summary>
public static bool IsRemoval(this MiniYamlNode node)
public static bool IsRemoval(this MiniYamlNodeBuilder node)
{
return node.Key[0].ToString() == "-";
}
/// <summary>Renames a yaml key preserving any @suffix.</summary>
public static void RenameKey(this MiniYamlNode node, string newKey, bool preserveSuffix = true, bool includeRemovals = true)
public static void RenameKey(this MiniYamlNodeBuilder node, string newKey, bool preserveSuffix = true, bool includeRemovals = true)
{
var prefix = includeRemovals && node.IsRemoval() ? "-" : "";
var split = node.Key.IndexOf("@", StringComparison.Ordinal);
@@ -353,52 +356,52 @@ namespace OpenRA.Mods.Common.UpdateRules
node.Key = prefix + newKey;
}
public static T NodeValue<T>(this MiniYamlNode node)
public static T NodeValue<T>(this MiniYamlNodeBuilder node)
{
return FieldLoader.GetValue<T>(node.Key, node.Value.Value);
}
public static void ReplaceValue(this MiniYamlNode node, string value)
public static void ReplaceValue(this MiniYamlNodeBuilder node, string value)
{
node.Value.Value = value;
}
public static void AddNode(this MiniYamlNode node, string key, object value)
public static void AddNode(this MiniYamlNodeBuilder node, string key, object value)
{
node.Value.Nodes.Add(new MiniYamlNode(key, FieldSaver.FormatValue(value)));
node.Value.Nodes.Add(new MiniYamlNodeBuilder(key, FieldSaver.FormatValue(value)));
}
public static void AddNode(this MiniYamlNode node, MiniYamlNode toAdd)
public static void AddNode(this MiniYamlNodeBuilder node, MiniYamlNodeBuilder toAdd)
{
node.Value.Nodes.Add(toAdd);
}
public static void RemoveNode(this MiniYamlNode node, MiniYamlNode toRemove)
public static void RemoveNode(this MiniYamlNodeBuilder node, MiniYamlNodeBuilder toRemove)
{
node.Value.Nodes.Remove(toRemove);
}
public static void MoveNode(this MiniYamlNode node, MiniYamlNode fromNode, MiniYamlNode toNode)
public static void MoveNode(this MiniYamlNodeBuilder node, MiniYamlNodeBuilder fromNode, MiniYamlNodeBuilder toNode)
{
toNode.Value.Nodes.Add(node);
fromNode.Value.Nodes.Remove(node);
}
public static void MoveAndRenameNode(this MiniYamlNode node,
MiniYamlNode fromNode, MiniYamlNode toNode, string newKey, bool preserveSuffix = true, bool includeRemovals = true)
public static void MoveAndRenameNode(this MiniYamlNodeBuilder node,
MiniYamlNodeBuilder fromNode, MiniYamlNodeBuilder toNode, string newKey, bool preserveSuffix = true, bool includeRemovals = true)
{
node.RenameKey(newKey, preserveSuffix, includeRemovals);
node.MoveNode(fromNode, toNode);
}
/// <summary>Removes children with keys equal to [match] or [match]@[arbitrary suffix].</summary>
public static int RemoveNodes(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
public static int RemoveNodes(this MiniYamlNodeBuilder node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
{
return node.Value.Nodes.RemoveAll(n => n.KeyMatches(match, ignoreSuffix, includeRemovals));
}
/// <summary>Returns true if the node is of the form [match] or [match]@[arbitrary suffix].</summary>
public static bool KeyMatches(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
public static bool KeyMatches(this MiniYamlNodeBuilder node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
{
if (node.Key == null)
return false;
@@ -416,7 +419,7 @@ namespace OpenRA.Mods.Common.UpdateRules
}
/// <summary>Returns true if the node is of the form [match], [match]@[arbitrary suffix] or [arbitrary suffix]@[match].</summary>
public static bool KeyContains(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
public static bool KeyContains(this MiniYamlNodeBuilder node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
{
if (node.Key == null)
return false;
@@ -431,23 +434,23 @@ namespace OpenRA.Mods.Common.UpdateRules
}
/// <summary>Returns children with keys equal to [match] or [match]@[arbitrary suffix].</summary>
public static IEnumerable<MiniYamlNode> ChildrenMatching(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
public static IEnumerable<MiniYamlNodeBuilder> ChildrenMatching(this MiniYamlNodeBuilder node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
{
return node.Value.Nodes.Where(n => n.KeyMatches(match, ignoreSuffix, includeRemovals));
}
/// <summary>Returns children whose keys contain 'match' (optionally in the suffix).</summary>
public static IEnumerable<MiniYamlNode> ChildrenContaining(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
public static IEnumerable<MiniYamlNodeBuilder> ChildrenContaining(this MiniYamlNodeBuilder node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
{
return node.Value.Nodes.Where(n => n.KeyContains(match, ignoreSuffix, includeRemovals));
}
public static MiniYamlNode LastChildMatching(this MiniYamlNode node, string match, bool includeRemovals = true)
public static MiniYamlNodeBuilder LastChildMatching(this MiniYamlNodeBuilder node, string match, bool includeRemovals = true)
{
return node.ChildrenMatching(match, includeRemovals: includeRemovals).LastOrDefault();
}
public static void RenameChildrenMatching(this MiniYamlNode node, string match, string newKey, bool preserveSuffix = true, bool includeRemovals = true)
public static void RenameChildrenMatching(this MiniYamlNodeBuilder node, string match, string newKey, bool preserveSuffix = true, bool includeRemovals = true)
{
var matching = node.ChildrenMatching(match);
foreach (var m in matching)