/// <summary>
        /// Erzeugt eine neue Planung.
        /// </summary>
        /// <param name="resource">Das zugehörige Gerät.</param>
        /// <param name="schedulePlan">Die zugehörige Gesamtplanung.</param>
        /// <param name="decryptionCounter">Die Zähler für die Entschlüsselung.</param>
        /// <param name="allocations">Optional alle bereits vorgenommenen Zuordnungen.</param>
        /// <param name="planTime">Der aktuelle Planungsbeginn, sofern bekannt.</param>
        /// <exception cref="ArgumentNullException">Es wurde kein Gerät angegeben.</exception>
        public ResourcePlan(IScheduleResource resource, SchedulePlan schedulePlan, HashSet <Guid> decryptionCounter = null, AllocationMap allocations = null, DateTime?planTime = null)
        {
            // Remember
            SchedulePlan = schedulePlan;
            Resource     = resource;

            // Register a single decryption counter
            DecryptionCounters = decryptionCounter ?? new HashSet <Guid> {
                schedulePlan.RegisterDecryption(Resource.Decryption.MaximumParallelSources)
            };

            // Check for allocation
            if (allocations != null)
            {
                // Just clone
                Allocations = allocations.Clone(planTime);
            }
            else
            {
                // Number of sources we may use
                var sourceLimit = resource.SourceLimit;
                if (sourceLimit < 1)
                {
                    sourceLimit = int.MaxValue;
                }

                // Create brand new
                Allocations = new AllocationMap(sourceLimit, CheckForSourceGroupMatch);
            }
        }
Example #2
0
            /// <summary>
            /// Beendet eine Aufzeichnung auf einem Gerät.
            /// </summary>
            /// <param name="scheduleIdentifier">Die eindeutige Kennung der Aufzeichnung.</param>
            public void Stop(Guid scheduleIdentifier)
            {
                // Locate and validate
                var index = this.FindIndex(scheduleIdentifier);

                // Keep current recording settings
                var recording = m_Recordings[index];

                // Remove it
                m_Recordings.RemoveAt(index);

                // Create a new plan
                try
                {
                    // Get the plan - should NEVER fail or we are in BIG trouble
                    var plan = CreateSchedulePlan();
                    if (plan == null)
                    {
                        throw new InvalidOperationException("Stop");
                    }

                    // Remember the new situation
                    m_CurrentPlan = plan;
                }
                catch
                {
                    // Recover
                    m_Recordings.Insert(index, recording);

                    // Forward
                    throw;
                }
            }
Example #3
0
        /// <summary>
        /// Ermittelt den besten Ausführungsplan.
        /// </summary>
        /// <param name="plans">Eine Liste von Plänen.</param>
        /// <param name="comparer">Der Algorithmus, der entscheidet, wann ein Plan besser als ein anderer ist.</param>
        /// <returns>Der beste Plan.</returns>
        /// <exception cref="ArgumentNullException">Es wurde keine Liste angegeben.</exception>
        public static SchedulePlan FindBest(IEnumerable <SchedulePlan> plans, IComparer <SchedulePlan> comparer)
        {
            // Validate
            if (plans == null)
            {
                throw new ArgumentNullException("plans");
            }

#if !SILVERLIGHT
            // Report
            if (RecordingScheduler.SchedulerTrace.TraceInfo)
            {
                Trace.TraceInformation(Properties.SchedulerResources.Trace_ChooseBestPlan, plans.Count());
            }
#endif

            // Reset
            SchedulePlan best = null;

            // Process all
            foreach (var plan in plans)
            {
                if (plan.CompareTo(best, comparer) > 0)
                {
                    best = plan;
                }
            }

            // Report
            return(best);
        }
 /// <summary>
 /// Erzeugt eine Kopie dieser Planung.
 /// </summary>
 /// <param name="original">Die originalen Planungsdaten.</param>
 /// <param name="schedulePlan">Die zugehörige Gesamtplanung.</param>
 private ResourcePlan(ResourcePlan original, SchedulePlan schedulePlan)
     : this(original.Resource, schedulePlan, original.DecryptionCounters, original.Allocations)
 {
     // Finish clone process
     m_Recordings.AddRange(original.m_Recordings);
     CutRecordings = original.CutRecordings;
     TotalCut      = original.TotalCut;
 }
