Improve lookups of nodes by key in MiniYaml.

When handling the Nodes collection in MiniYaml, individual nodes are located via one of two methods:

// Lookup a single key with linear search.
var node = yaml.Nodes.FirstOrDefault(n => n.Key == "SomeKey");

// Convert to dictionary, expecting many key lookups.
var dict = nodes.ToDictionary();

// Lookup a single key in the dictionary.
var node = dict["SomeKey"];

To simplify lookup of individual keys via linear search, provide helper methods NodeWithKeyOrDefault and NodeWithKey. These helpers do the equivalent of Single{OrDefault} searches. Whilst this requires checking the whole list, it provides a useful correctness check. Two duplicated keys in TS yaml are fixed as a result. We can also optimize the helpers to not use LINQ, avoiding allocation of the delegate to search for a key.

Adjust existing code to use either lnear searches or dictionary lookups based on whether it will be resolving many keys. Resolving few keys can be done with linear searches to avoid building a dictionary. Resolving many keys should be done with a dictionary to avoid quaradtic runtime from repeated linear searches.
This commit is contained in:
RoosterDragon
2023-07-07 08:31:29 +01:00
committed by Matthias Mailänder
parent 0ab7caedd9
commit b7e0ed9b87
60 changed files with 196 additions and 196 deletions

View File

@@ -94,17 +94,17 @@ namespace OpenRA
if (sheetBuilder != null)
{
var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon");
var iconNode = yaml.NodeWithKeyOrDefault("Icon");
if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value))
using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value)))
mod.Icon = sheetBuilder.Add(new Png(stream));
var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x");
var icon2xNode = yaml.NodeWithKeyOrDefault("Icon2x");
if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value))
using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value)))
mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2);
var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x");
var icon3xNode = yaml.NodeWithKeyOrDefault("Icon3x");
if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value))
using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value)))
mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3);

View File

@@ -40,16 +40,16 @@ namespace OpenRA.GameRules
static Dictionary<string, SoundPool> ParseSoundPool(MiniYaml y, string key)
{
var ret = new Dictionary<string, SoundPool>();
var classifiction = y.Nodes.First(x => x.Key == key);
var classifiction = y.NodeWithKey(key);
foreach (var t in classifiction.Value.Nodes)
{
var volumeModifier = 1f;
var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.VolumeModifier));
var volumeModifierNode = t.Value.NodeWithKeyOrDefault(nameof(SoundPool.VolumeModifier));
if (volumeModifierNode != null)
volumeModifier = FieldLoader.GetValue<float>(volumeModifierNode.Key, volumeModifierNode.Value.Value);
var interruptType = SoundPool.DefaultInterruptType;
var interruptTypeNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.InterruptType));
var interruptTypeNode = t.Value.NodeWithKeyOrDefault(nameof(SoundPool.InterruptType));
if (interruptTypeNode != null)
interruptType = FieldLoader.GetValue<SoundPool.InterruptType>(interruptTypeNode.Key, interruptTypeNode.Value.Value);

View File

@@ -145,7 +145,8 @@ namespace OpenRA.GameRules
static object LoadProjectile(MiniYaml yaml)
{
if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj))
var proj = yaml.NodeWithKeyOrDefault("Projectile")?.Value;
if (proj == null)
return null;
var ret = Game.CreateObject<IProjectileInfo>(proj.Value + "Info");

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Linq;
namespace OpenRA
{
@@ -38,7 +37,7 @@ namespace OpenRA
static object LoadSpeeds(MiniYaml y)
{
var ret = new Dictionary<string, GameSpeed>();
var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds");
var speedsNode = y.NodeWithKeyOrDefault("Speeds");
if (speedsNode == null)
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");

View File

@@ -27,7 +27,7 @@ namespace OpenRA.Graphics
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(
s => MiniYaml.FromStream(fileSystem.Open(s), s)));
var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary();
var cursorsYaml = new MiniYaml(null, sequenceYaml).NodeWithKey("Cursors").Value;
// Overwrite previous definitions if there are duplicates
var pals = new Dictionary<string, IProvidesCursorPaletteInfo>();
@@ -35,14 +35,14 @@ namespace OpenRA.Graphics
if (p.Palette != null)
pals[p.Palette] = p;
Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value)
Palettes = cursorsYaml.Nodes.Select(n => n.Value.Value)
.Where(p => p != null)
.Distinct()
.ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem));
var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders);
var cursors = new Dictionary<string, CursorSequence>();
foreach (var s in nodesDict["Cursors"].Nodes)
foreach (var s in cursorsYaml.Nodes)
foreach (var sequence in s.Value.Nodes)
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Linq;
namespace OpenRA
{
@@ -31,29 +30,26 @@ namespace OpenRA
if (!string.IsNullOrEmpty(node.Value))
Default = FieldLoader.GetValue<Hotkey>("value", node.Value);
var descriptionNode = node.Nodes.FirstOrDefault(n => n.Key == "Description");
if (descriptionNode != null)
Description = descriptionNode.Value.Value;
var nodeDict = node.ToDictionary();
var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types");
if (typesNode != null)
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesNode.Value.Value);
if (nodeDict.TryGetValue("Description", out var descriptionYaml))
Description = descriptionYaml.Value;
var contextsNode = node.Nodes.FirstOrDefault(n => n.Key == "Contexts");
if (contextsNode != null)
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextsNode.Value.Value);
if (nodeDict.TryGetValue("Types", out var typesYaml))
Types = FieldLoader.GetValue<HashSet<string>>("Types", typesYaml.Value);
var platformNode = node.Nodes.FirstOrDefault(n => n.Key == "Platform");
if (platformNode != null)
if (nodeDict.TryGetValue("Contexts", out var contextYaml))
Contexts = FieldLoader.GetValue<HashSet<string>>("Contexts", contextYaml.Value);
if (nodeDict.TryGetValue("Platform", out var platformYaml))
{
var platformOverride = platformNode.Value.Nodes.FirstOrDefault(n => n.Key == Platform.CurrentPlatform.ToString());
var platformOverride = platformYaml.NodeWithKeyOrDefault(Platform.CurrentPlatform.ToString());
if (platformOverride != null)
Default = FieldLoader.GetValue<Hotkey>("value", platformOverride.Value.Value);
}
var readonlyNode = node.Nodes.FirstOrDefault(n => n.Key == "Readonly");
if (readonlyNode != null)
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyNode.Value.Value);
if (nodeDict.TryGetValue("Readonly", out var readonlyYaml))
Readonly = FieldLoader.GetValue<bool>("Readonly", readonlyYaml.Value);
}
}
}

View File

@@ -214,7 +214,7 @@ namespace OpenRA
if (!yaml.ContainsKey(key))
return Array.Empty<string>();
return yaml[key].ToDictionary().Keys.ToArray();
return yaml[key].Nodes.Select(n => n.Key).ToArray();
}
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)

View File

