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.

This commit is contained in:
Tracker-Friendly 2025-02-23 14:50:16 +00:00
parent b66a76217c
commit 69746e0faa
13 changed files with 547 additions and 53 deletions

View file

@ -13,10 +13,30 @@ A dead-simple energy mod for Fabric.
### Installation ### 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 ### 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
Providers are the source of energy in the game. They can be blocks, entities, or any other object that can provide Providers are the source of energy in the game. They can be blocks, entities, or any other object that can provide

View file

@ -38,6 +38,16 @@ dependencies {
// Fabric API. This is technically optional, but you probably want it anyway. // Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 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 { processResources {

View file

@ -7,7 +7,7 @@ minecraft_version=1.21.4
yarn_mappings=1.21.4+build.8 yarn_mappings=1.21.4+build.8
loader_version=0.16.10 loader_version=0.16.10
# Mod Properties # Mod Properties
mod_version=1.0.0 mod_version=1.1.0
maven_group=arzumify.polyenergy maven_group=arzumify.polyenergy
archives_base_name=polyenergy archives_base_name=polyenergy
# Dependencies # Dependencies

View file

@ -3,10 +3,6 @@ package arzumify.polyenergy;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
public class Polyenergy implements 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 @Override
public void onInitialize() { public void onInitialize() {
} }

View file

@ -9,4 +9,12 @@ public interface EnergyReceiver {
* @param provider The provider that exists and is ready to provide energy to this receiver. * @param provider The provider that exists and is ready to provide energy to this receiver.
*/ */
void ready(EnergyProvider provider); 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);
} }

View file

@ -1,31 +1,61 @@
package arzumify.polyenergy.impl; package arzumify.polyenergy.impl;
import java.util.ArrayList; 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 { public class CoordinateMatchMaker {
private static final ArrayList<ProviderDetails> providers = new ArrayList<>(); private static final ArrayList<ProviderDetails> providers = new ArrayList<>();
private static final ArrayList<ReceiverDetails> receivers = new ArrayList<>(); private static final ArrayList<ReceiverDetails> receivers = new ArrayList<>();
private static final HashMap<ProviderDetails, ArrayList<ReceiverDetails>> matches = new HashMap<>();
public static void update() { public static void reset() {
for (ReceiverDetails receiver : receivers) { providers.clear();
for (ProviderDetails provider : providers) { receivers.clear();
if (provider.isInRange(receiver.pointsOfPresence())) { matches.clear();
provider.provider().exists(receiver.receiver(), provider.pointsOfPresence());
}
}
}
} }
public static void addProvider(ProviderDetails provider) { public static void addProvider(ProviderDetails provider) {
providers.add(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) { public static void addReceiver(ReceiverDetails receiver) {
receivers.add(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);
} }
} }

View file

@ -15,7 +15,10 @@ public record ProviderDetails(ArrayList<Vec3i> pointsOfPresence, EnergyProvider
public boolean isInRange(ArrayList<Vec3i> pointsOfPresence) { public boolean isInRange(ArrayList<Vec3i> pointsOfPresence) {
for (Vec3i point : pointsOfPresence) { for (Vec3i point : pointsOfPresence) {
for (Vec3i myPoint : this.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; return true;
} }
} }

View file

@ -5,22 +5,28 @@ import arzumify.polyenergy.api.EnergyReceiver;
import arzumify.polyenergy.impl.CoordinateMatchMaker; import arzumify.polyenergy.impl.CoordinateMatchMaker;
import arzumify.polyenergy.impl.ProviderDetails; import arzumify.polyenergy.impl.ProviderDetails;
import arzumify.polyenergy.impl.ReceiverDetails; import arzumify.polyenergy.impl.ReceiverDetails;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType; import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3i; import net.minecraft.util.math.Vec3i;
import net.minecraft.world.World;
import java.util.ArrayList; 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). * 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 capacity;
private final long inputRate; private final long inputRate;
private final long outputRate; private final long outputRate;
private final ArrayList<EnergyProvider> providers = new ArrayList<>(); private final ProviderDetails providerDetails = ProviderDetails.NewSimple(pos, this);
private final ReceiverDetails receiverDetails = ReceiverDetails.NewSimple(pos, this);
private final CopyOnWriteArrayList<EnergyProvider> providers = new CopyOnWriteArrayList<>();
private long energy = 0; private long energy = 0;
public SimpleBattery(BlockEntityType<?> type, BlockPos pos, BlockState state, long capacity, long inputRate, long outputRate) { 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.capacity = capacity;
this.inputRate = inputRate; this.inputRate = inputRate;
this.outputRate = outputRate; this.outputRate = outputRate;
CoordinateMatchMaker.addProvider(ProviderDetails.NewSimple(pos, this)); CoordinateMatchMaker.addProvider(providerDetails);
CoordinateMatchMaker.addReceiver(ReceiverDetails.NewSimple(pos, this)); 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 @Override
public long extract(long amount, EnergyReceiver receiver) { public long extract(long amount, EnergyReceiver receiver) {
long extracted = Math.min(Math.min(outputRate, amount), energy); long extracted = Math.min(Math.min(outputRate, amount), energy);
@ -47,41 +45,44 @@ public class SimpleBattery extends BlockEntity implements EnergyProvider, Energy
return extracted; 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 @Override
public void exists(EnergyReceiver receiver, ArrayList<Vec3i> pointsOfPresence) { public void exists(EnergyReceiver receiver, ArrayList<Vec3i> pointsOfPresence) {
receiver.ready(this); 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 @Override
public void ready(EnergyProvider provider) { public void ready(EnergyProvider provider) {
providers.add(provider); providers.add(provider);
} }
/** public static void tick(World world, BlockPos pos, BlockState state, SimpleBattery battery) {
* Called every tick to update the battery's energy level. var leftToFill = Math.min(battery.inputRate, battery.capacity - battery.energy);
*/ for (EnergyProvider provider : battery.providers) {
public void tick() { // Less than zero shouldn't be possible, but just in case...
var leftToFill = Math.min(inputRate, capacity - energy); if (leftToFill <= 0) {
for (EnergyProvider provider : providers) {
if (leftToFill == 0) {
break; break;
} }
long extracted = provider.extract(leftToFill, this); long extracted = provider.extract(leftToFill, battery);
energy += extracted; battery.energy += extracted;
leftToFill -= 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();
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 633 B

After

Width:  |  Height:  |  Size: 410 B

View file

@ -12,7 +12,7 @@
"sources": "https://git.ailur.dev/arzumify/polyenergy" "sources": "https://git.ailur.dev/arzumify/polyenergy"
}, },
"license": "GPL-3.0", "license": "GPL-3.0",
"icon": "assets/polyenergy/icon.png", "icon": "assets/polyenergy/icon-scaled.png",
"environment": "*", "environment": "*",
"entrypoints": { "entrypoints": {
"main": [ "main": [

View file

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

View file

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