241 lines
7.6 KiB
C#
241 lines
7.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using MultiWheelC.Fleet;
|
|
using MyParking.Shared;
|
|
|
|
namespace MultiWheelC.Tests
|
|
{
|
|
// 为同一测试进程中的各车辆端点提供共享FIFO消息队列。
|
|
internal sealed class InMemoryFleetTransportNetwork
|
|
{
|
|
private readonly SharedState _sharedState;
|
|
private readonly HashSet<int> _createdEndpointIds =
|
|
new HashSet<int>();
|
|
|
|
public InMemoryFleetTransportNetwork(
|
|
IReadOnlyList<int> vehicleIds,
|
|
int leaderVehicleId)
|
|
{
|
|
_sharedState = new SharedState(
|
|
vehicleIds,
|
|
leaderVehicleId);
|
|
}
|
|
|
|
public InMemoryFleetTransport CreateEndpoint(
|
|
int vehicleId)
|
|
{
|
|
lock (_sharedState.SyncRoot)
|
|
{
|
|
if (!_sharedState.CommandQueues.ContainsKey(
|
|
vehicleId))
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(vehicleId),
|
|
$"车辆{vehicleId}不属于当前内存车队网络。");
|
|
}
|
|
|
|
if (!_createdEndpointIds.Add(vehicleId))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"车辆{vehicleId}的内存通信端点已经创建。");
|
|
}
|
|
}
|
|
|
|
return new InMemoryFleetTransport(
|
|
_sharedState,
|
|
vehicleId);
|
|
}
|
|
|
|
internal sealed class SharedState
|
|
{
|
|
public SharedState(
|
|
IReadOnlyList<int> vehicleIds,
|
|
int leaderVehicleId)
|
|
{
|
|
if (vehicleIds == null)
|
|
{
|
|
throw new ArgumentNullException(
|
|
nameof(vehicleIds));
|
|
}
|
|
|
|
if (vehicleIds.Count == 0)
|
|
{
|
|
throw new ArgumentException(
|
|
"内存车队网络至少需要一辆车。",
|
|
nameof(vehicleIds));
|
|
}
|
|
|
|
if (leaderVehicleId <= 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(leaderVehicleId),
|
|
"主车编号必须大于零。");
|
|
}
|
|
|
|
CommandQueues =
|
|
new Dictionary<int, Queue<FleetCommand>>();
|
|
|
|
for (var index = 0;
|
|
index < vehicleIds.Count;
|
|
index++)
|
|
{
|
|
var vehicleId = vehicleIds[index];
|
|
if (vehicleId <= 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(vehicleIds),
|
|
$"第{index}辆车的编号必须大于零。");
|
|
}
|
|
|
|
if (CommandQueues.ContainsKey(vehicleId))
|
|
{
|
|
throw new ArgumentException(
|
|
$"内存车队网络包含重复车号{vehicleId}。",
|
|
nameof(vehicleIds));
|
|
}
|
|
|
|
CommandQueues.Add(
|
|
vehicleId,
|
|
new Queue<FleetCommand>());
|
|
}
|
|
|
|
if (!CommandQueues.ContainsKey(
|
|
leaderVehicleId))
|
|
{
|
|
throw new ArgumentException(
|
|
$"主车{leaderVehicleId}不在车辆列表中。",
|
|
nameof(leaderVehicleId));
|
|
}
|
|
|
|
LeaderVehicleId = leaderVehicleId;
|
|
}
|
|
|
|
public object SyncRoot { get; } = new object();
|
|
|
|
public int LeaderVehicleId { get; }
|
|
|
|
public Dictionary<int, Queue<FleetCommand>>
|
|
CommandQueues { get; }
|
|
|
|
public Queue<FleetMemberReport> ReportQueue { get; } =
|
|
new Queue<FleetMemberReport>();
|
|
}
|
|
}
|
|
|
|
// 单辆模拟车辆持有的通信端点;只负责消息路由,不解释控制语义。
|
|
internal sealed class InMemoryFleetTransport : IFleetTransport
|
|
{
|
|
private readonly InMemoryFleetTransportNetwork.SharedState
|
|
_sharedState;
|
|
private readonly int _localVehicleId;
|
|
|
|
internal InMemoryFleetTransport(
|
|
InMemoryFleetTransportNetwork.SharedState sharedState,
|
|
int localVehicleId)
|
|
{
|
|
_sharedState = sharedState ??
|
|
throw new ArgumentNullException(
|
|
nameof(sharedState));
|
|
_localVehicleId = localVehicleId;
|
|
}
|
|
|
|
public void SendCommand(FleetCommand command)
|
|
{
|
|
if (_localVehicleId !=
|
|
_sharedState.LeaderVehicleId)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"只有主车通信端点可以发送车队命令。");
|
|
}
|
|
|
|
lock (_sharedState.SyncRoot)
|
|
{
|
|
if (command.TargetVehicleId ==
|
|
FleetProtocol.BroadcastVehicleId)
|
|
{
|
|
foreach (var pair in
|
|
_sharedState.CommandQueues)
|
|
{
|
|
// 主车本地命令由运行入口直接执行,不通过通信回环。
|
|
if (pair.Key != _localVehicleId)
|
|
{
|
|
pair.Value.Enqueue(command);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!_sharedState.CommandQueues.TryGetValue(
|
|
command.TargetVehicleId,
|
|
out var queue))
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(command),
|
|
$"目标车辆{command.TargetVehicleId}不存在。");
|
|
}
|
|
|
|
queue.Enqueue(command);
|
|
}
|
|
}
|
|
|
|
public void SendReport(FleetMemberReport report)
|
|
{
|
|
if (report.VehicleId != _localVehicleId)
|
|
{
|
|
throw new ArgumentException(
|
|
$"车辆{_localVehicleId}不能发送属于车辆" +
|
|
$"{report.VehicleId}的状态报告。",
|
|
nameof(report));
|
|
}
|
|
|
|
lock (_sharedState.SyncRoot)
|
|
{
|
|
_sharedState.ReportQueue.Enqueue(report);
|
|
}
|
|
}
|
|
|
|
public bool TryReceiveCommand(
|
|
out FleetCommand command)
|
|
{
|
|
lock (_sharedState.SyncRoot)
|
|
{
|
|
var queue =
|
|
_sharedState.CommandQueues[_localVehicleId];
|
|
if (queue.Count == 0)
|
|
{
|
|
command = default;
|
|
return false;
|
|
}
|
|
|
|
command = queue.Dequeue();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public bool TryReceiveReport(
|
|
out FleetMemberReport report)
|
|
{
|
|
if (_localVehicleId !=
|
|
_sharedState.LeaderVehicleId)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"只有主车通信端点可以接收成员状态报告。");
|
|
}
|
|
|
|
lock (_sharedState.SyncRoot)
|
|
{
|
|
if (_sharedState.ReportQueue.Count == 0)
|
|
{
|
|
report = default;
|
|
return false;
|
|
}
|
|
|
|
report =
|
|
_sharedState.ReportQueue.Dequeue();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|