Exemplo n.º 1
0
        private void SerializeStatusInstances(InstanceType type, System.IO.BinaryWriter writer, Action <System.IO.BinaryWriter, StatusInstance <TObject>, StatusTracker <TObject> > statusInstanceCallback)
        {
            MultiValueDictionary <TBaseStatus, StatusInstance <TObject> > dict = statusInstances[type];

            writer.Write(dict.GetAllKeys().Count());
            foreach (IGrouping <TBaseStatus, StatusInstance <TObject> > pair in dict)
            {
                writer.Write(pair.Key);
                List <StatusInstance <TObject> > values = pair.ToList();
                writer.Write(values.Count);
                foreach (StatusInstance <TObject> instance in values)
                {
                    instance.SerializeInternal(writer);
                    statusInstanceCallback?.Invoke(writer, instance, this);
                }
            }
        }
Exemplo n.º 2
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);
        }