Move timers from ConditionManager to ExternalCondition

This commit is contained in:
atlimit8
2017-08-27 11:21:21 +00:00
committed by Paul Chote
parent c61cd37bec
commit fd6b2c0107
2 changed files with 56 additions and 79 deletions

View File

@@ -16,34 +16,14 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.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.")] [Desc("Attach this to a unit to enable dynamic conditions by warheads, experience, crates, support powers, etc.")]
public class ConditionManagerInfo : TraitInfo<ConditionManager>, Requires<IObservesVariablesInfo> { } public class ConditionManagerInfo : TraitInfo<ConditionManager>, Requires<IObservesVariablesInfo> { }
public class ConditionManager : INotifyCreated, ITick public class ConditionManager : INotifyCreated
{ {
/// <summary>Value used to represent an invalid token.</summary> /// <summary>Value used to represent an invalid token.</summary>
public static readonly int InvalidConditionToken = -1; 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 class ConditionState
{ {
/// <summary>Delegates that have registered to be notified when this condition changes.</summary> /// <summary>Delegates that have registered to be notified when this condition changes.</summary>
@@ -51,13 +31,9 @@ namespace OpenRA.Mods.Common.Traits
/// <summary>Unique integers identifying granted instances of the condition.</summary> /// <summary>Unique integers identifying granted instances of the condition.</summary>
public readonly HashSet<int> Tokens = new HashSet<int>(); public readonly HashSet<int> Tokens = new HashSet<int>();
/// <summary>External callbacks that are to be executed when a timed condition changes.</summary>
public readonly List<IConditionTimerWatcher> Watchers = new List<IConditionTimerWatcher>();
} }
Dictionary<string, ConditionState> state; Dictionary<string, ConditionState> state;
readonly Dictionary<string, List<ConditionTimer>> timers = new Dictionary<string, List<ConditionTimer>>();
/// <summary>Each granted condition receives a unique token that is used when revoking.</summary> /// <summary>Each granted condition receives a unique token that is used when revoking.</summary>
Dictionary<int, string> tokens = new Dictionary<int, string>(); Dictionary<int, string> tokens = new Dictionary<int, string>();
@@ -76,7 +52,6 @@ namespace OpenRA.Mods.Common.Traits
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache); readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
var allObservers = new HashSet<VariableObserverNotifier>(); var allObservers = new HashSet<VariableObserverNotifier>();
var allWatchers = self.TraitsImplementing<IConditionTimerWatcher>().ToList();
foreach (var provider in self.TraitsImplementing<IObservesVariables>()) foreach (var provider in self.TraitsImplementing<IObservesVariables>())
{ {
@@ -87,10 +62,6 @@ namespace OpenRA.Mods.Common.Traits
{ {
var cs = state.GetOrAdd(variable); var cs = state.GetOrAdd(variable);
cs.Notifiers.Add(variableUser.Notifier); cs.Notifiers.Add(variableUser.Notifier);
foreach (var w in allWatchers)
if (w.Condition == variable)
cs.Watchers.Add(w);
conditionCache[variable] = 0; conditionCache[variable] = 0;
} }
} }
@@ -131,15 +102,11 @@ namespace OpenRA.Mods.Common.Traits
/// <summary>Grants a specified condition.</summary> /// <summary>Grants a specified condition.</summary>
/// <returns>The token that is used to revoke this condition.</returns> /// <returns>The token that is used to revoke this condition.</returns>
/// <param name="duration">Automatically revoke condition after this delay if non-zero.</param> public int GrantCondition(Actor self, string condition)
public int GrantCondition(Actor self, string condition, int duration = 0)
{ {
var token = nextToken++; var token = nextToken++;
tokens.Add(token, condition); tokens.Add(token, condition);
if (duration > 0)
timers.GetOrAdd(condition).Add(new ConditionTimer(token, duration));
// Conditions may be granted before the state is initialized. // Conditions may be granted before the state is initialized.
// These conditions will be processed in INotifyCreated.Created. // These conditions will be processed in INotifyCreated.Created.
if (state != null) if (state != null)
@@ -159,15 +126,6 @@ namespace OpenRA.Mods.Common.Traits
tokens.Remove(token); tokens.Remove(token);
// Clean up timers
List<ConditionTimer> 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. // Conditions may be granted and revoked before the state is initialized.
if (state != null) if (state != null)
UpdateConditionState(self, condition, token, true); UpdateConditionState(self, condition, token, true);
@@ -180,38 +138,5 @@ namespace OpenRA.Mods.Common.Traits
{ {
return tokens.ContainsKey(token); return tokens.ContainsKey(token);
} }
readonly HashSet<int> timersToRemove = new HashSet<int>();
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();
}
} }
} }

