151 lines
4.5 KiB
C#
151 lines
4.5 KiB
C#
using System.Reflection;
|
|||
|
|
using MyParking.Simulation.Core;
|
||
|
|
|
||
|
|
namespace MyParking.Simulation.Commands;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 自动发现带SimulationAction特性的测试方法并分发网页命令。
|
||
|
|
/// </summary>
|
||
|
|
public sealed class SimulationCommandDispatcher
|
||
|
|
{
|
||
|
|
private readonly SimulationWorld _world;
|
||
|
|
private readonly IReadOnlyDictionary<string, RegisteredAction> _actions;
|
||
|
|
|
||
|
|
public SimulationCommandDispatcher(
|
||
|
|
SimulationWorld world)
|
||
|
|
{
|
||
|
|
_world = world;
|
||
|
|
_actions = DiscoverActions();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 返回网页动态生成按钮所需的全部命令。
|
||
|
|
/// </summary>
|
||
|
|
public IReadOnlyList<SimulationActionDescriptor> GetActions()
|
||
|
|
{
|
||
|
|
return _actions.Values
|
||
|
|
.Select(action => action.Descriptor)
|
||
|
|
.OrderBy(action => action.Group)
|
||
|
|
.ThenBy(action => action.Order)
|
||
|
|
.ToArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 对指定车辆执行一个已注册的测试方法。
|
||
|
|
/// </summary>
|
||
|
|
public CommandResult Execute(
|
||
|
|
int vehicleId,
|
||
|
|
string command)
|
||
|
|
{
|
||
|
|
if (!_actions.TryGetValue(
|
||
|
|
command,
|
||
|
|
out var registeredAction))
|
||
|
|
{
|
||
|
|
return new CommandResult(
|
||
|
|
false,
|
||
|
|
$"未注册仿真命令:{command}。");
|
||
|
|
}
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
return _world.WithVehicle(vehicleId, vehicle =>
|
||
|
|
{
|
||
|
|
var success = registeredAction.Handler(vehicle);
|
||
|
|
var message = success
|
||
|
|
? $"车辆{vehicleId}已执行:{registeredAction.Descriptor.DisplayName}。"
|
||
|
|
: $"车辆{vehicleId}暂时无法执行:{registeredAction.Descriptor.DisplayName}。";
|
||
|
|
|
||
|
|
return new CommandResult(success, message);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
catch (KeyNotFoundException exception)
|
||
|
|
{
|
||
|
|
return new CommandResult(
|
||
|
|
false,
|
||
|
|
exception.Message);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static IReadOnlyDictionary<string, RegisteredAction>
|
||
|
|
DiscoverActions()
|
||
|
|
{
|
||
|
|
var actions = new Dictionary<string, RegisteredAction>(
|
||
|
|
StringComparer.OrdinalIgnoreCase);
|
||
|
|
|
||
|
|
var methods = Assembly.GetExecutingAssembly()
|
||
|
|
.GetTypes()
|
||
|
|
.SelectMany(type => type.GetMethods(
|
||
|
|
BindingFlags.Public |
|
||
|
|
BindingFlags.NonPublic |
|
||
|
|
BindingFlags.Static));
|
||
|
|
|
||
|
|
foreach (var method in methods)
|
||
|
|
{
|
||
|
|
var attribute =
|
||
|
|
method.GetCustomAttribute<SimulationActionAttribute>();
|
||
|
|
|
||
|
|
if (attribute == null)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
ValidateMethod(method, attribute);
|
||
|
|
|
||
|
|
var handler =
|
||
|
|
(Func<SimulationVehicle, bool>)method.CreateDelegate(
|
||
|
|
typeof(Func<SimulationVehicle, bool>));
|
||
|
|
|
||
|
|
var descriptor = new SimulationActionDescriptor(
|
||
|
|
attribute.Key,
|
||
|
|
attribute.DisplayName,
|
||
|
|
attribute.Group,
|
||
|
|
attribute.Order);
|
||
|
|
|
||
|
|
if (!actions.TryAdd(
|
||
|
|
attribute.Key,
|
||
|
|
new RegisteredAction(descriptor, handler)))
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException(
|
||
|
|
$"仿真命令Key重复:{attribute.Key}。");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return actions;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void ValidateMethod(
|
||
|
|
MethodInfo method,
|
||
|
|
SimulationActionAttribute attribute)
|
||
|
|
{
|
||
|
|
var parameters = method.GetParameters();
|
||
|
|
|
||
|
|
if (method.ReturnType != typeof(bool) ||
|
||
|
|
parameters.Length != 1 ||
|
||
|
|
parameters[0].ParameterType !=
|
||
|
|
typeof(SimulationVehicle))
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException(
|
||
|
|
$"[{nameof(SimulationActionAttribute)}]方法" +
|
||
|
|
$"{method.DeclaringType?.FullName}.{method.Name}" +
|
||
|
|
"必须是static bool Xxx(SimulationVehicle vehicle)。");
|
||
|
|
}
|
||
|
|
|
||
|
|
if (string.IsNullOrWhiteSpace(attribute.Key) ||
|
||
|
|
string.IsNullOrWhiteSpace(attribute.DisplayName) ||
|
||
|
|
string.IsNullOrWhiteSpace(attribute.Group))
|
||
|
|
{
|
||
|
|
throw new InvalidOperationException(
|
||
|
|
$"仿真命令{method.Name}的特性参数不能为空。");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private sealed record RegisteredAction(
|
||
|
|
SimulationActionDescriptor Descriptor,
|
||
|
|
Func<SimulationVehicle, bool> Handler);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 网页测试命令的执行结果。
|
||
|
|
/// </summary>
|
||
|
|
public sealed record CommandResult(
|
||
|
|
bool Success,
|
||
|
|
string Message);
|