磁导航1.0内部交管和信号交互
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace StandardScene.MagCarSimulator
|
||||
{
|
||||
public sealed class MagCarSimHost : IDisposable
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
private readonly List<MagCarSimVehicle> _vehicles = new List<MagCarSimVehicle>();
|
||||
private Timer _timer;
|
||||
private SimpleLiteMap _map;
|
||||
private int _tickBusy;
|
||||
|
||||
public MagCarSimHost(MagCarSimConfig config)
|
||||
{
|
||||
Config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
public MagCarSimConfig Config { get; }
|
||||
public SimpleLiteMap Map => _map;
|
||||
public bool IsListening { get; private set; }
|
||||
public bool IsLooping { get; private set; }
|
||||
|
||||
public IReadOnlyList<MagCarSimVehicle> Vehicles
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _vehicles.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SimpleLiteMap LoadMap(string path)
|
||||
{
|
||||
var map = SimpleLiteMap.Load(path, Config.UseTagValueAsNode);
|
||||
var controlPath = MagControlAreaFile.GuessPath(map.FilePath, Config.ControlAreasPath);
|
||||
var controlSites = MagControlAreaFile.LoadSiteIds(controlPath);
|
||||
map.AddSingleOccupancySites(controlSites);
|
||||
lock (_syncRoot)
|
||||
{
|
||||
_map = map;
|
||||
Config.MapPath = map.FilePath;
|
||||
if (!string.IsNullOrWhiteSpace(controlPath))
|
||||
{
|
||||
Config.ControlAreasPath = controlPath;
|
||||
}
|
||||
|
||||
foreach (var vehicle in _vehicles)
|
||||
{
|
||||
vehicle.AttachMap(map);
|
||||
}
|
||||
}
|
||||
|
||||
var exclusive = map.ListSingleOccupancySiteIds();
|
||||
MagCarSimLog.WriteLine(
|
||||
$"[{DateTime.Now:HH:mm:ss.fff}] 已加载地图 {map.FileName},站点 {map.Sites.Count},路径 {map.Tracks.Count},停止/交管点 {(exclusive.Count == 0 ? "无" : string.Join(",", exclusive))}");
|
||||
return map;
|
||||
}
|
||||
|
||||
public void RebuildVehicles(IEnumerable<MagCarSimVehicleConfig> configs)
|
||||
{
|
||||
if (IsListening)
|
||||
{
|
||||
throw new InvalidOperationException("请先停止模拟再改车辆列表");
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
{
|
||||
DisposeVehiclesUnlocked();
|
||||
foreach (var config in configs ?? Array.Empty<MagCarSimVehicleConfig>())
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var vehicle = new MagCarSimVehicle(Clone(config), Config);
|
||||
if (_map != null)
|
||||
{
|
||||
vehicle.AttachMap(_map);
|
||||
}
|
||||
|
||||
_vehicles.Add(vehicle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StartListen()
|
||||
{
|
||||
List<MagCarSimVehicle> vehicles;
|
||||
SimpleLiteMap map;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (IsListening)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map == null)
|
||||
{
|
||||
throw new InvalidOperationException("请先加载 SimpleLite 地图");
|
||||
}
|
||||
|
||||
if (_vehicles.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("没有可启动的车辆");
|
||||
}
|
||||
|
||||
EnsureUniquePortsUnlocked();
|
||||
vehicles = _vehicles.ToList();
|
||||
map = _map;
|
||||
EnsureTimerUnlocked();
|
||||
IsListening = true;
|
||||
}
|
||||
|
||||
foreach (var vehicle in vehicles)
|
||||
{
|
||||
vehicle.AttachMap(map);
|
||||
vehicle.StartListen();
|
||||
}
|
||||
}
|
||||
|
||||
public void StartLoops()
|
||||
{
|
||||
if (!IsListening)
|
||||
{
|
||||
StartListen();
|
||||
}
|
||||
|
||||
List<MagCarSimVehicle> vehicles;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
vehicles = _vehicles.ToList();
|
||||
IsLooping = true;
|
||||
}
|
||||
|
||||
var now = DateTime.Now;
|
||||
foreach (var group in vehicles.GroupBy(RingKey).OrderBy(g => g.Key))
|
||||
{
|
||||
var members = group.OrderBy(v => v.VehicleCode).ToList();
|
||||
var dispatchAt = now;
|
||||
foreach (var vehicle in members)
|
||||
{
|
||||
vehicle.StartLoop(dispatchAt);
|
||||
dispatchAt = dispatchAt.AddMilliseconds(StaggerMs(vehicle));
|
||||
}
|
||||
|
||||
MagCarSimLog.WriteLine(
|
||||
$"[{DateTime.Now:HH:mm:ss.fff}] 同环依次发车 {members[0].Name} 等 {members.Count} 台,间隔 {StaggerMs(members[0])}ms");
|
||||
}
|
||||
}
|
||||
|
||||
public void StopAll()
|
||||
{
|
||||
Timer timer;
|
||||
List<MagCarSimVehicle> vehicles;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
timer = _timer;
|
||||
_timer = null;
|
||||
IsListening = false;
|
||||
IsLooping = false;
|
||||
vehicles = _vehicles.ToList();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
timer?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
foreach (var vehicle in vehicles)
|
||||
{
|
||||
vehicle.StopLoop();
|
||||
vehicle.StopListen();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryRelease(ushort vehicleCode)
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
var vehicle = _vehicles.FirstOrDefault(v => v.VehicleCode == vehicleCode);
|
||||
if (vehicle == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
vehicle.Release();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public List<MagCarSimVehicleSnapshot> Snapshots()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
return _vehicles.Select(v => v.Snapshot()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopAll();
|
||||
lock (_syncRoot)
|
||||
{
|
||||
DisposeVehiclesUnlocked();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureTimerUnlocked()
|
||||
{
|
||||
_timer ??= new Timer(_ => OnTick(), null, 50, 50);
|
||||
}
|
||||
|
||||
private void OnTick()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _tickBusy, 1) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
List<MagCarSimVehicle> vehicles;
|
||||
SimpleLiteMap map;
|
||||
lock (_syncRoot)
|
||||
{
|
||||
vehicles = _vehicles.ToList();
|
||||
map = _map;
|
||||
}
|
||||
|
||||
var occupancy = SnapshotExclusiveOccupancy(map, vehicles);
|
||||
var now = DateTime.Now;
|
||||
foreach (var vehicle in vehicles)
|
||||
{
|
||||
vehicle.Tick(now, occupancy);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _tickBusy, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureUniquePortsUnlocked()
|
||||
{
|
||||
var seen = new HashSet<int>();
|
||||
foreach (var vehicle in _vehicles)
|
||||
{
|
||||
if (!seen.Add(vehicle.ListenPort))
|
||||
{
|
||||
throw new InvalidOperationException($"端口 {vehicle.ListenPort} 被多辆车占用");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<int, ushort> SnapshotExclusiveOccupancy(
|
||||
SimpleLiteMap map,
|
||||
IEnumerable<MagCarSimVehicle> vehicles)
|
||||
{
|
||||
var occupancy = new Dictionary<int, ushort>();
|
||||
if (map == null)
|
||||
{
|
||||
return occupancy;
|
||||
}
|
||||
|
||||
foreach (var vehicle in vehicles)
|
||||
{
|
||||
if (vehicle.CurrentSiteId <= 0 ||
|
||||
!map.SingleOccupancy(vehicle.CurrentSiteId) ||
|
||||
occupancy.ContainsKey(vehicle.CurrentSiteId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
occupancy[vehicle.CurrentSiteId] = vehicle.VehicleCode;
|
||||
}
|
||||
|
||||
return occupancy;
|
||||
}
|
||||
|
||||
private int StaggerMs(MagCarSimVehicle vehicle)
|
||||
{
|
||||
if (Config.LaunchStaggerMs > 0)
|
||||
{
|
||||
return Math.Max(200, Config.LaunchStaggerMs);
|
||||
}
|
||||
|
||||
var interval = vehicle.Config.IntervalMs > 0 ? vehicle.Config.IntervalMs : Config.DefaultIntervalMs;
|
||||
return Math.Max(200, interval);
|
||||
}
|
||||
|
||||
private static string RingKey(MagCarSimVehicle vehicle)
|
||||
{
|
||||
var ids = vehicle.Config.LoopSiteIds;
|
||||
if (ids != null && ids.Count > 0)
|
||||
{
|
||||
return string.Join("-", ids);
|
||||
}
|
||||
|
||||
return $"s{vehicle.Config.StartSiteId}-e{vehicle.Config.EndSiteId}";
|
||||
}
|
||||
|
||||
private void DisposeVehiclesUnlocked()
|
||||
{
|
||||
foreach (var vehicle in _vehicles)
|
||||
{
|
||||
vehicle.Dispose();
|
||||
}
|
||||
|
||||
_vehicles.Clear();
|
||||
}
|
||||
|
||||
private static MagCarSimVehicleConfig Clone(MagCarSimVehicleConfig source)
|
||||
{
|
||||
return new MagCarSimVehicleConfig
|
||||
{
|
||||
Name = source.Name,
|
||||
VehicleCode = source.VehicleCode,
|
||||
ListenPort = source.ListenPort,
|
||||
StartSiteId = source.StartSiteId,
|
||||
EndSiteId = source.EndSiteId,
|
||||
IntervalMs = source.IntervalMs,
|
||||
LoopSiteIds = source.LoopSiteIds == null ? null : new List<int>(source.LoopSiteIds)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user