Skip to content

Commit f8efd06

Browse files
committed
Restructure transmitter chunk reloading tests so that the event listeners are only present for the test
1 parent 5fa4404 commit f8efd06

File tree

5 files changed

+206
-285
lines changed

5 files changed

+206
-285
lines changed

src/gameTest/java/mekanism/common/tests/network/InventoryNetworkTest.java

Lines changed: 4 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
package mekanism.common.tests.network;
22

3+
import static mekanism.common.tests.util.TransporterTestUtils.colored;
4+
import static mekanism.common.tests.util.TransporterTestUtils.configured;
5+
import static mekanism.common.tests.util.TransporterTestUtils.containing;
6+
37
import java.util.function.Supplier;
4-
import mekanism.api.NBTConstants;
58
import mekanism.api.text.EnumColor;
69
import mekanism.api.tier.AlloyTier;
710
import mekanism.common.lib.transmitter.ConnectionType;
811
import mekanism.common.registries.MekanismBlocks;
912
import mekanism.common.registries.MekanismItems;
1013
import mekanism.common.tests.MekanismTests;
1114
import mekanism.common.tests.util.GameTestUtils;
12-
import mekanism.common.util.EnumUtils;
13-
import mekanism.common.util.NBTUtils;
1415
import net.minecraft.SharedConstants;
1516
import net.minecraft.core.BlockPos;
1617
import net.minecraft.core.Direction;
1718
import net.minecraft.gametest.framework.GameTest;
18-
import net.minecraft.nbt.CompoundTag;
19-
import net.minecraft.nbt.ListTag;
2019
import net.minecraft.world.entity.player.Player;
2120
import net.minecraft.world.item.ItemStack;
2221
import net.minecraft.world.item.Items;
23-
import net.minecraft.world.level.ItemLike;
2422
import net.minecraft.world.level.block.Blocks;
2523
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
2624
import net.neoforged.testframework.DynamicTest;
@@ -29,7 +27,6 @@
2927
import net.neoforged.testframework.annotation.TestHolder;
3028
import net.neoforged.testframework.gametest.ExtendedGameTestHelper;
3129
import net.neoforged.testframework.gametest.StructureTemplateBuilder;
32-
import org.jetbrains.annotations.Nullable;
3330

