From b58c1ea5bc3162337bfdee12a5a741a2fc7d8c73 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sat, 13 Jan 2024 11:24:41 +0000 Subject: [PATCH] Provide names and pools when creating MiniYaml. - Rename the filename parameter to name and make it mandatory. Review all callers and ensure a useful string is provided as input, to ensure sufficient context is included for logging and debugging. This can be a filename, url, or any arbitrary text so include whatever context seems reasonable. - When several MiniYamls are created that have similar content, provide a shared string pool. This allows strings that are common between all the yaml to be shared, reducing long term memory usage. We also change the pool from a dictionary to a set. Originally a Dictionary had to be used so we could call TryGetValue to get a reference to the pooled string. Now that more recent versions of dotnet provide a TryGetValue on HashSet, we can use a set directly without the memory wasted by having to store both keys and values in a dictionary. --- OpenRA.Game/ExternalMods.cs | 5 ++- OpenRA.Game/Exts.cs | 14 +++++++ OpenRA.Game/FileFormats/ReplayMetadata.cs | 2 +- OpenRA.Game/GameInformation.cs | 4 +- OpenRA.Game/Graphics/ChromeProvider.cs | 3 +- OpenRA.Game/Graphics/CursorProvider.cs | 3 +- OpenRA.Game/LocalPlayerProfile.cs | 5 ++- OpenRA.Game/Manifest.cs | 5 ++- OpenRA.Game/Map/Map.cs | 2 +- OpenRA.Game/Map/MapCache.cs | 5 ++- OpenRA.Game/Map/MapPreview.cs | 11 ++++-- OpenRA.Game/MiniYaml.cs | 37 +++++++++++-------- OpenRA.Game/ModData.cs | 3 +- OpenRA.Game/Network/GameSave.cs | 8 ++-- OpenRA.Game/Network/Handshake.cs | 8 ++-- OpenRA.Game/Network/ReplayConnection.cs | 2 +- OpenRA.Game/Network/Session.cs | 4 +- OpenRA.Game/Network/UnitOrders.cs | 18 ++++----- OpenRA.Game/Server/Server.cs | 13 ++++--- OpenRA.Game/Widgets/ChromeMetrics.cs | 5 ++- OpenRA.Game/Widgets/WidgetLoader.cs | 4 +- .../Lint/CheckTranslationReference.cs | 2 +- .../ServerTraits/LobbyCommands.cs | 2 +- .../20210321/RemovePlaceBuildingPalette.cs | 2 +- .../20210321/RemoveRenderSpritesScale.cs | 2 +- .../20210321/RemoveSmokeTrailWhenDamaged.cs | 2 +- .../Rules/20210321/ReplaceShadowPalette.cs | 6 +-- .../ReplaceWithColoredOverlayPalette.cs | 2 +- .../UnhardcodeVeteranProductionIconOverlay.cs | 2 +- .../20230225/ExplicitSequenceFilenames.cs | 5 ++- .../RemoveExperienceFromInfiltrates.cs | 2 +- .../RemoveSequenceHasEmbeddedPalette.cs | 2 +- .../RemoveValidRelationsFromCapturable.cs | 2 +- OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs | 12 +++--- .../UtilityCommands/ExtractYamlStrings.cs | 4 +- .../Widgets/Logic/MissionBrowserLogic.cs | 3 +- .../Widgets/Logic/PlayerProfileLogic.cs | 5 ++- .../Widgets/Logic/ServerListLogic.cs | 6 ++- OpenRA.Test/OpenRA.Game/MiniYamlTest.cs | 32 ++++++++-------- 39 files changed, 146 insertions(+), 108 deletions(-) diff --git a/OpenRA.Game/ExternalMods.cs b/OpenRA.Game/ExternalMods.cs index 40dc1c30c4..d79ce87b52 100644 --- a/OpenRA.Game/ExternalMods.cs +++ b/OpenRA.Game/ExternalMods.cs @@ -66,6 +66,7 @@ namespace OpenRA // Several types of support directory types are available, depending on // how the player has installed and launched the game. // Read registration metadata from all of them + var stringPool = new HashSet(); // Reuse common strings in YAML foreach (var source in GetSupportDirs(ModRegistration.User | ModRegistration.System)) { var metadataPath = Path.Combine(source, "ModMetadata"); @@ -76,7 +77,7 @@ namespace OpenRA { try { - var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value; + var yaml = MiniYaml.FromFile(path, stringPool: stringPool).First().Value; LoadMod(yaml, path); } catch (Exception e) @@ -205,7 +206,7 @@ namespace OpenRA string modKey = null; try { - var yaml = MiniYaml.FromStream(File.OpenRead(path), path).First().Value; + var yaml = MiniYaml.FromFile(path).First().Value; var m = FieldLoader.Load(yaml); modKey = ExternalMod.MakeKey(m); diff --git a/OpenRA.Game/Exts.cs b/OpenRA.Game/Exts.cs index 7f3475cd52..a90f1cb652 100644 --- a/OpenRA.Game/Exts.cs +++ b/OpenRA.Game/Exts.cs @@ -130,6 +130,20 @@ namespace OpenRA return ret; } + public static T GetOrAdd(this HashSet set, T value) + { + if (!set.TryGetValue(value, out var ret)) + set.Add(ret = value); + return ret; + } + + public static T GetOrAdd(this HashSet set, T value, Func createFn) + { + if (!set.TryGetValue(value, out var ret)) + set.Add(ret = createFn(value)); + return ret; + } + public static int IndexOf(this T[] array, T value) { return Array.IndexOf(array, value); diff --git a/OpenRA.Game/FileFormats/ReplayMetadata.cs b/OpenRA.Game/FileFormats/ReplayMetadata.cs index 0d1fbb15af..bfb6d009f7 100644 --- a/OpenRA.Game/FileFormats/ReplayMetadata.cs +++ b/OpenRA.Game/FileFormats/ReplayMetadata.cs @@ -48,7 +48,7 @@ namespace OpenRA.FileFormats // Read game info (max 100K limit as a safeguard against corrupted files) var data = fs.ReadLengthPrefixedString(Encoding.UTF8, 1024 * 100); - GameInfo = GameInformation.Deserialize(data); + GameInfo = GameInformation.Deserialize(data, path); } public void Write(BinaryWriter writer) diff --git a/OpenRA.Game/GameInformation.cs b/OpenRA.Game/GameInformation.cs index 6845ae24f9..82cfccf22d 100644 --- a/OpenRA.Game/GameInformation.cs +++ b/OpenRA.Game/GameInformation.cs @@ -49,13 +49,13 @@ namespace OpenRA playersByRuntime = new Dictionary(); } - public static GameInformation Deserialize(string data) + public static GameInformation Deserialize(string data, string path) { try { var info = new GameInformation(); - var nodes = MiniYaml.FromString(data); + var nodes = MiniYaml.FromString(data, path); foreach (var node in nodes) { var keyParts = node.Key.Split('@'); diff --git a/OpenRA.Game/Graphics/ChromeProvider.cs b/OpenRA.Game/Graphics/ChromeProvider.cs index 713588950f..165bc16b22 100644 --- a/OpenRA.Game/Graphics/ChromeProvider.cs +++ b/OpenRA.Game/Graphics/ChromeProvider.cs @@ -77,8 +77,9 @@ namespace OpenRA.Graphics cachedPanelSprites = new Dictionary(); cachedCollectionSheets = new Dictionary(); + var stringPool = new HashSet(); // Reuse common strings in YAML var chrome = MiniYaml.Merge(modData.Manifest.Chrome - .Select(s => MiniYaml.FromStream(fileSystem.Open(s), s))); + .Select(s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool))); foreach (var c in chrome) if (!c.Key.StartsWith('^')) diff --git a/OpenRA.Game/Graphics/CursorProvider.cs b/OpenRA.Game/Graphics/CursorProvider.cs index 85919dbe7d..831a3db7dc 100644 --- a/OpenRA.Game/Graphics/CursorProvider.cs +++ b/OpenRA.Game/Graphics/CursorProvider.cs @@ -24,8 +24,9 @@ namespace OpenRA.Graphics public CursorProvider(ModData modData) { var fileSystem = modData.DefaultFileSystem; + var stringPool = new HashSet(); // Reuse common strings in YAML var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select( - s => MiniYaml.FromStream(fileSystem.Open(s), s))); + s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool))); var cursorsYaml = new MiniYaml(null, sequenceYaml).NodeWithKey("Cursors").Value; diff --git a/OpenRA.Game/LocalPlayerProfile.cs b/OpenRA.Game/LocalPlayerProfile.cs index f7a67655e0..63b4ff31c3 100644 --- a/OpenRA.Game/LocalPlayerProfile.cs +++ b/OpenRA.Game/LocalPlayerProfile.cs @@ -84,10 +84,11 @@ namespace OpenRA { var client = HttpClientFactory.Create(); - var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint); + var url = playerDatabase.Profile + Fingerprint; + var httpResponseMessage = await client.GetAsync(url); var result = await httpResponseMessage.Content.ReadAsStreamAsync(); - var yaml = MiniYaml.FromStream(result).First(); + var yaml = MiniYaml.FromStream(result, url).First(); if (yaml.Key == "Player") { innerData = FieldLoader.Load(yaml.Value); diff --git a/OpenRA.Game/Manifest.cs b/OpenRA.Game/Manifest.cs index bf75defec2..9bf719e7d2 100644 --- a/OpenRA.Game/Manifest.cs +++ b/OpenRA.Game/Manifest.cs @@ -94,7 +94,8 @@ namespace OpenRA Id = modId; Package = package; - var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), "mod.yaml"); + var stringPool = new HashSet(); // Reuse common strings in YAML + var nodes = MiniYaml.FromStream(package.GetStream("mod.yaml"), $"{package.Name}:mod.yaml", stringPool: stringPool); for (var i = nodes.Count - 1; i >= 0; i--) { if (nodes[i].Key != "Include") @@ -107,7 +108,7 @@ namespace OpenRA throw new YamlException($"{nodes[i].Location}: File `{filename}` not found."); nodes.RemoveAt(i); - nodes.InsertRange(i, MiniYaml.FromStream(contents, filename)); + nodes.InsertRange(i, MiniYaml.FromStream(contents, $"{package.Name}:{filename}", stringPool: stringPool)); } // Merge inherited overrides diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 18c04510ac..800042dcdd 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -361,7 +361,7 @@ namespace OpenRA if (!Package.Contains("map.yaml") || !Package.Contains("map.bin")) throw new InvalidDataException($"Not a valid map\n File: {package.Name}"); - var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), package.Name)); + var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), $"{package.Name}:map.yaml")); foreach (var field in YamlFields) field.Deserialize(this, yaml); diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index b6b3025868..12573558d2 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -38,7 +38,7 @@ namespace OpenRA readonly object syncRoot = new(); readonly Queue generateMinimap = new(); - public Dictionary StringPool { get; } = new Dictionary(); + public HashSet StringPool { get; } = new(); readonly List mapDirectoryTrackers = new(); @@ -238,6 +238,7 @@ namespace OpenRA Task.Run(async () => { var client = HttpClientFactory.Create(); + var stringPool = new HashSet(); // Reuse common strings in YAML // Limit each query to 50 maps at a time to avoid request size limits for (var i = 0; i < queryUids.Count; i += 50) @@ -249,7 +250,7 @@ namespace OpenRA var httpResponseMessage = await client.GetAsync(url); var result = await httpResponseMessage.Content.ReadAsStreamAsync(); - var yaml = MiniYaml.FromStream(result); + var yaml = MiniYaml.FromStream(result, url, stringPool: stringPool); foreach (var kv in yaml) previews[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, modData.Manifest.MapCompatibility, mapDetailsReceived); diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index bda6b01032..393ad68905 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -140,9 +140,10 @@ namespace OpenRA files = files.Append(mapFiles); } + var stringPool = new HashSet(); // Reuse common strings in YAML var sources = modDataRules.Select(x => x.Where(IsLoadableRuleDefinition).ToList()) - .Concat(files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s).Where(IsLoadableRuleDefinition).ToList())); + .Concat(files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s, stringPool: stringPool).Where(IsLoadableRuleDefinition).ToList())); if (RuleDefinitions.Nodes.Length > 0) sources = sources.Append(RuleDefinitions.Nodes.Where(IsLoadableRuleDefinition).ToList()); @@ -334,7 +335,7 @@ namespace OpenRA if (yamlStream == null) throw new FileNotFoundException("Required file map.yaml not present in this map"); - yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml", stringPool: cache.StringPool)).ToDictionary(); + yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, $"{p.Name}:map.yaml", stringPool: cache.StringPool)).ToDictionary(); } Package = p; @@ -475,10 +476,12 @@ namespace OpenRA } var playersString = Encoding.UTF8.GetString(Convert.FromBase64String(r.players_block)); - newData.Players = new MapPlayers(MiniYaml.FromString(playersString)); + newData.Players = new MapPlayers(MiniYaml.FromString(playersString, + $"{yaml.NodeWithKey(nameof(r.players_block)).Location.Name}:{nameof(r.players_block)}")); var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules)); - var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString)).ToDictionary(); + var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString, + $"{yaml.NodeWithKey(nameof(r.rules)).Location.Name}:{nameof(r.rules)}")).ToDictionary(); newData.SetCustomRules(modData, this, rulesYaml, null); // Map is for a different mod: update its information so it can be displayed diff --git a/OpenRA.Game/MiniYaml.cs b/OpenRA.Game/MiniYaml.cs index 833b5d2731..d2dd124eb1 100644 --- a/OpenRA.Game/MiniYaml.cs +++ b/OpenRA.Game/MiniYaml.cs @@ -61,16 +61,16 @@ namespace OpenRA { public readonly struct SourceLocation { - public readonly string Filename; + public readonly string Name; public readonly int Line; - public SourceLocation(string filename, int line) + public SourceLocation(string name, int line) { - Filename = filename; + Name = name; Line = line; } - public override string ToString() { return $"{Filename}:{Line}"; } + public override string ToString() { return $"{Name}:{Line}"; } } public readonly SourceLocation Location; @@ -203,9 +203,13 @@ namespace OpenRA Nodes = ImmutableArray.CreateRange(nodes); } - static List FromLines(IEnumerable> lines, string filename, bool discardCommentsAndWhitespace, Dictionary stringPool) + static List FromLines(IEnumerable> lines, string name, bool discardCommentsAndWhitespace, HashSet stringPool) { - stringPool ??= new Dictionary(); + // YAML config often contains repeated strings for key, values, comments. + // Pool these strings so we only need one copy of each unique string. + // This saves on long-term memory usage as parsed values can often live a long time. + // A caller can also provide a pool as input, allowing de-duplication across multiple parses. + stringPool ??= new HashSet(); var result = new List> { @@ -227,7 +231,7 @@ namespace OpenRA ReadOnlySpan key = default; ReadOnlySpan value = default; ReadOnlySpan comment = default; - var location = new MiniYamlNode.SourceLocation(filename, lineNo); + var location = new MiniYamlNode.SourceLocation(name, lineNo); if (line.Length > 0) { @@ -330,9 +334,9 @@ namespace OpenRA // (i.e. a lone # at the end of a line) can be correctly re-serialized var commentString = comment == default ? null : comment.ToString(); - keyString = keyString == null ? null : stringPool.GetOrAdd(keyString, keyString); - valueString = valueString == null ? null : stringPool.GetOrAdd(valueString, valueString); - commentString = commentString == null ? null : stringPool.GetOrAdd(commentString, commentString); + keyString = keyString == null ? null : stringPool.GetOrAdd(keyString); + valueString = valueString == null ? null : stringPool.GetOrAdd(valueString); + commentString = commentString == null ? null : stringPool.GetOrAdd(commentString); parsedLines.Add((level, keyString, valueString, commentString, location)); } @@ -376,19 +380,19 @@ namespace OpenRA } } - public static List FromFile(string path, bool discardCommentsAndWhitespace = true, Dictionary stringPool = null) + public static List FromFile(string path, bool discardCommentsAndWhitespace = true, HashSet stringPool = null) { return FromStream(File.OpenRead(path), path, discardCommentsAndWhitespace, stringPool); } - public static List FromStream(Stream s, string fileName = "", bool discardCommentsAndWhitespace = true, Dictionary stringPool = null) + public static List FromStream(Stream s, string name, bool discardCommentsAndWhitespace = true, HashSet stringPool = null) { - return FromLines(s.ReadAllLinesAsMemory(), fileName, discardCommentsAndWhitespace, stringPool); + return FromLines(s.ReadAllLinesAsMemory(), name, discardCommentsAndWhitespace, stringPool); } - public static List FromString(string text, string fileName = "", bool discardCommentsAndWhitespace = true, Dictionary stringPool = null) + public static List FromString(string text, string name, bool discardCommentsAndWhitespace = true, HashSet stringPool = null) { - return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None).Select(s => s.AsMemory()), fileName, discardCommentsAndWhitespace, stringPool); + return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None).Select(s => s.AsMemory()), name, discardCommentsAndWhitespace, stringPool); } public static List Merge(IEnumerable> sources) @@ -605,7 +609,8 @@ namespace OpenRA files = files.Append(mapFiles); } - IEnumerable> yaml = files.Select(s => FromStream(fileSystem.Open(s), s)); + var stringPool = new HashSet(); // Reuse common strings in YAML + IEnumerable> yaml = files.Select(s => FromStream(fileSystem.Open(s), s, stringPool: stringPool)); if (mapRules != null && mapRules.Nodes.Length > 0) yaml = yaml.Append(mapRules.Nodes); diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 22b3a59dc4..a7c148bfa5 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -158,7 +158,8 @@ namespace OpenRA public List[] GetRulesYaml() { - return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s)).ToArray(); + var stringPool = new HashSet(); // Reuse common strings in YAML + return Manifest.Rules.Select(s => MiniYaml.FromStream(DefaultFileSystem.Open(s), s, stringPool: stringPool)).ToArray(); } public void Dispose() diff --git a/OpenRA.Game/Network/GameSave.cs b/OpenRA.Game/Network/GameSave.cs index c9e81ebcef..29f8a3a224 100644 --- a/OpenRA.Game/Network/GameSave.cs +++ b/OpenRA.Game/Network/GameSave.cs @@ -122,10 +122,10 @@ namespace OpenRA.Network LastSyncFrame = rs.ReadInt32(); lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength); - var globalSettings = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength)); + var globalSettings = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:globalSettings"); GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value); - var slots = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength)); + var slots = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slots"); Slots = new Dictionary(); foreach (var s in slots) { @@ -133,7 +133,7 @@ namespace OpenRA.Network Slots.Add(slot.PlayerReference, slot); } - var slotClients = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength)); + var slotClients = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:slotClients"); SlotClients = new Dictionary(); foreach (var s in slotClients) { @@ -144,7 +144,7 @@ namespace OpenRA.Network if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker) throw new InvalidDataException("Invalid orasav file"); - var traitData = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength)); + var traitData = MiniYaml.FromString(rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength), $"{filepath}:traitData"); foreach (var td in traitData) TraitData.Add(Exts.ParseInt32Invariant(td.Key), td.Value); diff --git a/OpenRA.Game/Network/Handshake.cs b/OpenRA.Game/Network/Handshake.cs index ecf56b6b84..9f118bb03b 100644 --- a/OpenRA.Game/Network/Handshake.cs +++ b/OpenRA.Game/Network/Handshake.cs @@ -20,10 +20,10 @@ namespace OpenRA.Network public string Version; public string AuthToken; - public static HandshakeRequest Deserialize(string data) + public static HandshakeRequest Deserialize(string data, string name) { var handshake = new HandshakeRequest(); - FieldLoader.Load(handshake, MiniYaml.FromString(data).First().Value); + FieldLoader.Load(handshake, MiniYaml.FromString(data, name).First().Value); return handshake; } @@ -51,14 +51,14 @@ namespace OpenRA.Network [FieldLoader.Ignore] public Session.Client Client; - public static HandshakeResponse Deserialize(string data) + public static HandshakeResponse Deserialize(string data, string name) { var handshake = new HandshakeResponse { Client = new Session.Client() }; - var ys = MiniYaml.FromString(data); + var ys = MiniYaml.FromString(data, name); foreach (var y in ys) { switch (y.Key) diff --git a/OpenRA.Game/Network/ReplayConnection.cs b/OpenRA.Game/Network/ReplayConnection.cs index e03d46bbcb..b372d23acb 100644 --- a/OpenRA.Game/Network/ReplayConnection.cs +++ b/OpenRA.Game/Network/ReplayConnection.cs @@ -69,7 +69,7 @@ namespace OpenRA.Network if (o.OrderString == "StartGame") IsValid = true; else if (o.OrderString == "SyncInfo" && !IsValid) - LobbyInfo = Session.Deserialize(o.TargetString); + LobbyInfo = Session.Deserialize(o.TargetString, o.OrderString); } } } diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index d677cd80be..143a9575fc 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -41,13 +41,13 @@ namespace OpenRA.Network return null; } - public static Session Deserialize(string data) + public static Session Deserialize(string data, string name) { try { var session = new Session(); - var nodes = MiniYaml.FromString(data); + var nodes = MiniYaml.FromString(data, name); foreach (var node in nodes) { var strings = node.Key.Split('@'); diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index a2e365b1ad..a8cc6ea312 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -62,7 +62,7 @@ namespace OpenRA.Network if (string.IsNullOrEmpty(order.TargetString)) break; - var yaml = MiniYaml.FromString(order.TargetString); + var yaml = MiniYaml.FromString(order.TargetString, order.OrderString); foreach (var node in yaml) { var localizedMessage = new LocalizedMessage(node.Value); @@ -183,7 +183,7 @@ namespace OpenRA.Network if (!string.IsNullOrEmpty(order.TargetString)) { - var data = MiniYaml.FromString(order.TargetString); + var data = MiniYaml.FromString(order.TargetString, order.OrderString); var saveLastOrdersFrame = data.FirstOrDefault(n => n.Key == "SaveLastOrdersFrame"); if (saveLastOrdersFrame != null) orderManager.GameSaveLastFrame = @@ -203,7 +203,7 @@ namespace OpenRA.Network case "SaveTraitData": { - var data = MiniYaml.FromString(order.TargetString)[0]; + var data = MiniYaml.FromString(order.TargetString, order.OrderString)[0]; var traitIndex = Exts.ParseInt32Invariant(data.Key); world?.AddGameSaveTraitData(traitIndex, data.Value); @@ -244,7 +244,7 @@ namespace OpenRA.Network { // Switch to the server's mod if we need and are able to var mod = Game.ModData.Manifest; - var request = HandshakeRequest.Deserialize(order.TargetString); + var request = HandshakeRequest.Deserialize(order.TargetString, order.OrderString); var externalKey = ExternalMod.MakeKey(request.Mod, request.Version); if ((request.Mod != mod.Id || request.Version != mod.Metadata.Version) && @@ -312,7 +312,7 @@ namespace OpenRA.Network case "SyncInfo": { - orderManager.LobbyInfo = Session.Deserialize(order.TargetString); + orderManager.LobbyInfo = Session.Deserialize(order.TargetString, order.OrderString); Game.SyncLobbyInfo(); break; } @@ -320,7 +320,7 @@ namespace OpenRA.Network case "SyncLobbyClients": { var clients = new List(); - var nodes = MiniYaml.FromString(order.TargetString); + var nodes = MiniYaml.FromString(order.TargetString, order.OrderString); foreach (var node in nodes) { var strings = node.Key.Split('@'); @@ -336,7 +336,7 @@ namespace OpenRA.Network case "SyncLobbySlots": { var slots = new Dictionary(); - var nodes = MiniYaml.FromString(order.TargetString); + var nodes = MiniYaml.FromString(order.TargetString, order.OrderString); foreach (var node in nodes) { var strings = node.Key.Split('@'); @@ -354,7 +354,7 @@ namespace OpenRA.Network case "SyncLobbyGlobalSettings": { - var nodes = MiniYaml.FromString(order.TargetString); + var nodes = MiniYaml.FromString(order.TargetString, order.OrderString); foreach (var node in nodes) { var strings = node.Key.Split('@'); @@ -368,7 +368,7 @@ namespace OpenRA.Network case "SyncConnectionQuality": { - var nodes = MiniYaml.FromString(order.TargetString); + var nodes = MiniYaml.FromString(order.TargetString, order.OrderString); foreach (var node in nodes) { var strings = node.Key.Split('@'); diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 2eadac0ea7..462c6ea35e 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -463,7 +463,7 @@ namespace OpenRA.Server Conns.Add(newConn); } - void ValidateClient(Connection newConn, string data) + void ValidateClient(Connection newConn, string data, string name) { try { @@ -476,7 +476,7 @@ namespace OpenRA.Server return; } - var handshake = HandshakeResponse.Deserialize(data); + var handshake = HandshakeResponse.Deserialize(data, name); if (!string.IsNullOrEmpty(Settings.Password) && handshake.Password != Settings.Password) { @@ -618,10 +618,11 @@ namespace OpenRA.Server try { var httpClient = HttpClientFactory.Create(); - var httpResponseMessage = await httpClient.GetAsync(playerDatabase.Profile + handshake.Fingerprint); + var url = playerDatabase.Profile + handshake.Fingerprint; + var httpResponseMessage = await httpClient.GetAsync(url); var result = await httpResponseMessage.Content.ReadAsStreamAsync(); - var yaml = MiniYaml.FromStream(result).First(); + var yaml = MiniYaml.FromStream(result, url).First(); if (yaml.Key == "Player") { profile = FieldLoader.Load(yaml.Value); @@ -980,7 +981,7 @@ namespace OpenRA.Server if (!conn.Validated) { if (o.OrderString == "HandshakeResponse") - ValidateClient(conn, o.TargetString); + ValidateClient(conn, o.TargetString, o.OrderString); else { Log.Write("server", $"Rejected connection from {conn.EndPoint}; Order `{o.OrderString}` is not a `HandshakeResponse`."); @@ -1015,7 +1016,7 @@ namespace OpenRA.Server { if (GameSave != null) { - var data = MiniYaml.FromString(o.TargetString)[0]; + var data = MiniYaml.FromString(o.TargetString, o.OrderString)[0]; GameSave.AddTraitData(OpenRA.Exts.ParseInt32Invariant(data.Key), data.Value); } diff --git a/OpenRA.Game/Widgets/ChromeMetrics.cs b/OpenRA.Game/Widgets/ChromeMetrics.cs index 9770b4eb0c..a0020e2bc8 100644 --- a/OpenRA.Game/Widgets/ChromeMetrics.cs +++ b/OpenRA.Game/Widgets/ChromeMetrics.cs @@ -20,9 +20,10 @@ namespace OpenRA.Widgets public static void Initialize(ModData modData) { - data = new Dictionary(); + var stringPool = new HashSet(); // Reuse common strings in YAML var metrics = MiniYaml.Merge(modData.Manifest.ChromeMetrics.Select( - y => MiniYaml.FromStream(modData.DefaultFileSystem.Open(y), y))); + y => MiniYaml.FromStream(modData.DefaultFileSystem.Open(y), y, stringPool: stringPool))); + data = new Dictionary(); foreach (var m in metrics) foreach (var n in m.Value.Nodes) data[n.Key] = n.Value.Value; diff --git a/OpenRA.Game/Widgets/WidgetLoader.cs b/OpenRA.Game/Widgets/WidgetLoader.cs index 8cdbdb0777..c68dc94589 100644 --- a/OpenRA.Game/Widgets/WidgetLoader.cs +++ b/OpenRA.Game/Widgets/WidgetLoader.cs @@ -25,7 +25,9 @@ namespace OpenRA { this.modData = modData; - foreach (var file in modData.Manifest.ChromeLayout.Select(a => MiniYaml.FromStream(modData.DefaultFileSystem.Open(a), a))) + var stringPool = new HashSet(); // Reuse common strings in YAML + foreach (var file in modData.Manifest.ChromeLayout.Select( + a => MiniYaml.FromStream(modData.DefaultFileSystem.Open(a), a, stringPool: stringPool))) foreach (var w in file) { var key = w.Key[(w.Key.IndexOf('@') + 1)..]; diff --git a/OpenRA.Mods.Common/Lint/CheckTranslationReference.cs b/OpenRA.Mods.Common/Lint/CheckTranslationReference.cs index e97a59b163..50e3371cce 100644 --- a/OpenRA.Mods.Common/Lint/CheckTranslationReference.cs +++ b/OpenRA.Mods.Common/Lint/CheckTranslationReference.cs @@ -148,7 +148,7 @@ namespace OpenRA.Mods.Common.Lint foreach (var filename in modData.Manifest.ChromeLayout) { - var nodes = MiniYaml.FromStream(modData.DefaultFileSystem.Open(filename)); + var nodes = MiniYaml.FromStream(modData.DefaultFileSystem.Open(filename), filename); foreach (var node in nodes) CheckChrome(node, translation, Language, emitError, emitWarning, translatableFields); } diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index 3c2bbf87a8..ca68d18acd 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -1270,7 +1270,7 @@ namespace OpenRA.Mods.Common.Server try { - server.LobbyInfo = Session.Deserialize(s); + server.LobbyInfo = Session.Deserialize(s, nameof(SyncLobby)); server.SyncLobbyInfo(); } catch (Exception) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlaceBuildingPalette.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlaceBuildingPalette.cs index 3d7bd41ac5..90c233edee 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlaceBuildingPalette.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlaceBuildingPalette.cs @@ -64,7 +64,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules } if (removed > 0) - locations.Add($"{actorNode.Key} ({actorNode.Location.Filename})"); + locations.Add($"{actorNode.Key} ({actorNode.Location.Name})"); yield break; } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveRenderSpritesScale.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveRenderSpritesScale.cs index fe85dd4bd1..065b4795b2 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveRenderSpritesScale.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveRenderSpritesScale.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules { foreach (var renderSprites in actorNode.ChildrenMatching("RenderSprites")) if (renderSprites.RemoveNodes("Scale") > 0) - yield return $"The actor-level scaling has been removed from {actorNode.Key} ({actorNode.Location.Filename}).\n" + + yield return $"The actor-level scaling has been removed from {actorNode.Key} ({actorNode.Location.Name}).\n" + "You must manually define Scale on its sequences instead."; } } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveSmokeTrailWhenDamaged.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveSmokeTrailWhenDamaged.cs index b620779a06..17cbd82dc6 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveSmokeTrailWhenDamaged.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveSmokeTrailWhenDamaged.cs @@ -40,7 +40,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { - var locationKey = $"{actorNode.Key} ({actorNode.Location.Filename})"; + var locationKey = $"{actorNode.Key} ({actorNode.Location.Name})"; var anyConditionalSmokeTrail = false; foreach (var smokeTrail in actorNode.ChildrenMatching("SmokeTrailWhenDamaged")) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceShadowPalette.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceShadowPalette.cs index a910de648d..309af60a35 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceShadowPalette.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceShadowPalette.cs @@ -37,7 +37,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules { foreach (var projectileNode in weaponNode.ChildrenMatching("Projectile")) if (projectileNode.RemoveNodes("ShadowPalette") > 0) - locations.Add($"{weaponNode.Key}: {weaponNode.Key} ({weaponNode.Location.Filename})"); + locations.Add($"{weaponNode.Key}: {weaponNode.Key} ({weaponNode.Location.Name})"); yield break; } @@ -46,11 +46,11 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules { foreach (var node in actorNode.ChildrenMatching("WithShadow")) if (node.RemoveNodes("Palette") > 0) - locations.Add($"{actorNode.Key}: {node.Key} ({actorNode.Location.Filename})"); + locations.Add($"{actorNode.Key}: {node.Key} ({actorNode.Location.Name})"); foreach (var node in actorNode.ChildrenMatching("WithParachute")) if (node.RemoveNodes("ShadowPalette") > 0) - locations.Add($"{actorNode.Key}: {node.Key} ({actorNode.Location.Filename})"); + locations.Add($"{actorNode.Key}: {node.Key} ({actorNode.Location.Name})"); yield break; } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceWithColoredOverlayPalette.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceWithColoredOverlayPalette.cs index 6fbe10ad83..26949bc861 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceWithColoredOverlayPalette.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceWithColoredOverlayPalette.cs @@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules { foreach (var node in actorNode.ChildrenMatching("WithColoredOverlay")) if (node.RemoveNodes("Palette") > 0) - locations.Add($"{actorNode.Key}: {node.Key} ({actorNode.Location.Filename})"); + locations.Add($"{actorNode.Key}: {node.Key} ({actorNode.Location.Name})"); yield break; } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeVeteranProductionIconOverlay.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeVeteranProductionIconOverlay.cs index 5bdffb65ee..614869ec03 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeVeteranProductionIconOverlay.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeVeteranProductionIconOverlay.cs @@ -40,7 +40,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules } foreach (var producibleWithLevel in actorNode.ChildrenMatching("ProducibleWithLevel")) - locations.Add($"{actorNode.Key}: {producibleWithLevel.Key} ({actorNode.Location.Filename})"); + locations.Add($"{actorNode.Key}: {producibleWithLevel.Key} ({actorNode.Location.Name})"); yield break; } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs index a33a0def50..dfc5ab7f8a 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs @@ -37,7 +37,8 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules { // Keep a resolved copy of the sequences so we can account for values imported through inheritance or Defaults. // This will be modified during processing, so take a deep copy to avoid side-effects on other update rules. - this.resolvedImagesNodes = MiniYaml.FromString(resolvedImagesNodes.WriteToString()).ConvertAll(n => new MiniYamlNodeBuilder(n)); + this.resolvedImagesNodes = MiniYaml.FromString(resolvedImagesNodes.WriteToString(), nameof(BeforeUpdateSequences)) + .ConvertAll(n => new MiniYamlNodeBuilder(n)); var requiredMetadata = new HashSet(); foreach (var imageNode in resolvedImagesNodes) @@ -285,7 +286,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules imageNode.Value.Nodes.Insert(inheritsNodeIndex, defaultsNode); } - var nodes = MiniYaml.FromString(duplicateTilesetCount.First(kv => kv.Value == maxDuplicateTilesetCount).Key); + var nodes = MiniYaml.FromString(duplicateTilesetCount.First(kv => kv.Value == maxDuplicateTilesetCount).Key, nameof(UpdateSequenceNode)); defaultTilesetFilenamesNode = new MiniYamlNodeBuilder("TilesetFilenames", "", nodes); defaultsNode.Value.Nodes.Insert(0, defaultTilesetFilenamesNode); } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveExperienceFromInfiltrates.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveExperienceFromInfiltrates.cs index e705c2a3d2..3da522e061 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveExperienceFromInfiltrates.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveExperienceFromInfiltrates.cs @@ -40,7 +40,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules removed = true; if (removed) - locations.Add($"{actorNode.Key} ({actorNode.Location.Filename})"); + locations.Add($"{actorNode.Key} ({actorNode.Location.Name})"); yield break; } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveSequenceHasEmbeddedPalette.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveSequenceHasEmbeddedPalette.cs index 53da4f0b79..53b8f66511 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveSequenceHasEmbeddedPalette.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveSequenceHasEmbeddedPalette.cs @@ -48,7 +48,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules { traitNode.RemoveNodes("Image"); traitNode.RemoveNodes("Sequence"); - locations.Add($"{actorNode.Key} ({actorNode.Location.Filename})"); + locations.Add($"{actorNode.Key} ({actorNode.Location.Name})"); } yield break; diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20231010/RemoveValidRelationsFromCapturable.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20231010/RemoveValidRelationsFromCapturable.cs index 104a791925..27003bfd74 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20231010/RemoveValidRelationsFromCapturable.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20231010/RemoveValidRelationsFromCapturable.cs @@ -36,7 +36,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules foreach (var capturable in actorNode.ChildrenMatching("Capturable")) { if (capturable.RemoveNodes("ValidRelations") > 0) - locations.Add($"{actorNode.Key}: {capturable.Key} ({actorNode.Location.Filename})"); + locations.Add($"{actorNode.Key}: {capturable.Key} ({actorNode.Location.Name})"); } yield break; diff --git a/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs b/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs index 2743279e25..b54d77edd3 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs @@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.UpdateRules continue; } - yaml.Add(((IReadWritePackage)package, name, MiniYaml.FromStream(package.GetStream(name), name, false).ConvertAll(n => new MiniYamlNodeBuilder(n)))); + yaml.Add(((IReadWritePackage)package, name, MiniYaml.FromStream(package.GetStream(name), $"{package.Name}:{name}", false).ConvertAll(n => new MiniYamlNodeBuilder(n)))); } return yaml; @@ -68,7 +68,7 @@ namespace OpenRA.Mods.Common.UpdateRules { // Ignore any files that aren't in the map bundle if (!filename.Contains('|') && mapPackage.Contains(filename)) - fileSet.Add((mapPackage, filename, MiniYaml.FromStream(mapPackage.GetStream(filename), filename, false).ConvertAll(n => new MiniYamlNodeBuilder(n)))); + fileSet.Add((mapPackage, filename, MiniYaml.FromStream(mapPackage.GetStream(filename), $"{mapPackage.Name}:{filename}", false).ConvertAll(n => new MiniYamlNodeBuilder(n)))); else if (modData.ModFiles.Exists(filename)) externalFilenames.Add(filename); } @@ -94,7 +94,7 @@ namespace OpenRA.Mods.Common.UpdateRules return manualSteps; } - var yaml = new MiniYamlBuilder(null, MiniYaml.FromStream(mapStream, mapPackage.Name, false)); + var yaml = new MiniYamlBuilder(null, MiniYaml.FromStream(mapStream, $"{mapPackage.Name}:map.yaml", false)); files = new YamlFileSet() { (mapPackage, "map.yaml", yaml.Nodes) }; manualSteps.AddRange(rule.BeforeUpdate(modData)); @@ -170,9 +170,9 @@ namespace OpenRA.Mods.Common.UpdateRules { // Explicit package paths never refer to a map if (!filename.Contains('|') && mapPackage.Contains(filename)) - return MiniYaml.FromStream(mapPackage.GetStream(filename)); + return MiniYaml.FromStream(mapPackage.GetStream(filename), $"{mapPackage.Name}:{filename}"); - return MiniYaml.FromStream(fileSystem.Open(filename)); + return MiniYaml.FromStream(fileSystem.Open(filename), filename); })); } @@ -215,7 +215,7 @@ namespace OpenRA.Mods.Common.UpdateRules if (mapStream == null) continue; - var yaml = new MiniYamlBuilder(new MiniYaml(null, MiniYaml.FromStream(mapStream, package.Name, false))); + var yaml = new MiniYamlBuilder(new MiniYaml(null, MiniYaml.FromStream(mapStream, $"{package.Name}:map.yaml", false))); var mapRulesNode = yaml.NodeWithKeyOrDefault("Rules"); if (mapRulesNode != null) foreach (var f in LoadExternalMapYaml(modData, mapRulesNode.Value, externalFilenames)) diff --git a/OpenRA.Mods.Common/UtilityCommands/ExtractYamlStrings.cs b/OpenRA.Mods.Common/UtilityCommands/ExtractYamlStrings.cs index 567c1d91ca..4780c579b2 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ExtractYamlStrings.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ExtractYamlStrings.cs @@ -57,7 +57,7 @@ namespace OpenRA.Mods.Common.UtilityCommands if (mapStream == null) continue; - var yaml = new MiniYamlBuilder(null, MiniYaml.FromStream(mapStream, package.Name, false)); + var yaml = new MiniYamlBuilder(null, MiniYaml.FromStream(mapStream, $"{package.Name}:map.yaml", false)); var mapRulesNode = yaml.NodeWithKeyOrDefault("Rules"); if (mapRulesNode != null) modRules.AddRange(UpdateUtils.LoadExternalMapYaml(modData, mapRulesNode.Value, new HashSet())); @@ -76,7 +76,7 @@ namespace OpenRA.Mods.Common.UtilityCommands if (mapStream == null) continue; - var yaml = new MiniYamlBuilder(null, MiniYaml.FromStream(mapStream, package.Name, false)); + var yaml = new MiniYamlBuilder(null, MiniYaml.FromStream(mapStream, $"{package.Name}:map.yaml", false)); var mapRules = new YamlFileSet() { (package, "map.yaml", yaml.Nodes) }; var mapRulesNode = yaml.NodeWithKeyOrDefault("Rules"); diff --git a/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs index 563bc09691..d6a8cfa57b 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs @@ -128,8 +128,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic // Add a group for each campaign if (modData.Manifest.Missions.Length > 0) { + var stringPool = new HashSet(); // Reuse common strings in YAML var yaml = MiniYaml.Merge(modData.Manifest.Missions.Select( - m => MiniYaml.FromStream(modData.DefaultFileSystem.Open(m), m))); + m => MiniYaml.FromStream(modData.DefaultFileSystem.Open(m), m, stringPool: stringPool))); foreach (var kv in yaml) { diff --git a/OpenRA.Mods.Common/Widgets/Logic/PlayerProfileLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/PlayerProfileLogic.cs index 7864d6c66a..c2d21ee0be 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/PlayerProfileLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/PlayerProfileLogic.cs @@ -166,10 +166,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic { var httpClient = HttpClientFactory.Create(); - var httpResponseMessage = await httpClient.GetAsync(playerDatabase.Profile + client.Fingerprint); + var url = playerDatabase.Profile + client.Fingerprint; + var httpResponseMessage = await httpClient.GetAsync(url); var result = await httpResponseMessage.Content.ReadAsStreamAsync(); - var yaml = MiniYaml.FromStream(result).First(); + var yaml = MiniYaml.FromStream(result, url).First(); if (yaml.Key == "Player") { profile = FieldLoader.Load(yaml.Value); diff --git a/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs index 0a805996f0..8aeb70a7b1 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs @@ -456,7 +456,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var httpResponseMessage = await client.GetAsync(queryURL); var result = await httpResponseMessage.Content.ReadAsStreamAsync(); - var yaml = MiniYaml.FromStream(result); + var yaml = MiniYaml.FromStream(result, queryURL); games = new List(); foreach (var node in yaml) { @@ -479,6 +479,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic } var lanGames = new List(); + var stringPool = new HashSet(); // Reuse common strings in YAML foreach (var bl in lanGameLocations) { try @@ -486,7 +487,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (string.IsNullOrEmpty(bl.Data)) continue; - var game = new MiniYamlBuilder(MiniYaml.FromString(bl.Data)[0].Value); + var game = new MiniYamlBuilder(MiniYaml.FromString( + bl.Data, $"BeaconLocation_{bl.Address}_{bl.LastAdvertised:s}", stringPool: stringPool)[0].Value); var idNode = game.NodeWithKeyOrDefault("Id"); // Skip beacons created by this instance and replace Id by expected int value diff --git a/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs b/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs index 93fd45dd9b..55686d36a9 100644 --- a/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs +++ b/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs @@ -44,7 +44,7 @@ namespace OpenRA.Test 9.1.2: Test 9.1.3: # Test "; - var serialized = MiniYaml.FromString(Yaml, discardCommentsAndWhitespace: false).WriteToString(); + var serialized = MiniYaml.FromString(Yaml, "", discardCommentsAndWhitespace: false).WriteToString(); Console.WriteLine(); Assert.That(serialized, Is.EqualTo(Yaml)); } @@ -120,7 +120,7 @@ namespace OpenRA.Test 9.1.2: Test 9.1.3: "; - var serialized = MiniYaml.FromString(Yaml).WriteToString(); + var serialized = MiniYaml.FromString(Yaml, "").WriteToString(); Assert.That(serialized, Is.EqualTo(ExpectedYaml)); } @@ -152,9 +152,9 @@ Root2: Child1: Attribute1: Test "; - var tabs = MiniYaml.FromString(YamlTabStyle, "yamlTabStyle").WriteToString(); + var tabs = MiniYaml.FromString(YamlTabStyle, "").WriteToString(); Console.WriteLine(tabs); - var mixed = MiniYaml.FromString(YamlMixedStyle, "yamlMixedStyle").WriteToString(); + var mixed = MiniYaml.FromString(YamlMixedStyle, "").WriteToString(); Console.WriteLine(mixed); Assert.That(tabs, Is.EqualTo(mixed)); } @@ -376,7 +376,7 @@ TestB: Nothing: "; - var result = MiniYaml.FromString(Yaml).First(n => n.Key == "TestB"); + var result = MiniYaml.FromString(Yaml, "").First(n => n.Key == "TestB"); Assert.AreEqual(5, result.Location.Line); } @@ -549,27 +549,27 @@ Test: [TestCase(TestName = "Comments are correctly separated from values")] public void TestEscapedHashInValues() { - var trailingWhitespace = MiniYaml.FromString("key: value # comment", "trailingWhitespace", discardCommentsAndWhitespace: false)[0]; + var trailingWhitespace = MiniYaml.FromString("key: value # comment", "", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual("value", trailingWhitespace.Value.Value); Assert.AreEqual(" comment", trailingWhitespace.Comment); - var noWhitespace = MiniYaml.FromString("key:value# comment", "noWhitespace", discardCommentsAndWhitespace: false)[0]; + var noWhitespace = MiniYaml.FromString("key:value# comment", "", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual("value", noWhitespace.Value.Value); Assert.AreEqual(" comment", noWhitespace.Comment); - var escapedHashInValue = MiniYaml.FromString(@"key: before \# after # comment", "escapedHashInValue", discardCommentsAndWhitespace: false)[0]; + var escapedHashInValue = MiniYaml.FromString(@"key: before \# after # comment", "", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual("before # after", escapedHashInValue.Value.Value); Assert.AreEqual(" comment", escapedHashInValue.Comment); - var emptyValueAndComment = MiniYaml.FromString("key:#", "emptyValueAndComment", discardCommentsAndWhitespace: false)[0]; + var emptyValueAndComment = MiniYaml.FromString("key:#", "", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual(null, emptyValueAndComment.Value.Value); Assert.AreEqual("", emptyValueAndComment.Comment); - var noValue = MiniYaml.FromString("key:", "noValue", discardCommentsAndWhitespace: false)[0]; + var noValue = MiniYaml.FromString("key:", "", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual(null, noValue.Value.Value); Assert.AreEqual(null, noValue.Comment); - var emptyKey = MiniYaml.FromString(" : value", "emptyKey", discardCommentsAndWhitespace: false)[0]; + var emptyKey = MiniYaml.FromString(" : value", "", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual(null, emptyKey.Key); Assert.AreEqual("value", emptyKey.Value.Value); Assert.AreEqual(null, emptyKey.Comment); @@ -579,7 +579,7 @@ Test: public void TestGuardedWhitespace() { const string TestYaml = @"key: \ test value \ "; - var nodes = MiniYaml.FromString(TestYaml, "testYaml"); + var nodes = MiniYaml.FromString(TestYaml, ""); Assert.AreEqual(" test value ", nodes[0].Value.Value); } @@ -594,12 +594,12 @@ TestA: TestB: Nothing: "; - var resultDiscard = MiniYaml.FromString(Yaml); + var resultDiscard = MiniYaml.FromString(Yaml, ""); var resultDiscardLine = resultDiscard.First(n => n.Key == "TestB").Location.Line; Assert.That(resultDiscardLine, Is.EqualTo(6), "Node TestB should report its location as line 6, but is not (discarding comments)"); Assert.That(resultDiscard[1].Key, Is.EqualTo("TestB"), "Node TestB should be the second child of the root node, but is not (discarding comments)"); - var resultKeep = MiniYaml.FromString(Yaml, discardCommentsAndWhitespace: false); + var resultKeep = MiniYaml.FromString(Yaml, "", discardCommentsAndWhitespace: false); var resultKeepLine = resultKeep.First(n => n.Key == "TestB").Location.Line; Assert.That(resultKeepLine, Is.EqualTo(6), "Node TestB should report its location as line 6, but is not (parsing comments)"); Assert.That(resultKeep[4].Key, Is.EqualTo("TestB"), "Node TestB should be the fifth child of the root node, but is not (parsing comments)"); @@ -646,7 +646,7 @@ Parent: # comment without value Seventh: # embedded comment: still a comment # more comment ".Replace("\r\n", "\n"); - var result = MiniYaml.FromString(yaml, discardCommentsAndWhitespace: false).WriteToString(); + var result = MiniYaml.FromString(yaml, "", discardCommentsAndWhitespace: false).WriteToString(); Assert.AreEqual(canonicalYaml, result); } @@ -682,7 +682,7 @@ Parent: # comment without value Seventh: ".Replace("\r\n", "\n"); - var result = MiniYaml.FromString(Yaml).WriteToString(); + var result = MiniYaml.FromString(Yaml, "").WriteToString(); Assert.AreEqual(strippedYaml, result); } }