Files
StandardSence/StandardScene.MagCarSimulator/Runtime/MagCarSimHost.cs
T

336 lines
9.7 KiB
C#

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)
};
}
}
}