22
33import java .util .UUID ;
44import java .util .function .Supplier ;
5- import mekanism .api .functions .TriConsumer ;
5+ import mekanism .api .functions .ConstantPredicates ;
6+ import mekanism .common .Mekanism ;
67import mekanism .common .content .network .transmitter .Transmitter ;
78import mekanism .common .lib .transmitter .DynamicNetwork ;
89import mekanism .common .registries .MekanismBlocks ;
910import mekanism .common .tests .MekanismTests ;
1011import mekanism .common .tests .util .GameTestUtils ;
1112import mekanism .common .tile .transmitter .TileEntityTransmitter ;
13+ import mekanism .common .util .WorldUtils ;
1214import net .minecraft .SharedConstants ;
1315import net .minecraft .core .BlockPos ;
1416import 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 ;
1522import net .minecraft .world .level .ChunkPos ;
1623import net .minecraft .world .level .block .Blocks ;
1724import net .minecraft .world .level .levelgen .structure .templatesystem .StructureTemplate ;
25+ import net .neoforged .neoforge .event .level .ChunkEvent ;
26+ import net .neoforged .testframework .DynamicTest ;
1827import net .neoforged .testframework .annotation .ForEachTest ;
1928import net .neoforged .testframework .annotation .RegisterStructureTemplate ;
2029import net .neoforged .testframework .annotation .TestHolder ;
2130import net .neoforged .testframework .gametest .ExtendedGameTestHelper ;
2231import net .neoforged .testframework .gametest .StructureTemplateBuilder ;
23- import org .jetbrains .annotations .NotNull ;
2432import 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}
0 commit comments