3431
@ForEachTest(groups = "network.inventory")
3532
public class InventoryNetworkTest {
@@ -56,64 +53,6 @@ public class InventoryNetworkTest {
5653
.fill(9, 0, 0, 9, 0, 1, MekanismBlocks.BASIC_LOGISTICAL_TRANSPORTER.getBlock().defaultBlockState())
5754
);
5855

59-
60-
private static CompoundTag containing(ItemLike itemLike) {
61-
return containing(new ItemStack(itemLike));
62-
}
63-
64-
private static CompoundTag containing(ItemLike itemLike, int amount) {
65-
return containing(new ItemStack(itemLike, amount));
66-
}
67-
68-
private static CompoundTag containing(ItemStack... stacks) {
69-
CompoundTag tag = new CompoundTag();
70-
ListTag items = new ListTag();
71-
for (int i = 0; i < stacks.length; i++) {
72-
CompoundTag item = stacks[i].save(new CompoundTag());
73-
item.putByte(NBTConstants.SLOT, (byte) i);
74-
items.add(item);
75-
}
76-
tag.put(NBTConstants.ITEMS, items);
77-
return tag;
78-
}
79-
80-
@Nullable
81-
private static CompoundTag colored(EnumColor color) {
82-
return colored(color, null);
83-
}
84-
85-
@Nullable
86-
private static CompoundTag colored(@Nullable EnumColor color, @Nullable Direction pull) {
87-
return configured(color, pull, ConnectionType.PULL);
88-
}
89-
90-
@Nullable
91-
private static CompoundTag configured(Direction side) {
92-
return configured(side, ConnectionType.PULL);
93-
}
94-
95-
@Nullable
96-
private static CompoundTag configured(Direction side, ConnectionType connectionType) {
97-
return configured(null, side, connectionType);
98-
}
99-
100-
@Nullable
101-
private static CompoundTag configured(@Nullable EnumColor color, @Nullable Direction side, ConnectionType connectionType) {
102-
if (color == null && side == null) {
103-
return null;
104-
}
105-
CompoundTag tag = new CompoundTag();
106-
if (color != null) {
107-
NBTUtils.writeEnum(tag, NBTConstants.COLOR, color);
108-
}
109-
if (side != null) {
110-
int[] raw = new int[EnumUtils.DIRECTIONS.length];
111-
raw[side.ordinal()] = connectionType.ordinal();
112-
tag.putIntArray(NBTConstants.CONNECTION, raw);
113-
}
114-
return tag;
115-
}
116-
11756
//TODO: Do we want to somehow test the case of when we add a shorter path to the same destination, as the newly pulled items go via the new shorter path
11857
// but any already en-route ones continue the original way
11958

src/gameTest/java/mekanism/common/tests/network/TransmitterNetworkTest.java

Lines changed: 125 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,33 @@
22

33
import java.util.UUID;
44
import java.util.function.Supplier;
5-
import mekanism.api.functions.TriConsumer;
5+
import mekanism.api.functions.ConstantPredicates;
6+
import mekanism.common.Mekanism;
67
import mekanism.common.content.network.transmitter.Transmitter;
78
import mekanism.common.lib.transmitter.DynamicNetwork;
89
import mekanism.common.registries.MekanismBlocks;
910
import mekanism.common.tests.MekanismTests;
1011
import mekanism.common.tests.util.GameTestUtils;
1112
import mekanism.common.tile.transmitter.TileEntityTransmitter;
13+
import mekanism.common.util.WorldUtils;
1214
import net.minecraft.SharedConstants;
1315
import net.minecraft.core.BlockPos;
1416
import net.minecraft.gametest.framework.GameTest;
17+
import net.minecraft.server.level.ChunkHolder;
18+
import net.minecraft.server.level.ChunkLevel;
19+
import net.minecraft.server.level.ChunkMap;
20+
import net.minecraft.server.level.DistanceManager;
21+
import net.minecraft.server.level.ServerLevel;
1522
import net.minecraft.world.level.ChunkPos;
1623
import net.minecraft.world.level.block.Blocks;
1724
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
25+
import net.neoforged.neoforge.event.level.ChunkEvent;
26+
import net.neoforged.testframework.DynamicTest;
1827
import net.neoforged.testframework.annotation.ForEachTest;
1928
import net.neoforged.testframework.annotation.RegisterStructureTemplate;
2029
import net.neoforged.testframework.annotation.TestHolder;
2130
import net.neoforged.testframework.gametest.ExtendedGameTestHelper;
2231
import net.neoforged.testframework.gametest.StructureTemplateBuilder;
23-
import org.jetbrains.annotations.NotNull;
2432
import org.jetbrains.annotations.Nullable;
2533

2634
@ForEachTest(groups = "network.transmitter")
@@ -38,8 +46,22 @@ public class TransmitterNetworkTest {
3846

3947
@GameTest(template = STRAIGHT_CABLE, setupTicks = SETUP_TICKS, batch = "1")
4048
@TestHolder(description = "Tests that reloading intermediary chunks does not cause a network to break.")
41-
public static void reloadIntermediary(final ExtendedGameTestHelper helper) {
42-
GameTestUtils.succeedIfAfterReload(helper, new ChunkPos(1, 0), new MatchingNetworkValidator(helper));
49+
public static void reloadIntermediary(final DynamicTest test) {
50+
final ChunkData chunkData = new ChunkData(1, 0);
51+
test.eventListeners().forge().addListener((final ChunkEvent.Unload event) -> chunkData.updateChunk(event, false));
52+
test.eventListeners().forge().addListener((final ChunkEvent.Load event) -> chunkData.updateChunk(event, true));
53+
54+
test.onGameTest(helper -> helper.startSequence()
55+
.thenMap(() -> chunkData.updateChunkLoading(helper, false, GameTestUtils.UNLOAD_LEVEL))
56+
.thenWaitUntil(() -> chunkData.waitFor(helper, false))
57+
//Wait 5 ticks in case anything needs more time to process after the chunk unloads
58+
.thenExecuteAfter(5, level -> chunkData.updateChunkLoading(helper, true, level))
59+
.thenWaitUntil(() -> chunkData.waitFor(helper, true))
60+
//Wait 5 ticks in case anything needs more time to process after the chunk loads
61+
.thenIdle(5)
62+
.thenExecute(new MatchingNetworkValidator(helper))
63+
.thenSucceed()
64+
);
4365
}
4466

4567
/**
@@ -68,40 +90,10 @@ public static void inaccessibleNotUnloaded(final ExtendedGameTestHelper helper)
6890
.thenExecuteAfter(5, level -> GameTestUtils.setChunkLoadLevel(helper, relativeChunk, level))
6991
//Wait 5 ticks in case anything needs more time to process after the chunk loads
7092
.thenIdle(5)
71-
.thenWaitUntil(0, new MatchingNetworkValidator(helper))
93+
.thenExecute(new MatchingNetworkValidator(helper))
7294
.thenSucceed();
7395
}
7496

75-
private static void forEachTransmitter(ExtendedGameTestHelper helper, TriConsumer<TileEntityTransmitter, Transmitter<?, ?, ?>, BlockPos> consumer) {
76-
forEachTransmitter(helper, true, consumer);
77-
}
78-
79-
private static void forEachTransmitter(ExtendedGameTestHelper helper, boolean expectNetwork, TriConsumer<TileEntityTransmitter, Transmitter<?, ?, ?>, BlockPos> consumer) {
80-
helper.forEveryBlockInStructure(relativePos -> {
81-
TileEntityTransmitter blockEntity = getTransmitterNNAt(helper, relativePos);
82-
Transmitter<?, ?, ?> transmitter = blockEntity.getTransmitter();
83-
if (expectNetwork && !transmitter.hasTransmitterNetwork()) {
84-
helper.fail("No transmitter network found", relativePos);
85-
}
86-
consumer.accept(blockEntity, transmitter, relativePos);
87-
});
88-
}
89-
90-
@Nullable
91-
private static TileEntityTransmitter getTransmitterAt(ExtendedGameTestHelper helper, BlockPos relativePos) {
92-
return GameTestUtils.getBlockEntity(helper, TileEntityTransmitter.class, relativePos);
93-
}
94-
95-
@NotNull
96-
private static TileEntityTransmitter getTransmitterNNAt(ExtendedGameTestHelper helper, BlockPos relativePos) {
97-
TileEntityTransmitter transmitter = getTransmitterAt(helper, relativePos);
98-
if (transmitter == null) {
99-
helper.fail("Expected transmitter", relativePos);
100-
}
101-
//noinspection ConstantConditions (can't get heve if null as helper#fail throws an exception)
102-
return transmitter;
103-
}
104-
10597
private static class MatchingNetworkValidator implements Runnable {
10698

10799
private final ExtendedGameTestHelper helper;
@@ -113,14 +105,106 @@ public MatchingNetworkValidator(ExtendedGameTestHelper helper) {
113105

114106
@Override
115107
public void run() {
116-
forEachTransmitter(helper, (tile, transmitter, relativePos) -> {
117-
DynamicNetwork<?, ?, ?> network = transmitter.getTransmitterNetwork();
118-
if (networkUUID == null) {
119-
networkUUID = network.getUUID();
120-
} else if (!networkUUID.equals(network.getUUID())) {
121-
helper.fail("Multiple transmitter networks", relativePos);
108+
helper.forEveryBlockInStructure(relativePos -> {
109+
if (WorldUtils.isBlockLoaded(helper.getLevel(), helper.absolutePos(relativePos))) {
110+
Transmitter<?, ?, ?> transmitter = helper.requireBlockEntity(relativePos, TileEntityTransmitter.class).getTransmitter();
111+
if (!transmitter.hasTransmitterNetwork()) {
112+
helper.fail("No transmitter network found", relativePos);
113+
}
114+
DynamicNetwork<?, ?, ?> network = transmitter.getTransmitterNetwork();
115+
if (networkUUID == null) {
116+
networkUUID = network.getUUID();
117+
} else if (!networkUUID.equals(network.getUUID())) {
118+
helper.fail("Multiple transmitter networks", relativePos);
119+
}
120+
} else {
121+
helper.fail("Expected expected position to be loaded", relativePos);
122122
}
123123
});
124124
}
125125
}
126+
127+
private static class ChunkData {
128+
129+
private static final boolean DEBUG_CHUNK_LOADING = false;
130+
131+
private final ChunkPos relativePos;
132+
@Nullable
133+
private ChunkPos absolutePos;
134+
private long absPos;
135+
private boolean isLoaded;
136+
137+
public ChunkData(int x, int y) {
138+
this.relativePos = new ChunkPos(x, y);
139+
}
140+
141+
//TODO - GameTest: Can we make unloads not cause the game to crash if a player tries to run them in world? It crashes as we force unload a chunk that would be near the player and it crashes from a null pointer
142+
public int updateChunkLoading(ExtendedGameTestHelper helper, boolean load, int level) {
143+
if (absolutePos == null) {
144+
absolutePos = GameTestUtils.absolutePos(helper, relativePos);
145+
absPos = absolutePos.toLong();
146+
}
147+
ServerLevel serverLevel = helper.getLevel();
148+
if (WorldUtils.isChunkLoaded(serverLevel, absolutePos) != load) {
149+
//If the chunk isn't watched and is loaded we want to try and unload it
150+
ChunkMap chunkMap = serverLevel.getChunkSource().chunkMap;
151+
DistanceManager distanceManager = chunkMap.getDistanceManager();
152+
ChunkHolder holder = distanceManager.getChunk(absPos);
153+
//Watch the chunk and mark whether it is currently loaded or not
154+
if ((holder == null) == load) {
155+
//If it is loaded then we need to try and unload it
156+
isLoaded = !load;
157+
if (DEBUG_CHUNK_LOADING) {
158+
Mekanism.logger.info("Trying to {} chunk at: {}", load ? "load" : "unload", absolutePos);
159+
}
160+
if (load) {
161+
//Load the chunk to the level it was unloaded at
162+
holder = distanceManager.updateChunkScheduling(absPos, level, holder, GameTestUtils.UNLOAD_LEVEL);
163+
if (holder == null) {//Should never happen unless start value was unloaded
164+
fail(helper, "Error loading chunk");
165+
} else {
166+
//And ensure we schedule it based on the status (in general this should be ChunkStatus.FULL)
167+
chunkMap.schedule(holder, ChunkLevel.generationStatus(holder.getTicketLevel()));
168+
}
169+
} else {
170+
//If it is currently loaded, queue it for unload
171+
level = holder.getTicketLevel();
172+
distanceManager.updateChunkScheduling(absPos, GameTestUtils.UNLOAD_LEVEL, holder, level);
173+
//And then unload it
174+
chunkMap.processUnloads(ConstantPredicates.ALWAYS_TRUE);
175+
}
176+
} else if (DEBUG_CHUNK_LOADING) {
177+
//Note: Even with debug logging enabled odds are this case isn't even possible due to the earlier check to skip if unloaded
178+
Mekanism.logger.info("Trying to {} already {} chunk at: {}", load ? "load" : "unload", load ? "loaded" : "unloaded", absolutePos);
179+
}
180+
} else if (DEBUG_CHUNK_LOADING) {
181+
Mekanism.logger.info("Chunk at: {} is already {}", absolutePos, load ? "unloaded" : "loaded");
182+
}
183+
return level;
184+
}
185+
186+
public void updateChunk(ChunkEvent event, boolean loaded) {
187+
if (!event.getLevel().isClientSide() && event.getChunk().getPos().equals(absolutePos)) {
188+
//If we are watching the chunk and the loaded state isn't what we already had it as
189+
if (isLoaded != loaded) {
190+
isLoaded = loaded;
191+
if (DEBUG_CHUNK_LOADING) {
192+
Mekanism.logger.info("Chunk {}: {}", loaded ? "loaded" : "unloaded", absolutePos);
193+
}
194+
} else if (DEBUG_CHUNK_LOADING) {
195+
Mekanism.logger.info("Chunk was already {}: {}", loaded ? "loaded" : "unloaded", absolutePos);
196+
}
197+
}
198+
}
199+
200+
public void fail(ExtendedGameTestHelper helper, String message) {
201+
helper.fail(message + " at " + absolutePos + " (relative: " + relativePos + ")");
202+
}
203+
204+
public void waitFor(ExtendedGameTestHelper helper, boolean loaded) {
205+
if (isLoaded != loaded) {//If our loaded status does not match our desired one, keep throwing an exception until it does
206+
fail(helper, "Chunk has not been marked as " + (loaded ? "loaded" : "unloaded") + " yet");
207+
}
208+
}
209+
}
126210
}

src/gameTest/java/mekanism/common/tests/util/GameTestEventListeners.java

Lines changed: 0 additions & 49 deletions
This file was deleted.

0 commit comments

Comments
 (0)