@@ -95,9 +95,9 @@ namespace OpenRA
t == typeof(MiniYaml) ? Type.MiniYaml : Type.Normal;
}
public void Deserialize(Map map, ImmutableArray<MiniYamlNode> nodes)
public void Deserialize(Map map, MiniYaml yaml)
{
var node = nodes.FirstOrDefault(n => n.Key == key);
var node = yaml.NodeWithKeyOrDefault(key);
if (node == null)
{
if (required)
@@ -363,13 +363,13 @@ namespace OpenRA
var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), package.Name));
foreach (var field in YamlFields)
field.Deserialize(this, yaml.Nodes);
field.Deserialize(this, yaml);
if (MapFormat < SupportedMapFormat)
throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}");
PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players");
ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors");
PlayerDefinitions = yaml.NodeWithKeyOrDefault("Players")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
ActorDefinitions = yaml.NodeWithKeyOrDefault("Actors")?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
Grid = modData.Manifest.Get<MapGrid>();

View File

@@ -141,6 +141,34 @@ namespace OpenRA
return new MiniYaml(Value, newNodes);
}
public MiniYamlNode NodeWithKey(string key)
{
var result = NodeWithKeyOrDefault(key);
if (result == null)
throw new InvalidDataException($"No node with key '{key}'");
return result;
}
public MiniYamlNode NodeWithKeyOrDefault(string key)
{
// PERF: Avoid LINQ.
var first = true;
MiniYamlNode result = null;
foreach (var node in Nodes)
{
if (node.Key != key)
continue;
if (!first)
throw new InvalidDataException($"Duplicate key '{node.Key}' in {node.Location}");
first = false;
result = node;
}
return result;
}
public Dictionary<string, MiniYaml> ToDictionary()
{
return ToDictionary(MiniYamlIdentity);
@@ -175,11 +203,6 @@ namespace OpenRA
Nodes = ImmutableArray.CreateRange(nodes);
}
public static ImmutableArray<MiniYamlNode> NodesOrEmpty(MiniYaml y, string s)
{
return y.Nodes.FirstOrDefault(n => n.Key == s)?.Value.Nodes ?? ImmutableArray<MiniYamlNode>.Empty;
}
static List<MiniYamlNode> FromLines(IEnumerable<ReadOnlyMemory<char>> lines, string filename, bool discardCommentsAndWhitespace, Dictionary<string, string> stringPool)
{
stringPool ??= new Dictionary<string, string>();
@@ -668,6 +691,11 @@ namespace OpenRA
foreach (var line in Nodes.ToLines())
yield return "\t" + line;
}
public MiniYamlNodeBuilder NodeWithKeyOrDefault(string key)
{
return Nodes.SingleOrDefault(n => n.Key == key);
}
}
[Serializable]

View File

