320 lines
9.1 KiB
C#
320 lines
9.1 KiB
C#
using System;
|
|
|
|
namespace StandardScene.Fass2Simulator
|
|
{
|
|
/// <summary>
|
|
/// 按 0xB1 节点序列模拟行驶:距离推进 → 到站更新 Node/字段 → State 切为停止供调度判定到站。
|
|
/// </summary>
|
|
public sealed class Fass2SimMotionEngine
|
|
{
|
|
private readonly Fass2SimVehicle _vehicle;
|
|
private readonly Fass2SimConfig _config;
|
|
private readonly Fass2SimActionEngine _actions;
|
|
private readonly object _syncRoot = new object();
|
|
|
|
private ulong _activeTaskId;
|
|
private Fass2SimNodeMessage[] _windowNodes = Array.Empty<Fass2SimNodeMessage>();
|
|
private Fass2SimNodeMessage _segmentTarget;
|
|
private double _segmentProgress;
|
|
private double _segmentLength;
|
|
private bool _segmentActive;
|
|
|
|
public Fass2SimMotionEngine(Fass2SimVehicle vehicle, Fass2SimConfig config, Fass2SimActionEngine actions)
|
|
{
|
|
_vehicle = vehicle;
|
|
_config = config;
|
|
_actions = actions;
|
|
}
|
|
|
|
public void OnNodesCommand(ulong taskId, Fass2SimNodeMessage[] nodes)
|
|
{
|
|
if (nodes == null || nodes.Length == 0)
|
|
{
|
|
Fass2SimLog.WriteLine($"[{Now()}] -> 0xB1 节点为空,忽略");
|
|
return;
|
|
}
|
|
|
|
lock (_syncRoot)
|
|
{
|
|
var taskChanged = _activeTaskId != taskId;
|
|
_activeTaskId = taskId;
|
|
_windowNodes = nodes;
|
|
_vehicle.Task = taskId;
|
|
|
|
ApplyCurrentNodeFields(nodes);
|
|
LogNodes(nodes);
|
|
|
|
if (_vehicle.State == 3)
|
|
{
|
|
Fass2SimLog.WriteLine($"[{Now()}] -> 急停中,不启动运动");
|
|
return;
|
|
}
|
|
|
|
if (_segmentActive)
|
|
{
|
|
if (taskChanged)
|
|
{
|
|
Fass2SimLog.WriteLine($"[{Now()}] -> 新 taskId,重置路段");
|
|
ResetSegment();
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
TryStartNextSegment();
|
|
}
|
|
}
|
|
|
|
public void OnStartCommand()
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
if (_vehicle.State == 3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TryStartNextSegment();
|
|
}
|
|
}
|
|
|
|
public void OnStopCommand()
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
ResetSegment();
|
|
}
|
|
}
|
|
|
|
public void OnStationActionsCompleted()
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
TryResumeFromStation();
|
|
}
|
|
}
|
|
|
|
public void Tick(int deltaMs)
|
|
{
|
|
if (deltaMs <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
lock (_syncRoot)
|
|
{
|
|
if (!_segmentActive || _segmentTarget == null || _vehicle.State != 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var speed = ResolveSpeed(_segmentTarget);
|
|
var deltaDistance = speed * deltaMs / 1000.0;
|
|
_segmentProgress += deltaDistance;
|
|
_vehicle.Distance = (ushort)Math.Min(ushort.MaxValue, Math.Round(_segmentProgress));
|
|
|
|
if (_segmentProgress < _segmentLength)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ArriveAtTarget();
|
|
}
|
|
}
|
|
|
|
private void TryStartNextSegment()
|
|
{
|
|
if (_vehicle.State != 1 && _vehicle.State != 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_vehicle.State == 2 && !_actions.CanLeaveStation())
|
|
{
|
|
return;
|
|
}
|
|
|
|
var target = ResolveNextTarget(_windowNodes, _vehicle.Node);
|
|
if (target == null)
|
|
{
|
|
if (_vehicle.State == 2)
|
|
{
|
|
Fass2SimLog.WriteLine($"[{Now()}] -> 窗口内无下一段,等待下一帧 0xB1");
|
|
}
|
|
return;
|
|
}
|
|
|
|
_segmentTarget = target;
|
|
_segmentLength = ResolveSegmentLength(target);
|
|
_segmentProgress = 0;
|
|
_segmentActive = true;
|
|
_vehicle.State = 1;
|
|
_vehicle.Distance = 0;
|
|
|
|
if (target.Speed > 0)
|
|
{
|
|
_vehicle.Speed = target.Speed;
|
|
}
|
|
|
|
Fass2SimLog.WriteLine(
|
|
$"[{Now()}] -> 开始路段: {_vehicle.Node} -> {target.Node}, dist={_segmentLength:F0}mm, speed={ResolveSpeed(target):F0}mm/s");
|
|
}
|
|
|
|
private void ArriveAtTarget()
|
|
{
|
|
var target = _segmentTarget;
|
|
_vehicle.Node = target.Node;
|
|
_vehicle.Distance = 0;
|
|
ResetSegment();
|
|
_actions.OnStationArrival(target);
|
|
|
|
Fass2SimLog.WriteLine(
|
|
$"[{Now()}] -> 到站 node={target.Node}, StartStop={target.StartStop}, state={Fass2SimProtocol.StateText(_vehicle.State)}");
|
|
|
|
TryResumeFromStation();
|
|
}
|
|
|
|
private void TryResumeFromStation()
|
|
{
|
|
if (!_config.AutoContinueOnPass)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var expected = _actions.ExpectedStation;
|
|
if (expected == null || expected.StartStop != Fass2SimActionResolver.StartStopPass)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_vehicle.State != 2 || !_actions.CanLeaveStation() || !HasNextTarget(_vehicle.Node))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_vehicle.State = 1;
|
|
TryStartNextSegment();
|
|
}
|
|
|
|
private void ResetSegment()
|
|
{
|
|
_segmentTarget = null;
|
|
_segmentProgress = 0;
|
|
_segmentLength = 0;
|
|
_segmentActive = false;
|
|
}
|
|
|
|
private bool HasNextTarget(ushort currentNode)
|
|
{
|
|
return ResolveNextTarget(_windowNodes, currentNode) != null;
|
|
}
|
|
|
|
private static Fass2SimNodeMessage ResolveNextTarget(Fass2SimNodeMessage[] nodes, ushort currentNode)
|
|
{
|
|
if (nodes == null || nodes.Length == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var startIndex = 0;
|
|
for (var i = 0; i < nodes.Length; i++)
|
|
{
|
|
if (nodes[i].Node == currentNode)
|
|
{
|
|
startIndex = i + 1;
|
|
}
|
|
}
|
|
|
|
for (var i = startIndex; i < nodes.Length; i++)
|
|
{
|
|
var node = nodes[i];
|
|
if (node.Node == 0 || node.Node == currentNode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void ApplyCurrentNodeFields(Fass2SimNodeMessage[] nodes)
|
|
{
|
|
foreach (var node in nodes)
|
|
{
|
|
if (node.Node == _vehicle.Node && node.StartStop > 0)
|
|
{
|
|
_vehicle.StartStop = node.StartStop;
|
|
}
|
|
}
|
|
}
|
|
|
|
private double ResolveSegmentLength(Fass2SimNodeMessage target)
|
|
{
|
|
if (target.Distance > 0)
|
|
{
|
|
return target.Distance;
|
|
}
|
|
|
|
if (_config.DefaultSegmentDistance > 0)
|
|
{
|
|
return _config.DefaultSegmentDistance;
|
|
}
|
|
|
|
var speed = ResolveSpeed(target);
|
|
return Math.Max(100, speed * _config.SecondsPerSegment);
|
|
}
|
|
|
|
private double ResolveSpeed(Fass2SimNodeMessage target)
|
|
{
|
|
if (target.Speed > 0)
|
|
{
|
|
return target.Speed;
|
|
}
|
|
|
|
if (_vehicle.Speed > 0)
|
|
{
|
|
return _vehicle.Speed;
|
|
}
|
|
|
|
return _config.DefaultSpeed;
|
|
}
|
|
|
|
private void LogNodes(Fass2SimNodeMessage[] nodes)
|
|
{
|
|
var summary = string.Join(" -> ", Array.ConvertAll(nodes, n => n.Node.ToString()));
|
|
Fass2SimLog.WriteLine($"[{Now()}] -> 0xB1 taskId={_activeTaskId}, nodes=[{summary}]");
|
|
}
|
|
|
|
private static string Now()
|
|
{
|
|
return DateTime.Now.ToString("HH:mm:ss.fff");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 供界面展示路段进度:行驶中返回目标节点与路段长度,到站后 node 才会在协议里跳变。
|
|
/// </summary>
|
|
public bool TryGetSegmentStatus(out ushort targetNode, out int segmentLengthMm, out int progressMm)
|
|
{
|
|
lock (_syncRoot)
|
|
{
|
|
if (!_segmentActive || _segmentTarget == null)
|
|
{
|
|
targetNode = 0;
|
|
segmentLengthMm = 0;
|
|
progressMm = 0;
|
|
return false;
|
|
}
|
|
|
|
targetNode = _segmentTarget.Node;
|
|
segmentLengthMm = (int)Math.Round(_segmentLength);
|
|
progressMm = (int)Math.Round(_segmentProgress);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|