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

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