mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-04-24 22:41:39 -07:00
commit
b8e6e5df61
58 changed files with 1158 additions and 549 deletions
55
tests/src/test/java/power/DirectConsumerTests.java
Normal file
55
tests/src/test/java/power/DirectConsumerTests.java
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package power;
|
||||
|
||||
import io.anuke.mindustry.content.Items;
|
||||
import io.anuke.mindustry.content.UnitTypes;
|
||||
import io.anuke.mindustry.type.ItemStack;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.power.PowerGenerator;
|
||||
import io.anuke.mindustry.world.blocks.power.PowerGraph;
|
||||
import io.anuke.mindustry.world.blocks.units.UnitFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/** Tests for direct power consumers. */
|
||||
public class DirectConsumerTests extends PowerTestFixture{
|
||||
|
||||
@Test
|
||||
void noPowerRequestedWithNoItems(){
|
||||
testUnitFactory(0, 0, 0.08f, 0.08f, 0.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noPowerRequestedWithInsufficientItems(){
|
||||
testUnitFactory(30, 0, 0.08f, 0.08f, 0.0f);
|
||||
testUnitFactory(0, 30, 0.08f, 0.08f, 0.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void powerRequestedWithSufficientItems(){
|
||||
testUnitFactory(30, 30, 0.08f, 0.08f, 1.0f);
|
||||
}
|
||||
|
||||
void testUnitFactory(int siliconAmount, int leadAmount, float producedPower, float requestedPower, float expectedSatisfaction){
|
||||
Tile consumerTile = createFakeTile(0, 0, new UnitFactory("fakefactory"){{
|
||||
type = UnitTypes.spirit;
|
||||
produceTime = 60;
|
||||
consumes.power(requestedPower);
|
||||
consumes.items(new ItemStack(Items.silicon, 30), new ItemStack(Items.lead, 30));
|
||||
}});
|
||||
consumerTile.entity.items.add(Items.silicon, siliconAmount);
|
||||
consumerTile.entity.items.add(Items.lead, leadAmount);
|
||||
|
||||
Tile producerTile = createFakeTile(2, 0, createFakeProducerBlock(producedPower));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 0.5f; // 100%
|
||||
|
||||
PowerGraph graph = new PowerGraph();
|
||||
graph.add(producerTile);
|
||||
graph.add(consumerTile);
|
||||
|
||||
consumerTile.entity.update();
|
||||
graph.update();
|
||||
|
||||
assertEquals(expectedSatisfaction, consumerTile.entity.power.satisfaction);
|
||||
}
|
||||
}
|
||||
175
tests/src/test/java/power/ItemLiquidGeneratorTests.java
Normal file
175
tests/src/test/java/power/ItemLiquidGeneratorTests.java
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
package power;
|
||||
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.content.Items;
|
||||
import io.anuke.mindustry.content.Liquids;
|
||||
import io.anuke.mindustry.type.Item;
|
||||
import io.anuke.mindustry.type.Liquid;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.power.ItemLiquidGenerator;
|
||||
import org.junit.jupiter.api.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
/**
|
||||
* This class tests generators which can process items, liquids or both.
|
||||
* 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 expected power amount (produced, consumed, buffered) should be affected by FakeThreadHandler.fakeDelta but satisfaction should not!
|
||||
*/
|
||||
public class ItemLiquidGeneratorTests extends PowerTestFixture{
|
||||
|
||||
private ItemLiquidGenerator generator;
|
||||
private Tile tile;
|
||||
private ItemLiquidGenerator.ItemLiquidGeneratorEntity entity;
|
||||
private final float fakeItemDuration = 60f; // 60 ticks
|
||||
private final float maximumLiquidUsage = 0.5f;
|
||||
|
||||
public void createGenerator(ItemLiquidGenerator.InputType inputType){
|
||||
generator = new ItemLiquidGenerator(inputType, "fakegen"){
|
||||
{
|
||||
powerProduction = 0.1f;
|
||||
itemDuration = 60f;
|
||||
itemDuration = fakeItemDuration;
|
||||
maxLiquidGenerate = maximumLiquidUsage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getItemEfficiency(Item item){
|
||||
return item.flammability;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getLiquidEfficiency(Liquid liquid){
|
||||
return liquid.flammability;
|
||||
}
|
||||
};
|
||||
|
||||
tile = createFakeTile(0, 0, generator);
|
||||
entity = tile.entity();
|
||||
}
|
||||
|
||||
/** Tests the consumption and efficiency when being supplied with liquids. */
|
||||
@TestFactory
|
||||
DynamicTest[] generatorWorksProperlyWithLiquidInput(){
|
||||
|
||||
// Execute all tests for the case where only liquids are accepted and for the case where liquids and items are accepted (but supply only liquids)
|
||||
ItemLiquidGenerator.InputType[] inputTypesToBeTested = new ItemLiquidGenerator.InputType[]{
|
||||
ItemLiquidGenerator.InputType.LiquidsOnly,
|
||||
ItemLiquidGenerator.InputType.LiquidsAndItems
|
||||
};
|
||||
|
||||
ArrayList<DynamicTest> tests = new ArrayList<>();
|
||||
for(ItemLiquidGenerator.InputType inputType : inputTypesToBeTested){
|
||||
tests.add(dynamicTest("01", () -> simulateLiquidConsumption(inputType, Liquids.oil, 0.0f, "No liquids provided")));
|
||||
tests.add(dynamicTest("02", () -> simulateLiquidConsumption(inputType, Liquids.oil, maximumLiquidUsage / 4.0f, "Low oil provided")));
|
||||
tests.add(dynamicTest("03", () -> simulateLiquidConsumption(inputType, Liquids.oil, maximumLiquidUsage * 1.0f, "Sufficient oil provided")));
|
||||
tests.add(dynamicTest("04", () -> simulateLiquidConsumption(inputType, Liquids.oil, maximumLiquidUsage * 2.0f, "Excess oil provided")));
|
||||
// Note: The generator will decline any other liquid since it's not flammable
|
||||
}
|
||||
DynamicTest[] testArray = new DynamicTest[tests.size()];
|
||||
testArray = tests.toArray(testArray);
|
||||
return testArray;
|
||||
}
|
||||
|
||||
void simulateLiquidConsumption(ItemLiquidGenerator.InputType inputType, Liquid liquid, float availableLiquidAmount, String parameterDescription){
|
||||
final float baseEfficiency = 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 * Time.delta());
|
||||
|
||||
createGenerator(inputType);
|
||||
assertTrue(generator.acceptLiquid(tile, null, liquid, availableLiquidAmount), inputType + " | " + parameterDescription + ": Liquids which will be declined by the generator don't need to be tested - The code won't be called for those cases.");
|
||||
|
||||
entity.liquids.add(liquid, availableLiquidAmount);
|
||||
entity.cons.update(tile.entity);
|
||||
assertTrue(entity.cons.valid());
|
||||
|
||||
// Perform an update on the generator once - This should use up any resource up to the maximum liquid usage
|
||||
generator.update(tile);
|
||||
|
||||
assertEquals(expectedRemainingLiquidAmount, entity.liquids.get(liquid), inputType + " | " + parameterDescription + ": Remaining liquid amount mismatch.");
|
||||
assertEquals(expectedEfficiency, entity.productionEfficiency, inputType + " | " + parameterDescription + ": Efficiency mismatch.");
|
||||
}
|
||||
|
||||
/** Tests the consumption and efficiency when being supplied with items. */
|
||||
@TestFactory
|
||||
DynamicTest[] generatorWorksProperlyWithItemInput(){
|
||||
|
||||
// Execute all tests for the case where only items are accepted and for the case where liquids and items are accepted (but supply only items)
|
||||
ItemLiquidGenerator.InputType[] inputTypesToBeTested = new ItemLiquidGenerator.InputType[]{
|
||||
ItemLiquidGenerator.InputType.ItemsOnly,
|
||||
ItemLiquidGenerator.InputType.LiquidsAndItems
|
||||
};
|
||||
|
||||
ArrayList<DynamicTest> tests = new ArrayList<>();
|
||||
for(ItemLiquidGenerator.InputType inputType : inputTypesToBeTested){
|
||||
tests.add(dynamicTest("01", () -> simulateItemConsumption(inputType, Items.coal, 0, "No items provided")));
|
||||
tests.add(dynamicTest("02", () -> simulateItemConsumption(inputType, Items.coal, 1, "Sufficient coal provided")));
|
||||
tests.add(dynamicTest("03", () -> simulateItemConsumption(inputType, Items.coal, 10, "Excess coal provided")));
|
||||
tests.add(dynamicTest("04", () -> simulateItemConsumption(inputType, Items.blastCompound, 1, "Blast compound provided")));
|
||||
//dynamicTest("03", () -> simulateItemConsumption(inputType, Items.plastanium, 1, "Plastanium provided")), // Not accepted by generator due to low flammability
|
||||
tests.add(dynamicTest("05", () -> simulateItemConsumption(inputType, Items.biomatter, 1, "Biomatter provided")));
|
||||
tests.add(dynamicTest("06", () -> simulateItemConsumption(inputType, Items.pyratite, 1, "Pyratite provided")));
|
||||
}
|
||||
DynamicTest[] testArray = new DynamicTest[tests.size()];
|
||||
testArray = tests.toArray(testArray);
|
||||
return testArray;
|
||||
}
|
||||
|
||||
void simulateItemConsumption(ItemLiquidGenerator.InputType inputType, Item item, int amount, String parameterDescription){
|
||||
final float expectedEfficiency = Math.min(1.0f, amount > 0 ? item.flammability : 0f);
|
||||
final float expectedRemainingItemAmount = Math.max(0, amount - 1);
|
||||
|
||||
createGenerator(inputType);
|
||||
assertTrue(generator.acceptItem(item, tile, null), inputType + " | " + parameterDescription + ": Items which will be declined by the generator don't need to be tested - The code won't be called for those cases.");
|
||||
|
||||
if(amount > 0){
|
||||
entity.items.add(item, amount);
|
||||
}
|
||||
entity.cons.update(tile.entity);
|
||||
assertTrue(entity.cons.valid());
|
||||
|
||||
// Perform an update on the generator once - This should use up one or zero items - dependent on if the item is accepted and available or not.
|
||||
generator.update(tile);
|
||||
|
||||
assertEquals(expectedRemainingItemAmount, entity.items.get(item), inputType + " | " + parameterDescription + ": Remaining item amount mismatch.");
|
||||
assertEquals(expectedEfficiency, entity.productionEfficiency, inputType + " | " + parameterDescription + ": Efficiency mismatch.");
|
||||
}
|
||||
|
||||
/** Makes sure the efficiency stays equal during the item duration. */
|
||||
@Test
|
||||
void efficiencyRemainsConstantWithinItemDuration_ItemsOnly(){
|
||||
testItemDuration(ItemLiquidGenerator.InputType.ItemsOnly);
|
||||
}
|
||||
|
||||
/** Makes sure the efficiency stays equal during the item duration. */
|
||||
@Test
|
||||
void efficiencyRemainsConstantWithinItemDuration_ItemsAndLiquids(){
|
||||
testItemDuration(ItemLiquidGenerator.InputType.LiquidsAndItems);
|
||||
}
|
||||
|
||||
void testItemDuration(ItemLiquidGenerator.InputType inputType){
|
||||
createGenerator(inputType);
|
||||
|
||||
// Burn a single coal and test for the duration
|
||||
entity.items.add(Items.coal, 1);
|
||||
entity.cons.update(tile.entity);
|
||||
generator.update(tile);
|
||||
|
||||
float expectedEfficiency = entity.productionEfficiency;
|
||||
|
||||
float currentDuration = 0.0f;
|
||||
while((currentDuration += Time.delta()) <= fakeItemDuration){
|
||||
generator.update(tile);
|
||||
assertEquals(expectedEfficiency, entity.productionEfficiency, "Duration: " + String.valueOf(currentDuration));
|
||||
}
|
||||
generator.update(tile);
|
||||
assertEquals(0.0f, entity.productionEfficiency, "Duration: " + String.valueOf(currentDuration));
|
||||
}
|
||||
}
|
||||
105
tests/src/test/java/power/PowerTestFixture.java
Normal file
105
tests/src/test/java/power/PowerTestFixture.java
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package power;
|
||||
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.Vars;
|
||||
import io.anuke.mindustry.content.blocks.Blocks;
|
||||
import io.anuke.mindustry.core.ContentLoader;
|
||||
import io.anuke.mindustry.world.Block;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.PowerBlock;
|
||||
import io.anuke.mindustry.world.blocks.power.Battery;
|
||||
import io.anuke.mindustry.world.blocks.power.PowerGenerator;
|
||||
import io.anuke.mindustry.world.modules.ConsumeModule;
|
||||
import io.anuke.mindustry.world.modules.ItemModule;
|
||||
import io.anuke.mindustry.world.modules.LiquidModule;
|
||||
import io.anuke.mindustry.world.modules.PowerModule;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/** 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 = Mathf.FLOAT_ROUNDING_ERROR;
|
||||
public static final float mediumRoundingTolerance = Mathf.FLOAT_ROUNDING_ERROR * 10;
|
||||
public static final float highRoundingTolerance = Mathf.FLOAT_ROUNDING_ERROR * 100;
|
||||
|
||||
@BeforeAll
|
||||
static void initializeDependencies(){
|
||||
Vars.content = new ContentLoader();
|
||||
Vars.content.load();
|
||||
Time.setDeltaProvider(() -> 0.5f);
|
||||
}
|
||||
|
||||
protected static PowerGenerator createFakeProducerBlock(float producedPower){
|
||||
// Multiply produced power by 2 since production efficiency is defined to be 0.5 = 100%
|
||||
return new PowerGenerator("fakegen"){{
|
||||
powerProduction = producedPower * 2.0f;
|
||||
}};
|
||||
}
|
||||
|
||||
protected static Battery createFakeBattery(float capacity, float ticksToFill){
|
||||
return new Battery("fakebattery"){{
|
||||
consumes.powerBuffered(capacity, ticksToFill);
|
||||
}};
|
||||
}
|
||||
|
||||
protected static Block createFakeDirectConsumer(float powerPerTick, float minimumSatisfaction){
|
||||
return new PowerBlock("fakedirectconsumer"){{
|
||||
consumes.power(powerPerTick, minimumSatisfaction);
|
||||
}};
|
||||
}
|
||||
|
||||
protected static Block createFakeBufferedConsumer(float capacity, float ticksToFill){
|
||||
return new PowerBlock("fakebufferedconsumer"){{
|
||||
consumes.powerBuffered(capacity, ticksToFill);
|
||||
}};
|
||||
}
|
||||
/**
|
||||
* Creates a fake tile on the given location using the given block.
|
||||
* @param x The X coordinate.
|
||||
* @param y The y coordinate.
|
||||
* @param block The block on the tile.
|
||||
* @return The created tile or null in case of exceptions.
|
||||
*/
|
||||
protected static Tile createFakeTile(int x, int y, Block block){
|
||||
try{
|
||||
Tile tile = new Tile(x, y);
|
||||
|
||||
// Using the Tile(int, int, byte, byte) constructor would require us to register any fake block or tile we create
|
||||
// Since this part shall not be part of the test and would require more work anyway, we manually set the block and floor
|
||||
// through reflections and then simulate part of what the changed() method does.
|
||||
|
||||
Field field = Tile.class.getDeclaredField("wall");
|
||||
field.setAccessible(true);
|
||||
field.set(tile, block);
|
||||
|
||||
field = Tile.class.getDeclaredField("floor");
|
||||
field.setAccessible(true);
|
||||
field.set(tile, Blocks.sand);
|
||||
|
||||
// Simulate the "changed" method. Calling it through reflections would require half the game to be initialized.
|
||||
tile.entity = block.newEntity().init(tile, false);
|
||||
tile.entity.cons = new ConsumeModule();
|
||||
if(block.hasItems) tile.entity.items = new ItemModule();
|
||||
if(block.hasLiquids) tile.entity.liquids = new LiquidModule();
|
||||
if(block.hasPower){
|
||||
tile.entity.power = new PowerModule();
|
||||
tile.entity.power.graph.add(tile);
|
||||
}
|
||||
|
||||
// Assign incredibly high health so the block does not get destroyed on e.g. burning Blast Compound
|
||||
block.health = 100000;
|
||||
tile.entity.health = 100000.0f;
|
||||
|
||||
return tile;
|
||||
}catch(Exception ex){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
183
tests/src/test/java/power/PowerTests.java
Normal file
183
tests/src/test/java/power/PowerTests.java
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
package power;
|
||||
|
||||
import io.anuke.arc.math.Mathf;
|
||||
import io.anuke.arc.util.Time;
|
||||
import io.anuke.mindustry.world.Tile;
|
||||
import io.anuke.mindustry.world.blocks.power.PowerGenerator;
|
||||
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.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
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 Time.delta() but satisfaction should not!
|
||||
*/
|
||||
public class PowerTests extends PowerTestFixture{
|
||||
|
||||
@BeforeEach
|
||||
void initTest(){
|
||||
}
|
||||
|
||||
@Nested
|
||||
class PowerGraphTests{
|
||||
|
||||
/** Tests the satisfaction of a single consumer after a single update of the power graph which contains a single producer.
|
||||
*
|
||||
* Assumption: When the consumer requests zero power, satisfaction does not change. Default is 0.0f.
|
||||
*/
|
||||
@TestFactory
|
||||
DynamicTest[] directConsumerSatisfactionIsAsExpected(){
|
||||
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", () -> simulateDirectConsumption(0.0f, 1.0f, 0.0f, "0.0 produced, 1.0 consumed (no power available)")),
|
||||
dynamicTest("02", () -> simulateDirectConsumption(0.0f, 0.0f, 0.0f, "0.0 produced, 0.0 consumed (no power anywhere)")),
|
||||
dynamicTest("03", () -> simulateDirectConsumption(1.0f, 0.0f, 0.0f, "1.0 produced, 0.0 consumed (no power requested)")),
|
||||
dynamicTest("04", () -> simulateDirectConsumption(1.0f, 1.0f, 1.0f, "1.0 produced, 1.0 consumed (stable consumption)")),
|
||||
dynamicTest("05", () -> simulateDirectConsumption(0.5f, 1.0f, 0.5f, "0.5 produced, 1.0 consumed (power shortage)")),
|
||||
dynamicTest("06", () -> simulateDirectConsumption(1.0f, 0.5f, 1.0f, "1.0 produced, 0.5 consumed (power excess)")),
|
||||
dynamicTest("07", () -> simulateDirectConsumption(0.09f, 0.09f - Mathf.FLOAT_ROUNDING_ERROR / 10.0f, 1.0f, "floating point inaccuracy (stable consumption)"))
|
||||
};
|
||||
}
|
||||
void simulateDirectConsumption(float producedPower, float requiredPower, float expectedSatisfaction, String parameterDescription){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 0.5f; // Currently, 0.5f = 100%
|
||||
Tile directConsumerTile = createFakeTile(0, 1, createFakeDirectConsumer(requiredPower, 0.6f));
|
||||
|
||||
PowerGraph powerGraph = new PowerGraph();
|
||||
powerGraph.add(producerTile);
|
||||
powerGraph.add(directConsumerTile);
|
||||
|
||||
assertEquals(producedPower * Time.delta(), powerGraph.getPowerProduced(), Mathf.FLOAT_ROUNDING_ERROR);
|
||||
assertEquals(requiredPower * Time.delta(), powerGraph.getPowerNeeded(), Mathf.FLOAT_ROUNDING_ERROR);
|
||||
|
||||
// Update and check for the expected power satisfaction of the consumer
|
||||
powerGraph.update();
|
||||
assertEquals(expectedSatisfaction, directConsumerTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Satisfaction of direct consumer did not match");
|
||||
}
|
||||
|
||||
/** Tests the satisfaction of a single buffered consumer after a single update of the power graph which contains a single producer. */
|
||||
@TestFactory
|
||||
DynamicTest[] bufferedConsumerSatisfactionIsAsExpected(){
|
||||
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", () -> simulateBufferedConsumption(0.0f, 0.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power anywhere")),
|
||||
dynamicTest("02", () -> simulateBufferedConsumption(0.0f, 1.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power provided")),
|
||||
dynamicTest("03", () -> simulateBufferedConsumption(1.0f, 0.0f, 0.1f, 0.0f, 0.0f, "Empty Buffer, No power requested")),
|
||||
dynamicTest("04", () -> simulateBufferedConsumption(1.0f, 1.0f, 1.0f, 0.0f, 0.5f, "Empty Buffer, Stable Power, One tick to fill")),
|
||||
dynamicTest("05", () -> simulateBufferedConsumption(2.0f, 1.0f, 2.0f, 0.0f, 1.0f, "Empty Buffer, Stable Power, One delta to fill")),
|
||||
dynamicTest("06", () -> simulateBufferedConsumption(1.0f, 1.0f, 0.1f, 0.0f, 0.05f, "Empty Buffer, Stable Power, multiple ticks to fill")),
|
||||
dynamicTest("07", () -> simulateBufferedConsumption(1.2f, 0.5f, 1.0f, 0.0f, 1.0f, "Empty Buffer, Power excess, one delta to fill")),
|
||||
dynamicTest("08", () -> simulateBufferedConsumption(1.0f, 0.5f, 0.1f, 0.0f, 0.1f, "Empty Buffer, Power excess, multiple ticks to fill")),
|
||||
dynamicTest("09", () -> simulateBufferedConsumption(1.0f, 1.0f, 2.0f, 0.0f, 0.5f, "Empty Buffer, Power shortage, one delta to fill")),
|
||||
dynamicTest("10", () -> simulateBufferedConsumption(0.5f, 1.0f, 0.1f, 0.0f, 0.05f, "Empty Buffer, Power shortage, multiple ticks to fill")),
|
||||
dynamicTest("11", () -> simulateBufferedConsumption(0.0f, 1.0f, 0.1f, 0.5f, 0.5f, "Unchanged buffer with no power produced")),
|
||||
dynamicTest("12", () -> simulateBufferedConsumption(1.0f, 1.0f, 0.1f, 1.0f, 1.0f, "Unchanged buffer when already full")),
|
||||
dynamicTest("13", () -> simulateBufferedConsumption(0.2f, 1.0f, 0.5f, 0.5f, 0.6f, "Half buffer, power shortage")),
|
||||
dynamicTest("14", () -> simulateBufferedConsumption(1.0f, 1.0f, 0.5f, 0.9f, 1.0f, "Buffer does not get exceeded")),
|
||||
dynamicTest("15", () -> simulateBufferedConsumption(2.0f, 1.0f, 1.0f, 0.5f, 1.0f, "Half buffer, filled with excess"))
|
||||
};
|
||||
}
|
||||
void simulateBufferedConsumption(float producedPower, float maxBuffer, float powerConsumedPerTick, float initialSatisfaction, float expectedSatisfaction, String parameterDescription){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 0.5f; // Currently, 0.5 = 100%
|
||||
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);
|
||||
|
||||
assertEquals(producedPower * Time.delta(), powerGraph.getPowerProduced(), Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Produced power did not match");
|
||||
float expectedPowerUsage;
|
||||
if(initialSatisfaction == 1.0f){
|
||||
expectedPowerUsage = 0f;
|
||||
}else{
|
||||
expectedPowerUsage = Math.min(maxBuffer, powerConsumedPerTick * Time.delta());
|
||||
}
|
||||
assertEquals(expectedPowerUsage, powerGraph.getPowerNeeded(), Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Consumed power did not match");
|
||||
|
||||
// Update and check for the expected power satisfaction of the consumer
|
||||
powerGraph.update();
|
||||
assertEquals(expectedSatisfaction, bufferedConsumerTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Satisfaction of buffered consumer did not match");
|
||||
}
|
||||
|
||||
/** Tests the satisfaction of a single direct consumer after a single update of the power graph which contains a single producer and a single battery.
|
||||
* The used battery is created with a maximum capacity of 100 and receives ten power per tick.
|
||||
*/
|
||||
@TestFactory
|
||||
DynamicTest[] batteryCapacityIsAsExpected(){
|
||||
return new DynamicTest[]{
|
||||
// Note: expectedBatteryCapacity is currently adjusted to a delta of 0.5! (FakeThreadHandler sets it to that)
|
||||
dynamicTest("01", () -> simulateDirectConsumptionWithBattery(10.0f, 0.0f, 0.0f, 5.0f, 0.0f, "Empty battery, no consumer")),
|
||||
dynamicTest("02", () -> simulateDirectConsumptionWithBattery(10.0f, 0.0f, 94.999f, 99.999f, 0.0f, "Battery almost full after update, no consumer")),
|
||||
dynamicTest("03", () -> simulateDirectConsumptionWithBattery(10.0f, 0.0f, 100.0f, 100.0f, 0.0f, "Full battery, no consumer")),
|
||||
dynamicTest("04", () -> simulateDirectConsumptionWithBattery(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, "No producer, no consumer, empty battery")),
|
||||
dynamicTest("05", () -> simulateDirectConsumptionWithBattery(0.0f, 0.0f, 100.0f, 100.0f, 0.0f, "No producer, no consumer, full battery")),
|
||||
dynamicTest("06", () -> simulateDirectConsumptionWithBattery(0.0f, 10.0f, 0.0f, 0.0f, 0.0f, "No producer, empty battery")),
|
||||
dynamicTest("07", () -> simulateDirectConsumptionWithBattery(0.0f, 10.0f, 100.0f, 95.0f, 1.0f, "No producer, full battery")),
|
||||
dynamicTest("08", () -> simulateDirectConsumptionWithBattery(0.0f, 10.0f, 2.5f, 0.0f, 0.5f, "No producer, low battery")),
|
||||
dynamicTest("09", () -> simulateDirectConsumptionWithBattery(5.0f, 10.0f, 5.0f, 0.0f, 1.0f, "Producer + Battery = Consumed")),
|
||||
};
|
||||
}
|
||||
void simulateDirectConsumptionWithBattery(float producedPower, float requestedPower, float initialBatteryCapacity, float expectedBatteryCapacity, float expectedSatisfaction, String parameterDescription){
|
||||
PowerGraph powerGraph = new PowerGraph();
|
||||
|
||||
if(producedPower > 0.0f){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 0.5f;
|
||||
powerGraph.add(producerTile);
|
||||
}
|
||||
Tile directConsumerTile = null;
|
||||
if(requestedPower > 0.0f){
|
||||
directConsumerTile = createFakeTile(0, 1, createFakeDirectConsumer(requestedPower, 0.6f));
|
||||
powerGraph.add(directConsumerTile);
|
||||
}
|
||||
float maxCapacity = 100f;
|
||||
Tile batteryTile = createFakeTile(0, 2, createFakeBattery(maxCapacity, 10 ));
|
||||
batteryTile.entity.power.satisfaction = initialBatteryCapacity / maxCapacity;
|
||||
|
||||
powerGraph.add(batteryTile);
|
||||
|
||||
powerGraph.update();
|
||||
assertEquals(expectedBatteryCapacity / maxCapacity, batteryTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Expected battery satisfaction did not match");
|
||||
if(directConsumerTile != null){
|
||||
assertEquals(expectedSatisfaction, directConsumerTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR, parameterDescription + ": Satisfaction of direct consumer did not match");
|
||||
}
|
||||
}
|
||||
|
||||
/** Makes sure a direct consumer stops working after power production is set to zero. */
|
||||
@Test
|
||||
void directConsumptionStopsWithNoPower(){
|
||||
Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(10.0f));
|
||||
producerTile.<PowerGenerator.GeneratorEntity>entity().productionEfficiency = 1.0f;
|
||||
Tile consumerTile = createFakeTile(0, 1, createFakeDirectConsumer(5.0f, 0.6f));
|
||||
|
||||
PowerGraph powerGraph = new PowerGraph();
|
||||
powerGraph.add(producerTile);
|
||||
powerGraph.add(consumerTile);
|
||||
powerGraph.update();
|
||||
|
||||
assertEquals(1.0f, consumerTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR);
|
||||
|
||||
powerGraph.remove(producerTile);
|
||||
powerGraph.add(consumerTile);
|
||||
powerGraph.update();
|
||||
|
||||
assertEquals(0.0f, consumerTile.entity.power.satisfaction, Mathf.FLOAT_ROUNDING_ERROR);
|
||||
if(consumerTile.block().consumes.has(ConsumePower.class)){
|
||||
ConsumePower consumePower = consumerTile.block().consumes.get(ConsumePower.class);
|
||||
assertFalse(consumePower.valid(consumerTile.block(), consumerTile.entity()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue