using SimpleCore;
using SimpleCore.Compiler;
using SimpleCore.PropType;
using SimpleCore.Traffic;
using System;
using System.Collections.Generic;
using System.Linq;
namespace StandardScene.Magnetic.Tasking
{
///
/// 方案 B′:用任务下发的 siteIds 经 SegmentPlan.Forecast 建立 seqScope/pendingLocks,再段级 TryLock/Leave。
///
internal sealed class Fass2TrafficLocker
{
private readonly AbstractCar _car;
private readonly Action _log;
public Fass2TrafficLocker(AbstractCar car, Action log)
{
_car = car ?? throw new ArgumentNullException(nameof(car));
_log = log ?? (_ => { });
}
public void PrepareSequence(IReadOnlyList siteIds, int fromIndex)
{
if (siteIds == null || siteIds.Count == 0)
{
throw new InvalidOperationException("traffic plan siteIds is empty");
}
fromIndex = Math.Max(0, Math.Min(fromIndex, siteIds.Count - 1));
var startSiteId = siteIds[fromIndex];
var startSite = SimpleLib.GetSite(startSiteId);
if (startSite == null)
{
throw new InvalidOperationException($"traffic start site invalid: {startSiteId}");
}
// TrafficReset 会 programs.Clear() 把 now 置空;TryLock 在真正占点前要求 programs.now != null,
// 否则抛 Program obsoleted。环线拦截路径仍处在 move 脚本的 actualSendScript 中,需保留/重建锚点。
var keepProgram = _car.status.programs.now;
_log($"prepare reset start={startSiteId}, goal={siteIds[siteIds.Count - 1]}, fromIndex={fromIndex}, route=[{string.Join(",", siteIds)}]");
try
{
_car.TrafficReset(startSite, makeAvailable: true, strict: false);
}
catch (Exception ex) when (IsStartOccupiedByOtherCar(ex))
{
// 起点被他车占用时不能 Reset;若本车已持有该站则跳过 Reset 继续建序,否则交给上层等待。
if (Array.IndexOf(_car.status.holdingLocks, startSiteId) < 0)
{
throw new InvalidOperationException(
$"traffic start site {startSiteId} occupied by another car", ex);
}
_log($"prepare reset skipped, already holding start={startSiteId}, ex={ex.Message}");
}
EnsureProgramAnchor(keepProgram);
if (fromIndex >= siteIds.Count - 1)
{
LogTrafficState("prepare single-site");
return;
}
Fass2RouteHelper.ForecastTrafficSequence(_car, siteIds, fromIndex);
LogTrafficState("prepare forecast ok");
}
public void RebaseFrom(IReadOnlyList siteIds, int fromIndex, int windowSize)
{
PrepareSequence(siteIds, fromIndex);
EnsureWindowLocked(siteIds, fromIndex, Math.Max(1, windowSize));
}
private static bool IsStartOccupiedByOtherCar(Exception ex)
{
for (var cur = ex; cur != null; cur = cur.InnerException)
{
var msg = cur.Message ?? string.Empty;
if (msg.IndexOf("already locks it", StringComparison.OrdinalIgnoreCase) >= 0 ||
msg.IndexOf("unavailable site", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
}
return false;
}
public bool TryLockNext(int siteId)
{
if (!_car.status.usage.Get().scheduling)
{
throw new InvalidOperationException("abandoned");
}
if (Array.IndexOf(_car.status.holdingLocks, siteId) >= 0)
{
return true;
}
if (_car.status.pendingLocks.Length > 0 && _car.status.pendingLocks[0] != siteId)
{
// 窗口重入时可能再次请求已锁过的更早站点;交给 EnsureWindowLocked 跳过,避免 UDP 线程抛异常。
_log($"lock skip site={siteId}, expect pending0={_car.status.pendingLocks[0]}, holding=[{FormatInts(_car.status.holdingLocks)}]");
return false;
}
EnsureProgramAnchor(keepProgram: null);
if (TrafficControl.TryLock(_car, siteId))
{
_log($"lock ok site={siteId}, holding=[{FormatInts(_car.status.holdingLocks)}]");
return true;
}
_log($"lock wait site={siteId}, blockedBy={FormatBlocked()}, TCStat={FormatTcStat()}");
return false;
}
public void LeavePassed(int siteId)
{
if (_car.status.holdingLocks.Length <= 1)
{
_log($"leave skip site={siteId}, holding count={_car.status.holdingLocks.Length}");
return;
}
if (Array.IndexOf(_car.status.holdingLocks, siteId) < 0)
{
_log($"leave skip site={siteId}, not in holding");
return;
}
EnsureProgramAnchor(keepProgram: null);
TrafficControl.Leave(_car, siteId);
_log($"leave site={siteId}, holding=[{FormatInts(_car.status.holdingLocks)}]");
}
///
/// 释放路径上严格位于 keepFromIndex 之前的持锁站点(当前站及前方窗口保留)。
///
public void ReleaseLocksBehind(IReadOnlyList siteIds, int keepFromIndex)
{
if (siteIds == null || siteIds.Count == 0 || _car.status.holdingLocks.Length <= 1)
{
return;
}
keepFromIndex = Math.Max(0, Math.Min(keepFromIndex, siteIds.Count - 1));
var holdingSnapshot = _car.status.holdingLocks.ToArray();
foreach (var siteId in holdingSnapshot)
{
if (_car.status.holdingLocks.Length <= 1)
{
break;
}
var index = IndexOfSite(siteIds, siteId);
if (index < 0 || index >= keepFromIndex)
{
continue;
}
LeavePassed(siteId);
}
}
private static int IndexOfSite(IReadOnlyList siteIds, int siteId)
{
for (var i = 0; i < siteIds.Count; i++)
{
if (siteIds[i] == siteId)
{
return i;
}
}
return -1;
}
public int EnsureWindowLocked(IReadOnlyList siteIds, int fromIndex, int windowSize)
{
if (siteIds == null || siteIds.Count == 0)
{
return 0;
}
fromIndex = Math.Max(0, Math.Min(fromIndex, siteIds.Count - 1));
windowSize = Math.Max(1, Math.Min(windowSize, 10));
var maxIndex = Math.Min(siteIds.Count - 1, fromIndex + windowSize - 1);
// 返回值必须是从 fromIndex 起的连续已占/新锁站数,供 0xB1 窗口切片使用。
// UDP Tick 会反复进入:已在 holding 的站直接计数,禁止再次对已消费的 pending 站 TryLock。
var lockedCount = 0;
for (var i = fromIndex; i <= maxIndex; i++)
{
var siteId = siteIds[i];
if (Array.IndexOf(_car.status.holdingLocks, siteId) >= 0)
{
lockedCount++;
continue;
}
if (_car.status.pendingLocks.Length == 0 || _car.status.pendingLocks[0] != siteId)
{
_log(
$"window gap site={siteId}, pending0={(_car.status.pendingLocks.Length > 0 ? _car.status.pendingLocks[0].ToString() : "-")}, holding=[{FormatInts(_car.status.holdingLocks)}]");
break;
}
if (!TryLockNext(siteId))
{
break;
}
lockedCount++;
}
if (lockedCount == 0)
{
lockedCount = 1;
_log($"window warn: no holding overlap index={fromIndex}, site={siteIds[fromIndex]}, holding=[{FormatInts(_car.status.holdingLocks)}]");
}
_log($"window locked={lockedCount}/{windowSize}, fromIndex={fromIndex}, holding=[{FormatInts(_car.status.holdingLocks)}], pending=[{FormatInts(_car.status.pendingLocks)}]");
return lockedCount;
}
public void FinalizeAtSite(int siteId)
{
_log($"finalize site={siteId}, holding=[{FormatInts(_car.status.holdingLocks)}], pending=[{FormatInts(_car.status.pendingLocks)}]");
}
///
/// SimpleCore.TryLock 在写锁前检查 programs.now;为空则抛 Program obsoleted。
///
private void EnsureProgramAnchor(CarProgram keepProgram)
{
if (_car.status.programs.now != null)
{
return;
}
if (keepProgram != null)
{
_car.status.programs.now = keepProgram;
_log($"restore programs.now after TrafficReset, name={keepProgram.name}, state={keepProgram.status.state}");
return;
}
var dummy = new CarProgram
{
name = $"fass2-traffic-anchor:{_car.id}",
plans = new[]
{
new SegmentPlan { usingCar = _car }
}
};
dummy.status.state = CarProgram.StatusEnum.Dummy;
_car.status.programs.now = dummy;
_log($"install dummy programs.now for TryLock, name={dummy.name}");
}
private void LogTrafficState(string prefix)
{
_log(
$"{prefix}, holding=[{FormatInts(_car.status.holdingLocks)}], pending=[{FormatInts(_car.status.pendingLocks)}], seqScope=[{FormatInts(_car.status.seqScope)}], seqPtr={_car.status.seqPtr}");
}
private string FormatBlocked()
{
if (_car.status.blockedBy == null || _car.status.blockedBy.Length == 0)
{
return "-";
}
return string.Join(",", _car.status.blockedBy.Select(p => $"{p.Item1}:{p.Item2}"));
}
private string FormatTcStat()
{
try
{
var tc = _car.status.TCStat;
return string.IsNullOrWhiteSpace(tc) ? "-" : tc;
}
catch
{
return "-";
}
}
private static string FormatInts(int[] values)
{
return values == null || values.Length == 0 ? string.Empty : string.Join(",", values);
}
}
}