Tested/fixed delta calculations

Tests now use a fixed delta of 0.5 to make sure calculations work with deltas different from 1.0
This commit is contained in:
Timmeey86 2018-11-28 11:04:08 +01:00
parent 739219c57b
commit 1f8751054c
6 changed files with 53 additions and 33 deletions

View File

@ -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);

View File

@ -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{

View File

@ -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(){

View File

@ -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

View File

@ -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();

View File

@ -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");
}