@@ -140,7 +140,7 @@ namespace OpenRA.Network
static object LoadClients(MiniYaml yaml)
{
var clients = new List<GameClient>();
var clientsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Clients");
var clientsNode = yaml.NodeWithKeyOrDefault("Clients");
if (clientsNode != null)
{
var regex = new Regex(@"Client@\d+");
@@ -159,7 +159,7 @@ namespace OpenRA.Network
// Games advertised using the old API used a single Mods field
if (Mod == null || Version == null)
{
var modsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Mods");
var modsNode = yaml.NodeWithKeyOrDefault("Mods");
if (modsNode != null)
{
var modVersion = modsNode.Value.Value.Split('@');

View File

@@ -60,7 +60,7 @@ namespace OpenRA.Network
static object LoadArguments(MiniYaml yaml)
{
var arguments = new Dictionary<string, object>();
var argumentsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Arguments");
var argumentsNode = yaml.NodeWithKeyOrDefault("Arguments");
if (argumentsNode != null)
{
foreach (var argumentNode in argumentsNode.Value.Nodes)

View File

@@ -227,7 +227,7 @@ namespace OpenRA.Network
{
var gs = FieldLoader.Load<Global>(data);
var optionsNode = data.Nodes.FirstOrDefault(n => n.Key == "Options");
var optionsNode = data.NodeWithKeyOrDefault("Options");
if (optionsNode != null)
foreach (var n in optionsNode.Value.Nodes)
gs.LobbyOptions[n.Key] = FieldLoader.Load<LobbyOptionState>(n.Value);

View File

@@ -9,7 +9,6 @@
*/
#endregion
using System.Linq;
using System.Threading.Tasks;
using OpenRA.FileFormats;
using OpenRA.Graphics;
@@ -94,10 +93,10 @@ namespace OpenRA
});
}
var labelNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Label");
var icon24Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon24");
var icon48Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon48");
var icon72Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon72");
var labelNode = yaml.NodeWithKeyOrDefault("Label");
var icon24Node = yaml.NodeWithKeyOrDefault("Icon24");
var icon48Node = yaml.NodeWithKeyOrDefault("Icon48");
var icon72Node = yaml.NodeWithKeyOrDefault("Icon72");
if (labelNode == null)
return null;

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Linq;
namespace OpenRA
{
@@ -31,7 +30,7 @@ namespace OpenRA
{
var badges = new List<PlayerBadge>();
var badgesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Badges");
var badgesNode = yaml.NodeWithKeyOrDefault("Badges");
if (badgesNode != null)
{
var playerDatabase = Game.ModData.Manifest.Get<PlayerDatabase>();

View File

@@ -385,7 +385,7 @@ namespace OpenRA
else
{
// Update or add the custom value
var fieldYaml = sectionYaml.Value.Nodes.FirstOrDefault(n => n.Key == fli.YamlName);
var fieldYaml = sectionYaml.Value.NodeWithKeyOrDefault(fli.YamlName);
if (fieldYaml != null)
fieldYaml.Value.Value = serialized;
else

View File

@@ -11,7 +11,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -350,7 +349,7 @@ namespace OpenRA.Traits
public interface IGameSaveTraitData
{
List<MiniYamlNode> IssueTraitData(Actor self);
void ResolveTraitData(Actor self, ImmutableArray<MiniYamlNode> data);
void ResolveTraitData(Actor self, MiniYaml data);
}
[RequireExplicitImplementation]

View File

@@ -66,7 +66,7 @@ namespace OpenRA
foreach (var c in child.Value.Nodes)
LoadWidget(args, widget, c);
var logicNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Logic");
var logicNode = node.Value.NodeWithKeyOrDefault("Logic");
var logic = logicNode?.Value.ToDictionary();
args.Add("logicArgs", logic);

View File

@@ -423,7 +423,7 @@ namespace OpenRA
if (tp.Actor == null)
break;
tp.Trait.ResolveTraitData(tp.Actor, kv.Value.Nodes);
tp.Trait.ResolveTraitData(tp.Actor, kv.Value);
}
gameSaveTraitData.Clear();

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
@@ -39,10 +38,10 @@ namespace OpenRA.Mods.Cnc.Graphics
protected override IEnumerable<ReservationInfo> ParseFilenames(ModData modData, string tileset, int[] frames, MiniYaml data, MiniYaml defaults)
{
var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key);
var node = data.NodeWithKeyOrDefault(TilesetFilenames.Key) ?? defaults.NodeWithKeyOrDefault(TilesetFilenames.Key);
if (node != null)
{
var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset);
var tilesetNode = node.Value.NodeWithKeyOrDefault(tileset);
if (tilesetNode != null)
{
var loadFrames = CalculateFrameIndices(start, length, stride ?? length ?? 0, facings, frames, transpose, reverseFacings, shadowStart);
@@ -55,10 +54,10 @@ namespace OpenRA.Mods.Cnc.Graphics
protected override IEnumerable<ReservationInfo> ParseCombineFilenames(ModData modData, string tileset, int[] frames, MiniYaml data)
{
var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key);
var node = data.NodeWithKeyOrDefault(TilesetFilenames.Key);
if (node != null)
{
var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset);
var tilesetNode = node.Value.NodeWithKeyOrDefault(tileset);
if (tilesetNode != null)
{
if (frames == null)

View File

@@ -130,7 +130,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
protected MiniYamlNodeBuilder GetWorldNodeBuilderFromRules()
{
var worldNode = Map.RuleDefinitions.Nodes.FirstOrDefault(n => n.Key == "World");
var worldNode = Map.RuleDefinitions.NodeWithKeyOrDefault("World");
var worldNodeBuilder = worldNode != null
? new MiniYamlNodeBuilder(worldNode)
: new MiniYamlNodeBuilder("World", new MiniYamlBuilder("", new List<MiniYamlNode>()));
@@ -166,7 +166,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
var worldNodeBuilder = GetWorldNodeBuilderFromRules();
var missionData = worldNodeBuilder.Value.Nodes.FirstOrDefault(n => n.Key == "MissionData");
var missionData = worldNodeBuilder.Value.NodeWithKeyOrDefault("MissionData");
if (missionData == null)
{
missionData = new MiniYamlNodeBuilder("MissionData", new MiniYamlBuilder("", new List<MiniYamlNode>()));
@@ -237,7 +237,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands
{
var worldNodeBuilder = GetWorldNodeBuilderFromRules();
var missionData = worldNodeBuilder.Value.Nodes.FirstOrDefault(n => n.Key == "MissionData");
var missionData = worldNodeBuilder.Value.NodeWithKeyOrDefault("MissionData");
if (missionData == null)
{
missionData = new MiniYamlNodeBuilder("MissionData", new MiniYamlBuilder("", new List<MiniYamlNode>()));

View File

@@ -47,7 +47,7 @@ namespace OpenRA.Mods.Common.Graphics
IReadOnlyDictionary<string, ISpriteSequence> ISpriteSequenceLoader.ParseSequences(ModData modData, string tileset, SpriteCache cache, MiniYamlNode imageNode)
{
var sequences = new Dictionary<string, ISpriteSequence>();
var node = imageNode.Value.Nodes.SingleOrDefault(n => n.Key == "Defaults");
var node = imageNode.Value.NodeWithKeyOrDefault("Defaults");
var defaults = node?.Value ?? NoData;
imageNode = imageNode.WithValue(imageNode.Value.WithNodes(imageNode.Value.Nodes.Remove(node)));
@@ -262,7 +262,7 @@ namespace OpenRA.Mods.Common.Graphics
protected static T LoadField<T>(string key, T fallback, MiniYaml data, MiniYaml defaults = null)
{
var node = data.Nodes.FirstOrDefault(n => n.Key == key) ?? defaults?.Nodes.FirstOrDefault(n => n.Key == key);
var node = data.NodeWithKeyOrDefault(key) ?? defaults?.NodeWithKeyOrDefault(key);
if (node == null)
return fallback;
@@ -276,7 +276,7 @@ namespace OpenRA.Mods.Common.Graphics
protected static T LoadField<T>(SpriteSequenceField<T> field, MiniYaml data, MiniYaml defaults, out MiniYamlNode.SourceLocation location)
{
var node = data.Nodes.FirstOrDefault(n => n.Key == field.Key) ?? defaults?.Nodes.FirstOrDefault(n => n.Key == field.Key);
var node = data.NodeWithKeyOrDefault(field.Key) ?? defaults?.NodeWithKeyOrDefault(field.Key);
if (node == null)
{
location = default;
@@ -414,7 +414,7 @@ namespace OpenRA.Mods.Common.Graphics
var offset = LoadField(Offset, data, defaults);
var blendMode = LoadField(BlendMode, data, defaults);
var combineNode = data.Nodes.FirstOrDefault(n => n.Key == Combine.Key);
var combineNode = data.NodeWithKeyOrDefault(Combine.Key);
if (combineNode != null)
{
for (var i = 0; i < combineNode.Value.Nodes.Length; i++)

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
namespace OpenRA.Mods.Common.Graphics
@@ -37,10 +36,10 @@ namespace OpenRA.Mods.Common.Graphics
protected override IEnumerable<ReservationInfo> ParseFilenames(ModData modData, string tileset, int[] frames, MiniYaml data, MiniYaml defaults)
{
var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key);
var node = data.NodeWithKeyOrDefault(TilesetFilenames.Key) ?? defaults.NodeWithKeyOrDefault(TilesetFilenames.Key);
if (node != null)
{
var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset);
var tilesetNode = node.Value.NodeWithKeyOrDefault(tileset);
if (tilesetNode != null)
{
var loadFrames = CalculateFrameIndices(start, length, stride ?? length ?? 0, facings, frames, transpose, reverseFacings, shadowStart);
@@ -53,10 +52,10 @@ namespace OpenRA.Mods.Common.Graphics
protected override IEnumerable<ReservationInfo> ParseCombineFilenames(ModData modData, string tileset, int[] frames, MiniYaml data)
{
var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key);
var node = data.NodeWithKeyOrDefault(TilesetFilenames.Key);
if (node != null)
{
var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset);
var tilesetNode = node.Value.NodeWithKeyOrDefault(tileset);
if (tilesetNode != null)
{
if (frames == null)

View File

@@ -11,7 +11,6 @@
using System;
using System.IO;
using System.Linq;
using FS = OpenRA.FileSystem.FileSystem;
namespace OpenRA.Mods.Common.Installer
@@ -33,8 +32,8 @@ namespace OpenRA.Mods.Common.Installer
using (var fileStream = File.OpenRead(filePath))
{
var offsetNode = kv.Value.Nodes.FirstOrDefault(n => n.Key == "Offset");
var lengthNode = kv.Value.Nodes.FirstOrDefault(n => n.Key == "Length");
var offsetNode = kv.Value.NodeWithKeyOrDefault("Offset");
var lengthNode = kv.Value.NodeWithKeyOrDefault("Length");
if (offsetNode != null || lengthNode != null)
{
var offset = 0L;

View File

@@ -12,7 +12,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.Mods.Common.FileFormats;
using OpenRA.Mods.Common.Widgets.Logic;
using FS = OpenRA.FileSystem.FileSystem;
@@ -26,11 +25,11 @@ namespace OpenRA.Mods.Common.Installer
// Yaml path may be specified relative to a named directory (e.g. ^SupportDir) or the detected source path
var sourcePath = actionYaml.Value.StartsWith('^') ? Platform.ResolvePath(actionYaml.Value) : FS.ResolveCaseInsensitivePath(Path.Combine(path, actionYaml.Value));
var volumeNode = actionYaml.Nodes.FirstOrDefault(n => n.Key == "Volumes");
var volumeNode = actionYaml.NodeWithKeyOrDefault("Volumes");
if (volumeNode == null)
throw new InvalidDataException("extract-iscab entry doesn't define a Volumes node");
var extractNode = actionYaml.Nodes.FirstOrDefault(n => n.Key == "Extract");
var extractNode = actionYaml.NodeWithKeyOrDefault("Extract");
if (extractNode == null)
throw new InvalidDataException("extract-iscab entry doesn't define an Extract node");

View File

@@ -12,7 +12,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.Mods.Common.Widgets.Logic;
using FS = OpenRA.FileSystem.FileSystem;
@@ -37,14 +36,14 @@ namespace OpenRA.Mods.Common.Installer
continue;
}
var offsetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Offset");
var offsetNode = node.Value.NodeWithKeyOrDefault("Offset");
if (offsetNode == null)
{
Log.Write("install", "Skipping entry with missing Offset definition " + targetPath);
continue;
}
var lengthNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Length");
var lengthNode = node.Value.NodeWithKeyOrDefault("Length");
if (lengthNode == null)
{
Log.Write("install", "Skipping entry with missing Length definition " + targetPath);

View File

@@ -18,7 +18,7 @@ namespace OpenRA.Mods.Common.Installer
{
public string FindSourcePath(ModContent.ModSource modSource)
{
modSource.Type.ToDictionary().TryGetValue("AppId", out var appId);
var appId = modSource.Type.NodeWithKeyOrDefault("AppId");
if (appId == null)
return null;
@@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.Installer
foreach (var prefix in prefixes)
{
if (Registry.GetValue($"{prefix}GOG.com\\Games\\{appId.Value}", "path", null) is not string installDir)
if (Registry.GetValue($"{prefix}GOG.com\\Games\\{appId.Value.Value}", "path", null) is not string installDir)
continue;
if (InstallerUtils.IsValidSourcePath(installDir, modSource))

View File

@@ -23,14 +23,14 @@ namespace OpenRA.Mods.Common.Installer
{
public string FindSourcePath(ModContent.ModSource modSource)
{
modSource.Type.ToDictionary().TryGetValue("AppId", out var appId);
var appId = modSource.Type.NodeWithKeyOrDefault("AppId");
if (appId == null)
return null;
foreach (var steamDirectory in SteamDirectory())
{
var manifestPath = Path.Combine(steamDirectory, "steamapps", $"appmanifest_{appId.Value}.acf");
var manifestPath = Path.Combine(steamDirectory, "steamapps", $"appmanifest_{appId.Value.Value}.acf");
if (!File.Exists(manifestPath))
continue;

View File

@@ -33,11 +33,11 @@ namespace OpenRA.Mods.Common.Lint
{
var fileSystem = modData.DefaultFileSystem;
var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)));
var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary();
var cursorsYaml = new MiniYaml(null, sequenceYaml).NodeWithKey("Cursors").Value;
// Avoid using CursorProvider as it attempts to load palettes from the file system.
var cursors = new List<string>();
foreach (var s in nodesDict["Cursors"].Nodes)
foreach (var s in cursorsYaml.Nodes)
foreach (var sequence in s.Value.Nodes)
cursors.Add(sequence.Key);

View File

@@ -66,15 +66,15 @@ namespace OpenRA
ObjectCreator = objectCreator;
Title = yaml.Value;
var type = yaml.Nodes.FirstOrDefault(n => n.Key == "Type");
var type = yaml.NodeWithKeyOrDefault("Type");
if (type != null)
Type = type.Value;
var idFiles = yaml.Nodes.FirstOrDefault(n => n.Key == "IDFiles");
var idFiles = yaml.NodeWithKeyOrDefault("IDFiles");
if (idFiles != null)
IDFiles = idFiles.Value;
var installNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Install");
var installNode = yaml.NodeWithKeyOrDefault("Install");
if (installNode != null)
Install = installNode.Value.Nodes;
@@ -111,7 +111,7 @@ namespace OpenRA
static object LoadPackages(MiniYaml yaml)
{
var packages = new Dictionary<string, ModPackage>();
var packageNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Packages");
var packageNode = yaml.NodeWithKeyOrDefault("Packages");
if (packageNode != null)
foreach (var node in packageNode.Value.Nodes)
packages.Add(node.Key, new ModPackage(node.Value));
@@ -124,7 +124,7 @@ namespace OpenRA
static object LoadDownloads(MiniYaml yaml)
{
var downloadNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Downloads");
var downloadNode = yaml.NodeWithKeyOrDefault("Downloads");
return downloadNode != null ? downloadNode.Value.Nodes.Select(n => n.Key).ToArray() : Array.Empty<string>();
}
@@ -133,7 +133,7 @@ namespace OpenRA
static object LoadSources(MiniYaml yaml)
{
var sourceNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Sources");
var sourceNode = yaml.NodeWithKeyOrDefault("Sources");
return sourceNode != null ? sourceNode.Value.Nodes.Select(n => n.Key).ToArray() : Array.Empty<string>();
}
}

View File

@@ -37,7 +37,7 @@ namespace OpenRA.Mods.Common.Terrain
{
FieldLoader.Load(this, my);
var nodes = my.ToDictionary()["Tiles"].Nodes;
var nodes = my.NodeWithKey("Tiles").Value.Nodes;
if (!PickAny)
{

View File

@@ -41,7 +41,7 @@ namespace OpenRA.Mods.Common.Traits
else
{
var owner = map.PlayerDefinitions.Single(p => s.Get<OwnerInit>().InternalName == p.Value.Nodes.Last(k => k.Key == "Name").Value.Value);
var colorValue = owner.Value.Nodes.FirstOrDefault(n => n.Key == "Color");
var colorValue = owner.Value.NodeWithKeyOrDefault("Color");
var ownerColor = colorValue?.Value.Value ?? "FFFFFF";
Color.TryParse(ownerColor, out color);
}

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using OpenRA.Traits;
@@ -326,16 +325,16 @@ namespace OpenRA.Mods.Common.Traits
};
}
void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray<MiniYamlNode> data)
void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data)
{
if (self.World.IsReplay)
return;
var initialBaseCenterNode = data.FirstOrDefault(n => n.Key == "InitialBaseCenter");
var initialBaseCenterNode = data.NodeWithKeyOrDefault("InitialBaseCenter");
if (initialBaseCenterNode != null)
initialBaseCenter = FieldLoader.GetValue<CPos>("InitialBaseCenter", initialBaseCenterNode.Value.Value);
var defenseCenterNode = data.FirstOrDefault(n => n.Key == "DefenseCenter");
var defenseCenterNode = data.NodeWithKeyOrDefault("DefenseCenter");
if (defenseCenterNode != null)
DefenseCenter = FieldLoader.GetValue<CPos>("DefenseCenter", defenseCenterNode.Value.Value);
}

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using OpenRA.Traits;
@@ -213,12 +212,12 @@ namespace OpenRA.Mods.Common.Traits
};
}
void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray<MiniYamlNode> data)
void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data)
{
if (self.World.IsReplay)
return;
var initialBaseCenterNode = data.FirstOrDefault(n => n.Key == "InitialBaseCenter");
var initialBaseCenterNode = data.NodeWithKeyOrDefault("InitialBaseCenter");
if (initialBaseCenterNode != null)
initialBaseCenter = FieldLoader.GetValue<CPos>("InitialBaseCenter", initialBaseCenterNode.Value.Value);
}

View File

@@ -11,7 +11,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using OpenRA.Mods.Common.Traits.BotModules.Squads;
using OpenRA.Primitives;
@@ -526,52 +525,46 @@ namespace OpenRA.Mods.Common.Traits
};
}
void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray<MiniYamlNode> data)
void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data)
{
if (self.World.IsReplay)
return;
var initialBaseCenterNode = data.FirstOrDefault(n => n.Key == "InitialBaseCenter");
if (initialBaseCenterNode != null)
initialBaseCenter = FieldLoader.GetValue<CPos>("InitialBaseCenter", initialBaseCenterNode.Value.Value);
var nodes = data.ToDictionary();
var unitsHangingAroundTheBaseNode = data.FirstOrDefault(n => n.Key == "UnitsHangingAroundTheBase");
if (unitsHangingAroundTheBaseNode != null)
if (nodes.TryGetValue("InitialBaseCenter", out var initialBaseCenterNode))
initialBaseCenter = FieldLoader.GetValue<CPos>("InitialBaseCenter", initialBaseCenterNode.Value);
if (nodes.TryGetValue("UnitsHangingAroundTheBase", out var unitsHangingAroundTheBaseNode))
{
unitsHangingAroundTheBase.Clear();
unitsHangingAroundTheBase.AddRange(FieldLoader.GetValue<uint[]>("UnitsHangingAroundTheBase", unitsHangingAroundTheBaseNode.Value.Value)
unitsHangingAroundTheBase.AddRange(FieldLoader.GetValue<uint[]>("UnitsHangingAroundTheBase", unitsHangingAroundTheBaseNode.Value)
.Select(a => self.World.GetActorById(a)).Where(a => a != null));
}
var activeUnitsNode = data.FirstOrDefault(n => n.Key == "ActiveUnits");
if (activeUnitsNode != null)
if (nodes.TryGetValue("ActiveUnits", out var activeUnitsNode))
{
activeUnits.Clear();
activeUnits.UnionWith(FieldLoader.GetValue<uint[]>("ActiveUnits", activeUnitsNode.Value.Value)
activeUnits.UnionWith(FieldLoader.GetValue<uint[]>("ActiveUnits", activeUnitsNode.Value)
.Select(a => self.World.GetActorById(a)).Where(a => a != null));
}
var rushTicksNode = data.FirstOrDefault(n => n.Key == "RushTicks");
if (rushTicksNode != null)
rushTicks = FieldLoader.GetValue<int>("RushTicks", rushTicksNode.Value.Value);
if (nodes.TryGetValue("RushTicks", out var rushTicksNode))
rushTicks = FieldLoader.GetValue<int>("RushTicks", rushTicksNode.Value);
var assignRolesTicksNode = data.FirstOrDefault(n => n.Key == "AssignRolesTicks");
if (assignRolesTicksNode != null)
assignRolesTicks = FieldLoader.GetValue<int>("AssignRolesTicks", assignRolesTicksNode.Value.Value);
if (nodes.TryGetValue("AssignRolesTicks", out var assignRolesTicksNode))
assignRolesTicks = FieldLoader.GetValue<int>("AssignRolesTicks", assignRolesTicksNode.Value);
var attackForceTicksNode = data.FirstOrDefault(n => n.Key == "AttackForceTicks");
if (attackForceTicksNode != null)
attackForceTicks = FieldLoader.GetValue<int>("AttackForceTicks", attackForceTicksNode.Value.Value);
if (nodes.TryGetValue("AttackForceTicks", out var attackForceTicksNode))
attackForceTicks = FieldLoader.GetValue<int>("AttackForceTicks", attackForceTicksNode.Value);
var minAttackForceDelayTicksNode = data.FirstOrDefault(n => n.Key == "MinAttackForceDelayTicks");
if (minAttackForceDelayTicksNode != null)
minAttackForceDelayTicks = FieldLoader.GetValue<int>("MinAttackForceDelayTicks", minAttackForceDelayTicksNode.Value.Value);
if (nodes.TryGetValue("MinAttackForceDelayTicks", out var minAttackForceDelayTicksNode))
minAttackForceDelayTicks = FieldLoader.GetValue<int>("MinAttackForceDelayTicks", minAttackForceDelayTicksNode.Value);
var squadsNode = data.FirstOrDefault(n => n.Key == "Squads");
if (squadsNode != null)
if (nodes.TryGetValue("Squads", out var squadsNode))
{
Squads.Clear();
foreach (var n in squadsNode.Value.Nodes)
foreach (var n in squadsNode.Nodes)
Squads.Add(Squad.Deserialize(bot, this, n.Value));
}
}

View File

@@ -155,12 +155,12 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
var type = SquadType.Rush;
var target = ((Actor)null, WVec.Zero);
var typeNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Type");
var typeNode = yaml.NodeWithKeyOrDefault("Type");
if (typeNode != null)
type = FieldLoader.GetValue<SquadType>("Type", typeNode.Value.Value);
var actorToTargetNode = yaml.Nodes.FirstOrDefault(n => n.Key == "ActorToTarget");
var targetOffsetNode = yaml.Nodes.FirstOrDefault(n => n.Key == "TargetOffset");
var actorToTargetNode = yaml.NodeWithKeyOrDefault("ActorToTarget");
var targetOffsetNode = yaml.NodeWithKeyOrDefault("TargetOffset");
if (actorToTargetNode != null && targetOffsetNode != null)
{
var actorToTarget = squadManager.World.GetActorById(FieldLoader.GetValue<uint>("ActorToTarget", actorToTargetNode.Value.Value));
@@ -170,7 +170,7 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads
var squad = new Squad(bot, squadManager, type, target);
var unitsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Units");
var unitsNode = yaml.NodeWithKeyOrDefault("Units");
if (unitsNode != null)
squad.Units.UnionWith(FieldLoader.GetValue<uint[]>("Units", unitsNode.Value.Value)
.Select(a => squadManager.World.GetActorById(a)));

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using OpenRA.Traits;
@@ -27,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits
static object LoadDecisions(MiniYaml yaml)
{
var ret = new List<SupportPowerDecision>();
var decisions = yaml.Nodes.FirstOrDefault(n => n.Key == "Decisions");
var decisions = yaml.NodeWithKeyOrDefault("Decisions");
if (decisions != null)
foreach (var d in decisions.Value.Nodes)
ret.Add(new SupportPowerDecision(d.Value));
@@ -224,12 +223,12 @@ namespace OpenRA.Mods.Common.Traits
};
}
void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray<MiniYamlNode> data)
void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data)
{
if (self.World.IsReplay)
return;
var waitingPowersNode = data.FirstOrDefault(n => n.Key == "WaitingPowers");
var waitingPowersNode = data.NodeWithKeyOrDefault("WaitingPowers");
if (waitingPowersNode != null)
{
foreach (var n in waitingPowersNode.Value.Nodes)

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using OpenRA.Traits;
@@ -234,19 +233,19 @@ namespace OpenRA.Mods.Common.Traits
};
}
void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray<MiniYamlNode> data)
void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data)
{
if (self.World.IsReplay)
return;
var queuedBuildRequestsNode = data.FirstOrDefault(n => n.Key == "QueuedBuildRequests");
var queuedBuildRequestsNode = data.NodeWithKeyOrDefault("QueuedBuildRequests");
if (queuedBuildRequestsNode != null)
{
queuedBuildRequests.Clear();
queuedBuildRequests.AddRange(FieldLoader.GetValue<string[]>("QueuedBuildRequests", queuedBuildRequestsNode.Value.Value));
}
var idleUnitCountNode = data.FirstOrDefault(n => n.Key == "IdleUnitCount");
var idleUnitCountNode = data.NodeWithKeyOrDefault("IdleUnitCount");
if (idleUnitCountNode != null)
idleUnitCount = FieldLoader.GetValue<int>("IdleUnitCount", idleUnitCountNode.Value.Value);
}

View File

@@ -64,10 +64,10 @@ namespace OpenRA.Mods.Common.Traits
protected static object LoadFootprint(MiniYaml yaml)
{
var footprintYaml = yaml.Nodes.FirstOrDefault(n => n.Key == "Footprint");
var footprintYaml = yaml.NodeWithKeyOrDefault("Footprint");
var footprintChars = footprintYaml?.Value.Value.Where(x => !char.IsWhiteSpace(x)).ToArray() ?? new[] { 'x' };
var dimensionsYaml = yaml.Nodes.FirstOrDefault(n => n.Key == "Dimensions");
var dimensionsYaml = yaml.NodeWithKeyOrDefault("Dimensions");
var dim = dimensionsYaml != null ? FieldLoader.GetValue<CVec>("Dimensions", dimensionsYaml.Value.Value) : new CVec(1, 1);
if (footprintChars.Length != dim.X * dim.Y)

View File

@@ -43,7 +43,7 @@ namespace OpenRA.Mods.Common.Traits
{
IHitShape ret;
var shapeNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Type");
var shapeNode = yaml.NodeWithKeyOrDefault("Type");
var shape = shapeNode != null ? shapeNode.Value.Value : string.Empty;
if (!string.IsNullOrEmpty(shape))

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Traits;
@@ -50,13 +49,13 @@ namespace OpenRA.Mods.Common.Traits
return nodes;
}
void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray<MiniYamlNode> data)
void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data)
{
var viewportNode = data.FirstOrDefault(n => n.Key == "Viewport");
var viewportNode = data.NodeWithKeyOrDefault("Viewport");
if (viewportNode != null)
worldRenderer.Viewport.Center(FieldLoader.GetValue<WPos>("Viewport", viewportNode.Value.Value));
var renderPlayerNode = data.FirstOrDefault(n => n.Key == "RenderPlayer");
var renderPlayerNode = data.NodeWithKeyOrDefault("RenderPlayer");
if (renderPlayerNode != null)
{
var renderPlayerActorID = FieldLoader.GetValue<uint>("RenderPlayer", renderPlayerNode.Value.Value);

View File

@@ -133,9 +133,9 @@ namespace OpenRA.Mods.Common.Traits
};
}
void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray<MiniYamlNode> data)
void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data)
{
var groupsNode = data.FirstOrDefault(n => n.Key == "Groups");
var groupsNode = data.NodeWithKeyOrDefault("Groups");
if (groupsNode != null)
{
foreach (var n in groupsNode.Value.Nodes)

View File

@@ -28,7 +28,7 @@ namespace OpenRA.Mods.Common.Traits
protected static object LoadResourceTypes(MiniYaml yaml)
{
var ret = new Dictionary<string, ResourceLayerInfo.ResourceTypeInfo>();
var resources = yaml.Nodes.FirstOrDefault(n => n.Key == "ResourceTypes");
var resources = yaml.NodeWithKeyOrDefault("ResourceTypes");
if (resources != null)
foreach (var r in resources.Value.Nodes)
ret[r.Key] = new ResourceLayerInfo.ResourceTypeInfo(r.Value);

View File

@@ -84,16 +84,16 @@ namespace OpenRA.Mods.Common.Traits
protected static object LoadSpeeds(MiniYaml y)
{
var speeds = y.ToDictionary()["TerrainSpeeds"].Nodes;
var speeds = y.NodeWithKey("TerrainSpeeds").Value.Nodes;
var ret = new Dictionary<string, TerrainInfo>(speeds.Length);
foreach (var t in speeds)
{
var speed = FieldLoader.GetValue<int>("speed", t.Value.Value);
if (speed > 0)
{
var nodesDict = t.Value.ToDictionary();
var cost = nodesDict.TryGetValue("PathingCost", out var entry)
? FieldLoader.GetValue<short>("cost", entry.Value)
var pathingCost = t.Value.NodeWithKeyOrDefault("PathingCost");
var cost = pathingCost != null
? FieldLoader.GetValue<short>("cost", pathingCost.Value.Value)
: 10000 / speed;
ret.Add(t.Key, new TerrainInfo(speed, (short)cost));
}

View File

@@ -67,7 +67,7 @@ namespace OpenRA.Mods.Common.Traits
protected static object LoadResourceTypes(MiniYaml yaml)
{
var ret = new Dictionary<string, ResourceTypeInfo>();
var resources = yaml.Nodes.FirstOrDefault(n => n.Key == "ResourceTypes");
var resources = yaml.NodeWithKeyOrDefault("ResourceTypes");
if (resources != null)
foreach (var r in resources.Value.Nodes)
ret[r.Key] = new ResourceTypeInfo(r.Value);

View File

@@ -54,7 +54,7 @@ namespace OpenRA.Mods.Common.Traits
protected static object LoadResourceTypes(MiniYaml yaml)
{
var ret = new Dictionary<string, ResourceTypeInfo>();
var resources = yaml.Nodes.FirstOrDefault(n => n.Key == "ResourceTypes");
var resources = yaml.NodeWithKeyOrDefault("ResourceTypes");
if (resources != null)
foreach (var r in resources.Value.Nodes)
ret[r.Key] = new ResourceTypeInfo(r.Value);

View File

@@ -10,7 +10,6 @@
#endregion
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using OpenRA.Traits;
@@ -186,9 +185,9 @@ namespace OpenRA.Mods.Common.Traits
};
}
void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray<MiniYamlNode> data)
void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data)
{
var selectionNode = data.FirstOrDefault(n => n.Key == "Selection");
var selectionNode = data.NodeWithKeyOrDefault("Selection");
if (selectionNode != null)
{
var selected = FieldLoader.GetValue<uint[]>("Selection", selectionNode.Value.Value)

View File

@@ -56,11 +56,11 @@ namespace OpenRA.Mods.Common.Traits
public static object LoadInitialSmudges(MiniYaml yaml)
{
var nd = yaml.ToDictionary();
var smudges = new Dictionary<CPos, MapSmudge>();
if (nd.TryGetValue("InitialSmudges", out var smudgeYaml))
var smudgeYaml = yaml.NodeWithKeyOrDefault("InitialSmudges");
if (smudgeYaml != null)
{
foreach (var node in smudgeYaml.Nodes)
foreach (var node in smudgeYaml.Value.Nodes)
{
try
{

View File

@@ -230,7 +230,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules
if (!string.IsNullOrEmpty(sequenceNode.Value.Value))
explicitlyNamedSequences.Add(sequenceNode.Key);
var resolvedSequenceNode = resolvedImageNode.Value.Nodes.SingleOrDefault(n => n.Key == sequenceNode.Key);
var resolvedSequenceNode = resolvedImageNode.Value.NodeWithKeyOrDefault(sequenceNode.Key);
if (resolvedSequenceNode == null)
continue;

View File

@@ -99,7 +99,7 @@ namespace OpenRA.Mods.Common.UpdateRules
manualSteps.AddRange(rule.BeforeUpdate(modData));
var mapActorsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Actors");
var mapActorsNode = yaml.NodeWithKeyOrDefault("Actors");
if (mapActorsNode != null)
{
var mapActors = new YamlFileSet()
@@ -111,7 +111,7 @@ namespace OpenRA.Mods.Common.UpdateRules
files.AddRange(mapActors);
}
var mapRulesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Rules");
var mapRulesNode = yaml.NodeWithKeyOrDefault("Rules");
if (mapRulesNode != null)
{
if (rule is IBeforeUpdateActors before)
@@ -125,7 +125,7 @@ namespace OpenRA.Mods.Common.UpdateRules
files.AddRange(mapRules);
}
var mapWeaponsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Weapons");
var mapWeaponsNode = yaml.NodeWithKeyOrDefault("Weapons");
if (mapWeaponsNode != null)
{
if (rule is IBeforeUpdateWeapons before)
@@ -139,7 +139,7 @@ namespace OpenRA.Mods.Common.UpdateRules
files.AddRange(mapWeapons);
}
var mapSequencesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Sequences");
var mapSequencesNode = yaml.NodeWithKeyOrDefault("Sequences");
if (mapSequencesNode != null)
{
if (rule is IBeforeUpdateSequences before)
@@ -216,19 +216,19 @@ namespace OpenRA.Mods.Common.UpdateRules
continue;
var yaml = new MiniYamlBuilder(new MiniYaml(null, MiniYaml.FromStream(mapStream, package.Name, false)));
var mapRulesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Rules");
var mapRulesNode = yaml.NodeWithKeyOrDefault("Rules");
if (mapRulesNode != null)
foreach (var f in LoadExternalMapYaml(modData, mapRulesNode.Value, externalFilenames))
if (!modRules.Any(m => m.Item1 == f.Item1 && m.Item2 == f.Item2))
modRules.Add(f);
var mapWeaponsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Weapons");
var mapWeaponsNode = yaml.NodeWithKeyOrDefault("Weapons");
if (mapWeaponsNode != null)
foreach (var f in LoadExternalMapYaml(modData, mapWeaponsNode.Value, externalFilenames))
if (!modWeapons.Any(m => m.Item1 == f.Item1 && m.Item2 == f.Item2))
modWeapons.Add(f);
var mapSequencesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Sequences");
var mapSequencesNode = yaml.NodeWithKeyOrDefault("Sequences");
if (mapSequencesNode != null)
foreach (var f in LoadExternalMapYaml(modData, mapSequencesNode.Value, externalFilenames))
if (!modSequences.Any(m => m.Item1 == f.Item1 && m.Item2 == f.Item2))
@@ -285,7 +285,7 @@ namespace OpenRA.Mods.Common.UpdateRules
foreach (var manualStep in transform(modData, current))
yield return manualStep;
var childrenNode = current.Value.Nodes.FirstOrDefault(n => n.Key == "Children");
var childrenNode = current.Value.NodeWithKeyOrDefault("Children");
if (childrenNode != null)
foreach (var node in childrenNode.Value.Nodes)
if (node.Key != null)

View File

@@ -11,7 +11,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Lint;
using OpenRA.Traits;
@@ -47,27 +46,27 @@ namespace OpenRA.Mods.Common.Widgets
yield break;
var selectPrefix = "";
var selectPrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "SelectGroupKeyPrefix");
var selectPrefixNode = widgetNode.Value.NodeWithKeyOrDefault("SelectGroupKeyPrefix");
if (selectPrefixNode != null)
selectPrefix = selectPrefixNode.Value.Value;
var createPrefix = "";
var createPrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "CreateGroupKeyPrefix");
var createPrefixNode = widgetNode.Value.NodeWithKeyOrDefault("CreateGroupKeyPrefix");
if (createPrefixNode != null)
createPrefix = createPrefixNode.Value.Value;
var addToPrefix = "";
var addToPrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "AddToGroupKeyPrefix");
var addToPrefixNode = widgetNode.Value.NodeWithKeyOrDefault("AddToGroupKeyPrefix");
if (addToPrefixNode != null)
addToPrefix = addToPrefixNode.Value.Value;
var combineWithPrefix = "";
var combineWithPrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "CombineWithGroupKeyPrefix");
var combineWithPrefixNode = widgetNode.Value.NodeWithKeyOrDefault("CombineWithGroupKeyPrefix");
if (combineWithPrefixNode != null)
combineWithPrefix = combineWithPrefixNode.Value.Value;
var jumpToPrefix = "";
var jumpToPrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "JumpToGroupKeyPrefix");
var jumpToPrefixNode = widgetNode.Value.NodeWithKeyOrDefault("JumpToGroupKeyPrefix");
if (jumpToPrefixNode != null)
jumpToPrefix = jumpToPrefixNode.Value.Value;

View File

@@ -246,7 +246,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
void RunSourceActions(MiniYamlNode contentPackageYaml)
{
var sourceActionListYaml = contentPackageYaml.Value.Nodes.FirstOrDefault(x => x.Key == "Actions");
var sourceActionListYaml = contentPackageYaml.Value.NodeWithKeyOrDefault("Actions");
if (sourceActionListYaml == null)
return;
@@ -263,7 +263,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
foreach (var packageInstallationNode in modSource.Install.Where(x => x.Key == "ContentPackage"))
{
var packageName = packageInstallationNode.Value.Nodes.SingleOrDefault(x => x.Key == "Name")?.Value.Value;
var packageName = packageInstallationNode.Value.NodeWithKeyOrDefault("Name")?.Value.Value;
if (!string.IsNullOrEmpty(packageName) && selectedPackages.TryGetValue(packageName, out var required) && required)
RunSourceActions(packageInstallationNode);
}
@@ -338,9 +338,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
checkboxWidget.OnClick = () => selectedPackages[package.Identifier] = !selectedPackages[package.Identifier];
var contentPackageNode = source.Install.FirstOrDefault(x =>
x.Value.Nodes.FirstOrDefault(y => y.Key == "Name")?.Value.Value == package.Identifier);
x.Value.NodeWithKeyOrDefault("Name")?.Value.Value == package.Identifier);
var tooltipText = contentPackageNode?.Value.Nodes.FirstOrDefault(x => x.Key == nameof(ModContent.ModSource.TooltipText))?.Value.Value;
var tooltipText = contentPackageNode?.Value.NodeWithKeyOrDefault(nameof(ModContent.ModSource.TooltipText))?.Value.Value;
var tooltipIcon = containerWidget.Get<ImageWidget>("PACKAGE_INFO");
tooltipIcon.IsVisible = () => !string.IsNullOrWhiteSpace(tooltipText);
tooltipIcon.GetTooltipText = () => tooltipText;

View File

@@ -487,7 +487,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
continue;
var game = new MiniYamlBuilder(MiniYaml.FromString(bl.Data)[0].Value);
var idNode = game.Nodes.FirstOrDefault(n => n.Key == "Id");
var idNode = game.NodeWithKeyOrDefault("Id");
// Skip beacons created by this instance and replace Id by expected int value
if (idNode != null && idNode.Value.Value != Platform.SessionGUID.ToString())
@@ -495,7 +495,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
idNode.Value.Value = "-1";
// Rewrite the server address with the correct IP
var addressNode = game.Nodes.FirstOrDefault(n => n.Key == "Address");
var addressNode = game.NodeWithKeyOrDefault("Address");
if (addressNode != null)
addressNode.Value.Value = bl.Address.ToString().Split(':')[0] + ":" + addressNode.Value.Value.Split(':')[1];

View File

@@ -138,7 +138,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
foreach (var hg in hotkeyGroupsYaml.Nodes)
{
var typesNode = hg.Value.Nodes.FirstOrDefault(n => n.Key == "Types");
var typesNode = hg.Value.NodeWithKeyOrDefault("Types");
if (typesNode != null)
hotkeyGroups.Add(hg.Key, FieldLoader.GetValue<HashSet<string>>("Types", typesNode.Value.Value));
}

View File

@@ -132,12 +132,12 @@ namespace OpenRA.Mods.Common.Widgets
public static IEnumerable<string> LinterHotkeyNames(MiniYamlNode widgetNode, Action<string> emitError)
{
var prefix = "";
var prefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "HotkeyPrefix");
var prefixNode = widgetNode.Value.NodeWithKeyOrDefault("HotkeyPrefix");
if (prefixNode != null)
prefix = prefixNode.Value.Value;
var count = 0;
var countNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "HotkeyCount");
var countNode = widgetNode.Value.NodeWithKeyOrDefault("HotkeyCount");
if (countNode != null)
count = FieldLoader.GetValue<int>("HotkeyCount", countNode.Value.Value);

View File

@@ -70,12 +70,12 @@ namespace OpenRA.Mods.Common.Widgets
public static IEnumerable<string> LinterHotkeyNames(MiniYamlNode widgetNode, Action<string> emitError)
{
var prefix = "";
var prefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "HotkeyPrefix");
var prefixNode = widgetNode.Value.NodeWithKeyOrDefault("HotkeyPrefix");
if (prefixNode != null)
prefix = prefixNode.Value.Value;
var count = 0;
var countNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "HotkeyCount");
var countNode = widgetNode.Value.NodeWithKeyOrDefault("HotkeyCount");
if (countNode != null)
count = FieldLoader.GetValue<int>("HotkeyCount", countNode.Value.Value);

View File

@@ -105,17 +105,17 @@ namespace OpenRA.Mods.Common.Widgets
public static IEnumerable<string> LinterHotkeyNames(MiniYamlNode widgetNode, Action<string> emitError)
{
var savePrefix = "";
var savePrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "BookmarkSaveKeyPrefix");
var savePrefixNode = widgetNode.Value.NodeWithKeyOrDefault("BookmarkSaveKeyPrefix");
if (savePrefixNode != null)
savePrefix = savePrefixNode.Value.Value;
var restorePrefix = "";
var restorePrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "BookmarkRestoreKeyPrefix");
var restorePrefixNode = widgetNode.Value.NodeWithKeyOrDefault("BookmarkRestoreKeyPrefix");
if (restorePrefixNode != null)
restorePrefix = restorePrefixNode.Value.Value;
var count = 0;
var countNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "BookmarkKeyCount");
var countNode = widgetNode.Value.NodeWithKeyOrDefault("BookmarkKeyCount");
if (countNode != null)
count = FieldLoader.GetValue<int>("BookmarkKeyCount", countNode.Value.Value);

View File

@@ -384,7 +384,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands
actorNodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + actorNodes.Count), a.Save()));
if (map.PlayerDefinitions.Concat(playerNodes).All(
x => x.Value.Nodes.Single(y => y.Key == "Name").Value.Value != kvp.Owner))
x => x.Value.NodeWithKey("Name").Value.Value != kvp.Owner))
{
var playerInfo = PlayerReferenceDataByPlayerName[kvp.Owner];
var playerReference = new PlayerReference

View File

@@ -247,7 +247,7 @@ Test:
.First(n => n.Key == "Test").Value.Nodes;
Assert.IsTrue(result.Any(n => n.Key == "MockString"), "Node should have the MockString child, but does not.");
Assert.IsTrue(result.First(n => n.Key == "MockString").Value.ToDictionary()["AString"].Value == "Override",
Assert.IsTrue(result.First(n => n.Key == "MockString").Value.NodeWithKey("AString").Value.Value == "Override",
"MockString value has not been set with the correct override value for AString.");
}
@@ -272,7 +272,7 @@ Test:
.First(n => n.Key == "Test").Value.Nodes;
Assert.IsTrue(result.Any(n => n.Key == "MockString"), "Node should have the MockString child, but does not.");
Assert.IsTrue(result.First(n => n.Key == "MockString").Value.ToDictionary()["AString"].Value == "Override",
Assert.IsTrue(result.First(n => n.Key == "MockString").Value.NodeWithKey("AString").Value.Value == "Override",
"MockString value has not been set with the correct override value for AString.");
}

View File

@@ -779,7 +779,6 @@ ctdam:
damaged-idle-water-b:
Filename: ctdam_b.shp
Start: 24
Start: 8
Length: 8
Tick: 200

View File

@@ -1667,7 +1667,6 @@ napuls.nod:
ZOffset: 512
BlendMode: Additive
IgnoreWorldTint: True
IgnoreWorldTint: True
DepthSprite:
icon:
Filename: sidebar-nod|empicon.shp