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

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