diff --git a/OpenRA.Mods.Common/Traits/Conditions/ConditionManager.cs b/OpenRA.Mods.Common/Traits/Conditions/ConditionManager.cs index a6ff452b9a..3b53a78cf6 100644 --- a/OpenRA.Mods.Common/Traits/Conditions/ConditionManager.cs +++ b/OpenRA.Mods.Common/Traits/Conditions/ConditionManager.cs @@ -16,34 +16,14 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - [RequireExplicitImplementation] - public interface IConditionTimerWatcher - { - string Condition { get; } - void Update(int duration, int remaining); - } - [Desc("Attach this to a unit to enable dynamic conditions by warheads, experience, crates, support powers, etc.")] public class ConditionManagerInfo : TraitInfo, Requires { } - public class ConditionManager : INotifyCreated, ITick + public class ConditionManager : INotifyCreated { /// Value used to represent an invalid token. public static readonly int InvalidConditionToken = -1; - class ConditionTimer - { - public readonly int Token; - public readonly int Duration; - public int Remaining; - - public ConditionTimer(int token, int duration) - { - Token = token; - Duration = Remaining = duration; - } - } - class ConditionState { /// Delegates that have registered to be notified when this condition changes. @@ -51,13 +31,9 @@ namespace OpenRA.Mods.Common.Traits /// Unique integers identifying granted instances of the condition. public readonly HashSet Tokens = new HashSet(); - - /// External callbacks that are to be executed when a timed condition changes. - public readonly List Watchers = new List(); } Dictionary state; - readonly Dictionary> timers = new Dictionary>(); /// Each granted condition receives a unique token that is used when revoking. Dictionary tokens = new Dictionary(); @@ -76,7 +52,6 @@ namespace OpenRA.Mods.Common.Traits readOnlyConditionCache = new ReadOnlyDictionary(conditionCache); var allObservers = new HashSet(); - var allWatchers = self.TraitsImplementing().ToList(); foreach (var provider in self.TraitsImplementing()) { @@ -87,10 +62,6 @@ namespace OpenRA.Mods.Common.Traits { var cs = state.GetOrAdd(variable); cs.Notifiers.Add(variableUser.Notifier); - foreach (var w in allWatchers) - if (w.Condition == variable) - cs.Watchers.Add(w); - conditionCache[variable] = 0; } } @@ -131,15 +102,11 @@ namespace OpenRA.Mods.Common.Traits /// Grants a specified condition. /// The token that is used to revoke this condition. - /// Automatically revoke condition after this delay if non-zero. - public int GrantCondition(Actor self, string condition, int duration = 0) + public int GrantCondition(Actor self, string condition) { var token = nextToken++; tokens.Add(token, condition); - if (duration > 0) - timers.GetOrAdd(condition).Add(new ConditionTimer(token, duration)); - // Conditions may be granted before the state is initialized. // These conditions will be processed in INotifyCreated.Created. if (state != null) @@ -159,15 +126,6 @@ namespace OpenRA.Mods.Common.Traits tokens.Remove(token); - // Clean up timers - List ct; - if (timers.TryGetValue(condition, out ct)) - { - ct.RemoveAll(t => t.Token == token); - if (!ct.Any()) - timers.Remove(condition); - } - // Conditions may be granted and revoked before the state is initialized. if (state != null) UpdateConditionState(self, condition, token, true); @@ -180,38 +138,5 @@ namespace OpenRA.Mods.Common.Traits { return tokens.ContainsKey(token); } - - readonly HashSet timersToRemove = new HashSet(); - void ITick.Tick(Actor self) - { - // Watchers will be receiving notifications while the condition is enabled. - // They will also be provided with the number of ticks before the condition is disabled, - // as well as the duration of the longest active instance. - foreach (var kv in timers) - { - var duration = 0; - var remaining = 0; - foreach (var t in kv.Value) - { - if (--t.Remaining <= 0) - timersToRemove.Add(t.Token); - - // Track the duration and remaining time for the longest remaining timer - if (t.Remaining > remaining) - { - duration = t.Duration; - remaining = t.Remaining; - } - } - - foreach (var w in state[kv.Key].Watchers) - w.Update(duration, remaining); - } - - foreach (var t in timersToRemove) - RevokeCondition(self, t); - - timersToRemove.Clear(); - } } } diff --git a/OpenRA.Mods.Common/Traits/Conditions/ExternalCondition.cs b/OpenRA.Mods.Common/Traits/Conditions/ExternalCondition.cs index 98930bcd10..986698c0f2 100644 --- a/OpenRA.Mods.Common/Traits/Conditions/ExternalCondition.cs +++ b/OpenRA.Mods.Common/Traits/Conditions/ExternalCondition.cs @@ -16,6 +16,13 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { + [RequireExplicitImplementation] + public interface IConditionTimerWatcher + { + string Condition { get; } + void Update(int duration, int remaining); + } + [Desc("Allows a condition to be granted from an external source (Lua, warheads, etc).")] public class ExternalConditionInfo : ITraitInfo, Requires { @@ -32,7 +39,7 @@ namespace OpenRA.Mods.Common.Traits public object Create(ActorInitializer init) { return new ExternalCondition(init.Self, this); } } - public class ExternalCondition : ITick + public class ExternalCondition : ITick, INotifyCreated { struct TimedToken { @@ -54,6 +61,9 @@ namespace OpenRA.Mods.Common.Traits // Tokens are sorted on insert/remove by ascending expiry time readonly List timedTokens = new List(); + IConditionTimerWatcher[] watchers; + int duration; + int expires; public ExternalCondition(Actor self, ExternalConditionInfo info) { @@ -86,7 +96,7 @@ namespace OpenRA.Mods.Common.Traits if (!CanGrantCondition(self, source)) return ConditionManager.InvalidConditionToken; - var token = conditionManager.GrantCondition(self, Info.Condition, duration); + var token = conditionManager.GrantCondition(self, Info.Condition); HashSet permanent; permanentTokens.TryGetValue(source, out permanent); @@ -132,7 +142,13 @@ namespace OpenRA.Mods.Common.Traits if (index >= 0) timedTokens.Insert(index, timedToken); else + { timedTokens.Add(timedToken); + + // Track the duration and expiration for the longest remaining timer. + expires = timedToken.Expires; + this.duration = duration; + } } else if (permanent == null) permanentTokens.Add(source, new HashSet { token }); @@ -172,14 +188,50 @@ namespace OpenRA.Mods.Common.Traits void ITick.Tick(Actor self) { + if (timedTokens.Count == 0) + return; + // Remove expired tokens var worldTick = self.World.WorldTick; var count = 0; while (count < timedTokens.Count && timedTokens[count].Expires < worldTick) + { + var token = timedTokens[count].Token; + if (conditionManager.TokenValid(self, token)) + conditionManager.RevokeCondition(self, token); + count++; + } if (count > 0) + { timedTokens.RemoveRange(0, count); + if (timedTokens.Count == 0) + { + // Notify watchers that all timers have expired. + foreach (var w in watchers) + w.Update(0, 0); + + return; + } + } + + // Watchers will be receiving notifications while the condition is enabled. + // They will also be provided with the number of ticks before the last timer ends, + // as well as the duration of the longest active instance. + if (timedTokens.Count > 0) + { + var remaining = expires - worldTick; + foreach (var w in watchers) + w.Update(duration, remaining); + } + } + + bool Notifies(IConditionTimerWatcher watcher) { return watcher.Condition == Info.Condition; } + + void INotifyCreated.Created(Actor self) + { + watchers = self.TraitsImplementing().Where(Notifies).ToArray(); } } }