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 _vehicles = new List(); 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 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 configs) { if (IsListening) { throw new InvalidOperationException("请先停止模拟再改车辆列表"); } lock (_syncRoot) { DisposeVehiclesUnlocked(); foreach (var config in configs ?? Array.Empty()) { if (config == null) { continue; } var vehicle = new MagCarSimVehicle(Clone(config), Config); if (_map != null) { vehicle.AttachMap(_map); } _vehicles.Add(vehicle); } } } public void StartListen() { List 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 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 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 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 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(); foreach (var vehicle in _vehicles) { if (!seen.Add(vehicle.ListenPort)) { throw new InvalidOperationException($"端口 {vehicle.ListenPort} 被多辆车占用"); } } } private static Dictionary SnapshotExclusiveOccupancy( SimpleLiteMap map, IEnumerable vehicles) { var occupancy = new Dictionary(); 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(source.LoopSiteIds) }; } } }