Files

369 lines
11 KiB
C#
Raw Permalink Normal View History

2026-07-17 13:36:01 +08:00
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;
2026-08-14 09:49:23 +08:00
if (taskChanged)
{
_actions.ResetForNewTask();
ResetSegment();
}
2026-07-17 13:36:01 +08:00
ApplyCurrentNodeFields(nodes);
LogNodes(nodes);
if (_vehicle.State == 3)
{
Fass2SimLog.WriteLine($"[{Now()}] -> 急停中,不启动运动");
return;
}
if (_segmentActive)
{
if (taskChanged)
{
Fass2SimLog.WriteLine($"[{Now()}] -> 新 taskId,重置路段");
ResetSegment();
}
else
{
return;
}
}
2026-08-14 09:49:23 +08:00
if (!ContainsNode(nodes, _vehicle.Node))
{
Fass2SimLog.WriteLine(
$"[{Now()}] -> 0xB1 窗口不含当前 node={_vehicle.Node},忽略启动路段(避免回退到窗口首站)");
return;
}
2026-07-17 13:36:01 +08:00
TryStartNextSegment();
}
}
2026-08-14 09:49:23 +08:00
private static bool ContainsNode(Fass2SimNodeMessage[] nodes, ushort node)
{
if (nodes == null)
{
return false;
}
for (var i = 0; i < nodes.Length; i++)
{
if (nodes[i].Node == node)
{
return true;
}
}
return false;
}
2026-07-17 13:36:01 +08:00
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;
2026-08-14 09:49:23 +08:00
_segmentLength = ResolveSegmentLength(_vehicle.Node, target);
2026-07-17 13:36:01 +08:00
_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;
2026-08-14 09:49:23 +08:00
if (expected == null || !Fass2SimActionResolver.AllowsAutoContinue(expected.StartStop))
2026-07-17 13:36:01 +08:00
{
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;
}
2026-08-14 09:49:23 +08:00
// 必须先在窗口内定位当前站,再取其后一站。
// 旧逻辑在窗口不含当前站时会落到 nodes[0],把更早的站当成下一目标 → 车体 Node 瞬间回退/瞬移。
var foundCurrent = false;
2026-07-17 13:36:01 +08:00
var startIndex = 0;
for (var i = 0; i < nodes.Length; i++)
{
if (nodes[i].Node == currentNode)
{
2026-08-14 09:49:23 +08:00
foundCurrent = true;
2026-07-17 13:36:01 +08:00
startIndex = i + 1;
}
}
2026-08-14 09:49:23 +08:00
if (!foundCurrent)
{
return null;
}
2026-07-17 13:36:01 +08:00
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;
2026-08-14 09:49:23 +08:00
_actions.MergeExpected(node);
2026-07-17 13:36:01 +08:00
}
}
}
2026-08-14 09:49:23 +08:00
private double ResolveSegmentLength(ushort fromNode, Fass2SimNodeMessage target)
2026-07-17 13:36:01 +08:00
{
2026-08-14 09:49:23 +08:00
foreach (var node in _windowNodes)
{
if (node.Node == fromNode && node.Distance > 0)
{
return node.Distance;
}
}
2026-07-17 13:36:01 +08:00
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)
{
2026-08-14 09:49:23 +08:00
return Fass2SimProtocol.ProtocolSpeedToMmPerSec(target.Speed);
2026-07-17 13:36:01 +08:00
}
if (_vehicle.Speed > 0)
{
2026-08-14 09:49:23 +08:00
return Fass2SimProtocol.ProtocolSpeedToMmPerSec(_vehicle.Speed);
2026-07-17 13:36:01 +08:00
}
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;
}
}
}
}