From 69746e0faafbdb2cbb9f985b9fe46794f5264557 Mon Sep 17 00:00:00 2001 From: arzumify Date: Sun, 23 Feb 2025 14:50:16 +0000 Subject: [PATCH] Significantly optimised the MatchMaker, added unit tests, added support for chunk unloading, added a maven repository, added a modrinth, added an icon, bumped the version. --- README.md | 22 +- build.gradle | 10 + gradle.properties | 2 +- .../java/arzumify/polyenergy/Polyenergy.java | 4 - .../polyenergy/api/EnergyReceiver.java | 8 + .../polyenergy/impl/CoordinateMatchMaker.java | 52 ++- .../polyenergy/impl/ProviderDetails.java | 5 +- .../polyenergy/util/SimpleBattery.java | 69 ++-- .../assets/polyenergy/icon-scaled.png | Bin 0 -> 12473 bytes src/main/resources/assets/polyenergy/icon.png | Bin 633 -> 410 bytes src/main/resources/fabric.mod.json | 2 +- src/test/java/EnergyTests.java | 329 ++++++++++++++++++ src/test/java/SimpleCodeOnlyBattery.java | 97 ++++++ 13 files changed, 547 insertions(+), 53 deletions(-) create mode 100644 src/main/resources/assets/polyenergy/icon-scaled.png create mode 100644 src/test/java/EnergyTests.java create mode 100644 src/test/java/SimpleCodeOnlyBattery.java diff --git a/README.md b/README.md index 729eec8..a55fc0f 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,30 @@ A dead-simple energy mod for Fabric. ### Installation -This mod is in early development and is not yet available for download. +The mod is available on [Modrinth](https://modrinth.com/project/polyenergy). You can download it from there or build it +yourself. ### API +#### Installation + +To use the API, add the following to your `build.gradle`: + +```groovy +repositories { + maven { + name = "Arzumify's Maven" + url = "https://maven.ailur.dev" + } +} + +dependencies { + modImplementation "dev.ailur.polyenergy:polyenergy:{version}" +} +``` + +To get the latest version, check the [Maven repository](https://maven.ailur.dev/arzumify/polyenergy/). + #### Providers Providers are the source of energy in the game. They can be blocks, entities, or any other object that can provide diff --git a/build.gradle b/build.gradle index 17d7b96..fb77326 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,16 @@ dependencies { // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + // Testing + testImplementation(platform('org.junit:junit-bom:5.12.0')) + testImplementation('org.junit.jupiter:junit-jupiter') + testRuntimeOnly('org.junit.platform:junit-platform-launcher') +} + +tasks.named("test", Test) { + useJUnitPlatform() + + } processResources { diff --git a/gradle.properties b/gradle.properties index 0edffc2..9a20da9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ minecraft_version=1.21.4 yarn_mappings=1.21.4+build.8 loader_version=0.16.10 # Mod Properties -mod_version=1.0.0 +mod_version=1.1.0 maven_group=arzumify.polyenergy archives_base_name=polyenergy # Dependencies diff --git a/src/main/java/arzumify/polyenergy/Polyenergy.java b/src/main/java/arzumify/polyenergy/Polyenergy.java index 7602bb2..c185e4b 100644 --- a/src/main/java/arzumify/polyenergy/Polyenergy.java +++ b/src/main/java/arzumify/polyenergy/Polyenergy.java @@ -3,10 +3,6 @@ package arzumify.polyenergy; import net.fabricmc.api.ModInitializer; public class Polyenergy implements ModInitializer { - // We don't actually use either of these, so it's commented out. - // public static final String MOD_ID = "polyenergy"; - // public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); - @Override public void onInitialize() { } diff --git a/src/main/java/arzumify/polyenergy/api/EnergyReceiver.java b/src/main/java/arzumify/polyenergy/api/EnergyReceiver.java index c52e2dc..34e9500 100644 --- a/src/main/java/arzumify/polyenergy/api/EnergyReceiver.java +++ b/src/main/java/arzumify/polyenergy/api/EnergyReceiver.java @@ -9,4 +9,12 @@ public interface EnergyReceiver { * @param provider The provider that exists and is ready to provide energy to this receiver. */ void ready(EnergyProvider provider); + + /** + * When called by the server matchmaker, indicates that this provider is no longer able to provide energy to this receiver. + * Wait for another call to {@link EnergyReceiver#ready(EnergyProvider)} before attempting to extract energy from this provider. + * + * @param provider The provider that is no longer able to provide energy to this receiver. + */ + void unready(EnergyProvider provider); } diff --git a/src/main/java/arzumify/polyenergy/impl/CoordinateMatchMaker.java b/src/main/java/arzumify/polyenergy/impl/CoordinateMatchMaker.java index 2760d6f..95a0f6a 100644 --- a/src/main/java/arzumify/polyenergy/impl/CoordinateMatchMaker.java +++ b/src/main/java/arzumify/polyenergy/impl/CoordinateMatchMaker.java @@ -1,31 +1,61 @@ package arzumify.polyenergy.impl; import java.util.ArrayList; +import java.util.HashMap; -// TODO: This seems very inefficient. Measure performance and then try improving this? This is O(n * m) which is very bad! +// It's slightly more optimized now, as in it could realistically run in Minecraft. +// Reset: O(1) constant time, we're just deleting everything +// Add Provider: O(m * k^2) where m is the number of receivers and k is the number of points of presence +// Add Receiver: O(n * k^2) where n is the number of providers and k is the number of points of presence +// Remove Provider: O(n + m) where n is the number of providers and m is the number of matches for the provider +// Remove Receiver: O(m) where m is the number of receivers +// I think we stop the optimisations here, the bottleneck is no longer the matchmaker but Minecraft itself. +// If more optimisation is needed we can try to perform Vector math in a more efficient way, but I think this is good enough. public class CoordinateMatchMaker { private static final ArrayList providers = new ArrayList<>(); private static final ArrayList receivers = new ArrayList<>(); + private static final HashMap> matches = new HashMap<>(); - public static void update() { - for (ReceiverDetails receiver : receivers) { - for (ProviderDetails provider : providers) { - if (provider.isInRange(receiver.pointsOfPresence())) { - provider.provider().exists(receiver.receiver(), provider.pointsOfPresence()); - } - } - } + public static void reset() { + providers.clear(); + receivers.clear(); + matches.clear(); } public static void addProvider(ProviderDetails provider) { providers.add(provider); - update(); + matches.put(provider, new ArrayList<>()); + // Search for receivers within range of this provider + for (ReceiverDetails receiver : receivers) { + if (provider.isInRange(receiver.pointsOfPresence())) { + provider.provider().exists(receiver.receiver(), provider.pointsOfPresence()); + matches.get(provider).add(receiver); + } + } } public static void addReceiver(ReceiverDetails receiver) { receivers.add(receiver); - update(); + // Search for providers within range of this receiver + for (ProviderDetails provider : providers) { + if (provider.isInRange(receiver.pointsOfPresence())) { + provider.provider().exists(receiver.receiver(), provider.pointsOfPresence()); + matches.get(provider).add(receiver); + } + } + } + + public static void removeProvider(ProviderDetails provider) { + providers.remove(provider); + for (ReceiverDetails receiver : matches.get(provider)) { + receiver.receiver().unready(provider.provider()); + } + matches.remove(provider); + } + + public static void removeReceiver(ReceiverDetails receiver) { + receivers.remove(receiver); } } diff --git a/src/main/java/arzumify/polyenergy/impl/ProviderDetails.java b/src/main/java/arzumify/polyenergy/impl/ProviderDetails.java index 1a009bd..420937b 100644 --- a/src/main/java/arzumify/polyenergy/impl/ProviderDetails.java +++ b/src/main/java/arzumify/polyenergy/impl/ProviderDetails.java @@ -15,7 +15,10 @@ public record ProviderDetails(ArrayList pointsOfPresence, EnergyProvider public boolean isInRange(ArrayList pointsOfPresence) { for (Vec3i point : pointsOfPresence) { for (Vec3i myPoint : this.pointsOfPresence) { - if (point.isWithinDistance(myPoint, this.range)) { + var manhattanDistance = point.getManhattanDistance(myPoint); + if (manhattanDistance == 0) { + return false; + } else if (manhattanDistance <= range) { return true; } } diff --git a/src/main/java/arzumify/polyenergy/util/SimpleBattery.java b/src/main/java/arzumify/polyenergy/util/SimpleBattery.java index 54e3e7b..1cf6708 100644 --- a/src/main/java/arzumify/polyenergy/util/SimpleBattery.java +++ b/src/main/java/arzumify/polyenergy/util/SimpleBattery.java @@ -5,22 +5,28 @@ import arzumify.polyenergy.api.EnergyReceiver; import arzumify.polyenergy.impl.CoordinateMatchMaker; import arzumify.polyenergy.impl.ProviderDetails; import arzumify.polyenergy.impl.ReceiverDetails; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents; import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArrayList; /** * A simple battery that can provide and receive energy. Has a range of 1 block in all directions (directly adjacent blocks). */ -public class SimpleBattery extends BlockEntity implements EnergyProvider, EnergyReceiver { +public class SimpleBattery extends BlockEntity implements EnergyProvider, EnergyReceiver, ServerBlockEntityEvents.Unload, ServerBlockEntityEvents.Load { private final long capacity; private final long inputRate; private final long outputRate; - private final ArrayList providers = new ArrayList<>(); + private final ProviderDetails providerDetails = ProviderDetails.NewSimple(pos, this); + private final ReceiverDetails receiverDetails = ReceiverDetails.NewSimple(pos, this); + private final CopyOnWriteArrayList providers = new CopyOnWriteArrayList<>(); private long energy = 0; public SimpleBattery(BlockEntityType type, BlockPos pos, BlockState state, long capacity, long inputRate, long outputRate) { @@ -28,18 +34,10 @@ public class SimpleBattery extends BlockEntity implements EnergyProvider, Energy this.capacity = capacity; this.inputRate = inputRate; this.outputRate = outputRate; - CoordinateMatchMaker.addProvider(ProviderDetails.NewSimple(pos, this)); - CoordinateMatchMaker.addReceiver(ReceiverDetails.NewSimple(pos, this)); + CoordinateMatchMaker.addProvider(providerDetails); + CoordinateMatchMaker.addReceiver(receiverDetails); } - /** - * When called by a receiver, attempts to remove energy from the provider. - * Do not attempt to extract energy until {@link EnergyReceiver#ready(EnergyProvider)} is called by the receiver. - * - * @param amount The target amount of energy to extract - * @param receiver The receiver that is attempting to extract energy - * @return The amount of energy actually extracted. If zero, the receiver will not attempt to extract energy until {@link EnergyReceiver#ready(EnergyProvider)} is called. - */ @Override public long extract(long amount, EnergyReceiver receiver) { long extracted = Math.min(Math.min(outputRate, amount), energy); @@ -47,41 +45,44 @@ public class SimpleBattery extends BlockEntity implements EnergyProvider, Energy return extracted; } - /** - * When called by the server matchmaker, indicates that this is a possible receiver to call {@link EnergyReceiver#ready(EnergyProvider)} on. - * This particular overload is for coordinate-based matchmaking. - * - * @param receiver The receiver that exists. - * @param pointsOfPresence The points of presence of the receiver (can be multiple, e.g. for a network of wires). - */ @Override public void exists(EnergyReceiver receiver, ArrayList pointsOfPresence) { receiver.ready(this); } - /** - * When called by a provider, indicates that the provider is ready to have energy extracted from it. - * Do not attempt to call {@link EnergyProvider#extract(long, EnergyReceiver)} on any provider until this method is called. - * - * @param provider The provider that is ready to provide energy to this receiver - */ @Override public void ready(EnergyProvider provider) { providers.add(provider); } - /** - * Called every tick to update the battery's energy level. - */ - public void tick() { - var leftToFill = Math.min(inputRate, capacity - energy); - for (EnergyProvider provider : providers) { - if (leftToFill == 0) { + public static void tick(World world, BlockPos pos, BlockState state, SimpleBattery battery) { + var leftToFill = Math.min(battery.inputRate, battery.capacity - battery.energy); + for (EnergyProvider provider : battery.providers) { + // Less than zero shouldn't be possible, but just in case... + if (leftToFill <= 0) { break; } - long extracted = provider.extract(leftToFill, this); - energy += extracted; + long extracted = provider.extract(leftToFill, battery); + battery.energy += extracted; leftToFill -= extracted; } } + + @Override + public void unready(EnergyProvider provider) { + providers.remove(provider); + } + + @Override + public void onLoad(BlockEntity blockEntity, ServerWorld serverWorld) { + CoordinateMatchMaker.addProvider(providerDetails); + CoordinateMatchMaker.addReceiver(receiverDetails); + } + + @Override + public void onUnload(BlockEntity blockEntity, ServerWorld serverWorld) { + CoordinateMatchMaker.removeProvider(providerDetails); + CoordinateMatchMaker.removeReceiver(receiverDetails); + providers.clear(); + } } diff --git a/src/main/resources/assets/polyenergy/icon-scaled.png b/src/main/resources/assets/polyenergy/icon-scaled.png new file mode 100644 index 0000000000000000000000000000000000000000..067ba6b7499ea79ae37e6c096a2674637cff44d7 GIT binary patch literal 12473 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 a&-`~~NYweK)*Qej#o+1c=d#Wzp$Pz5+t^|N diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 3040eed..8b511c8 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -12,7 +12,7 @@ "sources": "https://git.ailur.dev/arzumify/polyenergy" }, "license": "GPL-3.0", - "icon": "assets/polyenergy/icon.png", + "icon": "assets/polyenergy/icon-scaled.png", "environment": "*", "entrypoints": { "main": [ diff --git a/src/test/java/EnergyTests.java b/src/test/java/EnergyTests.java new file mode 100644 index 0000000..dba9ad8 --- /dev/null +++ b/src/test/java/EnergyTests.java @@ -0,0 +1,329 @@ +import arzumify.polyenergy.impl.CoordinateMatchMaker; +import net.minecraft.util.math.Vec3i; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EnergyTests { + @BeforeEach + public void setup() { + // Delete all batteries + CoordinateMatchMaker.reset(); + System.out.println("Reset CoordinateMatchMaker"); + } + + @Test + public void testEnergyTransfer() { + // Create two batteries with coordinates (0, 0, 0) and (1, 0, 0) + // Battery1 should be able to provide energy to battery2 + var battery1 = new SimpleCodeOnlyBattery(new Vec3i(0, 0, 0), 16, 0, 1, "Battery1"); + var battery2 = new SimpleCodeOnlyBattery(new Vec3i(1, 0, 0), 16, 1, 0, "Battery2"); + + // Give battery1 some energy + battery1.energy = 16; + + // Tick the batteries 10 times + for (int i = 0; i < 10; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Output the energy of the batteries + System.out.println("Battery1 energy: " + battery1.energy); + System.out.println("Battery2 energy: " + battery2.energy); + + // Battery1 should have lost 10 energy and battery2 should have gained 10 energy + assertEquals(6, battery1.energy); + assertEquals(10, battery2.energy); + } + + @Test + public void testUnloadBattery1() { + // Create two batteries with coordinates (0, 0, 0) and (1, 0, 0) + // Battery1 should be able to provide energy to battery2 + var battery1 = new SimpleCodeOnlyBattery(new Vec3i(0, 0, 0), 16, 0, 1, "Battery1"); + var battery2 = new SimpleCodeOnlyBattery(new Vec3i(1, 0, 0), 16, 1, 0, "Battery2"); + + // Give battery1 some energy + battery1.energy = 16; + + // Tick 5 times + for (int i = 0; i < 5; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Unload battery1 + System.out.println("Unloading battery1"); + battery1.onUnload(null, null); + + // Tick 5 more times + for (int i = 0; i < 5; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Output the energy of the batteries + System.out.println("Battery1 energy: " + battery1.energy); + System.out.println("Battery2 energy: " + battery2.energy); + + // Battery1 should have lost 5 energy and battery2 should have gained 5 energy + assertEquals(11, battery1.energy); + assertEquals(5, battery2.energy); + } + + @Test + public void testUnloadBattery2() { + // Create two batteries with coordinates (0, 0, 0) and (1, 0, 0) + // Battery1 should be able to provide energy to battery2 + var battery1 = new SimpleCodeOnlyBattery(new Vec3i(0, 0, 0), 16, 0, 1, "Battery1"); + var battery2 = new SimpleCodeOnlyBattery(new Vec3i(1, 0, 0), 16, 1, 0, "Battery2"); + + // Give battery1 some energy + battery1.energy = 16; + + // Tick 5 times + for (int i = 0; i < 5; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Unload battery2 + System.out.println("Unloading battery2"); + battery2.onUnload(null, null); + + // Tick 5 more times + for (int i = 0; i < 5; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Output the energy of the batteries + System.out.println("Battery1 energy: " + battery1.energy); + System.out.println("Battery2 energy: " + battery2.energy); + + // Battery1 should have lost 5 energy and battery2 should have gained 5 energy + assertEquals(11, battery1.energy); + assertEquals(5, battery2.energy); + } + + @Test + public void testUnloadThenReloadBattery1() { + // Create two batteries with coordinates (0, 0, 0) and (1, 0, 0) + // Battery1 should be able to provide energy to battery2 + var battery1 = new SimpleCodeOnlyBattery(new Vec3i(0, 0, 0), 16, 0, 1, "Battery1"); + var battery2 = new SimpleCodeOnlyBattery(new Vec3i(1, 0, 0), 16, 1, 0, "Battery2"); + + // Give battery1 some energy + battery1.energy = 16; + + // Tick 5 times + for (int i = 0; i < 5; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Unload battery1 + System.out.println("Unloading battery1"); + battery1.onUnload(null, null); + + // Tick 5 more times + for (int i = 0; i < 5; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Reload battery1 + System.out.println("Reloading battery1"); + battery1.onLoad(null, null); + + // Tick 5 more times + for (int i = 0; i < 5; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Output the energy of the batteries + System.out.println("Battery1 energy: " + battery1.energy); + System.out.println("Battery2 energy: " + battery2.energy); + + // Battery1 should have lost 10 energy and battery2 should have gained 10 energy + assertEquals(6, battery1.energy); + assertEquals(10, battery2.energy); + } + + @Test + public void testUnloadThenReloadBattery2() { + // Create two batteries with coordinates (0, 0, 0) and (1, 0, 0) + // Battery1 should be able to provide energy to battery2 + var battery1 = new SimpleCodeOnlyBattery(new Vec3i(0, 0, 0), 16, 0, 1, "Battery1"); + var battery2 = new SimpleCodeOnlyBattery(new Vec3i(1, 0, 0), 16, 1, 0, "Battery2"); + + // Give battery1 some energy + battery1.energy = 16; + + // Tick 5 times + for (int i = 0; i < 5; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Unload battery2 + System.out.println("Unloading battery2"); + battery2.onUnload(null, null); + + // Tick 5 more times + for (int i = 0; i < 5; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Reload battery2 + System.out.println("Reloading battery2"); + battery2.onLoad(null, null); + + // Tick 5 more times + for (int i = 0; i < 5; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Output the energy of the batteries + System.out.println("Battery1 energy: " + battery1.energy); + System.out.println("Battery2 energy: " + battery2.energy); + + // Battery1 should have lost 10 energy and battery2 should have gained 10 energy + assertEquals(6, battery1.energy); + assertEquals(10, battery2.energy); + } + + @Test + public void twoHalfFullBatteries() { + System.out.println("Two half-full batteries"); + // Create two batteries with coordinates (0, 0, 0) and (1, 0, 0) + // They should both provide energy to each other and stay at equilibrium + var battery1 = new SimpleCodeOnlyBattery(new Vec3i(0, 0, 0), 16, 1, 1, "Battery1"); + var battery2 = new SimpleCodeOnlyBattery(new Vec3i(1, 0, 0), 16, 1, 1, "Battery2"); + + // Give battery1 and battery2 some energy + battery1.energy = 8; + battery2.energy = 8; + + // Tick 10 times + for (int i = 0; i < 10; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Output the energy of the batteries + System.out.println("Battery1 energy: " + battery1.energy); + System.out.println("Battery2 energy: " + battery2.energy); + + // Battery1 and battery2 should have lost no energy + assertEquals(8, battery1.energy); + assertEquals(8, battery2.energy); + } + + @Test + public void middleOneCharges() { + // Create three batteries with coordinates (0, 0, 0), (1, 0, 0), and (2, 0, 0) + // Battery2 should provide energy to battery1 and battery3 + var battery1 = new SimpleCodeOnlyBattery(new Vec3i(0, 0, 0), 16, 1, 0, "Battery1"); + var battery2 = new SimpleCodeOnlyBattery(new Vec3i(1, 0, 0), 16, 0, 1, "Battery2"); + var battery3 = new SimpleCodeOnlyBattery(new Vec3i(2, 0, 0), 16, 1, 0, "Battery3"); + + // Give battery2 some energy + battery2.energy = 16; + + // Tick 10 times + for (int i = 0; i < 10; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + SimpleCodeOnlyBattery.tick(battery3); + } + + // Output the energy of the batteries + System.out.println("Battery1 energy: " + battery1.energy); + System.out.println("Battery2 energy: " + battery2.energy); + System.out.println("Battery3 energy: " + battery3.energy); + + // Battery1 and battery3 should have gained 8 energy and battery2 should have lost 16 energy + assertEquals(8, battery1.energy); + assertEquals(0, battery2.energy); + assertEquals(8, battery3.energy); + } + + @Test + public void middleOneReceives() { + // Create three batteries with coordinates (0, 0, 0), (1, 0, 0), and (2, 0, 0) + // Battery1 and battery3 should provide energy to battery2 + var battery1 = new SimpleCodeOnlyBattery(new Vec3i(0, 0, 0), 16, 0, 1, "Battery1"); + var battery2 = new SimpleCodeOnlyBattery(new Vec3i(1, 0, 0), 16, 1, 0, "Battery2"); + var battery3 = new SimpleCodeOnlyBattery(new Vec3i(2, 0, 0), 16, 0, 1, "Battery3"); + + // Give battery1 and battery3 some energy + battery1.energy = 8; + battery3.energy = 8; + + // Tick 10 times + for (int i = 0; i < 16; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + SimpleCodeOnlyBattery.tick(battery3); + } + + // Output the energy of the batteries + System.out.println("Battery1 energy: " + battery1.energy); + System.out.println("Battery2 energy: " + battery2.energy); + System.out.println("Battery3 energy: " + battery3.energy); + + // Battery2 should have 16 energy and battery1 and battery3 should have 0 energy + assertEquals(0, battery1.energy); + assertEquals(16, battery2.energy); + assertEquals(0, battery3.energy); + } + + @Test + public void testEnergyTransferWithRange() { + // Create two batteries with coordinates (0, 0, 0) and (2, 0, 0) + // Battery1 should be able to provide energy to battery2 + var battery1 = new SimpleCodeOnlyBattery(new Vec3i(0, 0, 0), 16, 0, 1, "Battery1"); + var battery2 = new SimpleCodeOnlyBattery(new Vec3i(2, 0, 0), 16, 1, 0, "Battery2"); + + // Give battery1 some energy + battery1.energy = 16; + + // Tick the batteries 10 times + for (int i = 0; i < 10; i++) { + SimpleCodeOnlyBattery.tick(battery1); + SimpleCodeOnlyBattery.tick(battery2); + } + + // Output the energy of the batteries + System.out.println("Battery1 energy: " + battery1.energy); + System.out.println("Battery2 energy: " + battery2.energy); + + // Battery1 should have lost no energy and battery2 should have gained no energy + assertEquals(16, battery1.energy); + assertEquals(0, battery2.energy); + } + + @Test + public void stressTest() { + // Create 1000 batteries with coordinates (0, 0, 0) to (999, 0, 0) + // Each battery should be able to provide energy to the next battery + var batteries = new SimpleCodeOnlyBattery[1000]; + for (int i = 0; i < 1000; i++) { + batteries[i] = new SimpleCodeOnlyBattery(new Vec3i(i, 0, 0), 16, 1, 2, "Battery" + i); + } + + // Give the first battery some energy + batteries[0].energy = 16; + + // Tick once + for (SimpleCodeOnlyBattery battery : batteries) { + SimpleCodeOnlyBattery.tick(battery); + } + } +} diff --git a/src/test/java/SimpleCodeOnlyBattery.java b/src/test/java/SimpleCodeOnlyBattery.java new file mode 100644 index 0000000..f75c877 --- /dev/null +++ b/src/test/java/SimpleCodeOnlyBattery.java @@ -0,0 +1,97 @@ +import arzumify.polyenergy.api.EnergyProvider; +import arzumify.polyenergy.api.EnergyReceiver; +import arzumify.polyenergy.impl.CoordinateMatchMaker; +import arzumify.polyenergy.impl.ProviderDetails; +import arzumify.polyenergy.impl.ReceiverDetails; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.Vec3i; + +import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * SimpleBattery, but implemented using only the API and not the actual block entity for testing purposes. + * All fields are public for testing purposes. + */ +public class SimpleCodeOnlyBattery implements EnergyProvider, EnergyReceiver, ServerBlockEntityEvents.Unload, ServerBlockEntityEvents.Load { + public final long capacity; + public final long inputRate; + public final long outputRate; + public final String name; + public final ProviderDetails providerDetails; + public final ReceiverDetails receiverDetails; + public final CopyOnWriteArrayList providers = new CopyOnWriteArrayList<>(); + public long energy = 0; + + public SimpleCodeOnlyBattery(Vec3i pos, long capacity, long inputRate, long outputRate, String name) { + this.capacity = capacity; + this.inputRate = inputRate; + this.outputRate = outputRate; + this.providerDetails = ProviderDetails.NewSimple(pos, this); + this.receiverDetails = ReceiverDetails.NewSimple(pos, this); + this.name = name; + CoordinateMatchMaker.addProvider(providerDetails); + CoordinateMatchMaker.addReceiver(receiverDetails); + } + + public static void tick(SimpleCodeOnlyBattery battery) { + System.out.println(battery.name + " ticking"); + var leftToFill = Math.min(battery.inputRate, battery.capacity - battery.energy); + for (EnergyProvider provider : battery.providers) { + // Less than zero shouldn't be possible, but just in case... + if (leftToFill <= 0) { + break; + } + long extracted = provider.extract(leftToFill, battery); + if (extracted == 0) { + battery.providers.remove(provider); + } + battery.energy += extracted; + leftToFill -= extracted; + } + } + + @Override + public long extract(long amount, EnergyReceiver receiver) { + System.out.println(name + " extracting " + amount + " energy"); + long extracted = Math.min(Math.min(outputRate, amount), energy); + energy -= extracted; + System.out.println(name + " extracted " + extracted + " energy"); + if (energy == 0) { + CoordinateMatchMaker.removeProvider(providerDetails); + } + return extracted; + } + + @Override + public void exists(EnergyReceiver receiver, ArrayList pointsOfPresence) { + System.out.println(name + " found other receiver"); + receiver.ready(this); + } + + @Override + public void ready(EnergyProvider provider) { + System.out.println(name + " found other provider"); + providers.add(provider); + } + + @Override + public void unready(EnergyProvider provider) { + providers.remove(provider); + } + + @Override + public void onLoad(BlockEntity blockEntity, ServerWorld serverWorld) { + CoordinateMatchMaker.addProvider(providerDetails); + CoordinateMatchMaker.addReceiver(receiverDetails); + } + + @Override + public void onUnload(BlockEntity blockEntity, ServerWorld serverWorld) { + CoordinateMatchMaker.removeProvider(providerDetails); + CoordinateMatchMaker.removeReceiver(receiverDetails); + providers.clear(); + } +}