mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 05:50:39 +00:00
CORRECTIONS
- this is the corrections from the afoemrnioned commits
This commit is contained in:
46 files changed
+2425
-256
No files matched your search
+122
@@ -20,6 +20,11 @@ import org.junit.Test;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -107,6 +112,61 @@ public class NativeStructureVolumeIndexTest {
|
||||
assertTrue(unreachable.resolve(null, 0, 0, 15, 15).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overlappingColdQueriesCoordinateBeforeResolvingOrigins() throws Exception {
|
||||
BlockingResolver resolver = new BlockingResolver();
|
||||
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(resolver);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
try {
|
||||
Future<KList<NativeStructureVolume>> first = executor.submit(() -> index.resolve(null, 0, 0, 15, 15));
|
||||
assertTrue(resolver.awaitFirstEntry());
|
||||
Future<KList<NativeStructureVolume>> adjacent = executor.submit(() -> index.resolve(null, 16, 0, 31, 15));
|
||||
|
||||
assertFalse(resolver.awaitSecondEntry(250));
|
||||
resolver.release();
|
||||
assertTrue(first.get(5, TimeUnit.SECONDS).isEmpty());
|
||||
assertTrue(adjacent.get(5, TimeUnit.SECONDS).isEmpty());
|
||||
assertEquals(306, resolver.resolutions());
|
||||
} finally {
|
||||
resolver.release();
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void distantColdQueriesCanResolveOriginsConcurrently() throws Exception {
|
||||
BlockingResolver resolver = new BlockingResolver();
|
||||
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(resolver);
|
||||
int distantChunkX = findDisjointWindow(0, 0);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
try {
|
||||
Future<KList<NativeStructureVolume>> first = executor.submit(() -> index.resolve(null, 0, 0, 15, 15));
|
||||
assertTrue(resolver.awaitFirstEntry());
|
||||
Future<KList<NativeStructureVolume>> distant = executor.submit(
|
||||
() -> index.resolve(null, distantChunkX << 4, 0, (distantChunkX << 4) + 15, 15));
|
||||
|
||||
assertTrue(resolver.awaitSecondEntry(5_000));
|
||||
resolver.release();
|
||||
assertTrue(first.get(5, TimeUnit.SECONDS).isEmpty());
|
||||
assertTrue(distant.get(5, TimeUnit.SECONDS).isEmpty());
|
||||
} finally {
|
||||
resolver.release();
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coordinatedResolutionPreservesCanonicalOriginOrder() {
|
||||
NativeStructureVolumeIndex index = NativeStructureVolumeIndex.forTesting(new OrderedResolver());
|
||||
|
||||
KList<NativeStructureVolume> volumes = index.resolve(null, 0, 0, 15, 15);
|
||||
|
||||
assertEquals(289, volumes.size());
|
||||
assertEquals("-8:-8", volumes.getFirst().structure());
|
||||
assertEquals("0:0", volumes.get(144).structure());
|
||||
assertEquals("8:8", volumes.getLast().structure());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void volumeResolutionNeverReadsChunkOrOwnershipState() throws Exception {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.nativeStructureVolumeIndexSource")));
|
||||
@@ -134,6 +194,16 @@ public class NativeStructureVolumeIndexTest {
|
||||
return new StructureStart(source, new ChunkPos(chunkX, chunkZ), 0, new PiecesContainer(List.of(piece)));
|
||||
}
|
||||
|
||||
private static int findDisjointWindow(int chunkX, int chunkZ) {
|
||||
long sourceMask = NativeStructureVolumeIndex.originWindowStripeMask(chunkX, chunkZ);
|
||||
for (int candidate = 64; candidate < 16_384; candidate += 17) {
|
||||
if ((sourceMask & NativeStructureVolumeIndex.originWindowStripeMask(candidate, chunkZ)) == 0L) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Unable to find a disjoint native structure window stripe");
|
||||
}
|
||||
|
||||
private static final class CountingResolver implements NativeStructureVolumeIndex.OriginResolver {
|
||||
private final AtomicInteger resolutions = new AtomicInteger();
|
||||
private final int originChunkX;
|
||||
@@ -157,4 +227,56 @@ public class NativeStructureVolumeIndexTest {
|
||||
return NativeStructureVolumeIndex.appendPieces(null, STRUCTURE_KEY, swampHut(chunkX, chunkZ, 4, 6));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BlockingResolver implements NativeStructureVolumeIndex.OriginResolver {
|
||||
private final AtomicInteger resolutions = new AtomicInteger();
|
||||
private final CountDownLatch firstEntry = new CountDownLatch(1);
|
||||
private final CountDownLatch secondEntry = new CountDownLatch(1);
|
||||
private final CountDownLatch release = new CountDownLatch(1);
|
||||
|
||||
private int resolutions() {
|
||||
return resolutions.get();
|
||||
}
|
||||
|
||||
private boolean awaitFirstEntry() throws InterruptedException {
|
||||
return firstEntry.await(5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private boolean awaitSecondEntry(long timeoutMillis) throws InterruptedException {
|
||||
return secondEntry.await(timeoutMillis, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void release() {
|
||||
release.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KList<NativeStructureVolume> volumesAt(Engine engine, int chunkX, int chunkZ) {
|
||||
int resolution = resolutions.incrementAndGet();
|
||||
if (resolution == 1) {
|
||||
firstEntry.countDown();
|
||||
try {
|
||||
if (!release.await(5, TimeUnit.SECONDS)) {
|
||||
throw new IllegalStateException("Timed out waiting to release native structure resolution");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException("Interrupted while coordinating native structure resolution", e);
|
||||
}
|
||||
} else if (resolution == 2) {
|
||||
secondEntry.countDown();
|
||||
}
|
||||
return NativeStructureVolume.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class OrderedResolver implements NativeStructureVolumeIndex.OriginResolver {
|
||||
@Override
|
||||
public KList<NativeStructureVolume> volumesAt(Engine engine, int chunkX, int chunkZ) {
|
||||
return new KList<>(new NativeStructureVolume(
|
||||
chunkX + ":" + chunkZ,
|
||||
0, 0, 0,
|
||||
15, 255, 15));
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
-31
@@ -37,6 +37,7 @@ import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.event.world.WorldSaveEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -61,7 +62,8 @@ import java.util.concurrent.TimeoutException;
|
||||
|
||||
public final class PendingWorldReplacementManager implements Listener {
|
||||
private static final WorldSlotKey OVERWORLD_KEY = WorldSlotKey.minecraft("overworld");
|
||||
private static final long SAFE_ENTRY_TIMEOUT_SECONDS = 30L;
|
||||
private static final long SAVED_LOCATION_INSPECTION_TIMEOUT_SECONDS = 30L;
|
||||
private static final long SAFE_ENTRY_TIMEOUT_SECONDS = 600L;
|
||||
|
||||
private final Iris plugin;
|
||||
private final Set<UUID> cleanupInFlight = new HashSet<>();
|
||||
@@ -70,6 +72,7 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
private volatile WorldReplacementEntryGuard.Entry overworldEntryGuard;
|
||||
private volatile Path overworldEntryWorldDirectory;
|
||||
private volatile CompletableFuture<Location> overworldSafeEntry;
|
||||
private volatile CompletableFuture<Void> overworldSpawnPersistence;
|
||||
private volatile UUID overworldEntryAwaitingVerification;
|
||||
private volatile World overworldEntryWorld;
|
||||
private volatile boolean paperEntryListenerRegistered;
|
||||
@@ -377,22 +380,23 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
int chunkX = anchor.getBlockX() >> 4;
|
||||
int chunkZ = anchor.getBlockZ() >> 4;
|
||||
WorldRuntimeControlService runtime = WorldRuntimeControlService.get();
|
||||
CompletableFuture<Chunk> chunkFuture = runtime.requestChunkAsync(world, chunkX, chunkZ, true);
|
||||
CompletableFuture<Chunk> chunkFuture = runtime.requestChunkAsync(world, chunkX, chunkZ, true, true);
|
||||
if (chunkFuture == null) {
|
||||
throw new IOException("The replacement spawn chunk request was not accepted.");
|
||||
}
|
||||
chunkFuture
|
||||
.thenCompose(chunk -> runtime.resolveSafeEntry(world, anchor))
|
||||
.thenCompose(this::applyOverworldSpawn)
|
||||
.thenCompose(this::persistOverworldSpawn)
|
||||
.whenComplete((safeEntry, failure) -> {
|
||||
if (failure != null) {
|
||||
targetFuture.completeExceptionally(failure);
|
||||
Iris.reportError("Could not prepare a safe spawn for the replaced Overworld.", failure);
|
||||
return;
|
||||
}
|
||||
CompletableFuture<Void> persistence = new CompletableFuture<>();
|
||||
overworldSpawnPersistence = persistence;
|
||||
persistence.thenRun(() -> retireOverworldEntryIfComplete(guard.transactionId()));
|
||||
targetFuture.complete(safeEntry.clone());
|
||||
retireOverworldEntryIfComplete(guard.transactionId());
|
||||
});
|
||||
} catch (Throwable failure) {
|
||||
targetFuture.completeExceptionally(failure);
|
||||
@@ -422,35 +426,19 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
return applied;
|
||||
}
|
||||
|
||||
private CompletableFuture<Location> persistOverworldSpawn(Location safeEntry) {
|
||||
Location requiredSafeEntry = Objects.requireNonNull(safeEntry, "safeEntry").clone();
|
||||
World world = Objects.requireNonNull(requiredSafeEntry.getWorld(), "safeEntry.world");
|
||||
CompletableFuture<Location> persisted = new CompletableFuture<>();
|
||||
boolean scheduled = J.runGlobal(() -> {
|
||||
try {
|
||||
world.save();
|
||||
persisted.complete(requiredSafeEntry.clone());
|
||||
} catch (Throwable failure) {
|
||||
persisted.completeExceptionally(failure);
|
||||
}
|
||||
});
|
||||
if (!scheduled) {
|
||||
persisted.completeExceptionally(new IOException("Could not schedule replacement spawn persistence."));
|
||||
}
|
||||
return persisted;
|
||||
}
|
||||
|
||||
private CompletableFuture<Boolean> inspectLoginCollision(Location location) {
|
||||
Location requiredLocation = Objects.requireNonNull(location, "location").clone();
|
||||
World world = Objects.requireNonNull(requiredLocation.getWorld(), "location.world");
|
||||
int chunkX = requiredLocation.getBlockX() >> 4;
|
||||
int chunkZ = requiredLocation.getBlockZ() >> 4;
|
||||
WorldRuntimeControlService runtime = WorldRuntimeControlService.get();
|
||||
CompletableFuture<Chunk> chunkFuture = runtime.requestChunkAsync(world, chunkX, chunkZ, true);
|
||||
CompletableFuture<Chunk> chunkFuture = runtime.requestChunkAsync(world, chunkX, chunkZ, false, true);
|
||||
if (chunkFuture == null) {
|
||||
return CompletableFuture.failedFuture(new IOException("The saved login chunk request was not accepted."));
|
||||
}
|
||||
return chunkFuture.thenCompose(chunk -> inspectLoadedLoginCollision(requiredLocation));
|
||||
return chunkFuture.thenCompose(chunk -> chunk == null
|
||||
? CompletableFuture.completedFuture(false)
|
||||
: inspectLoadedLoginCollision(requiredLocation));
|
||||
}
|
||||
|
||||
private CompletableFuture<Boolean> inspectLoadedLoginCollision(Location location) {
|
||||
@@ -528,14 +516,14 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
private synchronized void retireOverworldEntryIfCompleteAsync(UUID transactionId) {
|
||||
WorldReplacementEntryGuard.Entry current = overworldEntryGuard;
|
||||
Path worldDirectory = overworldEntryWorldDirectory;
|
||||
CompletableFuture<Location> safeEntry = overworldSafeEntry;
|
||||
CompletableFuture<Void> persistence = overworldSpawnPersistence;
|
||||
if (current == null
|
||||
|| worldDirectory == null
|
||||
|| !current.transactionId().equals(transactionId)
|
||||
|| !current.pendingPlayers().isEmpty()
|
||||
|| safeEntry == null
|
||||
|| !safeEntry.isDone()
|
||||
|| safeEntry.isCompletedExceptionally()) {
|
||||
|| persistence == null
|
||||
|| !persistence.isDone()
|
||||
|| persistence.isCompletedExceptionally()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -545,6 +533,7 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
overworldEntryGuard = null;
|
||||
overworldEntryWorldDirectory = null;
|
||||
overworldSafeEntry = null;
|
||||
overworldSpawnPersistence = null;
|
||||
overworldEntryWorld = null;
|
||||
pendingEntryAcknowledgements.entrySet().removeIf(entry -> entry.getValue().equals(transactionId));
|
||||
} catch (Throwable failure) {
|
||||
@@ -581,6 +570,22 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
J.a(() -> discoverLoadedWorldTransaction(worldKey));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onWorldSave(WorldSaveEvent event) {
|
||||
World replacementWorld = overworldEntryWorld;
|
||||
CompletableFuture<Location> safeEntry = overworldSafeEntry;
|
||||
CompletableFuture<Void> persistence = overworldSpawnPersistence;
|
||||
if (event.getWorld() != replacementWorld
|
||||
|| safeEntry == null
|
||||
|| !safeEntry.isDone()
|
||||
|| safeEntry.isCompletedExceptionally()
|
||||
|| persistence == null
|
||||
|| persistence.isDone()) {
|
||||
return;
|
||||
}
|
||||
persistence.complete(null);
|
||||
}
|
||||
|
||||
ReplacementEntryRedirect prepareReplacementEntry(UUID playerId, Location savedLocation, boolean newPlayer)
|
||||
throws IOException, InterruptedException, ExecutionException, TimeoutException {
|
||||
WorldReplacementEntryGuard.Entry guard = overworldEntryGuard;
|
||||
@@ -603,12 +608,22 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
return null;
|
||||
}
|
||||
if (!newPlayer) {
|
||||
boolean collisionSafe = inspectLoginCollision(requiredSavedLocation)
|
||||
.get(SAFE_ENTRY_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
if (collisionSafe) {
|
||||
SavedLocationInspection inspection = awaitSavedLocationInspection(
|
||||
inspectLoginCollision(requiredSavedLocation),
|
||||
SAVED_LOCATION_INSPECTION_TIMEOUT_SECONDS,
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
if (inspection.safe()) {
|
||||
completeOverworldEntry(playerId, guard.transactionId());
|
||||
return null;
|
||||
}
|
||||
if (inspection.failure() instanceof TimeoutException) {
|
||||
Iris.warn("Saved Overworld login location inspection timed out for " + playerId
|
||||
+ "; using the prepared replacement spawn.");
|
||||
} else if (inspection.failure() != null) {
|
||||
Iris.reportError("Could not inspect the saved Overworld login location for " + playerId
|
||||
+ "; using the prepared replacement spawn.", inspection.failure());
|
||||
}
|
||||
}
|
||||
CompletableFuture<Location> safeEntry = overworldSafeEntry;
|
||||
if (safeEntry == null) {
|
||||
@@ -620,6 +635,26 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
return new ReplacementEntryRedirect(guard.transactionId(), prepared, pendingPlayer);
|
||||
}
|
||||
|
||||
static SavedLocationInspection awaitSavedLocationInspection(
|
||||
CompletableFuture<Boolean> inspection,
|
||||
long timeout,
|
||||
TimeUnit timeUnit
|
||||
) throws InterruptedException {
|
||||
CompletableFuture<Boolean> requiredInspection = Objects.requireNonNull(inspection, "inspection");
|
||||
TimeUnit requiredTimeUnit = Objects.requireNonNull(timeUnit, "timeUnit");
|
||||
try {
|
||||
return new SavedLocationInspection(
|
||||
Boolean.TRUE.equals(requiredInspection.get(timeout, requiredTimeUnit)),
|
||||
null
|
||||
);
|
||||
} catch (TimeoutException failure) {
|
||||
return new SavedLocationInspection(false, failure);
|
||||
} catch (ExecutionException failure) {
|
||||
Throwable cause = failure.getCause() == null ? failure : failure.getCause();
|
||||
return new SavedLocationInspection(false, cause);
|
||||
}
|
||||
}
|
||||
|
||||
void expectReplacementEntryAcknowledgement(UUID playerId, UUID transactionId) throws IOException {
|
||||
WorldReplacementEntryGuard.Entry guard = overworldEntryGuard;
|
||||
if (guard == null
|
||||
@@ -999,6 +1034,7 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
overworldEntryGuard = loaded.orElse(null);
|
||||
overworldEntryWorldDirectory = loaded.isPresent() ? target.worldDirectory() : null;
|
||||
overworldSafeEntry = null;
|
||||
overworldSpawnPersistence = null;
|
||||
overworldEntryAwaitingVerification = null;
|
||||
}
|
||||
|
||||
@@ -1006,6 +1042,7 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
overworldEntryGuard = null;
|
||||
overworldEntryWorldDirectory = null;
|
||||
overworldSafeEntry = null;
|
||||
overworldSpawnPersistence = null;
|
||||
overworldEntryAwaitingVerification = null;
|
||||
overworldEntryWorld = null;
|
||||
pendingEntryAcknowledgements.clear();
|
||||
@@ -1157,6 +1194,9 @@ public final class PendingWorldReplacementManager implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
record SavedLocationInspection(boolean safe, Throwable failure) {
|
||||
}
|
||||
|
||||
private static final class RestartBoundaryRequired extends IOException {
|
||||
private RestartBoundaryRequired(String message) {
|
||||
super(message);
|
||||
|
||||
+60
@@ -13,9 +13,16 @@ import org.mockito.MockedStatic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
@@ -121,6 +128,59 @@ public class PendingWorldReplacementManagerPolicyTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void savedLocationInspectionPreservesVerifiedSafeLocations() throws Exception {
|
||||
PendingWorldReplacementManager.SavedLocationInspection inspection =
|
||||
PendingWorldReplacementManager.awaitSavedLocationInspection(
|
||||
CompletableFuture.completedFuture(true),
|
||||
0L,
|
||||
TimeUnit.NANOSECONDS
|
||||
);
|
||||
|
||||
assertTrue(inspection.safe());
|
||||
assertNull(inspection.failure());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void savedLocationInspectionRedirectsUnsafeLocations() throws Exception {
|
||||
PendingWorldReplacementManager.SavedLocationInspection inspection =
|
||||
PendingWorldReplacementManager.awaitSavedLocationInspection(
|
||||
CompletableFuture.completedFuture(false),
|
||||
0L,
|
||||
TimeUnit.NANOSECONDS
|
||||
);
|
||||
|
||||
assertFalse(inspection.safe());
|
||||
assertNull(inspection.failure());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void savedLocationInspectionTimeoutFallsBackWithoutThrowing() throws Exception {
|
||||
PendingWorldReplacementManager.SavedLocationInspection inspection =
|
||||
PendingWorldReplacementManager.awaitSavedLocationInspection(
|
||||
new CompletableFuture<>(),
|
||||
0L,
|
||||
TimeUnit.NANOSECONDS
|
||||
);
|
||||
|
||||
assertFalse(inspection.safe());
|
||||
assertTrue(inspection.failure() instanceof TimeoutException);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void savedLocationInspectionFailureRetainsItsCauseForReporting() throws Exception {
|
||||
IOException failure = new IOException("chunk load failed");
|
||||
PendingWorldReplacementManager.SavedLocationInspection inspection =
|
||||
PendingWorldReplacementManager.awaitSavedLocationInspection(
|
||||
CompletableFuture.failedFuture(failure),
|
||||
0L,
|
||||
TimeUnit.NANOSECONDS
|
||||
);
|
||||
|
||||
assertFalse(inspection.safe());
|
||||
assertSame(failure, inspection.failure());
|
||||
}
|
||||
|
||||
private record EnvironmentExpectation(SlotKind slotKind, IrisEnvironment environment) {
|
||||
}
|
||||
}
|
||||
+17
-8
@@ -219,7 +219,7 @@ public class PendingWorldReplacementThreadAffinityTest {
|
||||
String listenerSource = Files.readString(Path.of(
|
||||
"src/main/java/art/arcane/iris/core/PaperWorldReplacementEntryListener.java")).replace("\r\n", "\n");
|
||||
String preparation = method(managerSource, "private void prepareOverworldEntry(World world)");
|
||||
String persistence = method(managerSource, "private CompletableFuture<Location> persistOverworldSpawn(Location safeEntry)");
|
||||
String persistence = method(managerSource, "public void onWorldSave(WorldSaveEvent event)");
|
||||
String retirement = method(
|
||||
managerSource,
|
||||
"private synchronized void retireOverworldEntryIfCompleteAsync(UUID transactionId)"
|
||||
@@ -230,18 +230,27 @@ public class PendingWorldReplacementThreadAffinityTest {
|
||||
);
|
||||
String generatorSource = Files.readString(Path.of(System.getProperty("iris.bukkitChunkGeneratorSource")));
|
||||
|
||||
assertBefore(preparation, ".thenCompose(this::applyOverworldSpawn)",
|
||||
".thenCompose(this::persistOverworldSpawn)");
|
||||
assertBefore(preparation, "targetFuture.complete(safeEntry.clone())",
|
||||
"retireOverworldEntryIfComplete(guard.transactionId())");
|
||||
assertTrue(persistence.contains("world.save()"));
|
||||
assertBefore(preparation, "overworldSpawnPersistence = persistence",
|
||||
"targetFuture.complete(safeEntry.clone())");
|
||||
assertTrue(persistence.contains("event.getWorld() != replacementWorld"));
|
||||
assertTrue(persistence.contains("persistence.complete(null)"));
|
||||
assertTrue(retirement.contains("!current.pendingPlayers().isEmpty()"));
|
||||
assertTrue(retirement.contains("!safeEntry.isDone()"));
|
||||
assertTrue(retirement.contains("safeEntry.isCompletedExceptionally()"));
|
||||
assertTrue(retirement.contains("!persistence.isDone()"));
|
||||
assertTrue(retirement.contains("persistence.isCompletedExceptionally()"));
|
||||
assertTrue(listener.contains("event.isNewPlayer()"));
|
||||
assertTrue(generatorSource.contains("world.getHighestBlockYAt(initialSpawn) + 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginCollisionInspectionDoesNotGenerateMissingReplacementChunks() throws Exception {
|
||||
String managerSource = Files.readString(Path.of(
|
||||
"src/main/java/art/arcane/iris/core/PendingWorldReplacementManager.java")).replace("\r\n", "\n");
|
||||
String inspection = method(managerSource, "private CompletableFuture<Boolean> inspectLoginCollision(Location location)");
|
||||
|
||||
assertTrue(inspection.contains("requestChunkAsync(world, chunkX, chunkZ, false, true)"));
|
||||
assertBefore(inspection, "chunk == null", "inspectLoadedLoginCollision(requiredLocation)");
|
||||
}
|
||||
|
||||
private static PendingWorldReplacementManager.PublishedWorldRuntimeState runtimeState(
|
||||
WorldSlotKey worldKey,
|
||||
long seed,
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ public class BukkitEngineLifecycleContractTest {
|
||||
|
||||
String waitedShutdown = method(jobSource,
|
||||
"private static boolean shutdownAndWait(PregeneratorJob inst, long timeoutMs)");
|
||||
assertBefore(waitedShutdown, "inst.worker.interrupt()", "inst.worker.join(");
|
||||
assertBefore(waitedShutdown, "inst.requestStop()", "inst.worker.join(");
|
||||
assertTrue(waitedShutdown.contains("inst.worker.isAlive()"));
|
||||
|
||||
String hooksSource = Files.readString(Path.of(System.getProperty("iris.bukkitEnginePlatformHooksSource")));
|
||||
|
||||
Reference in new issue
Block a user