internal HandlerRules(IHandlers <TObject, TBaseStatus> handlers, TBaseStatus status, TBaseStatus overridden, bool effect) { this.handlers = handlers; this.status = status; this.overridden = overridden; this.effect = effect; }
void IHandlers <TObject> .SetHandler(TBaseStatus status, TBaseStatus overridden, bool increased, bool effect, OnChangedHandler <TObject> handler) { if (!onChangedHandlers.ContainsKey(status)) { onChangedHandlers.Add(status, new DefaultValueDictionary <StatusChange, OnChangedHandler <TObject> >()); } onChangedHandlers[status][new StatusChange(overridden, increased, effect)] = handler; }
/// <param name="status">The status to which this instance will add its value</param> /// <param name="value">The amount by which this instance will increase its status</param> /// <param name="cancelPriority">An instance with lower cancel priority will be cancelled before an instance with /// higher priority when Cancel() is called on this status.</param> /// <param name="type"> /// The InstanceType determines whether this instance will feed, suppress, or prevent its status. /// (Feed is the default and most common. When a status is cancelled, its "Feed" StatusInstances are removed.) /// </param> public StatusInstance(TBaseStatus status, int value = 1, int cancelPriority = 0, InstanceType type = InstanceType.Feed, int?overrideSetIndex = null) { Status = status; internalValue = value; CancelPriority = cancelPriority; InstanceType = type; this.overrideSetIndex = overrideSetIndex; }
void IHandlers <TObject> .SetHandler(TBaseStatus ignored, TBaseStatus overridden, bool increased, bool effect, OnChangedHandler <TObject> handler) { if (onChangedOverrides == null) { onChangedOverrides = new DefaultValueDictionary <StatusChange, OnChangedHandler <TObject> >(); } onChangedOverrides[new StatusChange(overridden, increased, effect)] = handler; }
OnChangedHandler <TObject> IHandlers <TObject> .GetHandler(TBaseStatus status, TBaseStatus overridden, bool increased, bool effect) { if (!onChangedHandlers.ContainsKey(status)) { return(null); } return(onChangedHandlers[status][new StatusChange(overridden, increased, effect)]); }
OnChangedHandler <TObject> IHandlers <TObject> .GetHandler(TBaseStatus status, TBaseStatus ignored, bool increased, bool effect) { if (onChangedOverrides == null) { return(null); } return(onChangedOverrides[new StatusChange(status, increased, effect)]); }
/// <summary> /// Add a StatusInstance to this tracker, updating the value of the status associated with the given instance. /// </summary> public bool AddStatusInstance(StatusInstance <TObject> instance) { if (instance == null) { throw new ArgumentNullException(); } if (instance.tracker != null && instance.tracker != this) { throw new InvalidOperationException("Already added to another tracker"); } TBaseStatus status = instance.Status; InstanceType type = instance.InstanceType; if (type == InstanceType.Feed) { if (currentRaw[InstanceType.Prevent][status] > 0) { return(false); } var preventableStatuses = new List <TBaseStatus> { status }.Concat(rules.statusesExtendedBy[status]); foreach (var preventableStatus in preventableStatuses) { if (rules.extraPreventionConditions.AnyValues(preventableStatus)) { foreach (var condition in rules.extraPreventionConditions[preventableStatus]) { if (condition(obj, preventableStatus)) { return(false); } } } } if (rules.SingleInstance[status]) { foreach (StatusInstance <TObject> removedInstance in statusInstances[InstanceType.Feed][status]) { removedInstance.tracker = null; } statusInstances[InstanceType.Feed].Clear(status); } } if (statusInstances[type].AddUnique(status, instance)) { instance.tracker = this; CheckInstanceChanged(instance); return(true); } else { return(false); } }
/// <summary> /// Cancel the given status, removing all "Feed" StatusInstances that have been added to this tracker for this status. /// (This will return the value of this status to zero, unless other statuses are feeding this one.) /// </summary> public void Cancel(TBaseStatus status) { foreach (var instance in statusInstances[InstanceType.Feed][status].OrderBy(x => x.CancelPriority)) { RemoveStatusInstance(instance); } foreach (TBaseStatus extendingStatus in rules.statusesThatExtend[status]) { Cancel(extendingStatus); } }
internal Aggregator GetAggregator(TBaseStatus status, InstanceType type) { if (type == InstanceType.Feed) { Aggregator agg = valueAggs[status]; if (agg != null) { return(agg); } } return(defaultAggs[type]); }
private OnChangedHandler <TObject> GetHandler(TBaseStatus status, bool increased, bool effect) { var change = new StatusChange(status, increased, effect); OnChangedHandler <TObject> result; foreach (var dict in changeStack) { if (dict.TryGetValue(change, out result)) { return(result); } } return(null); }
protected static TBaseStatus[] Convert <TStatus>(TStatus[] statuses) where TStatus : struct { if (statuses == null) { return(null); } var result = new TBaseStatus[statuses.Length]; for (int i = 0; i < statuses.Length; ++i) { result[i] = BaseStatusSystem <TObject, TBaseStatus> .Convert(statuses[i]); } return(result); }
/// <summary> /// Create a new StatusInstance and add it to this tracker, updating the value of the given status. /// Returns the newly created instance, if successfully added, or null, if not. /// </summary> /// <param name="status">The status to which the instance will add its value</param> /// <param name="value">The amount by which the instance will increase the given status</param> /// <param name="cancelPriority">An instance with lower cancel priority will be cancelled before an instance with /// higher priority when Cancel() is called on its status.</param> /// <param name="type"> /// The InstanceType determines whether the instance will feed, suppress, or prevent its status. /// (Feed is the default and most common. When a status is cancelled, its "Feed" StatusInstances are removed.) /// </param> public StatusInstance <TObject> Add(TBaseStatus status, int value = 1, int cancelPriority = 0, InstanceType type = InstanceType.Feed, int?overrideSetIndex = null) { var instance = new StatusInstance <TObject>(status, value, cancelPriority, type, overrideSetIndex); if (AddStatusInstance(instance)) { return(instance); } else { return(null); } }
/// <summary> /// Retrieve the current int value of the given status. /// The status's value can also be directly set, but only if the SingleInstance property is true for this status. /// </summary> public int this[TBaseStatus status] { get { return(currentActualValues[status]); } set { if (!rules.SingleInstance[status]) { throw new InvalidOperationException("'SingleInstance' must be true in order to set a value directly."); } foreach (var instance in statusInstances[InstanceType.Feed][status]) { instance.Value = value; return; // If any instances exist, change the value of the first one, then return. } AddStatusInstance(new StatusInstance <TObject>(status, value)); // Otherwise, create a new one. } }
private void CheckActualValueChanged(TBaseStatus status) { int newValue; if (currentRaw[InstanceType.Suppress][status] > 0) { newValue = 0; } else { newValue = currentRaw[InstanceType.Feed][status]; } int oldValue = currentActualValues[status]; if (newValue != oldValue) { currentActualValues[status] = newValue; bool increased = newValue > oldValue; if (!GenerateNoMessages) { GetHandler(status, increased, false)?.Invoke(obj, status, oldValue, newValue); } if (!GenerateNoEffects) { GetHandler(status, increased, true)?.Invoke(obj, status, oldValue, newValue); } UpdateFeed(status, InstanceType.Feed, newValue); if (increased) { foreach (TBaseStatus cancelledStatus in rules.statusesCancelledBy[status]) { var pair = new StatusPair(status, cancelledStatus); var condition = rules.cancellationConditions[pair]; // if a condition exists, it must return true for the if (condition == null || condition(newValue)) { Cancel(cancelledStatus); // status to be cancelled. } } } UpdateFeed(status, InstanceType.Suppress, newValue); // Cancellations happen before suppression to prevent some infinite loops UpdateFeed(status, InstanceType.Prevent, newValue); } }
private void ReadOtherRule(TBaseStatus status) { string token = tokens[idx]; switch (token) { case "total": case "max": case "bool": SetAggregator(status, token); break; case "single": case "multiple": SetInstanceLimit(status, token); break; } ++idx; }
/// <summary> /// Remove a StatusInstance from this tracker, updating the value of the status associated with the given instance. /// Returns true if successful, or false if the instance wasn't in this tracker. /// </summary> public bool RemoveStatusInstance(StatusInstance <TObject> instance) { if (instance == null) { throw new ArgumentNullException(); } TBaseStatus status = instance.Status; InstanceType type = instance.InstanceType; if (statusInstances[type].Remove(status, instance)) { instance.tracker = null; CheckInstanceChanged(instance); return(true); } else { return(false); } }
private void ReadBasicRule(TBaseStatus status) { string comparisonOperator = null, comparisonValue = null, verb = null, otherStatus = null, fedValue = null; if (TryConsume(StatusParser.IsComparisonOperator, ref comparisonOperator)) { ConsumeOrError(StatusParser.IsNumber, ref comparisonValue); } ConsumeOrError(StatusParser.IsBasicVerb, ref verb); while (true) { ConsumeOrError(IsStatus, ref otherStatus); TryConsume(StatusParser.IsNumber, ref fedValue); SetRule(false, status, comparisonOperator, comparisonValue, verb, otherStatus, fedValue); if (!TryConsume(",")) { break; } } }
private bool TryParse(string token, out TBaseStatus status) { if (typeof(TBaseStatus).IsEnum && Enum.TryParse(token, true, out status)) { return(true); } if (extraEnums != null) { if (baseTryParse == null) { foreach (var method in typeof(Enum).GetMethods()) { var parameters = method.GetParameters(); if (method.Name == "TryParse" && parameters.Length == 3 && method.IsGenericMethod && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == typeof(bool) && parameters[2].ParameterType.GetElementType() == method.GetGenericArguments()[0]) { baseTryParse = method; break; } } if (baseTryParse == null) { throw new MissingMethodException("Enum.TryParse<TEnum>(string, bool, TEnum) seems to be missing."); } } foreach (Type t in extraEnums) { MethodInfo m = baseTryParse.MakeGenericMethod(t); object[] mParams = new object[] { token, true, null }; bool success = (bool)m.Invoke(null, mParams); if (success) { status = (TBaseStatus)mParams[2]; return(true); } } } status = default(TBaseStatus); return(false); }
private void ReadRule(TBaseStatus status) { string token = tokens[idx]; if (token.IsBasicVerb() || token.IsComparisonOperator()) { ReadBasicRule(status); } else if (token.IsInvertedVerb() || token.IsNumber()) { ReadInvertedRule(status); } else if (token.IsAggregator() || token.IsInstanceLimiter()) { ReadOtherRule(status); } else { throw new InvalidDataException($"Syntax error: Unexpected {token}."); } }
protected StatusInstance(StatusInstance <TObject> copyFrom, int?value = null, int?cancelPriority = null, InstanceType?type = null, int?overrideSetIndex = null) { if (copyFrom == null) { throw new ArgumentNullException("copyFrom"); } Status = copyFrom.Status; if (value == null) { internalValue = copyFrom.internalValue; } else { internalValue = value.Value; } if (cancelPriority == null) { CancelPriority = copyFrom.CancelPriority; } else { CancelPriority = cancelPriority.Value; } if (type == null) { InstanceType = copyFrom.InstanceType; } else { InstanceType = type.Value; } if (overrideSetIndex == null) { this.overrideSetIndex = copyFrom.overrideSetIndex; } else { this.overrideSetIndex = overrideSetIndex; } }
private void BeginBlock(TBaseStatus status) { while (idx < tokens.Count) { string token = tokens[idx]; switch (token) { case "}": ++idx; //consume the closing brace return; // then we're done with this block. case ";": case "\r\n": //ignore semicolons and newlines. ++idx; break; default: ReadRule(status); //Otherwise, it must be the start of a rule. break; } } throw new EndOfStreamException("Unexpected end of file: Expected closing brace '}'."); }
private void SetAggregator(TBaseStatus status, string aggString) { Func <IEnumerable <int>, int> agg = null; if (rulesDefaultAggregator == null || rulesDefaultAggregator != aggString) //if the rules default agg is something unknown, { switch (aggString) // or just doesn't match the current one, set it. { case "total": agg = rules.Total; break; case "bool": agg = rules.Bool; break; case "max": agg = rules.MaximumOrZero; break; } } rules[status].Aggregator = agg; }
private void UpdateFeed(TBaseStatus status, InstanceType type, int newValue) { foreach (TBaseStatus fedStatus in rules.statusesFedBy[type][status]) { int newFedValue = newValue; var pair = new StatusPair(status, fedStatus); Converter conv; if (rules.converters[type].TryGetValue(pair, out conv)) { newFedValue = conv(newFedValue); } int oldFedValue; Dictionary <TBaseStatus, int> fedValues; if (internalFeeds[type].TryGetValue(fedStatus, out fedValues)) { fedValues.TryGetValue(status, out oldFedValue); } else { oldFedValue = 0; } if (newFedValue != oldFedValue) { if (fedValues == null) { fedValues = new Dictionary <TBaseStatus, int>(); fedValues.Add(status, newFedValue); internalFeeds[type].Add(fedStatus, fedValues); } else { fedValues[status] = newFedValue; } CheckRawChanged(fedStatus, type); } } }
private void SetInstanceLimit(TBaseStatus status, string limitString) { rules[status].SingleInstance = (limitString == "single"); }
private void CheckRawChanged(TBaseStatus status, InstanceType type) { bool stacked = false; if (rules.overrideSetsForStatuses.TryGetValue(status, out int overrideSetIndex)) { stacked = true; OverrideSet <TObject> overrideSet = rules.overrideSets[overrideSetIndex]; if (overrideSet == null) { throw new InvalidOperationException($"Override set {overrideSetIndex} does not exist"); } changeStack.Add(overrideSet.onChangedOverrides); } else if (rules.onChangedHandlers[status] != null) { stacked = true; changeStack.Add(rules.onChangedHandlers[status]); } var values = statusInstances[type][status].Select(x => x.Value); if (internalFeeds[type].ContainsKey(status)) { values = values.Concat(internalFeeds[type][status].Values); } IEnumerable <TBaseStatus> upstreamStatuses; // 'Upstream' and 'downstream' statuses change depending on the InstanceType. IEnumerable <TBaseStatus> downstreamStatuses; // Value changes to a status are also applied to statuses that this one extends... if (type == InstanceType.Feed) { upstreamStatuses = rules.statusesThatExtend[status]; downstreamStatuses = rules.statusesExtendedBy[status]; } else { upstreamStatuses = rules.statusesExtendedBy[status]; // ...while negative changes to a status go the other way, downstreamStatuses = rules.statusesThatExtend[status]; // being applied to statuses that extend this one. } foreach (TBaseStatus otherStatus in upstreamStatuses) { values = values.Concat(statusInstances[type][otherStatus].Select(x => x.Value)); if (internalFeeds[type].ContainsKey(otherStatus)) { values = values.Concat(internalFeeds[type][otherStatus].Values); } } int newValue = rules.GetAggregator(status, type)(values); int oldValue = currentRaw[type][status]; if (newValue != oldValue) { currentRaw[type][status] = newValue; if (type == InstanceType.Feed || type == InstanceType.Suppress) { CheckActualValueChanged(status); } } foreach (TBaseStatus otherStatus in downstreamStatuses) { CheckRawChanged(otherStatus, type); } if (stacked) { changeStack.RemoveAt(changeStack.Count - 1); } }
/// <summary> /// Override message or effect behavior whenever a change in *this* status or StatusInstance (i.e., the status /// or StatusInstance which is using this override set) causes a change in *another* status. /// </summary> /// <param name="overridden">The status whose message/effect behavior should be overridden</param> public StatusSystem <TObject> .StatusHandlers Overrides(TBaseStatus overridden) => new StatusSystem <TObject> .StatusHandlers(this, default(TBaseStatus), overridden);
internal StatusRules(BaseStatusSystem <TObject, TBaseStatus> rules, TBaseStatus status) : base(rules, status, status) { this.rules = rules; this.status = status; }
private void SetRule(bool inverted, TBaseStatus status, string comparisonOperatorString, string comparisonValueString, string verbString, string otherStatusString, string fedValueString) { TBaseStatus otherStatus; if (!TryParse(otherStatusString, out otherStatus)) { throw new InvalidDataException($"Error: {otherStatusString} not recognized as any given type."); } TBaseStatus sourceStatus, targetStatus; if (inverted) { sourceStatus = otherStatus; targetStatus = status; verbString = GetBasicVerb(verbString); } else { sourceStatus = status; targetStatus = otherStatus; } Func <int, bool> condition = GetCondition(comparisonOperatorString, comparisonValueString); if (verbString != "feeds") // Only feeds can have fed values. { if (fedValueString != null) { throw new InvalidDataException($"Unexpected value with {verbString}."); } if (verbString == "extends") { if (comparisonOperatorString != null || comparisonValueString != null) // and no conditions for this one. { throw new InvalidDataException($"Unexpected condition with {verbString}."); } } } try { switch (verbString) { case "foils": rules[sourceStatus].Foils(condition, targetStatus); break; case "cancels": rules[sourceStatus].Cancels(condition, targetStatus); break; case "extends": rules[sourceStatus].Extends(targetStatus); break; default: if (fedValueString == null) { switch (verbString) { case "feeds": rules[sourceStatus].Feeds(condition, targetStatus); break; case "suppresses": rules[sourceStatus].Suppresses(condition, targetStatus); break; case "prevents": rules[sourceStatus].Prevents(condition, targetStatus); break; } } else { int fedValue = int.Parse(fedValueString); switch (verbString) { case "feeds": rules[sourceStatus].Feeds(fedValue, condition, targetStatus); break; /*case "suppresses": * rules[sourceStatus].Suppresses(fedValue, condition, targetStatus); * break; * case "prevents": * rules[sourceStatus].Prevents(fedValue, condition, targetStatus); * break;*/ } } break; } } catch (ArgumentException e) { throw new InvalidDataException("Likely illegal condition. See inner exception.", e); } }
/// <summary> /// Conveniently create a StatusInstance compatible with this tracker. Does not add the StatusInstance to the tracker automatically. /// </summary> /// <param name="status">The status to which the instance will add its value</param> /// <param name="value">The amount by which the instance will increase its status</param> /// <param name="cancelPriority">An instance with lower cancel priority will be cancelled before an instance with /// higher priority when Cancel() is called on this status.</param> /// <param name="type"> /// The InstanceType determines whether the instance will feed, suppress, or prevent its status. /// (Feed is the default and most common. When a status is cancelled, its "Feed" StatusInstances are removed.) /// </param> public StatusInstance <TObject> CreateStatusInstance(TBaseStatus status, int value = 1, int cancelPriority = 0, InstanceType type = InstanceType.Feed, int?overrideSetIndex = null) { return(new StatusInstance <TObject>(status, value, cancelPriority, type, overrideSetIndex)); }
/// <summary> /// Returns true if the current value of the given status is greater than zero. /// </summary> public bool HasStatus(TBaseStatus status) => currentActualValues[status] > 0;