From 0cbce8141f785b6d40002f3ab1c8cb3672ab01be Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Tue, 22 Mar 2016 19:44:39 +0000 Subject: [PATCH] Fix circular trait dependencies on UpgradeManager. Don't use Requires to force an ordering, instead just use INotifyCreated to grab all the traits once constructed. --- OpenRA.Mods.Common/Traits/Cloak.cs | 5 +- .../Traits/Upgrades/UpgradeManager.cs | 56 ++++++++++++------- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/Cloak.cs b/OpenRA.Mods.Common/Traits/Cloak.cs index 16d7dc9c45..d3359b5eaf 100644 --- a/OpenRA.Mods.Common/Traits/Cloak.cs +++ b/OpenRA.Mods.Common/Traits/Cloak.cs @@ -77,8 +77,11 @@ namespace OpenRA.Mods.Common.Traits void INotifyCreated.Created(Actor self) { upgradeManager = self.TraitOrDefault(); + + // The upgrade manager exists, but may not have finished being created yet. + // We'll defer the upgrades until the end of the tick, at which point it will be ready. if (Cloaked) - GrantUpgrades(self); + self.World.AddFrameEndTask(_ => GrantUpgrades(self)); } public bool Cloaked { get { return !IsTraitDisabled && remainingTime <= 0; } } diff --git a/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs b/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs index 84cfa545d6..81d2e0e4ad 100644 --- a/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs +++ b/OpenRA.Mods.Common/Traits/Upgrades/UpgradeManager.cs @@ -17,12 +17,18 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Attach this to a unit to enable dynamic upgrades by warheads, experience, crates, support powers, etc.")] - public class UpgradeManagerInfo : ITraitInfo, Requires + public class UpgradeManagerInfo : TraitInfo, IRulesetLoaded { - public object Create(ActorInitializer init) { return new UpgradeManager(init); } + public void RulesetLoaded(Ruleset rules, ActorInfo info) + { + if (!info.Name.StartsWith("^") && !info.TraitInfos().Any()) + throw new YamlException( + "There are no upgrades to be managed for actor '{0}'. You are either missing some upgradeable traits, or this UpgradeManager trait is not required.".F( + info.Name)); + } } - public class UpgradeManager : ITick + public class UpgradeManager : INotifyCreated, ITick { class TimedUpgrade { @@ -67,20 +73,21 @@ namespace OpenRA.Mods.Common.Traits } readonly List timedUpgrades = new List(); - readonly Lazy> upgrades; readonly Dictionary levels = new Dictionary(); + Dictionary upgrades; - public UpgradeManager(ActorInitializer init) + void INotifyCreated.Created(Actor self) { - upgrades = Exts.Lazy(() => - { - var ret = new Dictionary(); - foreach (var up in init.Self.TraitsImplementing()) - foreach (var t in up.UpgradeTypes) - ret.GetOrAdd(t).Traits.Add(up); + upgrades = new Dictionary(); + foreach (var up in self.TraitsImplementing()) + foreach (var t in up.UpgradeTypes) + upgrades.GetOrAdd(t).Traits.Add(up); + } - return ret; - }); + void CheckCanManageUpgrades() + { + if (upgrades == null) + throw new InvalidOperationException("Upgrades cannot be managed until the actor has been fully created."); } /// Upgrade level increments are limited to dupesAllowed per source, i.e., if a single @@ -139,8 +146,10 @@ namespace OpenRA.Mods.Common.Traits public void GrantUpgrade(Actor self, string upgrade, object source) { + CheckCanManageUpgrades(); + UpgradeState s; - if (!upgrades.Value.TryGetValue(upgrade, out s)) + if (!upgrades.TryGetValue(upgrade, out s)) return; // Track the upgrade source so that the upgrade can be removed without conflicts @@ -151,8 +160,10 @@ namespace OpenRA.Mods.Common.Traits public void RevokeUpgrade(Actor self, string upgrade, object source) { + CheckCanManageUpgrades(); + UpgradeState s; - if (!upgrades.Value.TryGetValue(upgrade, out s)) + if (!upgrades.TryGetValue(upgrade, out s)) return; if (!s.Sources.Remove(source)) @@ -164,14 +175,17 @@ namespace OpenRA.Mods.Common.Traits /// Returns true if the actor uses the given upgrade. Does not check the actual level of the upgrade. public bool AcknowledgesUpgrade(Actor self, string upgrade) { - return upgrades.Value.ContainsKey(upgrade); + CheckCanManageUpgrades(); + return upgrades.ContainsKey(upgrade); } /// Returns true only if the actor can accept another level of the upgrade. public bool AcceptsUpgrade(Actor self, string upgrade) { + CheckCanManageUpgrades(); + UpgradeState s; - if (!upgrades.Value.TryGetValue(upgrade, out s)) + if (!upgrades.TryGetValue(upgrade, out s)) return false; return s.Traits.Any(up => up.AcceptsUpgradeLevel(self, upgrade, GetOverallLevel(up) + 1)); @@ -179,8 +193,10 @@ namespace OpenRA.Mods.Common.Traits public void RegisterWatcher(string upgrade, Action action) { + CheckCanManageUpgrades(); + UpgradeState s; - if (!upgrades.Value.TryGetValue(upgrade, out s)) + if (!upgrades.TryGetValue(upgrade, out s)) return; s.Watchers.Add(action); @@ -192,6 +208,8 @@ namespace OpenRA.Mods.Common.Traits /// GrantTimedUpgrade). public void Tick(Actor self) { + CheckCanManageUpgrades(); + foreach (var u in timedUpgrades) { u.Tick(); @@ -201,7 +219,7 @@ namespace OpenRA.Mods.Common.Traits u.Sources.RemoveWhere(source => source.Remaining <= 0); - foreach (var a in upgrades.Value[u.Upgrade].Watchers) + foreach (var a in upgrades[u.Upgrade].Watchers) a(u.Duration, u.Remaining); }