using System; using System.Collections.Generic; namespace StandardScene.Fass2Simulator { /// /// 到站动作闭环:0xB1 登记期望 → 0xA1 或定时器补全字段 → 供调度 AtStation 判定完成。 /// public sealed class Fass2SimActionEngine { private readonly Fass2SimVehicle _vehicle; private readonly Fass2SimConfig _config; private readonly Fass2SimNodeProfileStore _profiles; private readonly object _syncRoot = new object(); private readonly List _pendingActions = new List(); private Fass2SimNodeMessage _expectedStation; public Fass2SimActionEngine(Fass2SimVehicle vehicle, Fass2SimConfig config, Fass2SimNodeProfileStore profiles) { _vehicle = vehicle; _config = config; _profiles = profiles ?? new Fass2SimNodeProfileStore(); } public event Action StationActionsCompleted; public void OnStationArrival(Fass2SimNodeMessage station) { if (station == null) { return; } lock (_syncRoot) { _pendingActions.Clear(); _expectedStation = Clone(station); ApplyArrivalFields(station); _vehicle.State = 2; var pending = Fass2SimActionResolver.DescribePending(_expectedStation, _vehicle.CopySnapshotUnsafe()); if (string.IsNullOrEmpty(pending)) { Fass2SimLog.WriteLine($"[{Now()}] -> 站点 node={station.Node} 无待执行动作"); NotifyStationCompleteIfReady(); return; } Fass2SimLog.WriteLine($"[{Now()}] -> 站点 node={station.Node} 待动作: {pending}"); if (_config.AutoCompleteStationActions) { SchedulePatch(Clone(station), 0, 0, ResolveActionDelayMs(station.Node)); Fass2SimLog.WriteLine($"[{Now()}] -> AutoComplete 已排队,delay={ResolveActionDelayMs(station.Node)}ms"); } } } public void OnActionCommand(ulong actionId, Fass2SimNodeMessage patch) { if (patch == null) { return; } lock (_syncRoot) { if (patch.Node != 0 && patch.Node != _vehicle.Node) { Fass2SimLog.WriteLine( $"[{Now()}] -> 0xA1 节点不匹配: patch={patch.Node}, current={_vehicle.Node},仍尝试应用"); } var delay = ResolveActionDelayMs(patch.Node != 0 ? patch.Node : _vehicle.Node); SchedulePatch(patch, actionId, 0, delay); Fass2SimLog.WriteLine( $"[{Now()}] -> 0xA1 actionId={actionId} 已排队,delay={delay}ms, pending={DescribePatch(patch)}"); } } public void Tick(int deltaMs) { if (deltaMs <= 0) { return; } List due = null; lock (_syncRoot) { for (var i = _pendingActions.Count - 1; i >= 0; i--) { _pendingActions[i].RemainingMs -= deltaMs; if (_pendingActions[i].RemainingMs <= 0) { due ??= new List(); due.Add(_pendingActions[i]); _pendingActions.RemoveAt(i); } } } if (due == null) { return; } foreach (var action in due) { lock (_syncRoot) { ApplyPatch(action.Patch); if (action.ActionId > 0) { Fass2SimLog.WriteLine( $"[{Now()}] -> 动作完成 actionId={action.ActionId}, node={_vehicle.Node}, fields={DescribePatch(action.Patch)}"); } else { Fass2SimLog.WriteLine( $"[{Now()}] -> 自动动作完成 node={_vehicle.Node}, fields={DescribePatch(action.Patch)}"); } var pending = Fass2SimActionResolver.DescribePending(_expectedStation, _vehicle.CopySnapshotUnsafe()); if (!string.IsNullOrEmpty(pending)) { Fass2SimLog.WriteLine($"[{Now()}] -> 仍有待动作: {pending}"); } NotifyStationCompleteIfReady(); } } } public bool IsStationComplete() { lock (_syncRoot) { return IsStationCompleteLocked(); } } public bool CanLeaveStation() { lock (_syncRoot) { return IsStationCompleteLocked(); } } public Fass2SimNodeMessage ExpectedStation { get { lock (_syncRoot) { return _expectedStation; } } } private bool IsStationCompleteLocked() { if (_pendingActions.Count > 0) { return false; } if (_expectedStation == null) { return true; } return Fass2SimActionResolver.IsStationActionComplete( _expectedStation, _vehicle.CopySnapshotUnsafe(), _vehicle.State); } private void NotifyStationCompleteIfReady() { if (!IsStationCompleteLocked()) { return; } if (_expectedStation != null) { Fass2SimLog.WriteLine($"[{Now()}] -> 站点 node={_expectedStation.Node} 动作已全部完成"); } StationActionsCompleted?.Invoke(); } private void ApplyArrivalFields(Fass2SimNodeMessage station) { if (station.StartStop > 0) { _vehicle.StartStop = station.StartStop; } if (station.Orientation > 0) { _vehicle.Orientation = station.Orientation; } if (station.Direction > 0) { _vehicle.Direction = station.Direction; } if (station.Byroad > 0) { _vehicle.Byroad = station.Byroad; } } private void SchedulePatch(Fass2SimNodeMessage patch, ulong actionId, int source, int delayMs) { _pendingActions.Add(new PendingAction { Patch = Clone(patch), ActionId = actionId, RemainingMs = Math.Max(0, delayMs) }); } private void ApplyPatch(Fass2SimNodeMessage patch) { if (patch.Node != 0) { _vehicle.Node = patch.Node; } if (patch.StartStop > 0) { _vehicle.StartStop = patch.StartStop; } if (patch.Speed > 0) { _vehicle.Speed = patch.Speed; } if (patch.Direction > 0) { _vehicle.Direction = patch.Direction; } if (patch.Orientation > 0) { _vehicle.Orientation = patch.Orientation; } if (patch.Byroad > 0) { _vehicle.Byroad = patch.Byroad; } if (patch.Obstacle > 0) { _vehicle.Obstacle = patch.Obstacle; } if (patch.Audio > 0) { _vehicle.Audio = patch.Audio; } if (patch.Light > 0) { _vehicle.Light = patch.Light; } if (patch.Charge > 0) { _vehicle.Charge = patch.Charge; } if (patch.Rest > 0) { _vehicle.Rest = patch.Rest; } if (patch.Lift > 0) { _vehicle.Lift = patch.Lift; } if (patch.Clamp > 0) { _vehicle.Clamp = patch.Clamp; } if (patch.Tray > 0) { _vehicle.Tray = patch.Tray; } if (patch.Roll > 0) { _vehicle.Roll = patch.Roll; } if (patch.Shutdown > 0) { _vehicle.Shutdown = patch.Shutdown; } } private int ResolveActionDelayMs(ushort nodeId) { return _profiles.ResolveActionDelayMs(nodeId, _config.ActionDelayMs); } private static Fass2SimNodeMessage Clone(Fass2SimNodeMessage source) { return new Fass2SimNodeMessage { Node = source.Node, Distance = source.Distance, StartStop = source.StartStop, Direction = source.Direction, Orientation = source.Orientation, Byroad = source.Byroad, Speed = source.Speed, Obstacle = source.Obstacle, Audio = source.Audio, Light = source.Light, Charge = source.Charge, Rest = source.Rest, Lift = source.Lift, Clamp = source.Clamp, Tray = source.Tray, Roll = source.Roll, Shutdown = source.Shutdown }; } private static string DescribePatch(Fass2SimNodeMessage patch) { var parts = new List(); if (patch.StartStop > 0) parts.Add($"StartStop={patch.StartStop}"); if (patch.Lift > 0) parts.Add($"Lift={patch.Lift}"); if (patch.Charge > 0) parts.Add($"Charge={patch.Charge}"); if (patch.Roll > 0) parts.Add($"Roll={patch.Roll}"); if (patch.Speed > 0) parts.Add($"Speed={patch.Speed}"); return parts.Count == 0 ? "(empty)" : string.Join(",", parts); } private static string Now() { return DateTime.Now.ToString("HH:mm:ss.fff"); } private sealed class PendingAction { public Fass2SimNodeMessage Patch { get; set; } public ulong ActionId { get; set; } public int RemainingMs { get; set; } } } }