using System.Collections.Generic; using System.Linq; namespace StandardScene.Magnetic.Tasking { /// /// 断线重连后,用当前占点匹配 tasklist 链路的结果。 /// public sealed class Fass2ChainMatch { public int TaskId { get; set; } public int TaskPriority { get; set; } public int CurrentSiteId { get; set; } public int TargetSiteId { get; set; } public int IndexInPath { get; set; } public int DistanceToTarget { get; set; } public bool IsAtStartPoint { get; set; } public bool IsAtEndPoint { get; set; } public IReadOnlyList FullPath { get; set; } = new List(); public IReadOnlyList RemainingPath { get; set; } = new List(); public override string ToString() { var position = IsAtStartPoint ? "start" : (IsAtEndPoint ? "end" : "mid"); return $"task={TaskId} {CurrentSiteId}->{TargetSiteId} pos={position} remain={DistanceToTarget} prio={TaskPriority}"; } } /// /// 一站多链路时的固定裁决:原 goal 仍在剩余路径 → 起点 → 优先级 → 距目标更近。 /// public static class Fass2ReconnectChainSelector { public static bool TrySelect( IReadOnlyList matches, int? preferGoalSiteId, out Fass2ChainMatch selected, out string reason) { selected = null; reason = "no chain"; if (matches == null || matches.Count == 0) { return false; } var candidates = matches.Where(m => m != null && m.TargetSiteId > 0).ToList(); if (candidates.Count == 0) { return false; } if (preferGoalSiteId != null && preferGoalSiteId.Value > 0) { var keepGoal = candidates .Where(m => RemainingContains(m, preferGoalSiteId.Value)) .ToList(); if (keepGoal.Count == 1) { selected = keepGoal[0]; reason = $"preferGoal={preferGoalSiteId.Value}"; return true; } if (keepGoal.Count > 1) { candidates = keepGoal; reason = $"preferGoal={preferGoalSiteId.Value} narrowed={keepGoal.Count}"; } } var ranked = candidates .OrderByDescending(m => m.IsAtStartPoint ? 1 : 0) .ThenByDescending(m => m.TaskPriority) .ThenBy(m => m.DistanceToTarget) .ThenBy(m => m.TaskId) .ToList(); if (ranked.Count == 0) { return false; } var best = ranked[0]; if (ranked.Count > 1) { var second = ranked[1]; var tied = best.IsAtStartPoint == second.IsAtStartPoint && best.TaskPriority == second.TaskPriority && best.DistanceToTarget == second.DistanceToTarget; if (tied && best.TargetSiteId != second.TargetSiteId) { reason = $"ambiguous {best} vs {second}"; return false; } } selected = best; if (string.IsNullOrEmpty(reason) || reason == "no chain") { reason = $"selected {best}"; } else { reason = $"{reason}; selected {best}"; } return true; } private static bool RemainingContains(Fass2ChainMatch match, int siteId) { if (match.RemainingPath != null) { for (var i = 0; i < match.RemainingPath.Count; i++) { if (match.RemainingPath[i] == siteId) { return true; } } } return match.TargetSiteId == siteId; } } }