Files
ParkingRobot/MultiWheelC.Tests/InMemoryFleetTransport.cs
T

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