コード例 #1
0
        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);
            }
        }
コード例 #2
0
 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);
         }
     }
 }
コード例 #3
0
        // There is room for improvement here:  errors/warnings could be proper objects, not just strings.
        internal List <string> GetErrors(bool includeWarnings)
        {
            List <string> result = new List <string>();

            CheckRpsErrors(result, includeWarnings);
            var negativeRelationships = new MultiValueDictionary <StatusPair, Relationship>();
            var positiveRelationships = new MultiValueDictionary <StatusPair, Relationship>();
            var mutualSuppressions    = new EasyHashSet <StatusPair>();

            foreach (Relationship r in relationships.GetAllValues())
            {
                if (r.Path.Count == 1)
                {
                    continue;                        // Skip any 'self' relationships.
                }
                if (!r.ChainBroken)                  // Tally negative and positive (direct) relationships. These are compared later.
                {
                    if (r.IsNegative)
                    {
                        negativeRelationships.Add(new StatusPair(r.SourceStatus, r.TargetStatus), r);
                    }
                    else
                    {
                        positiveRelationships.Add(new StatusPair(r.SourceStatus, r.TargetStatus), r);
                    }
                }
                if (r.SourceStatus.Equals(r.TargetStatus) && !r.ChainBroken && !r.IsNegative)
                {
                    if (r.IsConditional)
                    {
                        if (includeWarnings)
                        {
                            string error = $"CRITICAL WARNING:  Status \"{GetBestName(r.SourceStatus)}\" might feed itself infinitely:";
                            error += GetPathString(r.Path, true);
                            result.Add(error);
                        }
                    }
                    else
                    {
                        string error = $"CRITICAL ERROR:  Status \"{GetBestName(r.SourceStatus)}\" feeds itself infinitely:";
                        error += GetPathString(r.Path, false);
                        result.Add(error);
                    }
                }
                if (r.SourceStatus.Equals(r.TargetStatus) && !r.ChainBroken)
                {
                    switch (r.Relation)
                    {
                    case RelationType.Suppresses:
                    {
                        string error = $"CRITICAL ERROR:  Status \"{GetBestName(r.SourceStatus)}\" suppresses itself. This will always cause an infinite loop:";
                        error += GetPathString(r.Path, false);
                        result.Add(error);
                    }
                    break;

                    case RelationType.Cancels:
                        if (includeWarnings)
                        {
                            string error = $"WARNING:  Status \"{GetBestName(r.SourceStatus)}\" cancels itself:";
                            error += GetPathString(r.Path, false);
                            error += GetErrorLine("(Take a look at the 'Single Instance' setting to see if that's what you actually want.)");
                            result.Add(error);
                        }
                        break;

                    case RelationType.Prevents:
                        if (includeWarnings)
                        {
                            string error = $"WARNING:  Status \"{GetBestName(r.SourceStatus)}\" prevents itself:";
                            error += GetPathString(r.Path, false);
                            result.Add(error);
                        }
                        break;
                    }
                }
                if (r.SourceStatus.Equals(r.TargetStatus) && r.Path.Count >= 4 && r.IsNegative && r.ChainBroken &&
                    !r.Path.Where(x => x.Relation == RelationType.Cancels || x.Relation == RelationType.Prevents).Any())
                {
                    List <TStatus> negativeList = r.Path.Skip(1)
                                                  .Where(x => x.Relation != RelationType.Feeds && x.Relation != RelationType.Extends)
                                                  .Select(x => x.Status).ToList(); // Remove the extra links...
                    if (!visitedRps[negativeList])                                 // If this has already been handled as RPS, skip it.
                    {
                        RecordRpsVisited(negativeList);
                        if (r.IsConditional)
                        {
                            if (includeWarnings)
                            {
                                string error = $"CRITICAL WARNING:  Conditional suppression cycle. This might cause an infinite loop:";
                                error += GetPathString(r.Path, false);
                                result.Add(error);
                            }
                        }
                        else
                        {
                            string error = $"CRITICAL ERROR:  Suppression cycle. This will always cause an infinite loop:";
                            error += GetPathString(r.Path, false);
                            result.Add(error);
                        }
                    }
                }
                if (includeWarnings)
                {
                    if (!r.SourceStatus.Equals(r.TargetStatus) && !r.ChainBroken && r.Relation == RelationType.Suppresses)                      // Whenever a status suppresses another...
                    {
                        var otherWay = relationships[r.TargetStatus].ToList()
                                       .Find(x => x.TargetStatus.Equals(r.SourceStatus) && !x.ChainBroken && x.Relation == RelationType.Suppresses);
                        if (otherWay != null)                          // ...find out whether they both suppress one another.
                        {
                            var pair = new StatusPair(r.SourceStatus, r.TargetStatus);
                            if (!mutualSuppressions[pair])                              // If it hasn't already been handled...
                            {
                                mutualSuppressions[pair] = true;
                                mutualSuppressions[new StatusPair(r.TargetStatus, r.SourceStatus)] = true;
                                string error = $"WARNING:  Mutual suppression. (This warning exists to make certain that you don't expect these 2 statuses to \"cancel each other out\". Instead, whichever status is created first will win. See the docs for more info.):";
                                error += GetPathString(r.Path, false);
                                error += GetPathString(otherWay.Path, false);
                                result.Add(error);
                            }
                        }
                    }
                }
            }

            if (includeWarnings)
            {
                // Check for feed + extend.
                foreach (var pair in positiveRelationships)
                {
                    var list   = pair.ToList();
                    var feed   = list.Find(x => x.Relation == RelationType.Feeds);
                    var extend = list.Find(x => x.Relation == RelationType.Extends);
                    if (feed != null && extend != null)
                    {
                        string error = $"WARNING:  Possible conflict:  Status \"{GetBestName(pair.Key.status1)}\" extends AND feeds status \"{GetBestName(pair.Key.status2)}\".";
                        error += GetPathString(feed.Path, false, "feed");
                        error += GetPathString(extend.Path, false, "extend");
                        error += GetErrorLine("(Note for feed+extend: the 'feeds' value change is applied before the 'extends' value change.)");
                        result.Add(error);
                    }
                }
                // Check for positive + negative.
                foreach (var statusPair in negativeRelationships.GetAllKeys().Intersect(positiveRelationships.GetAllKeys()))
                {
                    string error = $"WARNING:  Possible conflict:  Status \"{GetBestName(statusPair.status1)}\"'s relationship to status \"{GetBestName(statusPair.status2)}\" has both negative & positive elements:";
                    var    allPairRelationships = negativeRelationships[statusPair].Concat(positiveRelationships[statusPair]).ToList();
                    foreach (Relationship r in allPairRelationships)
                    {
                        error += GetPathString(r.Path, false, r.Relation.ToString().ToLower());
                    }
                    if (ContainsBoth(RelationType.Feeds, RelationType.Suppresses, allPairRelationships))
                    {
                        error += GetErrorLine("(Note for feed+suppress: the 'feeds' value change is applied before the suppression.)");
                    }
                    if (ContainsBoth(RelationType.Extends, RelationType.Suppresses, allPairRelationships))
                    {
                        error += GetErrorLine("(Note for extend+suppress: the suppression is applied before the 'extends' value is propagated. Therefore, this is very unlikely to be useful unless a condition is present.)");
                    }
                    if (ContainsBoth(RelationType.Extends, RelationType.Cancels, allPairRelationships))
                    {
                        error += GetErrorLine("(Note for extend+cancel: the cancellation is applied before the 'extends' value is propagated. This is very unlikely to be useful.)");
                    }
                    result.Add(error);
                }
                // Check for inconsistent aggregators.
                foreach (var pair in rules.valueAggs)                  // For every status that has an aggregator defined...
                {
                    foreach (var otherStatus in rules.statusesThatExtend[pair.Key])
                    {
                        if (rules.valueAggs[pair.Key] != rules.valueAggs[otherStatus])
                        {
                            string error = $"WARNING:  Possibly inconsistent aggregators between extended statuses. Status \"{GetBestName(otherStatus)}\" doesn't seem to use the same aggregator as its parent status \"{GetBestName(pair.Key)}\".";
                            result.Add(error);
                        }
                    }
                }
                // Check for overlapping values in the enums.
                var allEnumTypes = rules.extraEnumTypes;
                if (typeof(TStatus).IsEnum)
                {
                    allEnumTypes.Add(typeof(TStatus));
                }
                if (allEnumTypes.Count > 1)
                {
                    MultiValueDictionary <int, string> enumNames           = new MultiValueDictionary <int, string>();
                    MultiValueDictionary <int, Type>   recordedIntsPerType = new MultiValueDictionary <int, Type>();
                    foreach (var enumType in allEnumTypes)
                    {
                        foreach (string enumName in Enum.GetNames(enumType))
                        {
                            object value = Enum.Parse(enumType, enumName);
                            enumNames.Add((int)value, $"{enumType.Name}.{enumName}");
                            recordedIntsPerType.AddUnique((int)value, enumType);                             // Note that this enum has a name for this value.
                        }
                    }
                    foreach (var pair in recordedIntsPerType)
                    {
                        if (pair.Count() > 1)                          // If more than one enum has a name for this value...
                        {
                            var    names = enumNames[pair.Key];
                            string error = $"WARNING:  Multiple enums with the same value ({pair.Key}):  ";
                            error += GetErrorLine(string.Join(", ", names));
                            result.Add(error);
                        }
                    }
                }
            }
            return(result);
        }
コード例 #4
0
        private IEnumerable <Relationship> GetConnectionsFrom(TStatus status)
        {
            foreach (TStatus targetStatus in rules.statusesExtendedBy[status])
            {
                yield return(new Relationship {
                    Path = new List <DirectRelation> {
                        new DirectRelation {
                            Status = status, Relation = RelationType.Self
                        },
                        new DirectRelation {
                            Status = targetStatus, Relation = RelationType.Extends
                        }
                    }
                });
            }
            foreach (TStatus targetStatus in rules.statusesCancelledBy[status])
            {
                var  pair        = new StatusPair(status, targetStatus);
                bool conditional = rules.cancellationConditions[pair] != null;
                yield return(new Relationship {
                    Path = new List <DirectRelation> {
                        new DirectRelation {
                            Status = status, Relation = RelationType.Self
                        },
                        new DirectRelation {
                            Status = targetStatus, Relation = RelationType.Cancels, IsConditional = conditional
                        }
                    }
                });
            }
            foreach (InstanceType instanceType in new InstanceType[] { InstanceType.Feed, InstanceType.Suppress, InstanceType.Prevent })
            {
                RelationType relation;
                switch (instanceType)
                {
                case InstanceType.Feed:
                    relation = RelationType.Feeds;
                    break;

                case InstanceType.Suppress:
                    relation = RelationType.Suppresses;
                    break;

                case InstanceType.Prevent:
                    relation = RelationType.Prevents;
                    break;

                default: throw new NotImplementedException();
                }
                foreach (TStatus targetStatus in rules.statusesFedBy[instanceType][status])
                {
                    var  pair        = new StatusPair(status, targetStatus);
                    bool conditional = rules.converters[instanceType].ContainsKey(pair);
                    yield return(new Relationship {
                        Path = new List <DirectRelation> {
                            new DirectRelation {
                                Status = status, Relation = RelationType.Self
                            },
                            new DirectRelation {
                                Status = targetStatus, Relation = relation, IsConditional = conditional
                            }
                        }
                    });
                }
            }
        }
コード例 #5
0
 public bool Equals(StatusPair <TStatus> other) => status1.Equals(other.status1) && status2.Equals(other.status2);
コード例 #6
0
            // These are disabled because there's currently no valid reason to use them.
            //public void Suppresses(int fedValue, params TBaseStatus[] suppressedStatuses) => FeedsInternal(SourceType.Suppression, null, fedValue, null, suppressedStatuses);
            //public void Suppresses<TStatus>(int fedValue, params TStatus[] suppressedStatuses) where TStatus : struct => FeedsInternal(SourceType.Suppression, null, fedValue, null, Convert(suppressedStatuses));
            //public void Suppresses(int fedValue, Func<int, bool> condition, params TBaseStatus[] suppressedStatuses) => FeedsInternal(SourceType.Suppression, null, fedValue, condition, suppressedStatuses);
            //public void Suppresses<TStatus>(int fedValue, Func<int, bool> condition, params TStatus[] suppressedStatuses) where TStatus : struct => FeedsInternal(SourceType.Suppression, null, fedValue, condition, Convert(suppressedStatuses));
            //public void Suppresses(Converter converter, params TBaseStatus[] suppressedStatuses) => FeedsInternal(SourceType.Suppression, converter, null, null, suppressedStatuses);
            //public void Suppresses<TStatus>(Converter converter, params TStatus[] suppressedStatuses) where TStatus : struct => FeedsInternal(SourceType.Suppression, converter, null, null, Convert(suppressedStatuses));
            //public void Prevents(int fedValue, params TBaseStatus[] preventedStatuses) => FeedsInternal(SourceType.Prevention, null, fedValue, null, preventedStatuses);
            //public void Prevents<TStatus>(int fedValue, params TStatus[] preventedStatuses) where TStatus : struct => FeedsInternal(SourceType.Prevention, null, fedValue, null, Convert(preventedStatuses));
            //public void Prevents(int fedValue, Func<int, bool> condition, params TBaseStatus[] preventedStatuses) => FeedsInternal(SourceType.Prevention, null, fedValue, condition, preventedStatuses);
            //public void Prevents<TStatus>(int fedValue, Func<int, bool> condition, params TStatus[] preventedStatuses) where TStatus : struct => FeedsInternal(SourceType.Prevention, null, fedValue, condition, Convert(preventedStatuses));
            //public void Prevents(Converter converter, params TBaseStatus[] preventedStatuses) => FeedsInternal(SourceType.Prevention, converter, null, null, preventedStatuses);
            //public void Prevents<TStatus>(Converter converter, params TStatus[] preventedStatuses) where TStatus : struct => FeedsInternal(SourceType.Prevention, converter, null, null, Convert(preventedStatuses));

            protected void FeedsInternal(SourceType type, Converter converter,
                                         int?fedValue, Func <int, bool> condition, params TBaseStatus[] fedStatuses)
            {
                if (fedStatuses.Length == 0)
                {
                    throw new ArgumentException(StatusExpected);
                }
                if (condition != null)
                {
                    if (fedValue != null)
                    {
                        int fed = fedValue.Value;
                        converter = i => {
                            if (condition(i))
                            {
                                return(fed);                                         //todo: cache this?
                            }
                            else
                            {
                                return(0);
                            }
                        };
                    }
                    else
                    {
                        converter = i => {
                            if (condition(i))
                            {
                                return(i);                                         //todo: cache?
                            }
                            else
                            {
                                return(0);
                            }
                        };
                    }
                }
                else
                {
                    if (fedValue != null)
                    {
                        int fed = fedValue.Value;
                        converter = i => {
                            if (i > 0)
                            {
                                return(fed);                                  //todo: cache?
                            }
                            else
                            {
                                return(0);
                            }
                        };
                    }
                }
                if (converter != null)
                {
                    rules.ValidateConverter(converter);
                }
                foreach (TBaseStatus fedStatus in fedStatuses)
                {
                    rules.statusesFedBy[type].AddUnique(status, fedStatus);
                    if (converter != null)
                    {
                        var pair = new StatusPair <TBaseStatus>(status, fedStatus);
                        rules.converters[type][pair] = converter;
                    }
                }
            }