Example #5
0
            /// <summary>
            /// Aktiviert eine Aufzeichnung auf einem Gerät.
            /// </summary>
            /// <param name="resource">Eines der verwalteten Geräte.</param>
            /// <param name="source">Optional eine Quelle, die auf dem Gerät angesteuert werden kann.</param>
            /// <param name="scheduleIdentifier">Die eindeutige Kennung der Aufzeichnung.</param>
            /// <param name="scheduleName">Der Anzeigename der Aufzeichnung.</param>
            /// <param name="plannedStart">Der ursprüngliche Start der Aufzeichnung in UTC / GMT Notation.</param>
            /// <param name="currentEnd">Das aktuelle Ende der Aufzeichnung in UTC / GMT Notation.</param>
            /// <returns>Gesetzt, wenn die Aufzeichnung auf dem gewünschten Gerät aktiviert werden kann.</returns>
            /// <exception cref="ArgumentNullException">Es wurde kein Gerät angegeben.</exception>
            /// <exception cref="ArgumentException">Das Gerät ist nicht bekannt oder kann die Quelle nicht empfangen.</exception>
            /// <exception cref="ArgumentOutOfRangeException">Die Laufzeit der Aufzeichnung ist nicht positiv.</exception>
            public bool Start(IScheduleResource resource, IScheduleSource source, Guid scheduleIdentifier, string scheduleName, DateTime plannedStart, DateTime currentEnd)
            {
                // Validate
                if (resource == null)
                {
                    throw new ArgumentNullException("resource");
                }
                if (!m_Resources.Contains(resource))
                {
                    throw new ArgumentException(resource.Name, "resource");
                }
                if (plannedStart >= currentEnd)
                {
                    throw new ArgumentOutOfRangeException("currentEnd");
                }
                if (m_Recordings.Any(r => r.UniqueIdentifier == scheduleIdentifier))
                {
                    throw new ArgumentException("resource");
                }

                // Create helper entry
                m_Recordings.Add(new ResourceAllocationInformation(resource, source, scheduleIdentifier, scheduleName, plannedStart, currentEnd - plannedStart));

                // May require cleanup
                try
                {
                    // Create the new plan
                    var plan = CreateSchedulePlan();

                    // Did it
                    if (plan != null)
                    {
                        // Remember as current
                        m_CurrentPlan = plan;

                        // Report
                        return(true);
                    }
                }
                catch
                {
                    // Cleanup
                    m_Recordings.RemoveAt(m_Recordings.Count - 1);

                    // Report
                    throw;
                }

                // Cleanup
                m_Recordings.RemoveAt(m_Recordings.Count - 1);

                // Add to list
                return(false);
            }
Example #6
0
        /// <summary>
        /// Erzeugt einen neuen Plan basierend auf der aktuellen Zuordnung.
        /// </summary>
        /// <param name="planTime">Der aktuelle Planungsbeginn, sofern bekannt.</param>
        /// <returns>Ein neuer Plan.</returns>
        public SchedulePlan Restart(DateTime?planTime)
        {
            // Create - make sure that we keep the decryption allocations
            var clone = new SchedulePlan(ResourceCollection, DecryptionCounters, planTime);

            // Fill
            clone.Resources = Resources.Select(r => r.Restart(clone, planTime)).ToArray();

            // Report
            return(clone);
        }
Example #7
0
        /// <summary>
        /// Erstellt eine exakte unabhängige Kopie die Planung.
        /// </summary>
        /// <param name="original">Die ursprüngliche Planung.</param>
        private SchedulePlan(SchedulePlan original)
            : this(original.ResourceCollection, original.DecryptionCounters, null)
        {
            // Create array
            Resources = new ResourcePlan[original.Resources.Length];

            // Deep clone
            for (int i = Resources.Length; i-- > 0;)
            {
                Resources[i] = original.Resources[i].Clone(this);
            }
        }
        /// <summary>
        /// Gibt einen Plan aus.
        /// </summary>
        /// <param name="plan">Der zu verwendende Plan.</param>
        /// <returns>Alle Aufzeichnungen, geordnet erst nach Zeit und dann nach der Priorität des Gerätes.</returns>
        private IEnumerable <ScheduleInfo> Dump(SchedulePlan plan)
        {
            // Skip
            if (plan == null)
            {
                yield break;
            }

#if !SILVERLIGHT
            // Dump decryption allocation
            if (SchedulerTrace.TraceVerbose)
            {
                foreach (var decryption in plan.DecryptionCounters.Values)
                {
                    if (decryption.IsEnabled)
                    {
                        Trace.TraceInformation(Properties.SchedulerResources.Trace_Decryption, decryption);
                    }
                }
            }
#endif

            // Artifical dump
            foreach (var info in plan.GetRecordings())
            {
                if (!m_ForbiddenDefinitions.Contains(info.Definition.UniqueIdentifier))
                {
#if !SILVERLIGHT
                    // Trace
                    if (SchedulerTrace.TraceInfo)
                    {
                        // Check type
                        var definition = info.Definition as IRecordingDefinition;

                        // Report
                        Trace.TraceInformation
                        (
                            info.StartsLate ? Properties.SchedulerResources.Trace_LateItem : Properties.SchedulerResources.Trace_NormalItem,
                            info.Resource,
                            (definition == null) ? null : definition.Source,
                            info.Time.Start,
                            info.Time.Duration
                        );
                    }
#endif

                    // Report
                    yield return(info);
                }
            }
        }
