using SimpleCore; using SimpleCore.Compiler; using SimpleCore.Library; using SimpleCore.PropType; using System; using System.Collections.Generic; namespace StandardScene.Magnetic.Tasking { /// /// 从站点序列构建 ,供交管 Forecast 与任务下发共用同一路径。 /// internal static class Fass2RouteHelper { public static List GetSitesBetween(int startSiteId, int endSiteId, AbstractCar car) { if (startSiteId == endSiteId) { return new List { startSiteId }; } if (car != null) { var startSite = SimpleLib.GetSite(startSiteId); var endSite = SimpleLib.GetSite(endSiteId); if (startSite != null && endSite != null) { var plan = new SegmentPlan { usingCar = car }; plan.fields["allow_destination_on_route"] = "true"; plan.FindRoute(startSite, endSite); var sites = ExtractSiteIds(plan); if (sites.Count > 0) { return sites; } } } return Fass2PathFinder.GetSitesBetweenBfs(startSiteId, endSiteId); } public static void ForecastTrafficSequence(AbstractCar car, IReadOnlyList siteIds, int fromIndex) { if (car == null || siteIds == null || siteIds.Count == 0) { throw new ArgumentException("traffic forecast requires car and siteIds"); } fromIndex = Math.Max(0, Math.Min(fromIndex, siteIds.Count - 1)); if (fromIndex >= siteIds.Count - 1) { return; } var routeSiteIds = new List(); for (var i = fromIndex; i < siteIds.Count; i++) { routeSiteIds.Add(siteIds[i]); } try { var plan = BuildSegmentPlan(car, routeSiteIds); var program = plan.Compile("fass2-traffic", false); program.Forecast(); } catch (Exception ex) when (IsRecoverableForecastFailure(ex)) { // 双车 Multi-car search Exhausted 时降级:只建 pending/seqScope,交由 TryLock 现场占点 ApplyManualTrafficSequence(car, routeSiteIds); Diagnosis.Post( $"Mag2Car forecast fallback manual sequence car={car.id}: {ex.Message}", "Mag2Car", true); } } /// /// 不走 SimpleCore 多车 Forecast,直接写入交管序列(持锁起点已由 TrafficReset 保证)。 /// public static void ApplyManualTrafficSequence(AbstractCar car, IReadOnlyList routeSiteIds) { if (car == null || routeSiteIds == null || routeSiteIds.Count == 0) { return; } var scope = new int[routeSiteIds.Count]; for (var i = 0; i < routeSiteIds.Count; i++) { scope[i] = routeSiteIds[i]; } var pending = new int[Math.Max(0, scope.Length - 1)]; for (var i = 0; i < pending.Length; i++) { pending[i] = scope[i + 1]; } car.status.seqScope = scope; car.status.pendingLocks = pending; car.status.seqPtr = scope.Length > 1 ? 1 : 0; car.status.escape = Array.Empty(); } private static bool IsRecoverableForecastFailure(Exception ex) { for (var cur = ex; cur != null; cur = cur.InnerException) { var msg = cur.Message ?? string.Empty; if (msg.IndexOf("Multi-car traffic search", StringComparison.OrdinalIgnoreCase) >= 0 || msg.IndexOf("Exhausted", StringComparison.OrdinalIgnoreCase) >= 0 || msg.IndexOf("BadForecast", StringComparison.OrdinalIgnoreCase) >= 0 || cur.GetType().Name.IndexOf("Forecast", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } public static SegmentPlan BuildSegmentPlan(AbstractCar car, IReadOnlyList siteIds) { if (car == null || siteIds == null || siteIds.Count == 0) { throw new ArgumentException("segment plan requires car and siteIds"); } var plan = new SegmentPlan { usingCar = car }; plan.fields["allow_destination_on_route"] = "true"; var firstSite = SimpleLib.GetSite(siteIds[0]); if (firstSite == null) { throw new InvalidOperationException($"site {siteIds[0]} not found"); } plan.segments.Add(firstSite); for (var i = 0; i < siteIds.Count - 1; i++) { var dstSite = SimpleLib.GetSite(siteIds[i + 1]); if (dstSite == null) { throw new InvalidOperationException($"site {siteIds[i + 1]} not found"); } var track = FindTrack(siteIds[i], siteIds[i + 1]); if (track == null) { throw new InvalidOperationException($"no track from site {siteIds[i]} to {siteIds[i + 1]}"); } plan.segments.Add(track); plan.segments.Add(dstSite); } return plan; } public static List ExtractSiteIds(SegmentPlan plan) { var sites = new List(); if (plan?.segments == null) { return sites; } foreach (var segment in plan.segments) { if (segment is Site site) { sites.Add(site.id); } } return sites; } public static Track FindTrack(int fromSiteId, int toSiteId) { foreach (var track in SimpleLib.GetAllTracks()) { if (track.direction == 0) { if ((track.siteA == fromSiteId && track.siteB == toSiteId) || (track.siteB == fromSiteId && track.siteA == toSiteId)) { return track; } continue; } if (track.direction == 1 && track.siteA == fromSiteId && track.siteB == toSiteId) { return track; } if (track.direction == 2 && track.siteB == fromSiteId && track.siteA == toSiteId) { return track; } } return null; } } }