多批次Phase1: RoutePlan改Id主键+Waypoint加GroupId+ScenarioService/Engine/所有测试适配

This commit is contained in:
tian 2026-06-12 15:33:37 +08:00
parent 42b390a264
commit 12cb6576fe
18 changed files with 103 additions and 36 deletions

View File

@ -68,6 +68,7 @@ namespace CounterDrone.Core
db.CreateTable<EquipmentDeployment>();
db.CreateTable<CloudDispersal>();
db.CreateTable<RoutePlan>();
db.CreateIndex("RoutePlan", new[] { "TaskId", "GroupId" }, true);
db.CreateTable<Waypoint>();
db.CreateTable<Group>();
db.CreateTable<SimulationReport>();

View File

@ -1,14 +1,19 @@
using System;
using SQLite;
namespace CounterDrone.Core.Models
{
/// <summary>步骤5航路规划</summary>
/// <summary>步骤5航路规划 — 多编队支持:每个(任务,编队)一条航路</summary>
[Table("RoutePlan")]
public class RoutePlan
{
[PrimaryKey]
public string Id { get; set; } = Guid.NewGuid().ToString();
[Indexed]
public string TaskId { get; set; } = string.Empty;
[Indexed]
public string GroupId { get; set; } = string.Empty;
public int FormationMode { get; set; } = (int)Models.FormationMode.Single;

View File

@ -11,8 +11,10 @@ namespace CounterDrone.Core.Models
public List<TargetConfig> Targets { get; set; } = new();
public List<EquipmentDeployment> Equipment { get; set; } = new();
public CloudDispersal Cloud { get; set; } = new();
public RoutePlan Route { get; set; } = new();
public List<Waypoint> Waypoints { get; set; } = new();
/// <summary>多编队航路(多批次支持)</summary>
public List<RoutePlan> Routes { get; set; } = new();
/// <summary>按 GroupId 分组的航路点</summary>
public Dictionary<string, List<Waypoint>> WaypointGroups { get; set; } = new();
}
/// <summary>分页结果</summary>

View File

@ -13,6 +13,9 @@ namespace CounterDrone.Core.Models
[Indexed]
public string TaskId { get; set; } = string.Empty;
/// <summary>关联的编队 ID多编队支持</summary>
public string GroupId { get; set; } = string.Empty;
[NotNull]
public int OrderIndex { get; set; }

View File

@ -1,3 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using CounterDrone.Core.Models;
using SQLite;
@ -6,5 +8,16 @@ namespace CounterDrone.Core.Repository
public class RoutePlanRepository : BaseRepository<RoutePlan>
{
public RoutePlanRepository(SQLiteConnection db) : base(db) { }
public List<RoutePlan> GetByTaskId(string taskId)
{
return Db.Table<RoutePlan>().Where(r => r.TaskId == taskId).ToList();
}
public RoutePlan GetByTaskAndGroup(string taskId, string groupId)
{
return Db.Table<RoutePlan>()
.FirstOrDefault(r => r.TaskId == taskId && r.GroupId == groupId);
}
}
}

View File

@ -17,6 +17,14 @@ namespace CounterDrone.Core.Repository
.ToList();
}
public List<Waypoint> GetByTaskAndGroup(string taskId, string groupId)
{
return Db.Table<Waypoint>()
.Where(w => w.TaskId == taskId && w.GroupId == groupId)
.OrderBy(w => w.OrderIndex)
.ToList();
}
public void DeleteByTaskId(string taskId)
{
var wps = GetByTaskId(taskId);

View File

@ -16,7 +16,7 @@ namespace CounterDrone.Core.Services
void SaveTarget(string taskId, TargetConfig target);
void SaveDeployment(string taskId, List<EquipmentDeployment> equips);
void SaveCloudDispersal(string taskId, CloudDispersal cloud);
void SaveRoute(string taskId, RoutePlan route, List<Waypoint> waypoints);
void SaveRoute(string taskId, string groupId, RoutePlan route, List<Waypoint> waypoints);
void UpdateStep(string taskId, int step);
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using CounterDrone.Core.Models;
using CounterDrone.Core.Repository;
@ -98,8 +99,10 @@ namespace CounterDrone.Core.Services
Targets = _targetRepo.GetByTaskId(id),
Equipment = _equipRepo.GetByTaskId(id),
Cloud = _cloudRepo.GetById(id) ?? new CloudDispersal { TaskId = id },
Route = _routeRepo.GetById(id) ?? new RoutePlan { TaskId = id },
Waypoints = _waypointRepo.GetByTaskId(id),
Routes = _routeRepo.GetByTaskId(id),
WaypointGroups = _waypointRepo.GetByTaskId(id)
.GroupBy(w => w.GroupId)
.ToDictionary(g => g.Key, g => g.OrderBy(w => w.OrderIndex).ToList()),
};
}
@ -166,19 +169,26 @@ namespace CounterDrone.Core.Services
TouchTask(taskId);
}
public void SaveRoute(string taskId, RoutePlan route, List<Waypoint> waypoints)
public void SaveRoute(string taskId, string groupId, RoutePlan route, List<Waypoint> waypoints)
{
route.TaskId = taskId;
var existingRoute = _routeRepo.GetById(taskId);
if (existingRoute != null)
route.GroupId = groupId;
var existing = _routeRepo.GetByTaskAndGroup(taskId, groupId);
if (existing != null)
{
route.Id = existing.Id;
_routeRepo.Update(route);
}
else
_routeRepo.Insert(route);
_waypointRepo.DeleteByTaskId(taskId);
// 删除该编队的旧航路点,插入新的
var oldWps = _waypointRepo.GetByTaskAndGroup(taskId, groupId);
foreach (var w in oldWps) _waypointRepo.Delete(w.Id);
for (int i = 0; i < waypoints.Count; i++)
{
waypoints[i].TaskId = taskId;
waypoints[i].GroupId = groupId;
waypoints[i].OrderIndex = i;
if (string.IsNullOrEmpty(waypoints[i].Id))
waypoints[i].Id = Guid.NewGuid().ToString();

View File

@ -86,11 +86,14 @@ namespace CounterDrone.Core.Simulation
_drones.Clear();
foreach (var target in config.Targets)
{
var mode = (FormationMode)config.Route.FormationMode;
var spacing = (float)config.Route.FormationSpacing;
var route = config.Routes.FirstOrDefault(r => r.GroupId == target.GroupId);
var wps = config.WaypointGroups.GetValueOrDefault(target.GroupId, new List<Waypoint>());
if (route == null || wps.Count == 0) continue;
var mode = (FormationMode)route.FormationMode;
var spacing = (float)route.FormationSpacing;
for (int i = 0; i < target.Quantity; i++)
_drones.Add(new DroneEntity($"drone_{++_entityCounter}", target.GroupId,
target, config.Waypoints, i, spacing, mode));
target, wps, i, spacing, mode));
}
_platforms.Clear();

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 24c080ea3d6cbcf45aa804fd10269e4a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -36,7 +36,7 @@ namespace CounterDrone.Unity
TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500,
});
scenarioMgr.SaveRoute(taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
scenarioMgr.SaveRoute(taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 500, PosZ = 0 },

View File

@ -35,7 +35,7 @@ namespace CounterDrone.Unity
public void SaveTarget(string taskId, TargetConfig t) => _service.SaveTarget(taskId, t);
public void SaveDeployment(string taskId, List<EquipmentDeployment> e) => _service.SaveDeployment(taskId, e);
public void SaveCloud(string taskId, CloudDispersal c) => _service.SaveCloudDispersal(taskId, c);
public void SaveRoute(string taskId, RoutePlan r, List<Waypoint> w) => _service.SaveRoute(taskId, r, w);
public void SaveRoute(string taskId, string groupId, RoutePlan r, List<Waypoint> w) => _service.SaveRoute(taskId, groupId, r, w);
public void UpdateStep(string taskId, int step) => _service.UpdateStep(taskId, step);
[ContextMenu("Verify")]

View File

@ -54,7 +54,7 @@ namespace CounterDrone.Unity
TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston,
Quantity = 1, TypicalSpeed = _droneSpeed, TypicalAltitude = 500,
});
scenario.SaveRoute(taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
scenario.SaveRoute(taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = _droneSpeed },

View File

@ -49,6 +49,7 @@ namespace CounterDrone.Core.Tests
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
_scenario.SaveTarget(_taskId, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.Piston,
Quantity = 1,
TypicalSpeed = 100,
@ -56,7 +57,7 @@ namespace CounterDrone.Core.Tests
});
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>());
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal());
_scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 300, PosZ = 0 },
@ -91,13 +92,14 @@ namespace CounterDrone.Core.Tests
});
_scenario.SaveTarget(_taskId, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.Piston,
Quantity = 1,
TypicalSpeed = 600, // 高速对抗强风
});
_scenario.SaveDeployment(_taskId, new List<EquipmentDeployment>());
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal());
_scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 300, PosZ = 0 },
@ -129,6 +131,7 @@ namespace CounterDrone.Core.Tests
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
_scenario.SaveTarget(_taskId, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.HighSpeed,
Quantity = 1,
TypicalSpeed = 500,
@ -137,7 +140,7 @@ namespace CounterDrone.Core.Tests
_scenario.SaveCloudDispersal(_taskId, new CloudDispersal());
// 500 km/h, 100km 航程
_scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 500, PosZ = 0 },

View File

@ -85,8 +85,8 @@ namespace CounterDrone.Core.Tests
{
Environment = detail.Scene,
Targets = detail.Targets,
Route = detail.Route,
Waypoints = detail.Waypoints,
Route = detail.Routes[0],
Waypoints = detail.WaypointGroups["default"],
};
var rec = new DefaultDefenseAdvisor(_ammoCatalog).Recommend(threat);
@ -151,13 +151,14 @@ namespace CounterDrone.Core.Tests
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
_scenario.SaveTarget(_taskId, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.FixedWing,
PowerType = (int)PowerType.Jet,
Quantity = 1,
TypicalSpeed = 600,
TypicalAltitude = 400,
});
_scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 400, PosZ = 0, Speed = 600 },
@ -183,13 +184,14 @@ namespace CounterDrone.Core.Tests
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
_scenario.SaveTarget(_taskId, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.Electric,
PowerType = (int)PowerType.Electric,
Quantity = 1,
TypicalSpeed = 300,
TypicalAltitude = 300,
});
_scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Speed = 300 },
@ -228,13 +230,14 @@ namespace CounterDrone.Core.Tests
});
_scenario.SaveTarget(_taskId, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.Piston,
PowerType = (int)PowerType.Piston,
Quantity = 1,
TypicalSpeed = 200,
TypicalAltitude = 500,
});
_scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 },
@ -294,13 +297,14 @@ namespace CounterDrone.Core.Tests
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
_scenario.SaveTarget(_taskId, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.HighSpeed,
PowerType = (int)PowerType.Jet,
Quantity = 1,
TypicalSpeed = 500,
TypicalAltitude = 800,
});
_scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
_scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 800, PosZ = 0, Speed = 500 },

View File

@ -103,6 +103,7 @@ namespace CounterDrone.Core.Tests
_service.SaveTarget(task.Id, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.FixedWing,
Quantity = 3,
});
@ -200,6 +201,7 @@ namespace CounterDrone.Core.Tests
var task = _service.CreateTask("Target Test", "");
_service.SaveTarget(task.Id, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.Piston,
Quantity = 5,
PowerType = (int)PowerType.Piston,
@ -265,14 +267,14 @@ namespace CounterDrone.Core.Tests
new Waypoint { PosX = 10000, PosY = 0, PosZ = 100, Altitude = 300, Speed = 80 },
};
_service.SaveRoute(task.Id, route, waypoints);
_service.SaveRoute(task.Id, "default", route, waypoints);
var detail = _service.GetTaskDetail(task.Id);
Assert.Equal((int)FormationMode.Formation, detail.Route.FormationMode);
Assert.Equal(3, detail.Waypoints.Count);
Assert.Equal(0, detail.Waypoints[0].OrderIndex);
Assert.Equal(2, detail.Waypoints[2].OrderIndex);
Assert.Equal(5000.0, detail.Waypoints[1].PosX);
Assert.Equal((int)FormationMode.Formation, detail.Routes[0].FormationMode);
Assert.Equal(3, detail.WaypointGroups["default"].Count);
Assert.Equal(0, detail.WaypointGroups["default"][0].OrderIndex);
Assert.Equal(2, detail.WaypointGroups["default"][2].OrderIndex);
Assert.Equal(5000.0, detail.WaypointGroups["default"][1].PosX);
}
// ========== Search & Pagination ==========
@ -392,6 +394,7 @@ namespace CounterDrone.Core.Tests
// 4. 步骤2目标配置
_service.SaveTarget(task.Id, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.HighSpeed,
Quantity = 2,
PowerType = (int)PowerType.Jet,
@ -433,7 +436,7 @@ namespace CounterDrone.Core.Tests
_service.UpdateStep(task.Id, 4);
// 7. 步骤5航路规划
_service.SaveRoute(task.Id, new RoutePlan
_service.SaveRoute(task.Id, "default", new RoutePlan
{
FormationMode = (int)FormationMode.Swarm,
FormationSpacing = 30.0,
@ -473,9 +476,9 @@ namespace CounterDrone.Core.Tests
Assert.Equal(50.0, detail.Cloud.Duration);
// 航路
Assert.Equal((int)FormationMode.Swarm, detail.Route.FormationMode);
Assert.Equal(2, detail.Waypoints.Count);
Assert.Equal(250.0, detail.Waypoints[0].Speed);
Assert.Equal((int)FormationMode.Swarm, detail.Routes[0].FormationMode);
Assert.Equal(2, detail.WaypointGroups["default"].Count);
Assert.Equal(250.0, detail.WaypointGroups["default"][0].Speed);
// 9. 搜索验证
var searchResult = _service.SearchTasks("完整流程", null, null, 1, 10);

View File

@ -53,6 +53,7 @@ namespace CounterDrone.Core.Tests
_scenarioService.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
_scenarioService.SaveTarget(_taskId, new TargetConfig
{
GroupId = "default",
TargetType = (int)TargetType.Piston,
Quantity = 1,
PowerType = (int)PowerType.Piston,
@ -79,7 +80,7 @@ namespace CounterDrone.Core.Tests
PositionZ = 0,
PositionMode = (int)PositionMode.AlgorithmRecommended,
});
_scenarioService.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single },
_scenarioService.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single },
new List<Waypoint>
{
new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },