2026-07-17 13:36:01 +08:00
|
|
|
using SimpleCore.PropType;
|
|
|
|
|
using StandardScene.Magnetic.Protocol;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
|
|
namespace StandardScene.Magnetic.Tasking
|
|
|
|
|
{
|
|
|
|
|
public enum Fass2TaskPhase
|
|
|
|
|
{
|
|
|
|
|
Idle,
|
|
|
|
|
Planning,
|
|
|
|
|
Dispatching,
|
|
|
|
|
Moving,
|
|
|
|
|
AtStation,
|
|
|
|
|
SegmentDone,
|
|
|
|
|
Complete,
|
|
|
|
|
Fault,
|
|
|
|
|
Cancelled
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 一次 FASS 2.0 任务执行上下文,供状态机推进与重启恢复使用。
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class Fass2TaskContext
|
|
|
|
|
{
|
|
|
|
|
public Fass2TaskPhase Phase { get; set; } = Fass2TaskPhase.Idle;
|
|
|
|
|
public int StartSiteId { get; set; }
|
|
|
|
|
public int GoalSiteId { get; set; }
|
|
|
|
|
public int CurrentIndex { get; set; }
|
|
|
|
|
public ulong TaskId { get; set; }
|
|
|
|
|
public Fass2TaskPlan Plan { get; set; }
|
|
|
|
|
public string FieldsSignature { get; set; } = string.Empty;
|
|
|
|
|
public double DefaultSpeed { get; set; } = -1;
|
|
|
|
|
public string FaultReason { get; set; }
|
|
|
|
|
public DateTime StartedAt { get; set; }
|
|
|
|
|
public DateTime LastDispatchAt { get; set; } = DateTime.MinValue;
|
|
|
|
|
public ulong LastActionId { get; set; }
|
|
|
|
|
public DateTime LastActionSentAt { get; set; } = DateTime.MinValue;
|
2026-08-14 09:49:23 +08:00
|
|
|
|
|
|
|
|
/// <summary>交管窗口未齐、正在等锁;此期间不消耗移动超时预算。</summary>
|
|
|
|
|
public bool WaitingForTraffic { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>停在管控停止点等待按车放行;此期间不消耗移动超时预算。</summary>
|
|
|
|
|
public bool WaitingForRelease { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>本任务已对哪个路径下标发过放行报文;-1 表示尚未放行。</summary>
|
|
|
|
|
public int ControlReleasedIndex { get; set; } = -1;
|
|
|
|
|
|
|
|
|
|
/// <summary>最近一次规划的站点链路,供断线重连判断是否仍在原路径上。</summary>
|
|
|
|
|
public int[] RouteSiteIds { get; set; } = Array.Empty<int>();
|
2026-07-17 13:36:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class Fass2TaskPersistence
|
|
|
|
|
{
|
|
|
|
|
public const string TagPhase = "Fass2Task_Phase";
|
|
|
|
|
public const string TagStartSite = "Fass2Task_StartSite";
|
|
|
|
|
public const string TagGoalSite = "Fass2Task_GoalSite";
|
|
|
|
|
public const string TagCurrentIndex = "Fass2Task_CurrentIndex";
|
|
|
|
|
public const string TagTaskId = "Fass2Task_TaskId";
|
|
|
|
|
public const string TagFieldsSignature = "Fass2Task_FieldsSig";
|
|
|
|
|
public const string TagDefaultSpeed = "Fass2Task_DefaultSpeed";
|
2026-08-14 09:49:23 +08:00
|
|
|
public const string TagRouteSites = "Fass2Task_RouteSites";
|
2026-07-17 13:36:01 +08:00
|
|
|
|
|
|
|
|
public static void Save(TagSet tags, Fass2TaskContext context)
|
|
|
|
|
{
|
|
|
|
|
if (tags == null || context == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetTag(tags, TagPhase, context.Phase.ToString());
|
|
|
|
|
SetTag(tags, TagStartSite, context.StartSiteId.ToString());
|
|
|
|
|
SetTag(tags, TagGoalSite, context.GoalSiteId.ToString());
|
|
|
|
|
SetTag(tags, TagCurrentIndex, context.CurrentIndex.ToString());
|
|
|
|
|
SetTag(tags, TagTaskId, context.TaskId.ToString());
|
|
|
|
|
SetTag(tags, TagFieldsSignature, context.FieldsSignature ?? string.Empty);
|
|
|
|
|
SetTag(tags, TagDefaultSpeed, context.DefaultSpeed.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
2026-08-14 09:49:23 +08:00
|
|
|
SetTag(tags, TagRouteSites, FormatIntList(context.RouteSiteIds));
|
2026-07-17 13:36:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool TryLoad(TagSet tags, out Fass2TaskContext context)
|
|
|
|
|
{
|
|
|
|
|
context = null;
|
|
|
|
|
if (tags == null || !tags.TryGetValue(TagPhase, out var phaseText) ||
|
|
|
|
|
!Enum.TryParse(phaseText, out Fass2TaskPhase phase))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (phase is Fass2TaskPhase.Idle or Fass2TaskPhase.Complete or Fass2TaskPhase.Cancelled)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!TryReadInt(tags, TagStartSite, out var startSite) ||
|
|
|
|
|
!TryReadInt(tags, TagGoalSite, out var goalSite) ||
|
|
|
|
|
!TryReadInt(tags, TagCurrentIndex, out var currentIndex))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tags.TryGetValue(TagFieldsSignature, out var fieldsSignature);
|
|
|
|
|
ulong.TryParse(tags.TryGetValue(TagTaskId, out var taskIdText) ? taskIdText : "0", out var taskId);
|
|
|
|
|
double.TryParse(
|
|
|
|
|
tags.TryGetValue(TagDefaultSpeed, out var speedText) ? speedText : "-1",
|
|
|
|
|
System.Globalization.NumberStyles.Float,
|
|
|
|
|
System.Globalization.CultureInfo.InvariantCulture,
|
|
|
|
|
out var defaultSpeed);
|
|
|
|
|
|
2026-08-14 09:49:23 +08:00
|
|
|
tags.TryGetValue(TagRouteSites, out var routeSitesText);
|
2026-07-17 13:36:01 +08:00
|
|
|
context = new Fass2TaskContext
|
|
|
|
|
{
|
|
|
|
|
Phase = phase,
|
|
|
|
|
StartSiteId = startSite,
|
|
|
|
|
GoalSiteId = goalSite,
|
|
|
|
|
CurrentIndex = currentIndex,
|
|
|
|
|
TaskId = taskId,
|
|
|
|
|
FieldsSignature = fieldsSignature ?? string.Empty,
|
2026-08-14 09:49:23 +08:00
|
|
|
DefaultSpeed = defaultSpeed,
|
|
|
|
|
RouteSiteIds = ParseIntList(routeSitesText)
|
2026-07-17 13:36:01 +08:00
|
|
|
};
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void Clear(TagSet tags)
|
|
|
|
|
{
|
|
|
|
|
if (tags == null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RemoveTag(tags, TagPhase);
|
|
|
|
|
RemoveTag(tags, TagStartSite);
|
|
|
|
|
RemoveTag(tags, TagGoalSite);
|
|
|
|
|
RemoveTag(tags, TagCurrentIndex);
|
|
|
|
|
RemoveTag(tags, TagTaskId);
|
|
|
|
|
RemoveTag(tags, TagFieldsSignature);
|
|
|
|
|
RemoveTag(tags, TagDefaultSpeed);
|
2026-08-14 09:49:23 +08:00
|
|
|
RemoveTag(tags, TagRouteSites);
|
2026-07-17 13:36:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void SetTag(TagSet tags, string key, string value)
|
|
|
|
|
{
|
|
|
|
|
if (tags.Contains(key))
|
|
|
|
|
{
|
|
|
|
|
tags.Remove(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tags.Add(key, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void RemoveTag(TagSet tags, string key)
|
|
|
|
|
{
|
|
|
|
|
if (tags.Contains(key))
|
|
|
|
|
{
|
|
|
|
|
tags.Remove(key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool TryReadInt(TagSet tags, string key, out int value)
|
|
|
|
|
{
|
|
|
|
|
value = 0;
|
|
|
|
|
return tags.TryGetValue(key, out var text) && int.TryParse(text, out value);
|
|
|
|
|
}
|
2026-08-14 09:49:23 +08:00
|
|
|
|
|
|
|
|
private static string FormatIntList(int[] values)
|
|
|
|
|
{
|
|
|
|
|
return values == null || values.Length == 0 ? string.Empty : string.Join(",", values);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int[] ParseIntList(string text)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
|
|
|
{
|
|
|
|
|
return Array.Empty<int>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var parts = text.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
var ids = new List<int>(parts.Length);
|
|
|
|
|
foreach (var part in parts)
|
|
|
|
|
{
|
|
|
|
|
if (int.TryParse(part.Trim(), out var id) && id > 0)
|
|
|
|
|
{
|
|
|
|
|
ids.Add(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ids.ToArray();
|
|
|
|
|
}
|
2026-07-17 13:36:01 +08:00
|
|
|
}
|
|
|
|
|
}
|