demo2
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "Demo2.EditMode.Tests",
|
||||
"rootNamespace": "Demo2.Tests",
|
||||
"references": [
|
||||
"Demo2.Domain",
|
||||
"Demo2.ECS",
|
||||
"Demo2.Runtime",
|
||||
"NightShiftProtocol.Rules",
|
||||
"Unity.Entities",
|
||||
"Unity.Collections",
|
||||
"UnityEngine.TestRunner",
|
||||
"UnityEditor.TestRunner"
|
||||
],
|
||||
"includePlatforms": ["Editor"],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": ["UNITY_INCLUDE_TESTS"],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4942298112ebe74e8c777d9494bdb4c
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Linq;
|
||||
using Demo2.Domain;
|
||||
using NightShiftProtocol;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Demo2.Tests
|
||||
{
|
||||
public sealed class Demo2DomainTests
|
||||
{
|
||||
[Test]
|
||||
public void BuiltInContent_HasFiveCyclesEighteenRulesAndFiveCategories()
|
||||
{
|
||||
var content = Demo2BuiltInContent.Create();
|
||||
|
||||
Assert.That(content.Rules.Count, Is.EqualTo(18));
|
||||
Assert.That(content.Rules.Values.Select(rule => rule.Category).Distinct().Count(), Is.EqualTo(5));
|
||||
Assert.That(content.Waves.Select(wave => wave.TotalHealth), Is.EqualTo(new long[] { 500, 900, 1620, 2916, 5248 }));
|
||||
Assert.That(content.ComputeContentHash(), Has.Length.EqualTo(64));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaturatingMath_ClampsBothDirectionsAndFormatsLargeValues()
|
||||
{
|
||||
Assert.That(SaturatingMath.Add(long.MaxValue, 1), Is.EqualTo(long.MaxValue));
|
||||
Assert.That(SaturatingMath.Add(long.MinValue, -1), Is.EqualTo(long.MinValue));
|
||||
Assert.That(SaturatingMath.Multiply(long.MaxValue, 2), Is.EqualTo(long.MaxValue));
|
||||
Assert.That(SaturatingMath.MultiplyScaled(200, 1500), Is.EqualTo(300));
|
||||
Assert.That(SaturatingMath.Format(1_250_000_000), Is.EqualTo("1.25e9"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RuleOrder_IsStrictlyLeftToRight()
|
||||
{
|
||||
var rules = Demo2BuiltInContent.BuildRules().ToDictionary(rule => rule.Id);
|
||||
var initial = new Demo2RuleContext { Payload = 100, MultiplierMilli = 1000 };
|
||||
|
||||
var addThenMultiply = Demo2RuleEngine.Evaluate(initial, new[] { rules["production.dense-ammo"], rules["machine.overclock"] });
|
||||
var multiplyThenAdd = Demo2RuleEngine.Evaluate(initial, new[] { rules["machine.overclock"], rules["production.dense-ammo"] });
|
||||
|
||||
Assert.That(addThenMultiply.Payload, Is.EqualTo(420));
|
||||
Assert.That(multiplyThenAdd.Payload, Is.EqualTo(330));
|
||||
Assert.That(addThenMultiply.Faults, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConditionalRule_OnlyRunsWhenEveryFlagMatches()
|
||||
{
|
||||
var content = Demo2BuiltInContent.Create();
|
||||
var rule = content.Rules["logistics.long-haul"];
|
||||
var missesFaultCondition = new Demo2RuleContext { Payload = 100, MultiplierMilli = 1000, BeltBelowEightyPercent = true, Faults = 1 };
|
||||
var matches = missesFaultCondition;
|
||||
matches.Faults = 0;
|
||||
|
||||
Assert.That(Demo2RuleEngine.Evaluate(missesFaultCondition, new[] { rule }).MultiplierMilli, Is.EqualTo(1000));
|
||||
Assert.That(Demo2RuleEngine.Evaluate(matches, new[] { rule }).MultiplierMilli, Is.EqualTo(1200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NightShiftMod_AddsMachineRecipeAndConditionalRule()
|
||||
{
|
||||
var content = Demo2BuiltInContent.Create();
|
||||
var before = content.ComputeContentHash();
|
||||
|
||||
new NightShiftContent().Register(content);
|
||||
|
||||
Assert.That(content.Machines.ContainsKey(Demo2MachineKind.NightShiftSmelter), Is.True);
|
||||
Assert.That(content.Recipes.ContainsKey(100), Is.True);
|
||||
Assert.That(content.Rules.ContainsKey("night-shift.continuous-production"), Is.True);
|
||||
Assert.That(content.ComputeContentHash(), Is.Not.EqualTo(before));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0018b00d882aac0418e7c356e3582bb1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,130 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Diagnostics;
|
||||
using Demo2.Domain;
|
||||
using Demo2.ECS;
|
||||
using Demo2.Runtime;
|
||||
using NUnit.Framework;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Demo2.Tests
|
||||
{
|
||||
public sealed class Demo2EcsTests
|
||||
{
|
||||
[Test]
|
||||
public void SameSeedAndCommands_RemainIdenticalForTenThousandTicks()
|
||||
{
|
||||
using var left = new Demo2EcsWorld(20260817, Demo2BuiltInContent.BuildRules());
|
||||
using var right = new Demo2EcsWorld(20260817, Demo2BuiltInContent.BuildRules());
|
||||
left.SetPhase(Demo2Phase.Production);
|
||||
right.SetPhase(Demo2Phase.Production);
|
||||
|
||||
for (var tick = 0; tick < 10_000; tick++)
|
||||
{
|
||||
left.Tick();
|
||||
right.Tick();
|
||||
}
|
||||
|
||||
var leftState = left.EntityManager.GetComponentData<Demo2SimulationState>(left.Singleton);
|
||||
var rightState = right.EntityManager.GetComponentData<Demo2SimulationState>(right.Singleton);
|
||||
Assert.That(leftState.Tick, Is.EqualTo(10_000));
|
||||
Assert.That(leftState.StateHash, Is.EqualTo(rightState.StateHash));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SnapshotRestore_ContinuesWithTheSameHash()
|
||||
{
|
||||
using var source = new Demo2EcsWorld(42, Demo2BuiltInContent.BuildRules());
|
||||
source.SetPhase(Demo2Phase.Production);
|
||||
for (var tick = 0; tick < 500; tick++) source.Tick();
|
||||
var snapshot = source.CaptureSnapshot(new[] { "production.dense-ammo" });
|
||||
|
||||
using var restored = new Demo2EcsWorld(999, Demo2BuiltInContent.BuildRules());
|
||||
restored.RestoreSnapshot(snapshot);
|
||||
for (var tick = 0; tick < 200; tick++)
|
||||
{
|
||||
source.Tick();
|
||||
restored.Tick();
|
||||
}
|
||||
|
||||
var sourceState = source.EntityManager.GetComponentData<Demo2SimulationState>(source.Singleton);
|
||||
var restoredState = restored.EntityManager.GetComponentData<Demo2SimulationState>(restored.Singleton);
|
||||
Assert.That(restoredState.StateHash, Is.EqualTo(sourceState.StateHash));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BuildCommands_AreIdempotentAndRemovalRefundsBeforeEntityDestruction()
|
||||
{
|
||||
using var service = new Demo2GameService();
|
||||
service.StartNewGame(17);
|
||||
var command = new Demo2BuildCommand
|
||||
{
|
||||
Sequence = 1,
|
||||
IdempotencyToken = "test-place",
|
||||
PlayerId = "test",
|
||||
Kind = Demo2MachineKind.Conveyor,
|
||||
X = 1,
|
||||
Y = 1
|
||||
};
|
||||
|
||||
var accepted = service.SubmitBuild(command);
|
||||
var duplicate = service.SubmitBuild(command);
|
||||
var removed = service.BuildLocal(Demo2MachineKind.Conveyor, 1, 1, remove: true);
|
||||
|
||||
Assert.That(accepted.Accepted, Is.True);
|
||||
Assert.That(duplicate.Accepted && duplicate.Duplicate, Is.True);
|
||||
Assert.That(removed.Accepted, Is.True);
|
||||
Assert.That(removed.Refund, Is.EqualTo(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultipleTurrets_LoadWithoutSingletonExceptions()
|
||||
{
|
||||
using var world = new Demo2EcsWorld(31);
|
||||
var manager = world.EntityManager;
|
||||
var state = manager.GetComponentData<Demo2SimulationState>(world.Singleton);
|
||||
var turret = Demo2EcsFactory.CreateMachine(manager, ++state.NextStableId, Demo2MachineKind.Turret, 22, 10, 0, 0);
|
||||
manager.AddComponentData(turret, new Demo2TurretAmmo());
|
||||
Demo2EcsFactory.CreateMaterial(manager, ++state.NextStableId, Demo2MaterialKind.Ammo, 0, 19, 0, 9);
|
||||
manager.SetComponentData(world.Singleton, state);
|
||||
world.SetPhase(Demo2Phase.Production);
|
||||
|
||||
world.Tick();
|
||||
|
||||
var query = manager.CreateEntityQuery(typeof(Demo2TurretAmmo));
|
||||
using var ammo = query.ToComponentDataArray<Demo2TurretAmmo>(Allocator.Temp);
|
||||
long total = 0;
|
||||
for (var i = 0; i < ammo.Length; i++) total += ammo[i].Value;
|
||||
Assert.That(total, Is.EqualTo(9));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Category("Demo2Performance")]
|
||||
public void StressTick_HandlesTenThousandItemsAndOneThousandMachines()
|
||||
{
|
||||
using var world = new Demo2EcsWorld(77);
|
||||
var manager = world.EntityManager;
|
||||
var state = manager.GetComponentData<Demo2SimulationState>(world.Singleton);
|
||||
for (var i = 0; i < 1_000; i++)
|
||||
Demo2EcsFactory.CreateMachine(manager, ++state.NextStableId, Demo2MachineKind.Conveyor, i % Demo2Protocol.GridWidth, (i / Demo2Protocol.GridWidth) % 10, 0, 0);
|
||||
for (var i = 0; i < 10_000; i++)
|
||||
Demo2EcsFactory.CreateMaterial(manager, ++state.NextStableId, Demo2MaterialKind.IronOre, i % Demo2Protocol.GridWidth, 19, 0, 1);
|
||||
manager.SetComponentData(world.Singleton, state);
|
||||
world.SetPhase(Demo2Phase.Production);
|
||||
var before = System.GC.GetAllocatedBytesForCurrentThread();
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
world.Tick();
|
||||
|
||||
stopwatch.Stop();
|
||||
var allocated = System.GC.GetAllocatedBytesForCurrentThread() - before;
|
||||
using var entities = manager.GetAllEntities(Allocator.Temp);
|
||||
UnityEngine.Debug.Log($"[Demo2 Benchmark] entities={entities.Length} tickMs={stopwatch.Elapsed.TotalMilliseconds:0.00} managedBytes={allocated}");
|
||||
Assert.That(entities.Length, Is.GreaterThanOrEqualTo(11_000));
|
||||
Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(10_000));
|
||||
Assert.That(allocated, Is.LessThanOrEqualTo(4096));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fee46ad8185d234eba8c1cf30f4aafa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user