View File

@@ -16,6 +16,13 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.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).")] [Desc("Allows a condition to be granted from an external source (Lua, warheads, etc).")]
public class ExternalConditionInfo : ITraitInfo, Requires<ConditionManagerInfo> public class ExternalConditionInfo : ITraitInfo, Requires<ConditionManagerInfo>
{ {
@@ -32,7 +39,7 @@ namespace OpenRA.Mods.Common.Traits
public object Create(ActorInitializer init) { return new ExternalCondition(init.Self, this); } public object Create(ActorInitializer init) { return new ExternalCondition(init.Self, this); }
} }
public class ExternalCondition : ITick public class ExternalCondition : ITick, INotifyCreated
{ {
struct TimedToken struct TimedToken
{ {
@@ -54,6 +61,9 @@ namespace OpenRA.Mods.Common.Traits
// Tokens are sorted on insert/remove by ascending expiry time // Tokens are sorted on insert/remove by ascending expiry time
readonly List<TimedToken> timedTokens = new List<TimedToken>(); readonly List<TimedToken> timedTokens = new List<TimedToken>();
IConditionTimerWatcher[] watchers;
int duration;
int expires;
public ExternalCondition(Actor self, ExternalConditionInfo info) public ExternalCondition(Actor self, ExternalConditionInfo info)
{ {
@@ -86,7 +96,7 @@ namespace OpenRA.Mods.Common.Traits
if (!CanGrantCondition(self, source)) if (!CanGrantCondition(self, source))
return ConditionManager.InvalidConditionToken; return ConditionManager.InvalidConditionToken;
var token = conditionManager.GrantCondition(self, Info.Condition, duration); var token = conditionManager.GrantCondition(self, Info.Condition);
HashSet<int> permanent; HashSet<int> permanent;
permanentTokens.TryGetValue(source, out permanent); permanentTokens.TryGetValue(source, out permanent);
@@ -132,7 +142,13 @@ namespace OpenRA.Mods.Common.Traits
if (index >= 0) if (index >= 0)
timedTokens.Insert(index, timedToken); timedTokens.Insert(index, timedToken);
else else
{
timedTokens.Add(timedToken); timedTokens.Add(timedToken);
// Track the duration and expiration for the longest remaining timer.
expires = timedToken.Expires;
this.duration = duration;
}
} }
else if (permanent == null) else if (permanent == null)
permanentTokens.Add(source, new HashSet<int> { token }); permanentTokens.Add(source, new HashSet<int> { token });
@@ -172,14 +188,50 @@ namespace OpenRA.Mods.Common.Traits
void ITick.Tick(Actor self) void ITick.Tick(Actor self)
{ {
if (timedTokens.Count == 0)
return;
// Remove expired tokens // Remove expired tokens
var worldTick = self.World.WorldTick; var worldTick = self.World.WorldTick;
var count = 0; var count = 0;
while (count < timedTokens.Count && timedTokens[count].Expires < worldTick) while (count < timedTokens.Count && timedTokens[count].Expires < worldTick)
{
var token = timedTokens[count].Token;
if (conditionManager.TokenValid(self, token))
conditionManager.RevokeCondition(self, token);
count++; count++;
}
if (count > 0) if (count > 0)
{
timedTokens.RemoveRange(0, count); 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<IConditionTimerWatcher>().Where(Notifies).ToArray();
} }
} }
} }