Example #9
0
            /// <summary>
            /// Erzeugt eine Planungsinstanz basierend auf den aktuell bekannten Aufzeichnungen.
            /// </summary>
            /// <returns>Der gewünschte Plan.</returns>
            private SchedulePlan CreateSchedulePlan()
            {
                // Create
                var plan = new SchedulePlan(m_Resources);

                // Report
                var resources = plan.Resources.ToDictionary(r => r.Resource, ReferenceComparer <IScheduleResource> .Default);

                // Merge in each schedule
                foreach (var recording in m_Recordings)
                {
                    // Attach to the resource
                    var resource     = recording.Resource;
                    var resourcePlan = resources[resource];

                    // Check mode
                    if (recording.Source == null)
                    {
                        // Try to reserve
                        if (!resourcePlan.Reserve(recording.Time.End))
                        {
                            return(null);
                        }

                        // Next
                        continue;
                    }

                    // Attach to the timing
                    SuggestedPlannedTime planned = recording.Time;

                    // Try add
                    if (!resourcePlan.Add(recording, planned, DateTime.MinValue))
                    {
                        return(null);
                    }

                    // Must not start late
                    if (planned.Planned.Start != recording.Time.Start)
                    {
                        return(null);
                    }
                }

                // Report
                return(plan);
            }
Example #10
0
            /// <summary>
            /// Verändert eine Aufzeichnung.
            /// </summary>
            /// <param name="scheduleIdentifier">Die eindeutige Kennung der Aufzeichnung.</param>
            /// <param name="newEnd">Der neue Endzeitpunkt der Aufzeichnung.</param>
            /// <returns>Gesetzt, wenn die Veränderung möglich ist.</returns>
            public bool Modify(Guid scheduleIdentifier, DateTime newEnd)
            {
                // Locate and validate
                var index = this.FindIndex(scheduleIdentifier);

                // Keep current recording settings
                var recording = m_Recordings[index];

                // Validate
                if (newEnd <= recording.Time.Start)
                {
                    throw new ArgumentOutOfRangeException("newEnd");
                }

                // Remove it
                m_Recordings[index] = new ResourceAllocationInformation(recording.Resource, recording.Source, recording.UniqueIdentifier, recording.Name, recording.Time.Start, newEnd - recording.Time.Start);

                // Create a new plan
                try
                {
                    // Try to apply the change
                    var plan = CreateSchedulePlan();
                    if (plan != null)
                    {
                        // Remember the new situation
                        m_CurrentPlan = plan;

                        // Confirm
                        return(true);
                    }
                }
                catch
                {
                    // Recover
                    m_Recordings[index] = recording;

                    // Forward
                    throw;
                }

                // Recover
                m_Recordings[index] = recording;

                // Back to normal
                return(false);
            }
Example #11
0
            /// <summary>
            /// Erzeugt eine passende Planungskomponente.
            /// </summary>
            /// <param name="excludeActiveRecordings">Gesetzt um alle bereits aktiven Aufzeichnungen auszublenden.</param>
            /// <returns>Die zur aktuellen Reservierung passende Planungskomponente.</returns>
            public RecordingScheduler CreateScheduler(bool excludeActiveRecordings = true)
            {
                // Time to create plan
                if (m_CurrentPlan == null)
                {
                    if ((m_CurrentPlan = CreateSchedulePlan()) == null)
                    {
                        throw new NotSupportedException("CreateScheduler");
                    }
                }

                // Create exclusion map
                var alreadyActive = new HashSet <Guid>(excludeActiveRecordings ? m_Recordings.Select(r => r.UniqueIdentifier) : Enumerable.Empty <Guid>());

                // Process
                return(new RecordingScheduler(m_Resources, alreadyActive, () => m_CurrentPlan, m_PlanComparer));
            }
Example #12
0
        /// <summary>
        /// Vergleicht zwei Pläne. Ein Plan ist umso besser, je mehr Aufzeichnung auf einem hoch priorisierten
        /// Gerät ausgeführt werden. Relevant ist die Reihenfolge der Geräte in der Liste.
        /// </summary>
        /// <param name="other">Ein anderer Plan.</param>
        /// <param name="comparer">Die Vergleichsregeln.</param>
        /// <returns>Der Unterschied zwischen den Plänen.</returns>
        public int CompareTo(SchedulePlan other, IComparer <SchedulePlan> comparer)
        {
            // Validate
            if (comparer == null)
            {
                throw new ArgumentNullException("comparer");
            }

            // Not possible
            if (other == null)
            {
                return(+1);
            }

            // Internal cross check
            if (Resources.Length != other.Resources.Length)
            {
                throw new InvalidOperationException("Resources.Length");
            }

            // Process
            return(comparer.Compare(this, other));
        }
Example #13
0
        /// <summary>
        /// Erstellt einen neuen Vergleichsalgorithmus auf Basis einer Dateibeschreibung.
        /// </summary>
        /// <param name="fileContents">Der tatsächliche Inhalt der Datei.</param>
        /// <param name="nameComparer">Der Algorithmus zum Vegleich von Gerätenamen.</param>
        /// <returns>Die gewünschte Beschreibung.</returns>
        internal static IComparer <SchedulePlan> Create(byte[] fileContents, IEqualityComparer <string> nameComparer)
        {
            // Validate
            if (fileContents == null)
            {
                throw new ArgumentNullException("fileContents");
            }

            // Result
            var resourceComparer = default(CompoundComparer <ResourcePlan>);
            var planComparer     = new CompoundComparer <SchedulePlan>();

            // Create reader
            using (var stream = new MemoryStream(fileContents, false))
                using (var reader = new StreamReader(stream, true))
                    for (string line; (line = reader.ReadLine()) != null;)
                    {
                        // Check for comment and empty line
                        var dataLength = line.IndexOf('#');
                        var data       = line.Substring(0, (dataLength < 0) ? line.Length : dataLength).Trim();
                        if (string.IsNullOrEmpty(data))
                        {
                            continue;
                        }

                        // Check operation
                        var parts = data.Split(':');
                        if (parts.Length != 2)
                        {
                            throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_BadScheduleFile, data));
                        }

                        // Check mode
                        if (resourceComparer == null)
                        {
                            // New rule to use
                            var rule = default(IComparer <SchedulePlan>);

                            // Check for supported operations
                            switch (parts[0])
                            {
                            case "StartTime": planComparer.Comparers.Add(SchedulePlan.CompareByOverlappingStart(parts[1], nameComparer)); continue;

                            case "ParallelSource": rule = SchedulePlan.CompareByParallelSourceTime; break;

                            case "ResourceCount": rule = SchedulePlan.CompareByResourceCount; break;

                            case "TotalCut": rule = SchedulePlan.CompareByTotalCut; break;

                            case "ByPriority":
                            {
                                // Create inner
                                resourceComparer = new CompoundComparer <ResourcePlan>();

                                // Check mode
                                switch (parts[1])
                                {
                                case "Ascending": rule = resourceComparer.ByPriority(false); break;

                                case "Descending": rule = resourceComparer.ByPriority(true); break;

                                default: throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_UnknownOrder, parts[0], parts[1]));
                                }

                                // Done
                                break;
                            }
                            }

                            // Validate
                            if (rule == null)
                            {
                                throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_UnknownProperty, parts[0]));
                            }

                            // Process adaption
                            if (resourceComparer == null)
                            {
                                switch (parts[1])
                                {
                                case "Min": rule = Invert(rule); break;

                                case "Max": break;

                                default: throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_UnknownOrder, parts[0], parts[1]));
                                }
                            }

                            // Remember
                            planComparer.Comparers.Add(rule);
                        }
                        else
                        {
                            // New rule to use
                            var rule = default(IComparer <ResourcePlan>);

                            // Check for supported operations
                            switch (parts[0])
                            {
                            case "RecordingCount": rule = ResourcePlan.CompareByRecordingCount; break;

                            case "SourceCount": rule = ResourcePlan.CompareBySourceCount; break;

                            case "ByPriority":
                            {
                                // Check mode
                                switch (parts[1])
                                {
                                case "End": resourceComparer = null; break;

                                default: throw new InvalidDataException(Properties.SchedulerResources.Exception_ResourcesNotTerminated);
                                }

                                // Done
                                break;
                            }
                            }

                            // Validate
                            if (rule != null)
                            {
                                // May invert
                                switch (parts[1])
                                {
                                case "Min": rule = Invert(rule); break;

                                case "Max": break;

                                default: throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_UnknownOrder, parts[0], parts[1]));
                                }

                                // Remember
                                resourceComparer.Comparers.Add(rule);
                            }
                            else if (resourceComparer != null)
                            {
                                throw new InvalidDataException(string.Format(Properties.SchedulerResources.Exception_UnknownProperty, parts[0]));
                            }
                        }
                    }

            // Validate
            if (resourceComparer != null)
            {
                throw new InvalidDataException(Properties.SchedulerResources.Exception_ResourcesNotTerminated);
            }

            // Report
            return(planComparer);
        }
        /// <summary>
        /// Meldet alle Aufzeichnungen ab einem bestimmten Zeitpunkt.
        /// </summary>
        /// <param name="minTime">Alle Aufzeichnungen, die vor diesem Zeitpunkt enden, werden
        /// nicht berücksichtigt. Die Angabe erfolgt in UTC / GMT Notation.</param>
        /// <returns>Alle Aufzeichnungen.</returns>
        private IEnumerable <ScheduleInfo> GetSchedulesForRecordings(DateTime minTime)
        {
            // All items to process
            var items = new _ScheduleList(m_PlanItems.Where(r => !m_ForbiddenDefinitions.Contains(r.Definition.UniqueIdentifier)), minTime);

            // Create the plans to extend
            var plans = new List <SchedulePlan> {
                m_PlanCreator()
            };
            var steps = 0;

            // As long as necessary
            while (items.MoveNext())
            {
                // Load the item
                var candidate    = items.Current;
                var candiateTime = candidate.Current;
                var planned      = candiateTime.Planned;

#if !SILVERLIGHT
                // Report
                if (SchedulerTrace.TraceVerbose)
                {
                    Trace.TraceInformation(Properties.SchedulerResources.Trace_Candidate, candidate.Definition.Source, planned.Start, planned.Duration);
                }
#endif

                // Get the current end of plans and see if we can dump the state - this may increase performance
                var planStart   = plans.SelectMany(p => p.Resources).Min(r => (DateTime?)r.PlanStart);
                var planEnd     = plans.SelectMany(p => p.Resources).Max(r => (DateTime?)r.PlanEnd);
                var canEndPlan  = planEnd.HasValue && (planEnd.Value != DateTime.MinValue) && (planned.Start >= planEnd.Value);
                var mustEndPlan = planStart.HasValue && (planStart.Value != DateTime.MaxValue) && planEnd.HasValue && (planEnd.Value != DateTime.MinValue) && ((planEnd.Value - planStart.Value).TotalDays > 2);

                // Count this effort
                if ((++steps > MaximumRecordingsInPlan) || canEndPlan || mustEndPlan || (plans.Count > MaximumAlternativesInPlan))
                {
                    // Find best plan
                    var best = SchedulePlan.FindBest(plans, m_comparer);

                    // Report
                    foreach (var info in Dump(best))
                    {
                        yield return(info);
                    }

                    // Reset
                    plans.Clear();
                    plans.Add(best.Restart(planned.Start));

                    // Reset
                    steps = 1;
                }

#if !SILVERLIGHT
                // Report
                if (SchedulerTrace.TraceVerbose)
                {
                    Trace.TraceInformation(Properties.SchedulerResources.Trace_PlanCount, plans.Count);
                }
#endif

                // All plans to extend
                var allPlans = plans.ToArray();

                // Discard list
                plans.Clear();

                // Iterate over all plans and try to add the current candidate - in worst case this will multiply possible plans by the number of resources available
                foreach (var plan in allPlans)
                {
                    for (int i = plan.Resources.Length; i-- > 0;)
                    {
                        // Clone of plan must be recreated for each resource because test is allowed to modify it
                        var clone = plan.Clone();

                        // Remember the time we tried - implicit cast is important, do NOT use var
                        SuggestedPlannedTime plannedTime = planned;

                        // See if resource can handle this
                        if (clone.Resources[i].Add(candidate.Definition, plannedTime, minTime))
                        {
                            plans.Add(clone);
                        }
                    }
                }

                // Must reset if the recording could not be scheduled at all
                if (plans.Count < 1)
                {
                    // Report
                    yield return(new ScheduleInfo(candidate.Definition, null, planned, false));

                    // Restore the original plans since we did nothing at all
                    plans.AddRange(allPlans);
                }
            }

            // Send all we found
            foreach (var info in Dump(SchedulePlan.FindBest(plans, m_comparer)))
            {
                yield return(info);
            }
        }
 /// <summary>
 /// Erzeugt eine neue Instanz unter Berücksichtigung der vorgenommenen Gerätezuordnung.
 /// </summary>
 /// <param name="plan">Ein neuer Plan.</param>
 /// <param name="planTime">Der aktuelle Planungsbeginn, sofern bekannt.</param>
 /// <returns>Eine Beschreibung des Gerätes.</returns>
 public ResourcePlan Restart(SchedulePlan plan, DateTime?planTime)
 {
     // Create new - this will reset the allocation map to improve performance
     return(new ResourcePlan(Resource, plan, DecryptionCounters, Allocations, planTime));
 }
 /// <summary>
 /// Erzeugt eine exakte Kopie dieser Planung.
 /// </summary>
 /// <param name="schedulePlan">Die zugehörige Gesamtplanung.</param>
 /// <returns>Die gewünschte Kopie.</returns>
 public ResourcePlan Clone(SchedulePlan schedulePlan)
 {
     // Forward
     return(new ResourcePlan(this, schedulePlan));
 }
Example #17
0
                /// <summary>
                /// Meldet, ob eine Planung unsere Regel verletzt.
                /// </summary>
                /// <param name="plan">Der zu prüfende Plan.</param>
                /// <returns>Zählt, wie oft ein Starte des problematischen Gerätes erfolgt.</returns>
                public int Count(SchedulePlan plan)
                {
                    // Find the primary resource
                    var primary = plan.Resources.FirstOrDefault(r => m_nameComparer.Equals(r.Resource.Name, m_leadingResource));

                    if (primary == null)
                    {
                        return(0);
                    }

                    // All times where we really started the resource
                    var startTimes = primary.Allocations.ResourceStartTimes;

                    if (startTimes.Length < 1)
                    {
                        return(0);
                    }

                    // Counter
                    var badCount = 0;

                    // Now inspect all resources of interest
                    foreach (var resource in plan.Resources)
                    {
                        // Our primary
                        var name = resource.Resource.Name;
                        if (m_nameComparer.Equals(name, m_leadingResource))
                        {
                            continue;
                        }

                        // Not of interest
                        if (m_testResources != null)
                        {
                            if (!m_testResources.Contains(name))
                            {
                                continue;
                            }
                        }

                        // Is idle
                        var allocations = resource.Allocations;
                        if (allocations.IsEmpty)
                        {
                            continue;
                        }

                        // Current index of inspection
                        var startTime  = startTimes[0];
                        var startIndex = 0;

                        // Process all allocations
                        foreach (var allocation in allocations)
                        {
                            // Doing nothing is no problem
                            if (allocation.IsIdle)
                            {
                                continue;
                            }

                            // As long as necessary
                            for (; ;)
                            {
                                // Too early
                                if (allocation.End <= startTime)
                                {
                                    break;
                                }

                                // Rule violated
                                if (allocation.Start <= startTime)
                                {
                                    badCount++;
                                }

                                // Adjust to next
                                if (++startIndex >= startTimes.Length)
                                {
                                    break;
                                }

                                // Reload for next try
                                startTime = startTimes[startIndex];
                            }

                            // We already processed all start times - speed up at least a tiny bit
                            if (startIndex >= startTimes.Length)
                            {
                                break;
                            }
                        }
                    }

                    // Report the total violations - overall: the less the better
                    return(badCount);
                }