From 1581ba951f72fb3eb35c5d1bf5a06c90deeed54f Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 30 Jan 2016 01:46:11 +0000 Subject: [PATCH 1/4] Add a new test case for actor info merging. --- OpenRA.Test/OpenRA.Game/ActorInfoTest.cs | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/OpenRA.Test/OpenRA.Game/ActorInfoTest.cs b/OpenRA.Test/OpenRA.Game/ActorInfoTest.cs index c216390fbe..ddb411de79 100644 --- a/OpenRA.Test/OpenRA.Game/ActorInfoTest.cs +++ b/OpenRA.Test/OpenRA.Game/ActorInfoTest.cs @@ -31,6 +31,8 @@ namespace OpenRA.Test class MockB2Info : MockTraitInfo { } class MockC2Info : MockTraitInfo { } + class MockStringInfo : MockTraitInfo { public string AString = null; } + [TestFixture] public class ActorInfoTest { @@ -142,6 +144,29 @@ Actor: Assert.IsFalse(actorInfo.HasTraitInfo(), "Actor should not have the MockA2 trait, but does."); } + [TestCase(TestName = "Trait can be removed and later overridden")] + public void TraitCanBeRemovedAndLaterOverridden() + { + var baseYaml = @" +^BaseA: + MockString: + AString: ""Base"" +Actor: + Inherits: ^BaseA + -MockString: +"; + var overrideYaml = @" +Actor: + MockString: + AString: ""Override"" +"; + + var actorInfo = CreateActorInfoFromYaml("Actor", null, baseYaml, overrideYaml); + Assert.IsTrue(actorInfo.HasTraitInfo(), "Actor should have the MockStringInfo trait, but does not."); + Assert.IsTrue(actorInfo.TraitInfo().AString == "\"Override\"", + "MockStringInfo trait has not been set with the correct override value for AString."); + } + // This needs to match the logic used in RulesetCache.LoadYamlRules ActorInfo CreateActorInfoFromYaml(string name, string mapYaml, params string[] yamls) { From ab921682c4793c160d84ee38ef94056c2afee359 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 30 Jan 2016 03:04:34 +0000 Subject: [PATCH 2/4] Rewrite yaml merger. --- OpenRA.Game/GameRules/ActorInfo.cs | 61 ++----- OpenRA.Game/GameRules/RulesetCache.cs | 27 ++- OpenRA.Game/Graphics/ChromeProvider.cs | 7 +- OpenRA.Game/Graphics/CursorProvider.cs | 6 +- OpenRA.Game/Graphics/SequenceProvider.cs | 8 +- OpenRA.Game/Graphics/VoxelProvider.cs | 6 +- OpenRA.Game/MiniYaml.cs | 168 +++++++++++------- OpenRA.Game/ModData.cs | 5 +- OpenRA.Game/Widgets/ChromeMetrics.cs | 5 +- .../Graphics/DefaultSpriteSequence.cs | 14 +- OpenRA.Mods.Common/Lint/CheckSequences.cs | 5 +- .../UtilityCommands/CheckSequenceSprites.cs | 8 +- .../Widgets/Logic/MissionBrowserLogic.cs | 13 +- OpenRA.Test/OpenRA.Game/ActorInfoTest.cs | 82 +-------- OpenRA.Test/OpenRA.Game/MiniYamlTest.cs | 143 ++++++++++----- 15 files changed, 253 insertions(+), 305 deletions(-) diff --git a/OpenRA.Game/GameRules/ActorInfo.cs b/OpenRA.Game/GameRules/ActorInfo.cs index 4d25b46ecc..14ed102841 100644 --- a/OpenRA.Game/GameRules/ActorInfo.cs +++ b/OpenRA.Game/GameRules/ActorInfo.cs @@ -31,30 +31,25 @@ namespace OpenRA readonly TypeDictionary traits = new TypeDictionary(); List constructOrderCache = null; - public ActorInfo(ObjectCreator creator, string name, MiniYaml node, Dictionary allUnits) + public ActorInfo(ObjectCreator creator, string name, MiniYaml node) { try { Name = name; - var allParents = new HashSet(); var abstractActorType = name.StartsWith("^"); - - // Guard against circular inheritance - allParents.Add(name); - - var partial = MergeWithParents(node, allUnits, allParents); - foreach (var t in MiniYaml.ApplyRemovals(partial.Nodes)) - if (t.Key != "Inherits" && !t.Key.StartsWith("Inherits@")) - try - { - traits.Add(LoadTraitInfo(creator, t.Key.Split('@')[0], t.Value)); - } - catch (FieldLoader.MissingFieldsException e) - { - if (!abstractActorType) - throw new YamlException(e.Message); - } + foreach (var t in node.Nodes) + { + try + { + traits.Add(LoadTraitInfo(creator, t.Key.Split('@')[0], t.Value)); + } + catch (FieldLoader.MissingFieldsException e) + { + if (!abstractActorType) + throw new YamlException(e.Message); + } + } } catch (YamlException e) { @@ -69,36 +64,6 @@ namespace OpenRA traits.Add(t); } - static Dictionary GetParents(MiniYaml node, Dictionary allUnits) - { - return node.Nodes.Where(n => n.Key == "Inherits" || n.Key.StartsWith("Inherits@")) - .ToDictionary(n => n.Value.Value, n => - { - MiniYaml i; - if (!allUnits.TryGetValue(n.Value.Value, out i)) - throw new YamlException( - "Bogus inheritance -- parent type {0} does not exist".F(n.Value.Value)); - - return i; - }); - } - - static MiniYaml MergeWithParents(MiniYaml node, Dictionary allUnits, HashSet allParents) - { - var parents = GetParents(node, allUnits); - - foreach (var kv in parents) - { - if (!allParents.Add(kv.Key)) - throw new YamlException( - "Bogus inheritance -- duplicate inheritance of {0}.".F(kv.Key)); - - node = MiniYaml.MergePartial(node, MergeWithParents(kv.Value, allUnits, allParents)); - } - - return node; - } - static ITraitInfo LoadTraitInfo(ObjectCreator creator, string traitName, MiniYaml my) { if (!string.IsNullOrEmpty(my.Value)) diff --git a/OpenRA.Game/GameRules/RulesetCache.cs b/OpenRA.Game/GameRules/RulesetCache.cs index 19cb3d83c7..93ea54c438 100644 --- a/OpenRA.Game/GameRules/RulesetCache.cs +++ b/OpenRA.Game/GameRules/RulesetCache.cs @@ -61,27 +61,27 @@ namespace OpenRA using (new PerfTimer("Actors")) actors = LoadYamlRules(actorCache, m.Rules, map != null ? map.RuleDefinitions : NoMapRules, - (k, y) => new ActorInfo(Game.ModData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value, y)); + k => new ActorInfo(Game.ModData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value)); using (new PerfTimer("Weapons")) weapons = LoadYamlRules(weaponCache, m.Weapons, map != null ? map.WeaponDefinitions : NoMapRules, - (k, _) => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value)); + k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value)); using (new PerfTimer("Voices")) voices = LoadYamlRules(voiceCache, m.Voices, map != null ? map.VoiceDefinitions : NoMapRules, - (k, _) => new SoundInfo(k.Value)); + k => new SoundInfo(k.Value)); using (new PerfTimer("Notifications")) notifications = LoadYamlRules(notificationCache, m.Notifications, map != null ? map.NotificationDefinitions : NoMapRules, - (k, _) => new SoundInfo(k.Value)); + k => new SoundInfo(k.Value)); using (new PerfTimer("Music")) music = LoadYamlRules(musicCache, m.Music, map != null ? map.MusicDefinitions : NoMapRules, - (k, _) => new MusicInfo(k.Key, k.Value)); + k => new MusicInfo(k.Key, k.Value)); using (new PerfTimer("TileSets")) tileSets = LoadTileSets(tileSetCache, sequenceCaches, m.TileSets); @@ -93,33 +93,30 @@ namespace OpenRA Dictionary LoadYamlRules( Dictionary itemCache, string[] files, List nodes, - Func, T> f) + Func f) { RaiseProgress(); var inputKey = string.Concat(string.Join("|", files), "|", nodes.WriteToString()); - - var partial = files - .Select(s => MiniYaml.FromFile(s)) - .Aggregate(nodes, MiniYaml.MergePartial); - - Func, T> wrap = (wkv, wyy) => + Func wrap = wkv => { var key = inputKey + wkv.Value.ToLines(wkv.Key).JoinWith("|"); T t; if (itemCache.TryGetValue(key, out t)) return t; - t = f(wkv, wyy); + t = f(wkv); itemCache.Add(key, t); RaiseProgress(); return t; }; - var yy = partial.ToDictionary(x => x.Key, x => x.Value); - var itemSet = partial.ToDictionaryWithConflictLog(kv => kv.Key.ToLowerInvariant(), kv => wrap(kv, yy), "LoadYamlRules", null, null); + var tree = MiniYaml.Merge(files.Select(MiniYaml.FromFile).Append(nodes)) + .ToDictionaryWithConflictLog(n => n.Key, n => n.Value, "LoadYamlRules", null, null); + RaiseProgress(); + var itemSet = tree.ToDictionary(kv => kv.Key.ToLowerInvariant(), kv => wrap(new MiniYamlNode(kv.Key, kv.Value))); RaiseProgress(); return itemSet; } diff --git a/OpenRA.Game/Graphics/ChromeProvider.cs b/OpenRA.Game/Graphics/ChromeProvider.cs index 6a24e654cf..c26d7cb968 100644 --- a/OpenRA.Game/Graphics/ChromeProvider.cs +++ b/OpenRA.Game/Graphics/ChromeProvider.cs @@ -33,12 +33,7 @@ namespace OpenRA.Graphics cachedSheets = new Dictionary(); cachedSprites = new Dictionary>(); - var partial = chromeFiles - .Select(s => MiniYaml.FromFile(s)) - .Aggregate(MiniYaml.MergePartial); - - var chrome = MiniYaml.ApplyRemovals(partial); - + var chrome = MiniYaml.Merge(chromeFiles.Select(MiniYaml.FromFile)); foreach (var c in chrome) LoadCollection(c.Key, c.Value); } diff --git a/OpenRA.Game/Graphics/CursorProvider.cs b/OpenRA.Game/Graphics/CursorProvider.cs index 5352054ec3..ed4b6d8f94 100644 --- a/OpenRA.Game/Graphics/CursorProvider.cs +++ b/OpenRA.Game/Graphics/CursorProvider.cs @@ -21,12 +21,8 @@ namespace OpenRA.Graphics public CursorProvider(ModData modData) { - var sequenceFiles = modData.Manifest.Cursors; - var partial = sequenceFiles - .Select(s => MiniYaml.FromFile(s)) - .Aggregate(MiniYaml.MergePartial); + var sequences = new MiniYaml(null, MiniYaml.Merge(modData.Manifest.Cursors.Select(MiniYaml.FromFile))); - var sequences = new MiniYaml(null, MiniYaml.ApplyRemovals(partial)); var shadowIndex = new int[] { }; var nodesDict = sequences.ToDictionary(); diff --git a/OpenRA.Game/Graphics/SequenceProvider.cs b/OpenRA.Game/Graphics/SequenceProvider.cs index 3838306b4d..5125fc12da 100644 --- a/OpenRA.Game/Graphics/SequenceProvider.cs +++ b/OpenRA.Game/Graphics/SequenceProvider.cs @@ -123,13 +123,7 @@ namespace OpenRA.Graphics Sequences Load(List sequenceNodes) { - var sequenceFiles = modData.Manifest.Sequences; - - var partial = sequenceFiles - .Select(s => MiniYaml.FromFile(s)) - .Aggregate(sequenceNodes, MiniYaml.MergePartial); - - var nodes = MiniYaml.ApplyRemovals(partial); + var nodes = MiniYaml.Merge(modData.Manifest.Sequences.Select(MiniYaml.FromFile).Append(sequenceNodes)); var items = new Dictionary(); foreach (var n in nodes) { diff --git a/OpenRA.Game/Graphics/VoxelProvider.cs b/OpenRA.Game/Graphics/VoxelProvider.cs index da5ef4e534..3a3b068776 100644 --- a/OpenRA.Game/Graphics/VoxelProvider.cs +++ b/OpenRA.Game/Graphics/VoxelProvider.cs @@ -23,11 +23,7 @@ namespace OpenRA.Graphics { units = new Dictionary>(); - var partial = voxelFiles - .Select(s => MiniYaml.FromFile(s)) - .Aggregate(voxelNodes, MiniYaml.MergePartial); - - var sequences = MiniYaml.ApplyRemovals(partial); + var sequences = MiniYaml.Merge(voxelFiles.Select(MiniYaml.FromFile)); foreach (var s in sequences) LoadVoxelsForUnit(s.Key, s.Value); diff --git a/OpenRA.Game/MiniYaml.cs b/OpenRA.Game/MiniYaml.cs index fca98a28a4..b057a6aec3 100644 --- a/OpenRA.Game/MiniYaml.cs +++ b/OpenRA.Game/MiniYaml.cs @@ -79,6 +79,11 @@ namespace OpenRA { return "{{YamlNode: {0} @ {1}}}".F(Key, Location); } + + public MiniYamlNode Clone() + { + return new MiniYamlNode(Key, Value.Clone()); + } } public class MiniYaml @@ -89,6 +94,11 @@ namespace OpenRA public string Value; public List Nodes; + public MiniYaml Clone() + { + return new MiniYaml(Value, Nodes.Select(n => n.Clone()).ToList()); + } + public Dictionary ToDictionary() { return ToDictionary(MiniYamlIdentity); @@ -245,89 +255,117 @@ namespace OpenRA return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries), fileName); } - public static List Merge(List a, List b) + public static List Merge(IEnumerable> sources) { - return ApplyRemovals(MergePartial(a, b)); + if (!sources.Any()) + return new List(); + + var tree = sources.Where(s => s != null).Aggregate(MergePartial) + .ToDictionary(n => n.Key, n => n.Value); + + var resolved = new Dictionary(); + foreach (var kv in tree) + { + var inherited = new Dictionary(); + inherited.Add(kv.Key, new MiniYamlNode.SourceLocation()); + + var children = ResolveInherits(kv.Key, kv.Value, tree, inherited); + resolved.Add(kv.Key, new MiniYaml(kv.Value.Value, children)); + } + + return resolved.Select(kv => new MiniYamlNode(kv.Key, kv.Value)).ToList(); } - public static List MergePartial(List a, List b) + static void MergeIntoResolved(MiniYamlNode overrideNode, List existingNodes, + Dictionary tree, Dictionary inherited) { - if (a.Count == 0) - return b; + var existingNode = existingNodes.FirstOrDefault(n => n.Key == overrideNode.Key); + if (existingNode != null) + { + existingNode.Value = MiniYaml.MergePartial(existingNode.Value, overrideNode.Value); + existingNode.Value.Nodes = ResolveInherits(existingNode.Key, existingNode.Value, tree, inherited); + } + else + existingNodes.Add(overrideNode.Clone()); + } - if (b.Count == 0) - return a; + static List ResolveInherits(string key, MiniYaml node, Dictionary tree, Dictionary inherited) + { + var resolved = new List(); + + // Inheritance is tracked from parent->child, but not from child->parentsiblings. + inherited = new Dictionary(inherited); + + foreach (var n in node.Nodes) + { + if (n.Key == "Inherits" || n.Key.StartsWith("Inherits@")) + { + MiniYaml parent; + if (!tree.TryGetValue(n.Value.Value, out parent)) + throw new YamlException( + "{0}: Parent type `{1}` not found".F(n.Location, n.Value.Value)); + + if (inherited.ContainsKey(n.Value.Value)) + throw new YamlException("{0}: Parent type `{1}` was already inherited by this yaml tree at {2} (note: may be from a derived tree)" + .F(n.Location, n.Value.Value, inherited[n.Value.Value])); + + inherited.Add(n.Value.Value, n.Location); + foreach (var r in ResolveInherits(n.Key, parent, tree, inherited)) + MergeIntoResolved(r, resolved, tree, inherited); + } + else if (n.Key.StartsWith("-")) + { + var removed = n.Key.Substring(1); + if (resolved.RemoveAll(r => r.Key == removed) == 0) + throw new YamlException("{0}: There are no elements with key `{1}` to remove".F(n.Location, removed)); + } + else + MergeIntoResolved(n, resolved, tree, inherited); + } + + return resolved; + } + + static MiniYaml MergePartial(MiniYaml existingNodes, MiniYaml overrideNodes) + { + if (existingNodes == null) + return overrideNodes; + + if (overrideNodes == null) + return existingNodes; + + return new MiniYaml(overrideNodes.Value ?? existingNodes.Value, MergePartial(existingNodes.Nodes, overrideNodes.Nodes)); + } + + static List MergePartial(List existingNodes, List overrideNodes) + { + if (existingNodes.Count == 0) + return overrideNodes; + + if (overrideNodes.Count == 0) + return existingNodes; var ret = new List(); - var dictA = a.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => "{0} (at {1})".F(x.Key, x.Location)); - var dictB = b.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => "{0} (at {1})".F(x.Key, x.Location)); - var allKeys = dictA.Keys.Union(dictB.Keys); + var existingDict = existingNodes.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => "{0} (at {1})".F(x.Key, x.Location)); + var overrideDict = overrideNodes.ToDictionaryWithConflictLog(x => x.Key, "MiniYaml.Merge", null, x => "{0} (at {1})".F(x.Key, x.Location)); + var allKeys = existingDict.Keys.Union(overrideDict.Keys); foreach (var key in allKeys) { - MiniYamlNode aa, bb; - dictA.TryGetValue(key, out aa); - dictB.TryGetValue(key, out bb); + MiniYamlNode existingNode, overrideNode; + existingDict.TryGetValue(key, out existingNode); + overrideDict.TryGetValue(key, out overrideNode); - var loc = aa == null ? default(MiniYamlNode.SourceLocation) : aa.Location; - var merged = (aa == null || bb == null) ? aa ?? bb : new MiniYamlNode(key, MergePartial(aa.Value, bb.Value), loc); + var loc = overrideNode == null ? default(MiniYamlNode.SourceLocation) : overrideNode.Location; + var merged = (existingNode == null || overrideNode == null) ? overrideNode ?? existingNode : + new MiniYamlNode(key, MergePartial(existingNode.Value, overrideNode.Value), loc); ret.Add(merged); } return ret; } - public static List ApplyRemovals(List a) - { - var removeKeys = a.Select(x => x.Key) - .Where(x => x.Length > 0 && x[0] == '-') - .Select(k => k.Substring(1)) - .ToHashSet(); - - var ret = new List(); - foreach (var x in a) - { - if (x.Key[0] == '-') - continue; - - if (removeKeys.Contains(x.Key)) - removeKeys.Remove(x.Key); - else - { - x.Value.Nodes = ApplyRemovals(x.Value.Nodes); - ret.Add(x); - } - } - - if (removeKeys.Any()) - throw new YamlException("Bogus yaml removals: {0}".F(removeKeys.JoinWith(", "))); - - return ret; - } - - public static MiniYaml MergePartial(MiniYaml a, MiniYaml b) - { - if (a == null) - return b; - - if (b == null) - return a; - - return new MiniYaml(a.Value ?? b.Value, MergePartial(a.Nodes, b.Nodes)); - } - - public static MiniYaml Merge(MiniYaml a, MiniYaml b) - { - if (a == null) - return b; - - if (b == null) - return a; - - return new MiniYaml(a.Value ?? b.Value, Merge(a.Nodes, b.Nodes)); - } - public IEnumerable ToLines(string name) { yield return name + ": " + Value; diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 2cb98bf78d..1031ff7346 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -133,10 +133,9 @@ namespace OpenRA return; } - var partial = Manifest.Translations.Select(MiniYaml.FromFile).Aggregate(MiniYaml.MergePartial); - Languages = partial.Select(t => t.Key).ToArray(); + var yaml = MiniYaml.Merge(Manifest.Translations.Select(MiniYaml.FromFile).Append(map.TranslationDefinitions)); + Languages = yaml.Select(t => t.Key).ToArray(); - var yaml = MiniYaml.Merge(map.TranslationDefinitions, partial); foreach (var y in yaml) { if (y.Key == Game.Settings.Graphics.Language) diff --git a/OpenRA.Game/Widgets/ChromeMetrics.cs b/OpenRA.Game/Widgets/ChromeMetrics.cs index f283de0e45..34e3e5ad38 100644 --- a/OpenRA.Game/Widgets/ChromeMetrics.cs +++ b/OpenRA.Game/Widgets/ChromeMetrics.cs @@ -20,11 +20,8 @@ namespace OpenRA.Widgets public static void Initialize(IEnumerable yaml) { data = new Dictionary(); - var partial = yaml - .Select(y => MiniYaml.FromFile(y)) - .Aggregate(MiniYaml.MergePartial); - var metrics = MiniYaml.ApplyRemovals(partial); + var metrics = MiniYaml.Merge(yaml.Select(MiniYaml.FromFile)); foreach (var m in metrics) foreach (var n in m.Value.Nodes) data[n.Key] = n.Value.Value; diff --git a/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs index 97657a15dc..f8e58f0bc8 100644 --- a/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs +++ b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs @@ -36,19 +36,11 @@ namespace OpenRA.Mods.Common.Graphics if (nodes.TryGetValue("Defaults", out defaults)) { nodes.Remove("Defaults"); - nodes = nodes.ToDictionary(kv => kv.Key, kv => MiniYaml.Merge(kv.Value, defaults)); - - // Merge 'Defaults' animation image value. An example follows. - // - // - Before - - // stand: - // Facings: 8 - // - // - After - - // stand: e1 - // Facings: 8 foreach (var n in nodes) + { + n.Value.Nodes = MiniYaml.Merge(new[] { defaults.Nodes, n.Value.Nodes }); n.Value.Value = n.Value.Value ?? defaults.Value; + } } foreach (var kvp in nodes) diff --git a/OpenRA.Mods.Common/Lint/CheckSequences.cs b/OpenRA.Mods.Common/Lint/CheckSequences.cs index c41dd8a6aa..b89153a2a4 100644 --- a/OpenRA.Mods.Common/Lint/CheckSequences.cs +++ b/OpenRA.Mods.Common/Lint/CheckSequences.cs @@ -31,10 +31,7 @@ namespace OpenRA.Mods.Common.Lint this.emitError = emitError; var sequenceSource = map != null ? map.SequenceDefinitions : new List(); - var partial = Game.ModData.Manifest.Sequences - .Select(MiniYaml.FromFile) - .Aggregate(MiniYaml.MergePartial); - sequenceDefinitions = MiniYaml.Merge(sequenceSource, partial); + sequenceDefinitions = MiniYaml.Merge(Game.ModData.Manifest.Sequences.Select(MiniYaml.FromFile).Append(sequenceSource)); var rules = map == null ? Game.ModData.DefaultRules : map.Rules; var factions = rules.Actors["world"].TraitInfos().Select(f => f.InternalName).ToArray(); diff --git a/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs b/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs index 9968528f99..e305cf48c9 100644 --- a/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs +++ b/OpenRA.Mods.Common/UtilityCommands/CheckSequenceSprites.cs @@ -36,13 +36,7 @@ namespace OpenRA.Mods.Common.UtilityCommands var ts = new TileSet(Game.ModData, t); Console.WriteLine("Tileset: " + ts.Name); var sc = new SpriteCache(modData.SpriteLoaders, new SheetBuilder(SheetType.Indexed)); - var sequenceFiles = modData.Manifest.Sequences; - - var partial = sequenceFiles - .Select(s => MiniYaml.FromFile(s)) - .Aggregate(MiniYaml.MergePartial); - - var nodes = MiniYaml.ApplyRemovals(partial); + var nodes = MiniYaml.Merge(modData.Manifest.Sequences.Select(MiniYaml.FromFile)); foreach (var n in nodes) Game.ModData.SpriteSequenceLoader.ParseSequences(Game.ModData, ts, sc, n); } diff --git a/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs index c951c1ae01..2cb94c94ef 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs @@ -49,6 +49,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic [ObjectCreator.UseCtor] public MissionBrowserLogic(Widget widget, World world, Action onStart, Action onExit) { + var modData = Game.ModData; this.onStart = onStart; missionList = widget.Get("MISSION_LIST"); @@ -92,18 +93,14 @@ namespace OpenRA.Mods.Common.Widgets.Logic missionList.RemoveChildren(); // Add a group for each campaign - if (Game.ModData.Manifest.Missions.Any()) + if (modData.Manifest.Missions.Any()) { - var partial = Game.ModData.Manifest.Missions - .Select(MiniYaml.FromFile) - .Aggregate(MiniYaml.MergePartial); - - var yaml = MiniYaml.ApplyRemovals(partial); + var yaml = MiniYaml.Merge(modData.Manifest.Missions.Select(MiniYaml.FromFile)); foreach (var kv in yaml) { var missionMapPaths = kv.Value.Nodes.Select(n => Path.GetFullPath(n.Key)).ToList(); - var maps = Game.ModData.MapCache + var maps = modData.MapCache .Where(p => p.Status == MapStatus.Available && missionMapPaths.Contains(Path.GetFullPath(p.Map.Path))) .Select(p => p.Map) .OrderBy(m => missionMapPaths.IndexOf(Path.GetFullPath(m.Path))); @@ -114,7 +111,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic } // Add an additional group for loose missions - var looseMissions = Game.ModData.MapCache + var looseMissions = modData.MapCache .Where(p => p.Status == MapStatus.Available && p.Map.Visibility.HasFlag(MapVisibility.MissionSelector) && !allMaps.Contains(p.Map)) .Select(p => p.Map); diff --git a/OpenRA.Test/OpenRA.Game/ActorInfoTest.cs b/OpenRA.Test/OpenRA.Game/ActorInfoTest.cs index ddb411de79..7d288db924 100644 --- a/OpenRA.Test/OpenRA.Game/ActorInfoTest.cs +++ b/OpenRA.Test/OpenRA.Game/ActorInfoTest.cs @@ -97,87 +97,19 @@ namespace OpenRA.Test } } - [TestCase(TestName = "Trait inheritance and removal can be composed")] - public void TraitInheritanceAndRemovalCanBeComposed() - { - var baseYaml = @" -^BaseA: - MockA2: -^BaseB: - Inherits@a: ^BaseA - MockB2: -"; - var extendedYaml = @" -Actor: - Inherits@b: ^BaseB - -MockA2: -"; - var mapYaml = @" -^BaseC: - MockC2: -Actor: - Inherits@c: ^BaseC -"; - - var actorInfo = CreateActorInfoFromYaml("Actor", mapYaml, baseYaml, extendedYaml); - Assert.IsFalse(actorInfo.HasTraitInfo(), "Actor should not have the MockA2 trait, but does."); - Assert.IsTrue(actorInfo.HasTraitInfo(), "Actor should have the MockB2 trait, but does not."); - Assert.IsTrue(actorInfo.HasTraitInfo(), "Actor should have the MockC2 trait, but does not."); - } - - [TestCase(TestName = "Trait can be removed after multiple inheritance")] - public void TraitCanBeRemovedAfterMultipleInheritance() - { - var baseYaml = @" -^BaseA: - MockA2: -Actor: - Inherits: ^BaseA - MockA2: -"; - var overrideYaml = @" -Actor: - -MockA2 -"; - - var actorInfo = CreateActorInfoFromYaml("Actor", null, baseYaml, overrideYaml); - Assert.IsFalse(actorInfo.HasTraitInfo(), "Actor should not have the MockA2 trait, but does."); - } - - [TestCase(TestName = "Trait can be removed and later overridden")] - public void TraitCanBeRemovedAndLaterOverridden() - { - var baseYaml = @" -^BaseA: - MockString: - AString: ""Base"" -Actor: - Inherits: ^BaseA - -MockString: -"; - var overrideYaml = @" -Actor: - MockString: - AString: ""Override"" -"; - - var actorInfo = CreateActorInfoFromYaml("Actor", null, baseYaml, overrideYaml); - Assert.IsTrue(actorInfo.HasTraitInfo(), "Actor should have the MockStringInfo trait, but does not."); - Assert.IsTrue(actorInfo.TraitInfo().AString == "\"Override\"", - "MockStringInfo trait has not been set with the correct override value for AString."); - } - // This needs to match the logic used in RulesetCache.LoadYamlRules ActorInfo CreateActorInfoFromYaml(string name, string mapYaml, params string[] yamls) { - var initialNodes = mapYaml == null ? new List() : MiniYaml.FromString(mapYaml); - var yaml = yamls - .Select(s => MiniYaml.FromString(s)) - .Aggregate(initialNodes, MiniYaml.MergePartial); + var nodes = mapYaml == null ? new List() : MiniYaml.FromString(mapYaml); + var sources = yamls.ToList(); + if (mapYaml != null) + sources.Add(mapYaml); + + var yaml = MiniYaml.Merge(sources.Select(s => MiniYaml.FromString(s))); var allUnits = yaml.ToDictionary(node => node.Key, node => node.Value); var unit = allUnits[name]; var creator = new ObjectCreator(new[] { typeof(ActorInfoTest).Assembly }); - return new ActorInfo(creator, name, unit, allUnits); + return new ActorInfo(creator, name, unit); } } } diff --git a/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs b/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs index a78d35af24..fc4c41ad9c 100644 --- a/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs +++ b/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs @@ -17,24 +17,6 @@ namespace OpenRA.Test [TestFixture] public class MiniYamlTest { - readonly string mixedMergeA = @" -Merge: - FromA: - FromARemovedB: - FromARemovedA: - -FromBRemovedA: - -FromARemovedA: -"; - - readonly string mixedMergeB = @" -Merge: - FromB: - FromBRemovedA: - FromBRemovedB: - -FromARemovedB: - -FromBRemovedB: -"; - readonly string yamlTabStyle = @" Root1: Child1: @@ -61,30 +43,6 @@ Root2: Attribute1: Test "; - [TestCase(TestName = "Merging: mixed addition and removal")] - public void MergeYamlA() - { - var a = MiniYaml.FromString(mixedMergeA, "mixedMergeA"); - var b = MiniYaml.FromString(mixedMergeB, "mixedMergeB"); - - // Merge order should not matter - // Note: All the Merge* variants are different plumbing over the same - // Internal logic. Testing only Merge is sufficient. - TestMixedMerge(MiniYaml.Merge(a, b).First().Value); - TestMixedMerge(MiniYaml.Merge(b, a).First().Value); - } - - void TestMixedMerge(MiniYaml result) - { - Console.WriteLine(result.ToLines("result").JoinWith("\n")); - Assert.That(result.Nodes.Any(n => n.Key == "FromA"), Is.True, "Node from A"); - Assert.That(result.Nodes.Any(n => n.Key == "FromB"), Is.True, "Node from B"); - Assert.That(result.Nodes.Any(n => n.Key == "FromARemovedA"), Is.Not.True, "Node from A removed by A"); - Assert.That(result.Nodes.Any(n => n.Key == "FromARemovedB"), Is.Not.True, "Node from A removed by B"); - Assert.That(result.Nodes.Any(n => n.Key == "FromBRemovedA"), Is.Not.True, "Node from B removed by A"); - Assert.That(result.Nodes.Any(n => n.Key == "FromBRemovedB"), Is.Not.True, "Node from B removed by B"); - } - [TestCase(TestName = "Mixed tabs & spaces indents")] public void TestIndents() { @@ -94,5 +52,106 @@ Root2: Console.WriteLine(mixed); Assert.That(tabs, Is.EqualTo(mixed)); } + + [TestCase(TestName = "Inheritance and removal can be composed")] + public void InheritanceAndRemovalCanBeComposed() + { + var baseYaml = @" +^BaseA: + MockA2: +^BaseB: + Inherits@a: ^BaseA + MockB2: +"; + var extendedYaml = @" +Test: + Inherits@b: ^BaseB + -MockA2: +"; + var mapYaml = @" +^BaseC: + MockC2: +Test: + Inherits@c: ^BaseC +"; + var result = MiniYaml.Merge(new[] { baseYaml, extendedYaml, mapYaml }.Select(s => MiniYaml.FromString(s, ""))) + .First(n => n.Key == "Test").Value.Nodes; + + Assert.IsFalse(result.Any(n => n.Key == "MockA2"), "Node should not have the MockA2 child, but does."); + Assert.IsTrue(result.Any(n => n.Key == "MockB2"), "Node should have the MockB2 child, but does not."); + Assert.IsTrue(result.Any(n => n.Key == "MockC2"), "Node should have the MockC2 child, but does not."); + } + + [TestCase(TestName = "Child can be removed after multiple inheritance")] + public void ChildCanBeRemovedAfterMultipleInheritance() + { + var baseYaml = @" +^BaseA: + MockA2: +Test: + Inherits: ^BaseA + MockA2: +"; + var overrideYaml = @" +Test: + -MockA2 +"; + + var result = MiniYaml.Merge(new[] { baseYaml, overrideYaml }.Select(s => MiniYaml.FromString(s, ""))) + .First(n => n.Key == "Test").Value.Nodes; + + Assert.IsFalse(result.Any(n => n.Key == "MockA2"), "Node should not have the MockA2 child, but does."); + } + + [TestCase(TestName = "Child can be removed and later overridden")] + public void ChildCanBeRemovedAndLaterOverridden() + { + var baseYaml = @" +^BaseA: + MockString: + AString: Base +Test: + Inherits: ^BaseA + -MockString: +"; + var overrideYaml = @" +Test: + MockString: + AString: Override +"; + + var result = MiniYaml.Merge(new[] { baseYaml, overrideYaml }.Select(s => MiniYaml.FromString(s, ""))) + .First(n => n.Key == "Test").Value.Nodes; + + Assert.IsTrue(result.Any(n => n.Key == "MockString"), "Node should have the MockString child, but does not."); + Assert.IsTrue(result.First(n => n.Key == "MockString").Value.ToDictionary()["AString"].Value == "Override", + "MockString value has not been set with the correct override value for AString."); + } + + [TestCase(TestName = "Child can be removed from intermediate parent")] + public void ChildCanBeOverriddenThenRemoved() + { + var baseYaml = @" +^BaseA: + MockString: + AString: Base +^BaseB: + Inherits: ^BaseA + MockString: + AString: Override +"; + var overrideYaml = @" +Test: + Inherits: ^BaseB + MockString: + -AString: +"; + + var result = MiniYaml.Merge(new[] { baseYaml, overrideYaml }.Select(s => MiniYaml.FromString(s, ""))) + .First(n => n.Key == "Test").Value.Nodes; + Assert.IsTrue(result.Any(n => n.Key == "MockString"), "Node should have the MockString child, but does not."); + Assert.IsFalse(result.First(n => n.Key == "MockString").Value.Nodes.Any(n => n.Key == "AString"), + "MockString value should have been removed, but was not."); + } } } From 3e3c11ea5ceefd0bafc8904433940a09e37fad37 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 30 Jan 2016 14:06:24 +0000 Subject: [PATCH 3/4] Remove duplication between vice and pvice sequences. --- mods/cnc/sequences/infantry.yaml | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/mods/cnc/sequences/infantry.yaml b/mods/cnc/sequences/infantry.yaml index 29f5519e77..9635ad9164 100644 --- a/mods/cnc/sequences/infantry.yaml +++ b/mods/cnc/sequences/infantry.yaml @@ -32,37 +32,7 @@ vice: icon: viceicnh pvice: - idle: - Length: * - muzzle: - Combine: - chem-n: - Length: * - Offset: 1,2 - chem-nw: - Length: * - Offset: 8,2 - chem-w: - Length: * - Offset: 8,-3 - chem-sw: - Length: * - Offset: 7,-6 - chem-s: - Length: * - Offset: 1,-6 - chem-se: - Length: * - Offset: -5,-6 - chem-e: - Length: * - Offset: -7,-3 - chem-ne: - Length: * - Offset: -3,2 - Facings: 8 - Length: 13 - icon: viceicnh + Inherits: vice e1: stand: From f8f3d1deb45f208b41d9e50f099ef38582612e92 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 30 Jan 2016 14:32:52 +0000 Subject: [PATCH 4/4] Fix allies-05a map yaml. --- mods/ra/maps/allies-05a/map.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mods/ra/maps/allies-05a/map.yaml b/mods/ra/maps/allies-05a/map.yaml index b64005964c..8df2078b03 100644 --- a/mods/ra/maps/allies-05a/map.yaml +++ b/mods/ra/maps/allies-05a/map.yaml @@ -1710,9 +1710,8 @@ Rules: RenderSprites: Image: E7 Colt: - -Huntable: - AutoTargetIgnore: Inherits: ^Defense + AutoTargetIgnore: Valued: Cost: 800 Building: @@ -1736,6 +1735,7 @@ Rules: AttackTurreted: AutoTarget: -Selectable: + -Huntable: E1.Autotarget: Inherits: E1 Buildable: