/// <summary> /// Returns a suitable storage location for the given pod. /// </summary> /// <param name="pod">The pod to fetch a storage location for.</param> /// <returns>The storage location to use.</returns> protected override Waypoint GetStorageLocationForPod(Pod pod) { // Ensure init if (_bestCandidateSelector == null) { Init(); } // Get output station this pod is coming from (it has to be the nearest one) _currentStation = Instance.OutputStations.ArgMin(s => Distances.CalculateEuclid(s, pod, Instance.WrongTierPenaltyDistance)); // Get current pod location (pod has to be at station's position right now) _currentPodLocation = _currentStation.Waypoint; // Determine whether the pod should be put in the cache double cacheUtilityValue = Instance.SharedControlElements.StoragePartitioner.GetCacheValue(pod, _currentStation, _config.WeightSpeed, _config.WeightUtility); double cacheFill = (double)Instance.SharedControlElements.StoragePartitioner.CachePartitions[_currentStation].Count(c => c.Pod != null) / Instance.SharedControlElements.StoragePartitioner.CachePartitions[_currentStation].Count; _currentCacheable = ((1 - cacheFill) * _config.WeightCacheFill + cacheUtilityValue * _config.WeightCacheUtility) / 2.0 > _config.PodCacheableThreshold; // Get best storage location _bestCandidateSelector.Recycle(); Waypoint bestStorageLocation = null; foreach (var storageLocation in Instance.ResourceManager.UnusedPodStorageLocations) { // Update current candidate _currentStorageLocation = storageLocation; // Assess new candidate if (_bestCandidateSelector.Reassess()) { bestStorageLocation = _currentStorageLocation; } } // Return it return(bestStorageLocation); }
/// <summary> /// Decides the next repositioning move to do for the given robot. /// </summary> /// <param name="robot">The robot that is asking to conduct such a move.</param> /// <returns>A repositioning move or <code>null</code> if no such move was available.</returns> protected override RepositioningMove GetRepositioningMove(Bot robot) { // Ensure init if (_bestCandidateSelector == null) { Init(); } // Prepare some meta info PrepareAssessment(); // Init Pod bestPod = null; Waypoint bestStorageLocation = null; // Clear potential old results _bestCandidateSelector.Recycle(); // Search for best move foreach (var pod in Instance.ResourceManager.UnusedPods) { // Update current candidate to assess _currentPod = pod; // If the pod is stored at a drop-off location, we need information about its cache potential if (pod.Waypoint.InfoTagCache == ZoneType.Dropoff) { // Update cacheable info foreach (var station in Instance.OutputStations) { _cacheableInfoPerStation[station] = // Determine cacheable Instance.SharedControlElements.StoragePartitioner.GetCacheValue(pod, station, _config.WeightSpeed, _config.WeightUtility) > _config.PodCacheableThreshold; } _cacheableAtAll = _cacheableInfoPerStation.Any(); } // Assess all valid pod destination combinations foreach (var storageLocation in Instance.ResourceManager.UnusedPodStorageLocations.Where(sl => IsValidMove(pod, sl))) { // Update current candidate to assess _currentStorageLocation = storageLocation; // Check whether the current combination is better if (_bestCandidateSelector.Reassess()) { // Update best candidate bestPod = _currentPod; bestStorageLocation = _currentStorageLocation; } } } // Check for unavailable or useless move if (bestPod != null) { // Return the move return(new RepositioningMove() { Pod = bestPod, StorageLocation = bestStorageLocation }); } else { // No suitable move ... penalize with a timeout GlobalTimeout = Instance.Controller.CurrentTime + _config.GlobalTimeout; return(null); } }
/// <summary> /// This is called to decide about potentially pending orders. /// This method is being timed for statistical purposes and is also ONLY called when <code>SituationInvestigated</code> is <code>false</code>. /// Hence, set the field accordingly to react on events not tracked by this outer skeleton. /// </summary> protected override void DecideAboutPendingOrders() { // If not initialized, do it now if (_bestCandidateSelectNormal == null) { Initialize(); } // Define filter functions Func <OutputStation, bool> validStationNormalAssignment = _config.FastLane ? (Func <OutputStation, bool>)IsAssignableKeepFastLaneSlot : IsAssignable; Func <OutputStation, bool> validStationFastLaneAssignment = IsAssignable; // Get some meta info PrepareAssessment(); // Assign fast lane orders while possible bool furtherOptions = true; while (furtherOptions && _config.FastLane) { // Prepare helpers OutputStation chosenStation = null; Order chosenOrder = null; _bestCandidateSelectFastLane.Recycle(); // Look for next station to assign orders to foreach (var station in Instance.OutputStations // Station has to be valid .Where(s => validStationFastLaneAssignment(s))) { // Set station _currentStation = station; // Check whether there is a suitable pod if (_nearestInboundPod[station] != null && _nearestInboundPod[station].GetDistance(station) < Instance.SettingConfig.Tolerance) { // Search for best order for the station in all fulfillable orders foreach (var order in _pendingOrders.Where(o => // Order needs to be immediately fulfillable o.Positions.All(p => _nearestInboundPod[station].CountAvailable(p.Key) >= p.Value))) { // Set order _currentOrder = order; // --> Assess combination if (_bestCandidateSelectFastLane.Reassess()) { chosenStation = _currentStation; chosenOrder = _currentOrder; } } } } // Assign best order if available if (chosenOrder != null) { // Assign the order AllocateOrder(chosenOrder, chosenStation); // Log fast lane assignment Instance.StatCustomControllerInfo.CustomLogOB1++; } else { // No more options to assign orders to stations furtherOptions = false; } } // Assign orders while possible furtherOptions = true; while (furtherOptions) { // Prepare helpers OutputStation chosenStation = null; Order chosenOrder = null; _bestCandidateSelectNormal.Recycle(); // Look for next station to assign orders to foreach (var station in Instance.OutputStations // Station has to be valid .Where(s => validStationNormalAssignment(s))) { // Set station _currentStation = station; // Search for best order for the station in all fulfillable orders foreach (var order in _pendingOrders.Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value))) { // Set order _currentOrder = order; // --> Assess combination if (_bestCandidateSelectNormal.Reassess()) { chosenStation = _currentStation; chosenOrder = _currentOrder; } } } // Assign best order if available if (chosenOrder != null) { // Assign the order AllocateOrder(chosenOrder, chosenStation); // Log score statistics if (_statScorerValues == null) { _statScorerValues = _bestCandidateSelectNormal.BestScores.ToArray(); } else { for (int i = 0; i < _bestCandidateSelectNormal.BestScores.Length; i++) { _statScorerValues[i] += _bestCandidateSelectNormal.BestScores[i]; } } _statAssignments++; Instance.StatCustomControllerInfo.CustomLogOB2 = _statScorerValues[_statLinesInCommonScoreIndex] / _statAssignments; } else { // No more options to assign orders to stations furtherOptions = false; } } }
/// <summary> /// This is called to decide about potentially pending orders. /// This method is being timed for statistical purposes and is also ONLY called when <code>SituationInvestigated</code> is <code>false</code>. /// Hence, set the field accordingly to react on events not tracked by this outer skeleton. /// </summary> protected override void DecideAboutPendingOrders() { // --> Define filter functions // Define station filter for order to station assignment Func <OutputStation, bool> validStation = _config.FastLane ? (Func <OutputStation, bool>)ValidStationKeepFastLane : ValidStationIgnoreFastLane; // Only assign as many orders to the queue as specified Func <HashSet <Order>, bool> validQueue = (HashSet <Order> q) => { return(q.Count < _config.QueueLength); }; // --> Initiate Init(); // Keep track of assignment success bool success = false; // --> Prepare meta information (if any valid assignment opportunity) if (Instance.OutputStations.Any(s => (_config.FastLane && ValidStationIgnoreFastLane(s)) || validStation(s)) || _stationQueues.Values.Any(q => validQueue(q))) { PrepareAssessment(); } // --> ASSIGN ORDERS TO QUEUES do { // Reset indicator success = false; // Reset _bestCandidateSelectorQueue.Recycle(); Order bestOrder = null; OutputStation bestStation = null; // Try all assignable orders foreach (var order in _pendingOrders.Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value))) { // Update current order _currentOrder = order; // Try all station queues foreach (var stationQueueKVP in _stationQueues.Where(q => validQueue(q.Value))) { // Update current station _currentStation = stationQueueKVP.Key; // Assess if (_bestCandidateSelectorQueue.Reassess()) { bestOrder = _currentOrder; bestStation = _currentStation; } } } // Commit best assignment if (bestOrder != null && bestStation != null) { success = true; _pendingOrders.Remove(bestOrder); _stationQueues[bestStation].Add(bestOrder); // Announce queue decision Instance.Controller.Allocator.Queue(bestOrder, bestStation); } }while (success); // --> ASSIGN ORDERS TO FAST-LANE OF THE STATIONS if (_config.FastLane) { do { // Reset indicator success = false; // Assign orders from queue for all valid stations foreach (var station in Instance.OutputStations.Where(s => ValidStationIgnoreFastLane(s))) { // Only consider, if pod is in front of station if (_nearestInboundPod[station] == null || _nearestInboundPod[station].GetDistance(station) > Instance.SettingConfig.Tolerance) { continue; } // Reset _bestCandidateSelectorFastLane.Recycle(); Order bestOrder = null; _currentStation = station; // Check all queued orders foreach (var order in _stationQueues[station]) { // Update current order _currentOrder = order; // Only consider valid assignment if (!ValidFastLaneAssignment(_currentStation, _currentOrder)) { continue; } // Assess if (_bestCandidateSelectorFastLane.Reassess()) { bestOrder = _currentOrder; } } // Commit best assignment if (bestOrder != null) { success = true; _stationQueues[station].Remove(bestOrder); AllocateOrder(bestOrder, station); } else { // Reset _bestCandidateSelectorFastLane.Recycle(); bestOrder = null; _currentStation = station; // Check all pending orders foreach (var order in _pendingOrders) { // Update current order _currentOrder = order; // Only consider valid assignment if (!ValidFastLaneAssignment(_currentStation, _currentOrder)) { continue; } // Assess if (_bestCandidateSelectorFastLane.Reassess()) { bestOrder = _currentOrder; } } // Commit best assignment if (bestOrder != null) { success = true; _pendingOrders.Remove(bestOrder); AllocateOrder(bestOrder, station); } } } }while (success); } // --> ASSIGN ORDERS FROM QUEUES TO STATIONS do { // Reset indicator success = false; // Assign orders from queue for all valid stations foreach (var station in Instance.OutputStations.Where(s => validStation(s))) { // Reset _bestCandidateSelectorStation.Recycle(); Order bestOrder = null; _currentStation = station; // Check all queued orders foreach (var order in _stationQueues[station].Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value))) { // Update current order _currentOrder = order; // Assess if (_bestCandidateSelectorStation.Reassess()) { bestOrder = _currentOrder; } } // Commit best assignment if (bestOrder != null) { success = true; _stationQueues[station].Remove(bestOrder); AllocateOrder(bestOrder, station); } } }while (success); }
/// <summary> /// Creates all zones or ensures their consistency between different managers using those. /// </summary> /// <param name="config">The configuration to use for creating the zones.</param> internal void CreateOrEnsureZones(CacheConfiguration config) { // Check whether this was already done if (_config != null) { // --> Ensure compatibility if (!_config.Match(config)) { throw new ArgumentException("Incompatible cache configurations: " + _config.ToString() + " vs. " + config.ToString()); } } else { // Init _config = config; List <ZoneType> zoneTypes = new List <ZoneType>() { ZoneType.Cache, ZoneType.Dropoff }; UnzonedStorageLocations = Instance.Waypoints.Where(w => w.PodStorageLocation).ToHashSet(); CachePartitions = new VolatileIDDictionary <OutputStation, HashSet <Waypoint> >(Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, HashSet <Waypoint> >(s, new HashSet <Waypoint>())).ToList()); int cacheWPCountPerStation = (int)Math.Ceiling(UnzonedStorageLocations.Count * _config.CacheFraction / Instance.OutputStations.Count); DropoffPartitions = new VolatileIDDictionary <OutputStation, HashSet <Waypoint> >(Instance.OutputStations.Select(s => new VolatileKeyValuePair <OutputStation, HashSet <Waypoint> >(s, new HashSet <Waypoint>())).ToList()); int dropoffWPCountPerStation = _config.DropoffCount; // Init selector BestCandidateSelector scorer = new BestCandidateSelector(false, // First adhere to preference between zone types () => { switch (_config.ZonePriority) { case ZonePriority.CacheFirst: return(_currentZoneType == ZoneType.Cache ? 0 : 1); case ZonePriority.DropoffFirst: return(_currentZoneType == ZoneType.Dropoff ? 0 : 1); case ZonePriority.CacheDropoffEqual: return(0); default: throw new ArgumentException("Unknown priority: " + _config.ZonePriority); } }, // Then assess the main distance metric () => { switch (_currentZoneType) { case ZoneType.Cache: return(Distances.CalculateShortestTimePathPodSafe(_currentStorageLocation, _currentStation.Waypoint, Instance)); case ZoneType.Dropoff: return(Distances.CalculateShortestTimePathPodSafe(_currentStation.Waypoint, _currentStorageLocation, Instance)); default: throw new ArgumentException("Unknown zone type for partitioning: " + _currentZoneType.ToString()); } }, // Break ties based on the value for the other zone () => { switch (_currentZoneType) { case ZoneType.Cache: return(-Distances.CalculateShortestTimePathPodSafe(_currentStation.Waypoint, _currentStorageLocation, Instance)); case ZoneType.Dropoff: return(-Distances.CalculateShortestTimePathPodSafe(_currentStorageLocation, _currentStation.Waypoint, Instance)); default: throw new ArgumentException("Unknown zone type for partitioning: " + _currentZoneType.ToString()); } }); // --> Create partitions // Assign storage locations to different zones while ( // Check for any remaining assignments CachePartitions.Values.Any(p => p.Count < cacheWPCountPerStation) || DropoffPartitions.Values.Any(p => p.Count < dropoffWPCountPerStation)) { // Search for next assignment scorer.Recycle(); OutputStation bestStation = null; Waypoint bestStorageLocation = null; ZoneType bestZoneType = ZoneType.None; // Check all unzoned storage locations foreach (var storageLocation in UnzonedStorageLocations) { _currentStorageLocation = storageLocation; // Check all stations foreach (var station in Instance.OutputStations) { _currentStation = station; // Check all types foreach (var zoneType in zoneTypes) { _currentZoneType = zoneType; // Skip invalid assignments if (zoneType == ZoneType.Cache && CachePartitions[station].Count >= cacheWPCountPerStation) { continue; } if (zoneType == ZoneType.Dropoff && DropoffPartitions[station].Count >= dropoffWPCountPerStation) { continue; } // Determine score and update assignment if (scorer.Reassess()) { bestStation = _currentStation; bestStorageLocation = _currentStorageLocation; bestZoneType = _currentZoneType; } } } } // Sanity check if (bestStation == null) { throw new InvalidOperationException("Ran out of available assignments while partitioning the caches - partitions so far: " + "Cache: " + string.Join(",", CachePartitions.Select(p => p.Key.ToString() + "(" + p.Value.Count + ")")) + "Dropoff: " + string.Join(",", DropoffPartitions.Select(p => p.Key.ToString() + "(" + p.Value.Count + ")"))); } // Set assignment switch (bestZoneType) { case ZoneType.Cache: CachePartitions[bestStation].Add(bestStorageLocation); bestStorageLocation.InfoTagCache = ZoneType.Cache; break; case ZoneType.Dropoff: DropoffPartitions[bestStation].Add(bestStorageLocation); bestStorageLocation.InfoTagCache = ZoneType.Dropoff; break; default: throw new InvalidOperationException("Invalid zone determined: " + bestZoneType); } UnzonedStorageLocations.Remove(bestStorageLocation); } } }
/// <summary> /// This is called to decide about potentially pending orders. /// This method is being timed for statistical purposes and is also ONLY called when <code>SituationInvestigated</code> is <code>false</code>. /// Hence, set the field accordingly to react on events not tracked by this outer skeleton. /// </summary> protected override void DecideAboutPendingOrders() { // If not initialized, do it now if (_bestCandidateSelectOrder == null) { Initialize(); } // Define filter functions Func <OutputStation, bool> validStationNormalAssignment = _config.FastLane ? (Func <OutputStation, bool>)IsAssignableKeepFastLaneSlot : IsAssignable; Func <OutputStation, bool> validStationFastLaneAssignment = IsAssignable; // Get some meta info PrepareAssessment(); // Assign fast lane orders while possible bool furtherOptions = true; while (furtherOptions && _config.FastLane) { // Prepare helpers OutputStation chosenStation = null; Order chosenOrder = null; _bestCandidateSelectFastLane.Recycle(); // Look for next station to assign orders to foreach (var station in Instance.OutputStations // Station has to be valid .Where(s => validStationFastLaneAssignment(s))) { // Set station _currentStation = station; // Check whether there is a suitable pod if (_nearestInboundPod[station] != null && _nearestInboundPod[station].GetDistance(station) < Instance.SettingConfig.Tolerance) { // Search for best order for the station in all fulfillable orders foreach (var order in _pendingOrders.Where(o => // Order needs to be immediately fulfillable o.Positions.All(p => _nearestInboundPod[station].CountAvailable(p.Key) >= p.Value))) { // Set order _currentOrder = order; // --> Assess combination if (_bestCandidateSelectFastLane.Reassess()) { chosenStation = _currentStation; chosenOrder = _currentOrder; } } } } // Assign best order if available if (chosenOrder != null) { // Assign the order AllocateOrder(chosenOrder, chosenStation); // Log fast lane assignment Instance.StatCustomControllerInfo.CustomLogOB1++; } else { // No more options to assign orders to stations furtherOptions = false; } } // Repeat normal assignment until no further options furtherOptions = true; while (furtherOptions) { // Prepare some helper values if (_config.OrderSelectionRule == DefaultOrderSelection.FrequencyAge) { _oldestOrderTimestamp = _pendingOrders.Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value)).MinOrDefault(o => o.TimeStamp, -1); _newestOrderTimestamp = _pendingOrders.Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value)).MaxOrDefault(o => o.TimeStamp, -1); // Avoid division by zero, if necessary if (_oldestOrderTimestamp == _newestOrderTimestamp) { _newestOrderTimestamp += 1; } } // Choose order _bestCandidateSelectOrder.Recycle(); Order bestOrder = null; foreach (var order in _pendingOrders.Where(o => o.Positions.All(p => Instance.StockInfo.GetActualStock(p.Key) >= p.Value))) { // Update candidate _currentOrder = order; // Assess next order if (_bestCandidateSelectOrder.Reassess()) { bestOrder = _currentOrder; } } // Check success if (bestOrder != null) { // Try to reuse the last station for this order OutputStation bestStation = null; bool recycling = false; if (_config.Recycle && _lastChosenStation != null && _lastChosenStation.Active && _lastChosenStation.FitsForReservation(bestOrder)) { // Last chosen station can be used for this order too bestStation = _lastChosenStation; recycling = true; } else { // Choose new station _bestCandidateSelectStation.Recycle(); foreach (var station in Instance.OutputStations.Where(s => validStationNormalAssignment(s))) { // Update candidate _currentStation = station; // Assess next order if (_bestCandidateSelectStation.Reassess()) { bestStation = _currentStation; } } // Store decision _lastChosenStation = bestStation; } // Check success if (bestStation != null) { // Add the assignment to the ready list AllocateOrder(bestOrder, bestStation); // Log score statistics (order) if (_statScorerValuesOrder == null) { _statScorerValuesOrder = _bestCandidateSelectOrder.BestScores.ToArray(); } else { for (int i = 0; i < _bestCandidateSelectOrder.BestScores.Length; i++) { _statScorerValuesOrder[i] += _bestCandidateSelectOrder.BestScores[i]; } } _statOrderSelections++; Instance.StatCustomControllerInfo.CustomLogOB1 = _statScorerValuesOrder[0] / _statOrderSelections; // Log score statistics (station) if (!recycling) { if (_statScorerValuesStation == null) { _statScorerValuesStation = _bestCandidateSelectStation.BestScores.ToArray(); } else { for (int i = 0; i < _bestCandidateSelectStation.BestScores.Length; i++) { _statScorerValuesStation[i] += _bestCandidateSelectStation.BestScores[i]; } } _statStationSelections++; Instance.StatCustomControllerInfo.CustomLogOB2 = _statScorerValuesStation[0] / _statStationSelections; } } else { // No further options furtherOptions = false; } } else { // No further options furtherOptions = false; } } }
/// <summary> /// Decides the next repositioning move to do for the given robot. /// </summary> /// <param name="robot">The robot that is asking to conduct such a move.</param> /// <returns>A repositioning move or <code>null</code> if no such move was available.</returns> protected override RepositioningMove GetRepositioningMove(Bot robot) { // Prepare hot zones if (_chacheStorageLocations == null) { // Ensure valid zones Instance.SharedControlElements.StoragePartitioner.CreateOrEnsureZones(_config.ZoningConfiguration); // Store zones for fast access _chacheStorageLocations = Instance.SharedControlElements.StoragePartitioner.CachePartitions; foreach (var station in _chacheStorageLocations.Keys) { foreach (var storageLocation in _chacheStorageLocations[station]) { _stationsOfStorageLocations[storageLocation] = station; } } _regularStorageLocations = Instance.Waypoints.Except(_chacheStorageLocations.SelectMany(c => c.Value)).ToHashSet(); } // Init if (_bestCandidateSelectorClear == null) { _bestCandidateSelectorClear = new BestCandidateSelector(false, // First try to keep the move on the same tier as the robot () => { return(_currentPod.Tier == robot.Tier && _currentStorageLocation.Tier == robot.Tier ? 0 : 1); }, // Then try to find a pod useless for the station () => { // Check the number of potential picks possible with the pod (given by station orders' demand) int potentialPicks = Instance.ResourceManager.GetExtractRequestsOfStation(_currentStation) .Concat(Instance.ResourceManager.GetQueuedExtractRequestsOfStation(_currentStation)) .GroupBy(r => r.Item).Sum(g => Math.Min(_currentPod.CountContained(g.Key), g.Count())); // Use negative potential picks to mark useless pod return(potentialPicks); }, // Then try to find a pod useless overall () => { // Check the number of potential picks possible with the pod (given by all orders' demand) int potentialPicks = _currentPod.ItemDescriptionsContained.Sum(i => Math.Min(_currentPod.CountContained(i), Instance.ResourceManager.GetDemandAssigned(i) + Instance.ResourceManager.GetDemandQueued(i) + (_config.UselessConsiderBacklog ? Instance.ResourceManager.GetDemandBacklog(i) : 0))); // Use negative potential picks to mark useless pod return(potentialPicks); }, // Then try to use an empty pod () => { return(_currentPod.CapacityInUse); }, // Then try to get a destination location most near to the input-stations (if pod is considered empty) or the shortest move distance (if pod still has sufficient content) () => { return((_currentPod.CapacityInUse / _currentPod.Capacity < _config.PodEmptyThreshold) ? _currentStorageLocation.ShortestPodPathDistanceToNextInputStation : Distances.CalculateShortestPathPodSafe(_currentPod.Waypoint, _currentStorageLocation, Instance)); }, // Then try to make a move with the pod most near to an output-station () => { return(_currentPod.Waypoint.ShortestPodPathDistanceToNextOutputStation); }); } if (_bestCandidateSelectorFill == null) { _bestCandidateSelectorFill = new BestCandidateSelector(false, // First try to keep the move on the same tier as the robot () => { return(_currentPod.Tier == robot.Tier && _currentStorageLocation.Tier == robot.Tier ? 0 : 1); }, // Then try to find a pod useful for the station () => { // Check the number of potential picks possible with the pod (given by station orders' demand) int potentialPicks = Instance.ResourceManager.GetExtractRequestsOfStation(_currentStation) .Concat(Instance.ResourceManager.GetQueuedExtractRequestsOfStation(_currentStation)) .GroupBy(r => r.Item).Sum(g => Math.Min(_currentPod.CountContained(g.Key), g.Count())); // Use negative potential picks to mark useless pod return(-potentialPicks); }, // Then try to find a pod useful overall () => { // Check the number of potential picks possible with the pod (given by all orders' demand) int potentialPicks = _currentPod.ItemDescriptionsContained.Sum(i => Math.Min(_currentPod.CountContained(i), Instance.ResourceManager.GetDemandAssigned(i) + Instance.ResourceManager.GetDemandQueued(i) + (_config.UselessConsiderBacklog ? Instance.ResourceManager.GetDemandBacklog(i) : 0))); // Use negative potential picks to mark useless pod return(-potentialPicks); }, // Then try to use a full pod () => { return(-_currentPod.CapacityInUse); }, // Then try to do a short move () => { return(Distances.CalculateShortestPathPodSafe(_currentPod.Waypoint, _currentStorageLocation, Instance)); }); } // Init Pod bestPod = null; Waypoint bestStorageLocation = null; // Check whether any cache has too many pods if (Instance.OutputStations.Any(s => NeedsClearingRepositioning(s))) { // Clear potential old results _bestCandidateSelectorClear.Recycle(); // Check all stations foreach (var station in Instance.OutputStations.Where(s => NeedsClearingRepositioning(s))) { // Update current candidate to assess _currentStation = station; // Check occupied storage locations of cache of station foreach (var from in _chacheStorageLocations[station].Where(w => w.Pod != null && !Instance.ResourceManager.IsPodClaimed(w.Pod))) { // Check unoccupied storage locations not in a cache foreach (var to in Instance.ResourceManager.UnusedPodStorageLocations.Where(w => _regularStorageLocations.Contains(w))) { // Update current candidate to assess _currentPod = from.Pod; _currentStorageLocation = to; // Check whether the current combination is better if (_bestCandidateSelectorClear.Reassess()) { // Update best candidate bestPod = _currentPod; bestStorageLocation = _currentStorageLocation; } } } } } // Check whether any cache has too few pods if (Instance.OutputStations.Any(s => NeedsFillingRepositioning(s))) { // Clear potential old results _bestCandidateSelectorFill.Recycle(); // Check all stations foreach (var station in Instance.OutputStations.Where(s => NeedsFillingRepositioning(s))) { // Update current candidate to assess _currentStation = station; // Check unused pods foreach (var pod in Instance.ResourceManager.UnusedPods.Where(p => _regularStorageLocations.Contains(p.Waypoint))) { // Check unoccupied storage locations of cache of station foreach (var to in _chacheStorageLocations[station].Where(w => !Instance.ResourceManager.IsStorageLocationClaimed(w))) { // Update current candidate to assess _currentPod = pod; _currentStorageLocation = to; // Check whether the current combination is better if (_bestCandidateSelectorFill.Reassess()) { // Update best candidate bestPod = _currentPod; bestStorageLocation = _currentStorageLocation; } } } } } // Check whether a move was obtained if (bestPod != null) { // Return the move return(new RepositioningMove() { Pod = bestPod, StorageLocation = bestStorageLocation }); } else { // No move available - block calls for a while GlobalTimeout = Instance.Controller.CurrentTime + _config.GlobalTimeout; return(null); } }