From 1f8751054ceee612728f3731fa8e2f470d47cf8a Mon Sep 17 00:00:00 2001 From: Timmeey86 Date: Wed, 28 Nov 2018 11:04:08 +0100 Subject: [PATCH] Tested/fixed delta calculations Tests now use a fixed delta of 0.5 to make sure calculations work with deltas different from 1.0 --- .../blocks/power/ItemLiquidGenerator.java | 2 +- .../world/blocks/power/PowerGraph.java | 2 +- .../test/java/power/FakeThreadHandler.java | 3 +- .../java/power/ItemLiquidGeneratorTests.java | 13 ++-- .../src/test/java/power/PowerTestFixture.java | 7 +++ tests/src/test/java/power/PowerTests.java | 59 +++++++++++-------- 6 files changed, 53 insertions(+), 33 deletions(-) diff --git a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java index 83d433b8c6..c3e19143e2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -54,7 +54,7 @@ public abstract class ItemLiquidGenerator extends ItemGenerator{ if(liquid != null && entity.liquids.get(liquid) >= 0.001f && entity.cons.valid()){ float baseLiquidEfficiency = getLiquidEfficiency(liquid) * this.liquidPowerMultiplier; float maximumPossible = maxLiquidGenerate * calculationDelta; - float used = Math.min(entity.liquids.get(liquid), maximumPossible); + float used = Math.min(entity.liquids.get(liquid) * calculationDelta, maximumPossible); entity.liquids.remove(liquid, used); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerGraph.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerGraph.java index 8439d79c11..e9e693755a 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerGraph.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerGraph.java @@ -121,7 +121,7 @@ public class PowerGraph{ if(consumePower.isBuffered){ // Add a percentage of the requested amount, but limit it to the mission amount. // TODO This can maybe be calculated without converting to absolute values first - float maximumRate = consumePower.requestedPower(consumer.block(), consumer.entity()) * coverage; + float maximumRate = consumePower.requestedPower(consumer.block(), consumer.entity()) * coverage * consumer.entity.delta(); float missingAmount = consumePower.powerCapacity * (1 - consumer.entity.power.satisfaction); consumer.entity.power.satisfaction += Math.min(missingAmount, maximumRate) / consumePower.powerCapacity; }else{ diff --git a/tests/src/test/java/power/FakeThreadHandler.java b/tests/src/test/java/power/FakeThreadHandler.java index 9b2676c49b..9767268df3 100644 --- a/tests/src/test/java/power/FakeThreadHandler.java +++ b/tests/src/test/java/power/FakeThreadHandler.java @@ -6,11 +6,12 @@ import io.anuke.ucore.core.Timers; /** Fake thread handler which produces a new frame each time getFrameID is called and always provides a delta of 1. */ public class FakeThreadHandler extends ThreadHandler{ private int fakeFrameId = 0; + public static final float fakeDelta = 0.5f; FakeThreadHandler(){ super(); - Timers.setDeltaProvider(() -> 1.0f); + Timers.setDeltaProvider(() -> fakeDelta); } @Override public long getFrameID(){ diff --git a/tests/src/test/java/power/ItemLiquidGeneratorTests.java b/tests/src/test/java/power/ItemLiquidGeneratorTests.java index f8685709c7..471b59bdf6 100644 --- a/tests/src/test/java/power/ItemLiquidGeneratorTests.java +++ b/tests/src/test/java/power/ItemLiquidGeneratorTests.java @@ -9,10 +9,8 @@ import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.power.BurnerGenerator; import io.anuke.mindustry.world.blocks.power.ItemGenerator; import io.anuke.mindustry.world.blocks.power.ItemLiquidGenerator; -import io.anuke.mindustry.world.blocks.power.PowerGenerator; import org.junit.jupiter.api.*; -import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.DynamicTest.dynamicTest; @@ -20,6 +18,10 @@ import static org.junit.jupiter.api.DynamicTest.dynamicTest; /** * This class tests ItemLiquidGenerators. Currently, testing is only performed on the BurnerGenerator subclass, * which means only power calculations based on flammability are tested. + * All tests are run with a fixed delta of 0.5 so delta considerations can be tested as well. + * Additionally, each PowerGraph::update() call will have its own thread frame, i.e. the method will never be called twice within the same frame. + * Both of these constraints are handled by FakeThreadHandler within PowerTestFixture. + * Any power amount (produced, consumed, buffered) should be affected by FakeThreadHandler.fakeDelta but satisfaction should not! */ public class ItemLiquidGeneratorTests extends PowerTestFixture{ @@ -58,8 +60,11 @@ public class ItemLiquidGeneratorTests extends PowerTestFixture{ } void test_liquidConsumption(Liquid liquid, float availableLiquidAmount, String parameterDescription){ - final float expectedEfficiency = Math.min(1.0f, availableLiquidAmount / maximumLiquidUsage) * fakeLiquidPowerMultiplier * liquid.flammability; - final float expectedRemainingLiquidAmount = liquid.flammability > 0f ? Math.max(0.0f, availableLiquidAmount - maximumLiquidUsage) : availableLiquidAmount; + final float baseEfficiency = fakeLiquidPowerMultiplier * liquid.flammability; + final float expectedEfficiency = Math.min(1.0f, availableLiquidAmount / maximumLiquidUsage) * baseEfficiency; + final float expectedConsumptionPerTick = Math.min(maximumLiquidUsage, availableLiquidAmount); + final float expectedRemainingLiquidAmount = Math.max(0.0f, availableLiquidAmount - expectedConsumptionPerTick * FakeThreadHandler.fakeDelta); + assertTrue(generator.acceptLiquid(tile, null, liquid, availableLiquidAmount), parameterDescription + ": Liquids which will be declined by the generator don't need to be tested - The code won't be called for those cases."); // Reset liquids since BeforeEach will not be called between dynamic tests diff --git a/tests/src/test/java/power/PowerTestFixture.java b/tests/src/test/java/power/PowerTestFixture.java index 1588f54188..1e46d1e287 100644 --- a/tests/src/test/java/power/PowerTestFixture.java +++ b/tests/src/test/java/power/PowerTestFixture.java @@ -1,5 +1,6 @@ package power; +import com.badlogic.gdx.math.MathUtils; import io.anuke.mindustry.Vars; import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.core.ContentLoader; @@ -24,9 +25,15 @@ import static io.anuke.mindustry.Vars.world; /** This class provides objects commonly used by power related unit tests. * For now, this is a helper with static methods, but this might change. + * + * Note: All tests which subclass this will run with a fixed delta of 0.5! * */ public class PowerTestFixture{ + public static final float smallRoundingTolerance = MathUtils.FLOAT_ROUNDING_ERROR; + public static final float mediumRoundingTolerance = MathUtils.FLOAT_ROUNDING_ERROR * 10; + public static final float highRoundingTolerance = MathUtils.FLOAT_ROUNDING_ERROR * 100; + @BeforeAll static void initializeDependencies(){ Vars.content = new ContentLoader(); diff --git a/tests/src/test/java/power/PowerTests.java b/tests/src/test/java/power/PowerTests.java index 48b5cf8c6f..36d4e258dc 100644 --- a/tests/src/test/java/power/PowerTests.java +++ b/tests/src/test/java/power/PowerTests.java @@ -1,20 +1,23 @@ package power; import com.badlogic.gdx.math.MathUtils; -import io.anuke.mindustry.Vars; -import io.anuke.mindustry.core.ContentLoader; import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.power.PowerGraph; import io.anuke.mindustry.world.consumers.ConsumePower; import org.junit.jupiter.api.*; -import static org.junit.jupiter.api.Assertions.assertAll; 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 static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; +/** + * Tests code related to the power system in general, but not specific blocks. + * All tests are run with a fixed delta of 0.5 so delta considerations can be tested as well. + * Additionally, each PowerGraph::update() call will have its own thread frame, i.e. the method will never be called twice within the same frame. + * Both of these constraints are handled by FakeThreadHandler within PowerTestFixture. + * Any power amount (produced, consumed, buffered) should be affected by FakeThreadHandler.fakeDelta but satisfaction should not! + */ public class PowerTests extends PowerTestFixture{ @BeforeEach @@ -33,6 +36,7 @@ public class PowerTests extends PowerTestFixture{ return new DynamicTest[]{ // Note: Unfortunately, the display names are not yet output through gradle. See https://github.com/gradle/gradle/issues/5975 // That's why we inject the description into the test method for now. + // Additional Note: If you don't see any labels in front of the values supplied as function parameters, use a better IDE like IntelliJ IDEA. dynamicTest("01", () -> test_directConsumptionCalculation(0.0f, 1.0f, 0.0f, "0.0 produced, 1.0 consumed (no power available)")), dynamicTest("02", () -> test_directConsumptionCalculation(0.0f, 0.0f, 0.0f, "0.0 produced, 0.0 consumed (no power anywhere)")), dynamicTest("03", () -> test_directConsumptionCalculation(1.0f, 0.0f, 0.0f, "1.0 produced, 0.0 consumed (no power requested)")), @@ -50,8 +54,8 @@ public class PowerTests extends PowerTestFixture{ powerGraph.add(producerTile); powerGraph.add(directConsumerTile); - assumeTrue(MathUtils.isEqual(producedPower, powerGraph.getPowerProduced())); - assumeTrue(MathUtils.isEqual(requiredPower, powerGraph.getPowerNeeded())); + assertEquals(producedPower * FakeThreadHandler.fakeDelta, powerGraph.getPowerProduced(), MathUtils.FLOAT_ROUNDING_ERROR); + assertEquals(requiredPower * FakeThreadHandler.fakeDelta, powerGraph.getPowerNeeded(), MathUtils.FLOAT_ROUNDING_ERROR); // Update and check for the expected power satisfaction of the consumer powerGraph.update(); @@ -63,33 +67,35 @@ public class PowerTests extends PowerTestFixture{ DynamicTest[] testBufferedConsumption(){ return new DynamicTest[]{ // Note: powerPerTick may not be 0 in any of the test cases. This would equal a "ticksToFill" of infinite. + // Note: Due to a fixed delta of 0.5, only half of what is defined here will in fact be produced/consumed. Keep this in mind when defining expectedSatisfaction! dynamicTest("01", () -> test_bufferedConsumptionCalculation(0.0f, 0.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power anywhere")), dynamicTest("02", () -> test_bufferedConsumptionCalculation(0.0f, 1.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power provided")), dynamicTest("03", () -> test_bufferedConsumptionCalculation(1.0f, 0.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power requested")), - dynamicTest("04", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, "Empty Buffer, Stable Power, One tick to fill")), - dynamicTest("05", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.1f, 0.0f, 0.1f, "Empty Buffer, Stable Power, multiple ticks to fill")), - dynamicTest("06", () -> test_bufferedConsumptionCalculation(1.0f, 0.5f, 0.5f, 0.0f, 1.0f, "Empty Buffer, Power excess, one tick to fill")), - dynamicTest("07", () -> test_bufferedConsumptionCalculation(1.0f, 0.5f, 0.1f, 0.0f, 0.2f, "Empty Buffer, Power excess, multiple ticks to fill")), - dynamicTest("08", () -> test_bufferedConsumptionCalculation(0.5f, 1.0f, 1.0f, 0.0f, 0.5f, "Empty Buffer, Power shortage, one tick to fill")), - dynamicTest("09", () -> test_bufferedConsumptionCalculation(0.5f, 1.0f, 0.1f, 0.0f, 0.1f, "Empty Buffer, Power shortage, multiple ticks to fill")), - dynamicTest("10", () -> test_bufferedConsumptionCalculation(0.0f, 1.0f, 0.1f, 0.5f, 0.5f, "Unchanged buffer with no power produced")), - dynamicTest("11", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.1f, 1.0f, 1.0f, "Unchanged buffer when already full")), - dynamicTest("12", () -> test_bufferedConsumptionCalculation(0.2f, 1.0f, 0.5f, 0.5f, 0.7f, "Half buffer, power shortage")), - dynamicTest("13", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.5f, 0.7f, 1.0f, "Buffer does not get exceeded")), - dynamicTest("14", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.5f, 0.5f, 1.0f, "Half buffer, filled with excess")) + dynamicTest("04", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 1.0f, 0.0f, 0.5f, "Empty Buffer, Stable Power, One tick to fill")), + dynamicTest("05", () -> test_bufferedConsumptionCalculation(2.0f, 1.0f, 2.0f, 0.0f, 1.0f, "Empty Buffer, Stable Power, One delta to fill")), + dynamicTest("06", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.1f, 0.0f, 0.05f, "Empty Buffer, Stable Power, multiple ticks to fill")), + dynamicTest("07", () -> test_bufferedConsumptionCalculation(1.2f, 0.5f, 1.0f, 0.0f, 1.0f, "Empty Buffer, Power excess, one delta to fill")), + dynamicTest("08", () -> test_bufferedConsumptionCalculation(1.0f, 0.5f, 0.1f, 0.0f, 0.1f, "Empty Buffer, Power excess, multiple ticks to fill")), + dynamicTest("09", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 2.0f, 0.0f, 0.5f, "Empty Buffer, Power shortage, one delta to fill")), + dynamicTest("10", () -> test_bufferedConsumptionCalculation(0.5f, 1.0f, 0.1f, 0.0f, 0.05f, "Empty Buffer, Power shortage, multiple ticks to fill")), + dynamicTest("11", () -> test_bufferedConsumptionCalculation(0.0f, 1.0f, 0.1f, 0.5f, 0.5f, "Unchanged buffer with no power produced")), + dynamicTest("12", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.1f, 1.0f, 1.0f, "Unchanged buffer when already full")), + dynamicTest("13", () -> test_bufferedConsumptionCalculation(0.2f, 1.0f, 0.5f, 0.5f, 0.6f, "Half buffer, power shortage")), + dynamicTest("14", () -> test_bufferedConsumptionCalculation(1.0f, 1.0f, 0.5f, 0.9f, 1.0f, "Buffer does not get exceeded")), + dynamicTest("15", () -> test_bufferedConsumptionCalculation(2.0f, 1.0f, 1.0f, 0.5f, 1.0f, "Half buffer, filled with excess")) }; } - void test_bufferedConsumptionCalculation(float producedPower, float maxBuffer, float powerPerTick, float initialSatisfaction, float expectedSatisfaction, String parameterDescription){ + void test_bufferedConsumptionCalculation(float producedPower, float maxBuffer, float powerConsumedPerTick, float initialSatisfaction, float expectedSatisfaction, String parameterDescription){ Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower)); - Tile bufferedConsumerTile = createFakeTile(0, 1, createFakeBufferedConsumer(maxBuffer, maxBuffer > 0.0f ? maxBuffer/powerPerTick : 1.0f)); + Tile bufferedConsumerTile = createFakeTile(0, 1, createFakeBufferedConsumer(maxBuffer, maxBuffer > 0.0f ? maxBuffer/powerConsumedPerTick : 1.0f)); bufferedConsumerTile.entity.power.satisfaction = initialSatisfaction; PowerGraph powerGraph = new PowerGraph(); powerGraph.add(producerTile); powerGraph.add(bufferedConsumerTile); - assumeTrue(MathUtils.isEqual(producedPower, powerGraph.getPowerProduced())); - //assumeTrue(MathUtils.isEqual(Math.min(maxBuffer, powerPerTick), powerGraph.getPowerNeeded())); + assertEquals(producedPower * FakeThreadHandler.fakeDelta, powerGraph.getPowerProduced(), MathUtils.FLOAT_ROUNDING_ERROR, parameterDescription + ": Produced power did not match"); + assertEquals(Math.min(maxBuffer, powerConsumedPerTick * FakeThreadHandler.fakeDelta), powerGraph.getPowerNeeded(), MathUtils.FLOAT_ROUNDING_ERROR, parameterDescription + ": ConsumedPower did not match"); // Update and check for the expected power satisfaction of the consumer powerGraph.update(); @@ -102,14 +108,15 @@ public class PowerTests extends PowerTestFixture{ @TestFactory DynamicTest[] testDirectConsumptionWithBattery(){ return new DynamicTest[]{ - dynamicTest("01", () -> test_directConsumptionWithBattery(10.0f, 0.0f, 0.0f, 10.0f, 0.0f, "Empty battery, no consumer")), - dynamicTest("02", () -> test_directConsumptionWithBattery(10.0f, 0.0f, 90.0f, 100.0f, 0.0f, "Battery full after update, no consumer")), + // Note: expectedBatteryCapacity is currently adjusted to a delta of 0.5! (FakeThreadHandler sets it to that) + dynamicTest("01", () -> test_directConsumptionWithBattery(10.0f, 0.0f, 0.0f, 5.0f, 0.0f, "Empty battery, no consumer")), + dynamicTest("02", () -> test_directConsumptionWithBattery(10.0f, 0.0f, 94.999f, 99.999f, 0.0f, "Battery almost full after update, no consumer")), dynamicTest("03", () -> test_directConsumptionWithBattery(10.0f, 0.0f, 100.0f, 100.0f, 0.0f, "Full battery, no consumer")), dynamicTest("04", () -> test_directConsumptionWithBattery(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, "No producer, no consumer, empty battery")), dynamicTest("05", () -> test_directConsumptionWithBattery(0.0f, 0.0f, 100.0f, 100.0f, 0.0f, "No producer, no consumer, full battery")), dynamicTest("06", () -> test_directConsumptionWithBattery(0.0f, 10.0f, 0.0f, 0.0f, 0.0f, "No producer, empty battery")), - dynamicTest("07", () -> test_directConsumptionWithBattery(0.0f, 10.0f, 100.0f, 90.0f, 1.0f, "No producer, full battery")), - dynamicTest("08", () -> test_directConsumptionWithBattery(0.0f, 10.0f, 5.0f, 0.0f, 0.5f, "No producer, low battery")), + dynamicTest("07", () -> test_directConsumptionWithBattery(0.0f, 10.0f, 100.0f, 95.0f, 1.0f, "No producer, full battery")), + dynamicTest("08", () -> test_directConsumptionWithBattery(0.0f, 10.0f, 2.5f, 0.0f, 0.5f, "No producer, low battery")), dynamicTest("09", () -> test_directConsumptionWithBattery(5.0f, 10.0f, 5.0f, 0.0f, 1.0f, "Producer + Battery = Consumed")), }; } @@ -132,7 +139,7 @@ public class PowerTests extends PowerTestFixture{ powerGraph.add(batteryTile); powerGraph.update(); - assertEquals(expectedBatteryCapacity, batteryTile.entity.power.satisfaction * maxCapacity, MathUtils.FLOAT_ROUNDING_ERROR, parameterDescription + ": Expected battery capacity did not match"); + assertEquals(expectedBatteryCapacity / maxCapacity, batteryTile.entity.power.satisfaction, MathUtils.FLOAT_ROUNDING_ERROR, parameterDescription + ": Expected battery satisfaction did not match"); if(directConsumerTile != null){ assertEquals(expectedSatisfaction, directConsumerTile.entity.power.satisfaction, MathUtils.FLOAT_ROUNDING_ERROR, parameterDescription + ": Satisfaction of direct consumer did not match"); }