From 11e071289bb60f0f00a5b9010b2a1a5bf2a77f0f Mon Sep 17 00:00:00 2001 From: Timmeey86 Date: Tue, 27 Nov 2018 08:51:19 +0100 Subject: [PATCH 1/6] Reorganized power tests ... ... and changed creation of fake tiles so update() implementations have all required dependencies --- .../java/{ => power}/FakeThreadHandler.java | 3 +- .../java/power/ItemLiquidGeneratorTests.java | 37 +++++++++++++++++ .../java/{ => power}/PowerTestFixture.java | 29 +++++++++++--- .../src/test/java/{ => power}/PowerTests.java | 40 ++++++++++++++----- 4 files changed, 92 insertions(+), 17 deletions(-) rename tests/src/test/java/{ => power}/FakeThreadHandler.java (94%) create mode 100644 tests/src/test/java/power/ItemLiquidGeneratorTests.java rename tests/src/test/java/{ => power}/PowerTestFixture.java (67%) rename tests/src/test/java/{ => power}/PowerTests.java (88%) diff --git a/tests/src/test/java/FakeThreadHandler.java b/tests/src/test/java/power/FakeThreadHandler.java similarity index 94% rename from tests/src/test/java/FakeThreadHandler.java rename to tests/src/test/java/power/FakeThreadHandler.java index 3588205e98..9b2676c49b 100644 --- a/tests/src/test/java/FakeThreadHandler.java +++ b/tests/src/test/java/power/FakeThreadHandler.java @@ -1,4 +1,5 @@ -import com.badlogic.gdx.Gdx; +package power; + import io.anuke.mindustry.core.ThreadHandler; import io.anuke.ucore.core.Timers; diff --git a/tests/src/test/java/power/ItemLiquidGeneratorTests.java b/tests/src/test/java/power/ItemLiquidGeneratorTests.java new file mode 100644 index 0000000000..930f257cd9 --- /dev/null +++ b/tests/src/test/java/power/ItemLiquidGeneratorTests.java @@ -0,0 +1,37 @@ +package power; + +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.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** This class tests the abstract ItemLiquidGenerator class and maybe some of its dependencies. */ +public class ItemLiquidGeneratorTests extends PowerTestFixture{ + + private ItemLiquidGenerator sut; // system under test (https://en.wikipedia.org/wiki/System_under_test) + private Tile tile; + + @BeforeEach + public void createItemLiquidGenerator(){ + sut = new ItemLiquidGenerator("fakegen"){ + @Override + protected float getLiquidEfficiency(Liquid liquid){ + return liquid.flammability; + } + + @Override + protected float getItemEfficiency(Item item){ + return item.flammability; + } + }; + tile = createFakeTile(0, 0, sut); + } + + @Test + void detectCrashes(){ + sut.update(tile); + } +} diff --git a/tests/src/test/java/PowerTestFixture.java b/tests/src/test/java/power/PowerTestFixture.java similarity index 67% rename from tests/src/test/java/PowerTestFixture.java rename to tests/src/test/java/power/PowerTestFixture.java index 2ecd5a7dc7..e2bc95ec4e 100644 --- a/tests/src/test/java/PowerTestFixture.java +++ b/tests/src/test/java/power/PowerTestFixture.java @@ -1,18 +1,31 @@ +package power; + +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.Floor; +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.PowerModule; +import org.junit.jupiter.api.BeforeAll; import java.lang.reflect.Field; +import java.lang.reflect.Method; /** This class provides objects commonly used by power related unit tests. * For now, this is a helper with static methods, but this might change. * */ public class PowerTestFixture{ + @BeforeAll + static void initializeDependencies(){ + Vars.content = new ContentLoader(); + Vars.content.load(); + Vars.threads = new FakeThreadHandler(); + } + protected static PowerGenerator createFakeProducerBlock(float producedPower){ return new PowerGenerator("fakegen"){{ powerProduction = producedPower; @@ -26,13 +39,13 @@ public class PowerTestFixture{ } protected static Block createFakeDirectConsumer(float powerPerTick, float minimumSatisfaction){ - return new Block("fakedirectconsumer"){{ + return new PowerBlock("fakedirectconsumer"){{ consumes.powerDirect(powerPerTick, minimumSatisfaction); }}; } protected static Block createFakeBufferedConsumer(float capacity, float ticksToFill){ - return new Block("fakebufferedconsumer"){{ + return new PowerBlock("fakebufferedconsumer"){{ consumes.powerBuffered(capacity, ticksToFill); }}; } @@ -47,6 +60,10 @@ public class PowerTestFixture{ 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 + // and call the private changed() method through reflections. + Field field = Tile.class.getDeclaredField("wall"); field.setAccessible(true); field.set(tile, block); @@ -55,8 +72,10 @@ public class PowerTestFixture{ field.setAccessible(true); field.set(tile, Blocks.sand); - tile.entity = block.newEntity(); - tile.entity.power = new PowerModule(); + Method method = Tile.class.getDeclaredMethod("changed"); + method.setAccessible(true); + method.invoke(tile); + return tile; }catch(Exception ex){ return null; diff --git a/tests/src/test/java/PowerTests.java b/tests/src/test/java/power/PowerTests.java similarity index 88% rename from tests/src/test/java/PowerTests.java rename to tests/src/test/java/power/PowerTests.java index 3b93652c0e..48b5cf8c6f 100644 --- a/tests/src/test/java/PowerTests.java +++ b/tests/src/test/java/power/PowerTests.java @@ -1,28 +1,22 @@ +package power; + import com.badlogic.gdx.math.MathUtils; import io.anuke.mindustry.Vars; -import io.anuke.mindustry.content.blocks.PowerBlocks; -import io.anuke.mindustry.content.blocks.ProductionBlocks; 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 org.junit.jupiter.params.provider.ValueSource; -import org.junit.jupiter.params.ParameterizedTest; +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; public class PowerTests extends PowerTestFixture{ - @BeforeAll - static void initializeDependencies(){ - Vars.content = new ContentLoader(); - Vars.content.load(); - Vars.threads = new FakeThreadHandler(); - } - @BeforeEach void initTest(){ } @@ -143,5 +137,29 @@ public class PowerTests extends PowerTestFixture{ assertEquals(expectedSatisfaction, directConsumerTile.entity.power.satisfaction, MathUtils.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 testDirectConsumptionStopsWithNoPower(){ + Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(10.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, MathUtils.FLOAT_ROUNDING_ERROR); + + powerGraph.remove(producerTile); + powerGraph.add(consumerTile); + powerGraph.update(); + + assertEquals(0.0f, consumerTile.entity.power.satisfaction, MathUtils.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())); + } + } } } From f56e1933a66c92d9f7cefe58b68c77ce5d48f6ce Mon Sep 17 00:00:00 2001 From: Timmeey86 Date: Tue, 27 Nov 2018 21:27:35 +0100 Subject: [PATCH 2/6] Fixed occassional crashes of Power Tests and continued TDD --- .../mindustry/content/blocks/PowerBlocks.java | 10 ++-- .../blocks/power/ItemLiquidGenerator.java | 15 +++--- .../world/blocks/power/LiquidGenerator.java | 5 +- .../blocks/power/LiquidHeatGenerator.java | 4 +- .../world/blocks/power/PowerGenerator.java | 2 +- .../java/power/ItemLiquidGeneratorTests.java | 50 +++++++++++++------ .../src/test/java/power/PowerTestFixture.java | 23 +++++++-- 7 files changed, 72 insertions(+), 37 deletions(-) diff --git a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java index 6b47d31a67..db497f5043 100644 --- a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java @@ -19,17 +19,17 @@ public class PowerBlocks extends BlockList implements ContentList{ thermalGenerator = new LiquidHeatGenerator("thermal-generator"){{ maxLiquidGenerate = 4f; - // TODO: Adapt to new power system - powerProduction = -1; - powerPerLiquid = 0.1f; + // TODO: Balance + powerProduction = 0.17f; + liquidPowerMultiplier = 0.1f; generateEffect = BlockFx.redgeneratespark; size = 2; }}; turbineGenerator = new TurbineGenerator("turbine-generator"){{ - // TODO: Adapt to new power system + // TODO: Balance powerProduction = 0.28f; - powerPerLiquid = 0.1f; + liquidPowerMultiplier = 0.3f; itemDuration = 30f; consumes.liquid(Liquids.water, 0.05f); size = 2; 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 07f16b8767..83031719d2 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -14,7 +14,7 @@ import static io.anuke.mindustry.Vars.tilesize; public abstract class ItemLiquidGenerator extends ItemGenerator{ protected float minLiquidEfficiency = 0.2f; - protected float powerPerLiquid = 0.13f; + protected float liquidPowerMultiplier = 1.3f; // A liquid with 100% flammability will be 30% more efficient than an item with 100% flammability. /**Maximum liquid used per frame.*/ protected float maxLiquidGenerate = 0.4f; @@ -46,15 +46,16 @@ public abstract class ItemLiquidGenerator extends ItemGenerator{ } //liquid takes priority over solids + float calculationDelta = entity.delta(); if(liquid != null && entity.liquids.get(liquid) >= 0.001f && entity.cons.valid()){ - float powerPerLiquid = getLiquidEfficiency(liquid) * this.powerPerLiquid; - float used = Math.min(entity.liquids.get(liquid), maxLiquidGenerate * entity.delta()); - // TODO: Adapt to new power system - //used = Math.min(used, (powerCapacity - entity.power.amount) / powerPerLiquid); + float baseLiquidEfficiency = getLiquidEfficiency(liquid) * this.liquidPowerMultiplier; + float maximumPossible = maxLiquidGenerate * calculationDelta; + float used = Math.min(entity.liquids.get(liquid), maximumPossible); entity.liquids.remove(liquid, used); - // TODO: Adapt to new power system - //entity.power.amount += used * powerPerLiquid; + + // Note: 1 Item with 100% Flammability = 100% efficiency. This means 100% is not max but rather a reference point for this generator. + entity.productionEfficiency = baseLiquidEfficiency * used / maximumPossible; if(used > 0.001f && Mathf.chance(0.05 * entity.delta())){ Effects.effect(generateEffect, tile.drawx() + Mathf.range(3f), tile.drawy() + Mathf.range(3f)); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java index 0b19f94fc0..7314c56a0d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/LiquidGenerator.java @@ -13,7 +13,7 @@ import io.anuke.ucore.util.Mathf; public abstract class LiquidGenerator extends PowerGenerator{ protected float minEfficiency = 0.2f; - protected float powerPerLiquid; + protected float liquidPowerMultiplier; /**Maximum liquid used per frame.*/ protected float maxLiquidGenerate; protected Effect generateEffect = BlockFx.generatespark; @@ -50,8 +50,9 @@ public abstract class LiquidGenerator extends PowerGenerator{ public void update(Tile tile){ TileEntity entity = tile.entity(); + // TODO Code duplication with ItemLiquidGenerator if(entity.liquids.get(entity.liquids.current()) >= 0.001f){ - float powerPerLiquid = getEfficiency(entity.liquids.current()) * this.powerPerLiquid; + //float powerPerLiquid = getEfficiency(entity.liquids.current()) * this.powerPerLiquid; float used = Math.min(entity.liquids.currentAmount(), maxLiquidGenerate * entity.delta()); // TODO Adapt to new power system //used = Math.min(used, (powerCapacity - entity.power.amount) / powerPerLiquid); diff --git a/core/src/io/anuke/mindustry/world/blocks/power/LiquidHeatGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/LiquidHeatGenerator.java index 8b5665b4c5..9129e884e9 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/LiquidHeatGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/LiquidHeatGenerator.java @@ -14,9 +14,9 @@ public class LiquidHeatGenerator extends LiquidGenerator{ public void setStats(){ super.setStats(); - // TODO Verify for new power system stats.remove(BlockStat.basePowerGeneration); - stats.add(BlockStat.basePowerGeneration, maxLiquidGenerate * powerPerLiquid * 60f, StatUnit.powerSecond); + // TODO Adapt to new new power system. Maybe this override can be removed. + //stats.add(BlockStat.basePowerGeneration, * 60f, StatUnit.powerSecond); } @Override diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java index 915ef48021..48d8bd0106 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java @@ -10,7 +10,7 @@ import io.anuke.mindustry.world.meta.BlockStat; public class PowerGenerator extends PowerDistributor{ /** The amount of power produced per tick. */ - public float powerProduction; + protected float powerProduction; public BlockStat generationType = BlockStat.basePowerGeneration; public PowerGenerator(String name){ diff --git a/tests/src/test/java/power/ItemLiquidGeneratorTests.java b/tests/src/test/java/power/ItemLiquidGeneratorTests.java index 930f257cd9..cf54743faf 100644 --- a/tests/src/test/java/power/ItemLiquidGeneratorTests.java +++ b/tests/src/test/java/power/ItemLiquidGeneratorTests.java @@ -1,37 +1,57 @@ package power; +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.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.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + /** This class tests the abstract ItemLiquidGenerator class and maybe some of its dependencies. */ public class ItemLiquidGeneratorTests extends PowerTestFixture{ - private ItemLiquidGenerator sut; // system under test (https://en.wikipedia.org/wiki/System_under_test) + private ItemLiquidGenerator generator; private Tile tile; + private ItemGenerator.ItemGeneratorEntity entity; + private final float fakeLiquidPowerMultiplier = 2.0f; + private final float fakeMaxLiquidGenerate = 0.5f; @BeforeEach - public void createItemLiquidGenerator(){ - sut = new ItemLiquidGenerator("fakegen"){ - @Override - protected float getLiquidEfficiency(Liquid liquid){ - return liquid.flammability; - } + public void createBurnerGenerator(){ + // Use a burner generator instead of a custom ItemLiquidGenerator subclass since we would implement abstract methods the same way. + generator = new BurnerGenerator("fakegen"){{ + powerProduction = 0.1f; + itemDuration = 60f; + liquidPowerMultiplier = fakeLiquidPowerMultiplier; + maxLiquidGenerate = fakeMaxLiquidGenerate; + }}; - @Override - protected float getItemEfficiency(Item item){ - return item.flammability; - } - }; - tile = createFakeTile(0, 0, sut); + tile = createFakeTile(0, 0, generator); + entity = tile.entity(); } @Test - void detectCrashes(){ - sut.update(tile); + void testLiquidConsumption(){ + final float providedUsage = 0.1f; + final float expectedEfficiency = providedUsage / fakeMaxLiquidGenerate * fakeLiquidPowerMultiplier * Liquids.oil.flammability; + + entity.liquids.add(Liquids.oil, providedUsage); + entity.cons.update(tile.entity); + assumeTrue(entity.cons.valid()); + + // Perform an update on the generator once - This should use up all oil and produce a fraction of what's possible + generator.update(tile); + + assertEquals(0.0f, entity.liquids.get(Liquids.oil)); + assertEquals(expectedEfficiency, entity.productionEfficiency); } } diff --git a/tests/src/test/java/power/PowerTestFixture.java b/tests/src/test/java/power/PowerTestFixture.java index e2bc95ec4e..1588f54188 100644 --- a/tests/src/test/java/power/PowerTestFixture.java +++ b/tests/src/test/java/power/PowerTestFixture.java @@ -3,17 +3,25 @@ package power; import io.anuke.mindustry.Vars; import io.anuke.mindustry.content.blocks.Blocks; import io.anuke.mindustry.core.ContentLoader; +import io.anuke.mindustry.core.World; +import io.anuke.mindustry.entities.TileEntity; 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 io.anuke.ucore.entities.Entities; import org.junit.jupiter.api.BeforeAll; import java.lang.reflect.Field; import java.lang.reflect.Method; +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. * */ @@ -62,7 +70,7 @@ public class PowerTestFixture{ // 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 - // and call the private changed() method through reflections. + // through reflections and then simulate part of what the changed() method does. Field field = Tile.class.getDeclaredField("wall"); field.setAccessible(true); @@ -72,10 +80,15 @@ public class PowerTestFixture{ field.setAccessible(true); field.set(tile, Blocks.sand); - Method method = Tile.class.getDeclaredMethod("changed"); - method.setAccessible(true); - method.invoke(tile); - + // 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); + } return tile; }catch(Exception ex){ return null; From 739219c57b7c703a6ed7ed9549030c8c45b8c949 Mon Sep 17 00:00:00 2001 From: Timmeey86 Date: Tue, 27 Nov 2018 22:28:41 +0100 Subject: [PATCH 3/6] Finished first draft of ItemLiquidGenerator through TDD --- .../blocks/power/ItemLiquidGenerator.java | 14 ++- .../java/power/ItemLiquidGeneratorTests.java | 87 +++++++++++++++---- 2 files changed, 78 insertions(+), 23 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 83031719d2..83d433b8c6 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -45,8 +45,12 @@ public abstract class ItemLiquidGenerator extends ItemGenerator{ } } - //liquid takes priority over solids + entity.productionEfficiency = 0.0f; + // Note: Do not use this delta when calculating the amount of power or the power efficiency, but use it for resource consumption if necessary. + // Power amount is delta'd by PowerGraph class already. float calculationDelta = entity.delta(); + + //liquid takes priority over solids if(liquid != null && entity.liquids.get(liquid) >= 0.001f && entity.cons.valid()){ float baseLiquidEfficiency = getLiquidEfficiency(liquid) * this.liquidPowerMultiplier; float maximumPossible = maxLiquidGenerate * calculationDelta; @@ -62,22 +66,16 @@ public abstract class ItemLiquidGenerator extends ItemGenerator{ } }else if(entity.cons.valid()){ - // TODO: Adapt to new power system - //float maxPower = Math.min(powerCapacity - entity.power.amount, powerOutput * entity.delta()) * entity.efficiency; - if(entity.generateTime <= 0f && entity.items.total() > 0){ Effects.effect(generateEffect, tile.worldx() + Mathf.range(3f), tile.worldy() + Mathf.range(3f)); Item item = entity.items.take(); - // TODO: Adapt to new power system - //entity.efficiency = getItemEfficiency(item); + entity.productionEfficiency = getItemEfficiency(item); entity.explosiveness = item.explosiveness; entity.generateTime = 1f; } if(entity.generateTime > 0f){ entity.generateTime -= 1f / itemDuration * entity.delta(); - // TODO: Adapt to new power system - //entity.power.amount += maxPower; entity.generateTime = Mathf.clamp(entity.generateTime); if(Mathf.chance(entity.delta() * 0.06 * Mathf.clamp(entity.explosiveness - 0.25f))){ diff --git a/tests/src/test/java/power/ItemLiquidGeneratorTests.java b/tests/src/test/java/power/ItemLiquidGeneratorTests.java index cf54743faf..f8685709c7 100644 --- a/tests/src/test/java/power/ItemLiquidGeneratorTests.java +++ b/tests/src/test/java/power/ItemLiquidGeneratorTests.java @@ -1,5 +1,7 @@ package power; +import io.anuke.mindustry.Vars; +import io.anuke.mindustry.content.Items; import io.anuke.mindustry.content.Liquids; import io.anuke.mindustry.type.Item; import io.anuke.mindustry.type.Liquid; @@ -8,22 +10,25 @@ 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.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +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; -/** This class tests the abstract ItemLiquidGenerator class and maybe some of its dependencies. */ +/** + * This class tests ItemLiquidGenerators. Currently, testing is only performed on the BurnerGenerator subclass, + * which means only power calculations based on flammability are tested. + */ public class ItemLiquidGeneratorTests extends PowerTestFixture{ private ItemLiquidGenerator generator; private Tile tile; private ItemGenerator.ItemGeneratorEntity entity; private final float fakeLiquidPowerMultiplier = 2.0f; - private final float fakeMaxLiquidGenerate = 0.5f; + private final float fakeItemDuration = 0.5f; + private final float maximumLiquidUsage = 0.5f; @BeforeEach public void createBurnerGenerator(){ @@ -32,26 +37,78 @@ public class ItemLiquidGeneratorTests extends PowerTestFixture{ powerProduction = 0.1f; itemDuration = 60f; liquidPowerMultiplier = fakeLiquidPowerMultiplier; - maxLiquidGenerate = fakeMaxLiquidGenerate; + itemDuration = fakeItemDuration; + maxLiquidGenerate = maximumLiquidUsage; }}; tile = createFakeTile(0, 0, generator); entity = tile.entity(); } - @Test - void testLiquidConsumption(){ - final float providedUsage = 0.1f; - final float expectedEfficiency = providedUsage / fakeMaxLiquidGenerate * fakeLiquidPowerMultiplier * Liquids.oil.flammability; + /** Tests the consumption and efficiency when being supplied with liquids. */ + @TestFactory + DynamicTest[] testLiquidConsumption(){ + return new DynamicTest[]{ + dynamicTest("01", () -> test_liquidConsumption(Liquids.oil, 0.0f, "No liquids provided")), + dynamicTest("02", () -> test_liquidConsumption(Liquids.oil, maximumLiquidUsage / 4.0f, "Low oil provided")), + dynamicTest("03", () -> test_liquidConsumption(Liquids.oil, maximumLiquidUsage * 1.0f, "Sufficient oil provided")), + dynamicTest("04", () -> test_liquidConsumption(Liquids.oil, maximumLiquidUsage * 2.0f, "Excess oil provided")) + // Note: The generator will decline any other liquid since it's not flammable + }; + } - entity.liquids.add(Liquids.oil, providedUsage); + 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; + 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 + for(Liquid tmpLiquid : Vars.content.liquids()){ + entity.liquids.reset(tmpLiquid, 0.0f); + } + entity.liquids.add(liquid, availableLiquidAmount); entity.cons.update(tile.entity); - assumeTrue(entity.cons.valid()); + assertTrue(entity.cons.valid()); - // Perform an update on the generator once - This should use up all oil and produce a fraction of what's possible + // Perform an update on the generator once - This should use up any resource up to the maximum liquid usage generator.update(tile); - assertEquals(0.0f, entity.liquids.get(Liquids.oil)); - assertEquals(expectedEfficiency, entity.productionEfficiency); + assertEquals(expectedRemainingLiquidAmount, entity.liquids.get(liquid), parameterDescription + ": Remaining liquid amount mismatch."); + assertEquals(expectedEfficiency, entity.productionEfficiency, parameterDescription + ": Efficiency mismatch."); + } + + /** Tests the consumption and efficiency when being supplied with items. */ + @TestFactory + DynamicTest[] testItemConsumption(){ + return new DynamicTest[]{ + dynamicTest("01", () -> test_itemConsumption(Items.coal, 0, "No items provided")), + dynamicTest("02", () -> test_itemConsumption(Items.coal, 1, "Sufficient coal provided")), + dynamicTest("03", () -> test_itemConsumption(Items.coal, 10, "Excess coal provided")), + dynamicTest("04", () -> test_itemConsumption(Items.blastCompound, 1, "Blast compound provided")), + //dynamicTest("03", () -> test_itemConsumption(Items.plastanium, 1, "Plastanium provided")), // Not accepted by generator due to low flammability + dynamicTest("05", () -> test_itemConsumption(Items.biomatter, 1, "Biomatter provided")), + dynamicTest("06", () -> test_itemConsumption(Items.pyratite, 1, "Pyratite provided")) + }; + } + + void test_itemConsumption(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); + assertTrue(generator.acceptItem(item, tile, null), parameterDescription + ": Items which will be declined by the generator don't need to be tested - The code won't be called for those cases."); + + + // Reset items since BeforeEach will not be called between dynamic tests + entity.items.clear(); + 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), parameterDescription + ": Remaining item amount mismatch."); + assertEquals(expectedEfficiency, entity.productionEfficiency, parameterDescription + ": Efficiency mismatch."); } } From 1f8751054ceee612728f3731fa8e2f470d47cf8a Mon Sep 17 00:00:00 2001 From: Timmeey86 Date: Wed, 28 Nov 2018 11:04:08 +0100 Subject: [PATCH 4/6] 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"); } From cf3d2c3def279031e104b48953c24c2d19c4d262 Mon Sep 17 00:00:00 2001 From: Timmeey86 Date: Wed, 28 Nov 2018 11:59:58 +0100 Subject: [PATCH 5/6] Fixed handling of item duration --- .../blocks/power/ItemLiquidGenerator.java | 11 +++++--- .../java/power/ItemLiquidGeneratorTests.java | 27 +++++++++++++++++-- 2 files changed, 33 insertions(+), 5 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 c3e19143e2..c41f8d1c39 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/ItemLiquidGenerator.java @@ -45,13 +45,16 @@ public abstract class ItemLiquidGenerator extends ItemGenerator{ } } - entity.productionEfficiency = 0.0f; // Note: Do not use this delta when calculating the amount of power or the power efficiency, but use it for resource consumption if necessary. // Power amount is delta'd by PowerGraph class already. float calculationDelta = entity.delta(); + if(!entity.cons.valid()){ + entity.productionEfficiency = 0.0f; + return; + } //liquid takes priority over solids - if(liquid != null && entity.liquids.get(liquid) >= 0.001f && entity.cons.valid()){ + if(liquid != null && entity.liquids.get(liquid) >= 0.001f){ float baseLiquidEfficiency = getLiquidEfficiency(liquid) * this.liquidPowerMultiplier; float maximumPossible = maxLiquidGenerate * calculationDelta; float used = Math.min(entity.liquids.get(liquid) * calculationDelta, maximumPossible); @@ -64,7 +67,7 @@ public abstract class ItemLiquidGenerator extends ItemGenerator{ if(used > 0.001f && Mathf.chance(0.05 * entity.delta())){ Effects.effect(generateEffect, tile.drawx() + Mathf.range(3f), tile.drawy() + Mathf.range(3f)); } - }else if(entity.cons.valid()){ + }else{ if(entity.generateTime <= 0f && entity.items.total() > 0){ Effects.effect(generateEffect, tile.worldx() + Mathf.range(3f), tile.worldy() + Mathf.range(3f)); @@ -82,6 +85,8 @@ public abstract class ItemLiquidGenerator extends ItemGenerator{ entity.damage(Mathf.random(8f)); Effects.effect(explodeEffect, tile.worldx() + Mathf.range(size * tilesize / 2f), tile.worldy() + Mathf.range(size * tilesize / 2f)); } + }else{ + entity.productionEfficiency = 0.0f; } } } diff --git a/tests/src/test/java/power/ItemLiquidGeneratorTests.java b/tests/src/test/java/power/ItemLiquidGeneratorTests.java index 471b59bdf6..c1b1ff6bf8 100644 --- a/tests/src/test/java/power/ItemLiquidGeneratorTests.java +++ b/tests/src/test/java/power/ItemLiquidGeneratorTests.java @@ -29,7 +29,7 @@ public class ItemLiquidGeneratorTests extends PowerTestFixture{ private Tile tile; private ItemGenerator.ItemGeneratorEntity entity; private final float fakeLiquidPowerMultiplier = 2.0f; - private final float fakeItemDuration = 0.5f; + private final float fakeItemDuration = 60f; // 60 ticks private final float maximumLiquidUsage = 0.5f; @BeforeEach @@ -102,8 +102,11 @@ public class ItemLiquidGeneratorTests extends PowerTestFixture{ assertTrue(generator.acceptItem(item, tile, null), parameterDescription + ": Items which will be declined by the generator don't need to be tested - The code won't be called for those cases."); - // Reset items since BeforeEach will not be called between dynamic tests + // Clean up manually since BeforeEach will not be called between dynamic tests entity.items.clear(); + entity.generateTime = 0.0f; + entity.productionEfficiency = 0.0f; + if(amount > 0){ entity.items.add(item, amount); } @@ -116,4 +119,24 @@ public class ItemLiquidGeneratorTests extends PowerTestFixture{ assertEquals(expectedRemainingItemAmount, entity.items.get(item), parameterDescription + ": Remaining item amount mismatch."); assertEquals(expectedEfficiency, entity.productionEfficiency, parameterDescription + ": Efficiency mismatch."); } + + /** Makes sure the efficiency stays equal during the item duration. */ + @Test + void test_efficiencyConstantDuringItemDuration(){ + + // 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 += FakeThreadHandler.fakeDelta) <= fakeItemDuration){ + generator.update(tile); + assertEquals(expectedEfficiency, entity.productionEfficiency, "Duration: " + String.valueOf(currentDuration)); + } + generator.update(tile); + assertEquals(0.0f, entity.productionEfficiency, "Duration: " + String.valueOf(currentDuration)); + } } From 2972780bed875a0129829d358699b13f1e903ac7 Mon Sep 17 00:00:00 2001 From: Timmeey86 Date: Wed, 28 Nov 2018 13:19:52 +0100 Subject: [PATCH 6/6] Initial Efficiency is now zero. Blocks display efficiency. --- .../mindustry/content/blocks/PowerBlocks.java | 27 ++++++++++++++----- core/src/io/anuke/mindustry/world/Block.java | 3 +-- .../world/blocks/power/PowerGenerator.java | 17 +++++++++++- .../world/consumers/ConsumePower.java | 4 ++- tests/src/test/java/power/PowerTests.java | 5 ++++ 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java index db497f5043..cf59a636d1 100644 --- a/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java +++ b/core/src/io/anuke/mindustry/content/blocks/PowerBlocks.java @@ -4,6 +4,7 @@ import io.anuke.mindustry.content.Liquids; import io.anuke.mindustry.content.fx.BlockFx; import io.anuke.mindustry.game.ContentList; import io.anuke.mindustry.world.Block; +import io.anuke.mindustry.world.Tile; import io.anuke.mindustry.world.blocks.power.*; public class PowerBlocks extends BlockList implements ContentList{ @@ -41,14 +42,26 @@ public class PowerBlocks extends BlockList implements ContentList{ itemDuration = 220f; }}; - solarPanel = new PowerGenerator("solar-panel"){{ - powerProduction = 0.0045f; - }}; + // TODO: Maybe reintroduce a class for the initial production efficiency + solarPanel = new PowerGenerator("solar-panel"){ + { + powerProduction = 0.0045f; + } + @Override + public void update(Tile tile){ + tile.entity().productionEfficiency = 1.0f; + } + }; - largeSolarPanel = new PowerGenerator("solar-panel-large"){{ - powerProduction = 0.055f; - size = 3; - }}; + largeSolarPanel = new PowerGenerator("solar-panel-large"){ + { + powerProduction = 0.055f; + } + @Override + public void update(Tile tile){ + tile.entity().productionEfficiency = 1.0f; + } + }; thoriumReactor = new NuclearReactor("thorium-reactor"){{ size = 3; diff --git a/core/src/io/anuke/mindustry/world/Block.java b/core/src/io/anuke/mindustry/world/Block.java index 21ee395822..4e6caa78d2 100644 --- a/core/src/io/anuke/mindustry/world/Block.java +++ b/core/src/io/anuke/mindustry/world/Block.java @@ -337,9 +337,8 @@ public class Block extends BaseBlock { } public void setBars(){ - if(consumes.has(ConsumePower.class)){ + if(consumes.has(ConsumePower.class)) bars.add(new BlockBar(BarType.power, true, tile -> tile.entity.power.satisfaction)); - } if(hasLiquids) bars.add(new BlockBar(BarType.liquid, true, tile -> tile.entity.liquids.total() / liquidCapacity)); if(hasItems) diff --git a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java index 48d8bd0106..8a3e59dd9d 100644 --- a/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java +++ b/core/src/io/anuke/mindustry/world/blocks/power/PowerGenerator.java @@ -1,5 +1,7 @@ package io.anuke.mindustry.world.blocks.power; +import io.anuke.mindustry.world.BarType; +import io.anuke.mindustry.world.meta.BlockBar; import io.anuke.mindustry.world.meta.StatUnit; import io.anuke.ucore.util.EnumSet; @@ -11,6 +13,11 @@ import io.anuke.mindustry.world.meta.BlockStat; public class PowerGenerator extends PowerDistributor{ /** The amount of power produced per tick. */ protected float powerProduction; + /** The maximum possible efficiency for this generator. Supply values larger than 1.0f if more than 100% is possible. + * This could be the case when e.g. an item with 100% flammability is the reference point, but a more effective liquid + * can be supplied as an alternative. + */ + protected float maxEfficiency = 1.0f; public BlockStat generationType = BlockStat.basePowerGeneration; public PowerGenerator(String name){ @@ -40,8 +47,16 @@ public class PowerGenerator extends PowerDistributor{ return new GeneratorEntity(); } + @Override + public void setBars(){ + super.setBars(); + if(hasPower){ + bars.add(new BlockBar(BarType.power, true, tile -> tile.entity().productionEfficiency / maxEfficiency)); + } + } + public static class GeneratorEntity extends TileEntity{ public float generateTime; - public float productionEfficiency = 1; + public float productionEfficiency = 0.0f; } } diff --git a/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java b/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java index 2fd27ace54..d4d9a3dffb 100644 --- a/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java +++ b/core/src/io/anuke/mindustry/world/consumers/ConsumePower.java @@ -86,7 +86,9 @@ public class ConsumePower extends Consume{ * @return The amount of power which is requested per tick. */ public float requestedPower(Block block, TileEntity entity){ - // TODO Is it possible to make the block not consume power while items/liquids are missing? + // TODO Make the block not consume power on the following conditions, either here or in PowerGraph: + // - Other consumers are not valid, e.g. additional input items/liquids are missing + // - Buffer is full return powerPerTick; } diff --git a/tests/src/test/java/power/PowerTests.java b/tests/src/test/java/power/PowerTests.java index 36d4e258dc..25a85c58d2 100644 --- a/tests/src/test/java/power/PowerTests.java +++ b/tests/src/test/java/power/PowerTests.java @@ -2,6 +2,7 @@ package power; import com.badlogic.gdx.math.MathUtils; 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.*; @@ -48,6 +49,7 @@ public class PowerTests extends PowerTestFixture{ } void test_directConsumptionCalculation(float producedPower, float requiredPower, float expectedSatisfaction, String parameterDescription){ Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower)); + producerTile.entity().productionEfficiency = 1.0f; Tile directConsumerTile = createFakeTile(0, 1, createFakeDirectConsumer(requiredPower, 0.6f)); PowerGraph powerGraph = new PowerGraph(); @@ -87,6 +89,7 @@ public class PowerTests extends PowerTestFixture{ } void test_bufferedConsumptionCalculation(float producedPower, float maxBuffer, float powerConsumedPerTick, float initialSatisfaction, float expectedSatisfaction, String parameterDescription){ Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower)); + producerTile.entity().productionEfficiency = 1.0f; Tile bufferedConsumerTile = createFakeTile(0, 1, createFakeBufferedConsumer(maxBuffer, maxBuffer > 0.0f ? maxBuffer/powerConsumedPerTick : 1.0f)); bufferedConsumerTile.entity.power.satisfaction = initialSatisfaction; @@ -125,6 +128,7 @@ public class PowerTests extends PowerTestFixture{ if(producedPower > 0.0f){ Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(producedPower)); + producerTile.entity().productionEfficiency = 1.0f; powerGraph.add(producerTile); } Tile directConsumerTile = null; @@ -149,6 +153,7 @@ public class PowerTests extends PowerTestFixture{ @Test void testDirectConsumptionStopsWithNoPower(){ Tile producerTile = createFakeTile(0, 0, createFakeProducerBlock(10.0f)); + producerTile.entity().productionEfficiency = 1.0f; Tile consumerTile = createFakeTile(0, 1, createFakeDirectConsumer(5.0f, 0.6f)); PowerGraph powerGraph = new PowerGraph();