Further Tests & Progress
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
using NUnit.Framework;
|
||||
using ProjectM.Server;
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Core;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Plain-Entities EditMode tests for the server-only <see cref="BuildPlaceSystem"/> — the RPC structure-placement
|
||||
/// handler. A bare world is seeded with StructureCatalog (+ a Turret entry referencing a Prefab-tagged prefab),
|
||||
/// BaseAnchor, ResourceLedger (+ Ore), NetworkTime, and synthetic BuildPlaceRequest + ReceiveRpcCommandRequest
|
||||
/// entities. The headline case is co-op atomicity: two same-tick requests for one cell must place EXACTLY one
|
||||
/// structure and withdraw the cost ONCE (the in-place commit). Also pins cost/plot validation and request cleanup.
|
||||
/// </summary>
|
||||
public class BuildPlaceSystemTests
|
||||
{
|
||||
static (World world, SimulationSystemGroup group) MakeWorld(string name, int oreCount)
|
||||
{
|
||||
var world = new World(name);
|
||||
var group = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
|
||||
group.AddSystemToUpdateList(world.GetOrCreateSystem<BuildPlaceSystem>());
|
||||
group.SortSystems();
|
||||
world.SetTime(new TimeData(elapsedTime: 0f, deltaTime: 1f / 60f));
|
||||
var em = world.EntityManager;
|
||||
|
||||
var nt = em.CreateEntity(typeof(NetworkTime));
|
||||
em.SetComponentData(nt, new NetworkTime { ServerTick = new NetworkTick(300) });
|
||||
|
||||
var anchor = em.CreateEntity(typeof(BaseAnchor));
|
||||
em.SetComponentData(anchor, new BaseAnchor
|
||||
{
|
||||
AnchorPos = new float3(5, 0, 5),
|
||||
GridOrigin = new float3(0, 0, 0),
|
||||
CellSize = 2f,
|
||||
GridDims = new int2(5, 5),
|
||||
});
|
||||
|
||||
// Turret prefab: LocalTransform (looked up for placement) + PlacedStructure (SetComponent target on the
|
||||
// clone) + a real Prefab tag so it is excluded from the live-structure occupancy scan.
|
||||
var prefab = em.CreateEntity(typeof(LocalTransform), typeof(PlacedStructure));
|
||||
em.AddComponent<Prefab>(prefab);
|
||||
|
||||
var catalogE = em.CreateEntity(typeof(StructureCatalog));
|
||||
var catalog = em.AddBuffer<StructureCatalogEntry>(catalogE);
|
||||
catalog.Add(new StructureCatalogEntry
|
||||
{
|
||||
Type = StructureType.Turret, Prefab = prefab, CostResourceId = ResourceId.Ore, CostAmount = 10,
|
||||
});
|
||||
|
||||
var ledgerE = em.CreateEntity(typeof(ResourceLedger));
|
||||
var ledger = em.AddBuffer<StorageEntry>(ledgerE);
|
||||
ledger.Add(new StorageEntry { ItemId = ResourceId.Ore, Count = oreCount });
|
||||
|
||||
return (world, group);
|
||||
}
|
||||
|
||||
static void MakeBuildRequest(EntityManager em, byte type, int cellX, int cellZ)
|
||||
{
|
||||
var e = em.CreateEntity();
|
||||
em.AddComponentData(e, new BuildPlaceRequest { StructureType = type, CellX = cellX, CellZ = cellZ });
|
||||
em.AddComponentData(e, default(ReceiveRpcCommandRequest));
|
||||
}
|
||||
|
||||
static int StructureCount(EntityManager em)
|
||||
{
|
||||
using var q = em.CreateEntityQuery(typeof(PlacedStructure));
|
||||
return q.CalculateEntityCount();
|
||||
}
|
||||
|
||||
static int OreCount(EntityManager em)
|
||||
{
|
||||
using var q = em.CreateEntityQuery(typeof(ResourceLedger));
|
||||
var ledger = em.GetBuffer<StorageEntry>(q.GetSingletonEntity());
|
||||
for (int i = 0; i < ledger.Length; i++)
|
||||
if (ledger[i].ItemId == ResourceId.Ore) return ledger[i].Count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Valid_Request_Places_Structure_Withdraws_Cost_And_Destroys_Request()
|
||||
{
|
||||
var (world, group) = MakeWorld("BuildValid", oreCount: 50);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
MakeBuildRequest(em, StructureType.Turret, cellX: 1, cellZ: 1);
|
||||
|
||||
group.Update();
|
||||
|
||||
Assert.AreEqual(1, StructureCount(em), "A valid request places exactly one structure.");
|
||||
Assert.AreEqual(40, OreCount(em), "The build cost (10) is withdrawn from the ledger.");
|
||||
using var reqQ = em.CreateEntityQuery(typeof(BuildPlaceRequest));
|
||||
Assert.AreEqual(0, reqQ.CalculateEntityCount(), "The handled request is destroyed.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Two_Same_Cell_Requests_Place_Only_One_And_Withdraw_Once()
|
||||
{
|
||||
var (world, group) = MakeWorld("BuildAtomic", oreCount: 50);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
MakeBuildRequest(em, StructureType.Turret, cellX: 1, cellZ: 1);
|
||||
MakeBuildRequest(em, StructureType.Turret, cellX: 1, cellZ: 1);
|
||||
|
||||
group.Update();
|
||||
|
||||
Assert.AreEqual(1, StructureCount(em),
|
||||
"Two same-tick requests for one cell place exactly one structure (co-op atomicity).");
|
||||
Assert.AreEqual(40, OreCount(em), "The cost is withdrawn exactly once, not twice.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Insufficient_Resources_Places_Nothing()
|
||||
{
|
||||
var (world, group) = MakeWorld("BuildPoor", oreCount: 5);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
MakeBuildRequest(em, StructureType.Turret, cellX: 1, cellZ: 1);
|
||||
|
||||
group.Update();
|
||||
|
||||
Assert.AreEqual(0, StructureCount(em), "A request that can't afford the cost places nothing.");
|
||||
Assert.AreEqual(5, OreCount(em), "The ledger is untouched on an unaffordable request.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Out_Of_Plot_Cell_Places_Nothing()
|
||||
{
|
||||
var (world, group) = MakeWorld("BuildOOB", oreCount: 50);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
MakeBuildRequest(em, StructureType.Turret, cellX: 99, cellZ: 99);
|
||||
|
||||
group.Update();
|
||||
|
||||
Assert.AreEqual(0, StructureCount(em), "An out-of-plot cell places nothing.");
|
||||
Assert.AreEqual(50, OreCount(em), "No cost is withdrawn for an illegal placement.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user