From fbe1ccbbc3239c72a2d4e81e3186b1d2222bee2f Mon Sep 17 00:00:00 2001 From: George Date: Sun, 7 Jun 2026 13:34:35 +0100 Subject: [PATCH] Test and fix scheduler cycle budgets --- .../core/simulation/SimulationScheduler.java | 3 +- .../core/simulation/SchedulerBudgetTest.java | 86 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/livingworld/core/simulation/SchedulerBudgetTest.java diff --git a/src/main/java/com/livingworld/core/simulation/SimulationScheduler.java b/src/main/java/com/livingworld/core/simulation/SimulationScheduler.java index 866ddfa..c996397 100644 --- a/src/main/java/com/livingworld/core/simulation/SimulationScheduler.java +++ b/src/main/java/com/livingworld/core/simulation/SimulationScheduler.java @@ -37,7 +37,8 @@ public final class SimulationScheduler { } public boolean shouldRunSimulationCycle() { - return minecraftTickCounter % config.getSimulationIntervalTicks() == 0; + return minecraftTickCounter > 0 + && minecraftTickCounter % config.getSimulationIntervalTicks() == 0; } public void queueRegion(RegionUpdateJob job) { diff --git a/src/test/java/com/livingworld/core/simulation/SchedulerBudgetTest.java b/src/test/java/com/livingworld/core/simulation/SchedulerBudgetTest.java new file mode 100644 index 0000000..08c70b0 --- /dev/null +++ b/src/test/java/com/livingworld/core/simulation/SchedulerBudgetTest.java @@ -0,0 +1,86 @@ +package com.livingworld.core.simulation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.livingworld.config.SimulationConfig; +import com.livingworld.regions.RegionCoordinate; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; + +class SchedulerBudgetTest { + + @Test + void oneCycleReturnsAtMostConfiguredRegionBudget() { + SimulationScheduler scheduler = scheduler(50, 100); + for (int index = 0; index < 500; index++) { + scheduler.queueRegion(job(index, index % 10)); + } + + List jobs = scheduler.pollJobsForCycle(); + + assertEquals(50, jobs.size()); + assertEquals(450, scheduler.getQueuedJobCount()); + } + + @Test + void higherPriorityJobsAreReturnedFirst() { + SimulationScheduler scheduler = scheduler(3, 100); + scheduler.queueRegion(job(0, 1)); + scheduler.queueRegion(job(1, 20)); + scheduler.queueRegion(job(2, 5)); + scheduler.queueRegion(job(3, 10)); + + assertEquals( + List.of(20, 10, 5), + scheduler.pollJobsForCycle().stream() + .map(RegionUpdateJob::priority) + .toList()); + } + + @Test + void jobsOutsideCurrentBudgetRemainQueued() { + SimulationScheduler scheduler = scheduler(2, 100); + scheduler.queueRegion(job(0, 3)); + scheduler.queueRegion(job(1, 2)); + scheduler.queueRegion(job(2, 1)); + + scheduler.pollJobsForCycle(); + + assertEquals(1, scheduler.getQueuedJobCount()); + assertEquals(1, scheduler.pollJobsForCycle().getFirst().priority()); + } + + @Test + void simulationIntervalStartsAfterTheConfiguredNumberOfTicks() { + SimulationScheduler scheduler = scheduler(50, 3); + + assertFalse(scheduler.shouldRunSimulationCycle()); + scheduler.onMinecraftTick(); + assertFalse(scheduler.shouldRunSimulationCycle()); + scheduler.onMinecraftTick(); + assertFalse(scheduler.shouldRunSimulationCycle()); + scheduler.onMinecraftTick(); + assertTrue(scheduler.shouldRunSimulationCycle()); + scheduler.onMinecraftTick(); + assertFalse(scheduler.shouldRunSimulationCycle()); + } + + private static SimulationScheduler scheduler(int maxRegionsPerCycle, int intervalTicks) { + SimulationConfig config = new SimulationConfig(); + config.setMaxRegionsPerCycle(maxRegionsPerCycle); + config.setSimulationIntervalTicks(intervalTicks); + return new SimulationScheduler(config); + } + + private static RegionUpdateJob job(int coordinateX, int priority) { + return new RegionUpdateJob( + new RegionCoordinate("minecraft:overworld", coordinateX, 0), + priority, + 0, + Set.of(), + UpdateReason.NORMAL_ROLLING_UPDATE); + } +}