/// <summary>Merge 'routes' into 'TradeRoutes' in order from most to least profitable</summary> private void MergeTradeRoutes(Location origin, IList <TradeRoute> routes) { // Remove any routes that don't start at 'origin_station' TradeRoutes.RemoveIf(x => x.Origin.Station.ID != origin.Station.ID); // Merge 'routes' into 'TradeRoutes' in order foreach (var route in routes) { // Remove duplicates TradeRoutes.RemoveIf(x => x.Destination.System.ID == route.Destination.System.ID && x.Destination.Station.ID == route.Destination.Station.ID && x.CommodityID == route.CommodityID); // Merge routes var idx = TradeRoutes.BinarySearch(r => route.Profit.CompareTo(r.Profit), find_insert_position: true); if (idx < Settings.Instance.RouteCount) { TradeRoutes.Insert(idx, route); } if (TradeRoutes.Count > Settings.Instance.RouteCount) { TradeRoutes.RemoveToEnd(Settings.Instance.RouteCount); } } // Notify trade routes updated TradeRoutesChanged?.Invoke(this, EventArgs.Empty); }
/// <summary>Search for trades</summary> private async Task FindTradeRoutes(Settings settings, int issue) // worker thread context { if (!CanFindTradeRoutes(settings)) { // Settings not valid, ignore until they are. RunOnMainThread(() => { if (issue != m_trade_routes_issue) { return; } m_trade_routes_issue_current = issue; }); return; } else { // Settings are valid, reset the trade routes before starting RunOnMainThread(() => { if (issue != m_trade_routes_issue || TradeRoutes.Count == 0) { return; } TradeRoutes.Clear(); TradeRoutesChanged?.Invoke(this, EventArgs.Empty); }); } // Get the start point var origin_system = await Src.GetStarSystem(settings.Origin.StarSystemID.Value); var origin_station = await Src.GetStation(settings.Origin.StationID.Value); var origin = new Location(origin_system, origin_station); // If a specific destination is given, find trades between the origin and that destination if (!settings.AnyDestination) { var destination_system = await Src.GetStarSystem(settings.Destination.StarSystemID.Value); var destination_station = settings.Destination.StationID != null ? await Src.GetStation(settings.Destination.StationID.Value) : null; Log.Write(ELogLevel.Info, $"Find trade routes {origin_system.Name}/{origin_station.Name} → {destination_system.Name}/{destination_station?.Name ?? "Any"}"); var stations = destination_station != null ? new[] { destination_station } : Src.EnumStations( system_id: destination_system.ID, max_station_distance: settings.MaxStationDistance, required_pad_size: settings.RequiredPadSize, facilities_incl: EFacilities.Market | EFacilities.Docking, ignore_planetary: settings.IgnorePlanetBases); // Look for trade routes between the given stations foreach (var station in stations) { // Abort if the issue number changes if (m_trade_routes_issue != issue) { return; } var routes = await FindTradeRoutes(settings, origin, new Location(destination_system, station), issue); if (routes.Count == 0) { continue; } RunOnMainThread(() => { if (issue != m_trade_routes_issue) { return; } MergeTradeRoutes(origin, routes); }); } } else { Log.Write(ELogLevel.Info, $"Find trade routes {origin_system.Name}/{origin_station.Name} within {settings.MaxTradeRouteDistance} LY"); int considered_systems = 0, last_considered_systems = 0; var considered = new HashSet <StarSystemRef>(); // A queue of systems to consider var queue = new Queue <StarSystem>(); using (var msg = StatusStack.NewStatusMessage("Finding Trade Routes...")) { var candidates = Src.Search(origin_system.Position, settings.MaxTradeRouteDistance.Value).ToList(); KDTree_.Build(candidates, 3); // Find all systems within the maximum trade route distance. for (queue.Enqueue(origin_system); queue.Count != 0 && !Shutdown.IsCancellationRequested;) { // Abort if the issue number changes if (m_trade_routes_issue != issue) { return; } // Get the next system to consider var hop = queue.Dequeue(); // Find the systems within the maximum jump range that have not been considered already var system_refs = KDTree_.Search(candidates, 3, (double[])hop.Position, settings.MaxJumpRange).Where(x => !considered.Contains(x)).ToList(); if (system_refs.Count == 0) { continue; } // When the considered number gets large enough, remove them from the map and regenerate it const int RebuildTreeThreshold = 256; considered.AddRange(system_refs); if (considered.Count > RebuildTreeThreshold) { candidates.RemoveSet(considered); KDTree_.Build(candidates, 3); considered.Clear(); } // Check the stations in each system foreach (var system_ref in system_refs) { // Abort if the issue number changes if (m_trade_routes_issue != issue) { return; } // Get the system var destination_system = await Src.GetStarSystem(system_ref.ID); // Update status if (++considered_systems - last_considered_systems > 100) { var distance = (destination_system.Position - origin_system.Position).Length; msg.Message = $"Finding Trade Routes... (checked:{considered_systems} queued:{queue.Count} distance:{distance})"; last_considered_systems = considered_systems; } // Needs a permit? if (destination_system.NeedPermit && settings.IgnorePermitSystems) { continue; } // Check the suitable stations var stations = Src.EnumStations( system_id: destination_system.ID, max_station_distance: settings.MaxStationDistance, required_pad_size: settings.RequiredPadSize, facilities_incl: EFacilities.Market | EFacilities.Docking, ignore_planetary: settings.IgnorePlanetBases); foreach (var station in stations) { // Abort if the issue number changes if (m_trade_routes_issue != issue) { return; } var routes = await FindTradeRoutes(settings, origin, new Location(destination_system, station), issue); if (routes.Count == 0) { continue; } RunOnMainThread(() => { if (issue != m_trade_routes_issue) { return; } MergeTradeRoutes(origin, routes); }); } // Add this system to be considered for the next hop queue.Enqueue(destination_system); } } } } m_trade_routes_issue_current = issue; }