mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 16:17:44 +00:00
IA Fixes
- Thsi fixes an integration between Itemsadder and Oraxen. - Waiting on oraxen folks
This commit is contained in:
1 parent
894cef73a6
commit
d3245aa1c2
61 files changed
+3497
-160
No files matched your search
@@ -671,9 +671,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
IrisServices.register(IrisCompat.class, compat);
|
||||
ServerConfigurator.configure();
|
||||
StartupValidationOutcome datapackValidation = DatapackIngestService.validateOnStartup();
|
||||
if (datapackValidation == StartupValidationOutcome.READY) {
|
||||
generatorResolver.validateAllPacks();
|
||||
}
|
||||
IrisSafeguard.execute();
|
||||
getSender().setTag(getTag());
|
||||
// A cosmetic banner must never abort the bootstrap.
|
||||
@@ -727,12 +724,16 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
Iris.reportError("Failed to register listener for " + service.getClass().getSimpleName() + ".", e);
|
||||
}
|
||||
}
|
||||
if (datapackValidation == StartupValidationOutcome.READY) {
|
||||
IrisServices.get(ExternalDataSVC.class).setContentChangeListener(generatorResolver::requestExternalContentRefresh);
|
||||
generatorResolver.validateAllPacks();
|
||||
}
|
||||
addShutdownHook();
|
||||
pendingWorldReplacements.processPendingStartupReplacements();
|
||||
pendingWorldDeletes.processPendingStartupWorldDeletes();
|
||||
|
||||
if (J.isFolia() && IrisStartupValidation.isReady()) {
|
||||
J.s(() -> worldReconciler.checkForBukkitWorlds(s -> true), 1);
|
||||
J.s(this::reconcileStartupWorlds, 1);
|
||||
}
|
||||
|
||||
J.s(() -> {
|
||||
@@ -747,7 +748,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
autoStartStudio();
|
||||
}
|
||||
if (!J.isFolia() && IrisStartupValidation.isReady()) {
|
||||
worldReconciler.checkForBukkitWorlds(s -> true);
|
||||
reconcileStartupWorlds();
|
||||
}
|
||||
IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName());
|
||||
// The mantle stores block values as PlatformBlockState, so a BlockData retention can never
|
||||
@@ -757,6 +758,16 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void reconcileStartupWorlds() {
|
||||
generatorResolver.startupWorldsReady().whenComplete((ignored, failure) -> {
|
||||
if (failure != null) {
|
||||
Iris.reportError("Could not resume Iris startup world reconciliation.", failure);
|
||||
return;
|
||||
}
|
||||
J.s(() -> worldReconciler.checkForBukkitWorlds(s -> true));
|
||||
});
|
||||
}
|
||||
|
||||
public void addShutdownHook() {
|
||||
removeShutdownHook();
|
||||
serverLifecycleThread = Thread.currentThread();
|
||||
|
||||
+187
-10
@@ -20,6 +20,7 @@ package art.arcane.iris.core;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.lifecycle.WorldLifecycleStaging;
|
||||
import art.arcane.iris.core.compat.CompatRegistry;
|
||||
import art.arcane.iris.core.lifecycle.WorldReplacementSeed;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.pack.BrokenPackException;
|
||||
@@ -29,8 +30,11 @@ import art.arcane.iris.core.pack.PackValidationCache;
|
||||
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||
import art.arcane.iris.core.pack.PackValidationResult;
|
||||
import art.arcane.iris.core.pack.PackValidator;
|
||||
import art.arcane.iris.core.service.ExternalDataSVC;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.IrisServices;
|
||||
import art.arcane.iris.engine.framework.EngineTarget;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.iris.engine.history.GenerationEpoch;
|
||||
@@ -42,6 +46,7 @@ import art.arcane.iris.engine.history.GenerationRegistryContract;
|
||||
import art.arcane.iris.engine.history.GenerationRegistryContractFactory;
|
||||
import art.arcane.iris.engine.platform.BukkitChunkGenerator;
|
||||
import art.arcane.iris.util.common.plugin.VolmitPlugin;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.bukkit.WorldIdentity;
|
||||
import lombok.NonNull;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -60,6 +65,10 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -72,17 +81,102 @@ public final class IrisWorldGeneratorResolver {
|
||||
private static final Object SNAPSHOT_VALIDATION_LOCK = new Object();
|
||||
private static final String IRIS_DIMENSION_NAMESPACE = "iris";
|
||||
private static final String PLOT_SQUARED_DISCOVERY_WORLD = "CheckingPlotSquaredGenerator";
|
||||
private static final long EXTERNAL_CONTENT_STARTUP_TIMEOUT_SECONDS = 120L;
|
||||
|
||||
private final VolmitPlugin plugin;
|
||||
private final AtomicBoolean externalContentRefreshQueued = new AtomicBoolean();
|
||||
private final AtomicBoolean externalContentRefreshRequested = new AtomicBoolean();
|
||||
private final Map<Path, PendingSnapshot> pendingSnapshots = new ConcurrentHashMap<>();
|
||||
|
||||
public IrisWorldGeneratorResolver(VolmitPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void validateAllPacks() {
|
||||
validateAllPacks(false);
|
||||
}
|
||||
|
||||
public void requestExternalContentRefresh() {
|
||||
externalContentRefreshRequested.set(true);
|
||||
if (!externalContentRefreshQueued.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
J.a(this::runExternalContentRefresh);
|
||||
} catch (RuntimeException | LinkageError error) {
|
||||
externalContentRefreshQueued.set(false);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> startupWorldsReady() {
|
||||
return CompletableFuture.allOf(pendingSnapshots.values().stream()
|
||||
.map(PendingSnapshot::ready)
|
||||
.toArray(CompletableFuture<?>[]::new));
|
||||
}
|
||||
|
||||
private void runExternalContentRefresh() {
|
||||
externalContentRefreshRequested.set(false);
|
||||
try {
|
||||
retryPendingSnapshots();
|
||||
refreshExternalContent();
|
||||
} catch (RuntimeException | LinkageError error) {
|
||||
Iris.reportError("Failed to revalidate Iris packs after external content changed.", error);
|
||||
} finally {
|
||||
externalContentRefreshQueued.set(false);
|
||||
if (externalContentRefreshRequested.get()) {
|
||||
requestExternalContentRefresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void retryPendingSnapshots() {
|
||||
for (Map.Entry<Path, PendingSnapshot> entry : pendingSnapshots.entrySet()) {
|
||||
PendingSnapshot pending = entry.getValue();
|
||||
if (pending.contentReady().isDone()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
ExternalDataSVC external = IrisServices.getOrNull(ExternalDataSVC.class);
|
||||
long revision = external == null ? 0L : external.contentRevision();
|
||||
boolean wasPending = hasPendingExternalContent(PackValidationRegistry.get(entry.getKey()), external);
|
||||
PackValidationRegistry.remove(entry.getKey());
|
||||
IrisData.invalidateLoadedContentRegistries(entry.getKey().toFile());
|
||||
PackValidationResult validation = validateSnapshot(entry.getKey().toFile());
|
||||
if (external != null && revision != external.contentRevision()) {
|
||||
requestExternalContentRefresh();
|
||||
continue;
|
||||
}
|
||||
boolean stillPending = hasPendingExternalContent(validation, external);
|
||||
if (wasPending && !stillPending) {
|
||||
requestExternalContentRefresh();
|
||||
continue;
|
||||
}
|
||||
if (stillPending) {
|
||||
continue;
|
||||
}
|
||||
PackValidationRegistry.requireLoadable(entry.getKey());
|
||||
requireHistoricalDimension(pending.history(), entry.getKey().toFile(), pending.dimensionKey());
|
||||
pending.contentReady().complete(null);
|
||||
} catch (RuntimeException | LinkageError failure) {
|
||||
pending.contentReady().completeExceptionally(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void refreshExternalContent() {
|
||||
for (File pack : PackDirectoryResolver.listVisiblePackDirectories(plugin.getDataFolder("packs"))) {
|
||||
IrisData.invalidateLoadedContentRegistries(pack);
|
||||
}
|
||||
validateAllPacks(true);
|
||||
}
|
||||
|
||||
private synchronized void validateAllPacks(boolean externalContentChanged) {
|
||||
File packsRoot = plugin.getDataFolder("packs");
|
||||
List<File> packDirs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
|
||||
PackValidationRegistry.clear();
|
||||
if (!externalContentChanged) {
|
||||
PackValidationRegistry.clear();
|
||||
}
|
||||
List<String> packNames = packDirs.stream().map(File::getName).sorted().toList();
|
||||
Path cacheFile = IrisPlatforms.get().dataFile("cache", "pack-validation.json").toPath();
|
||||
ServerConfigurator.PackContentSnapshot contentSnapshot =
|
||||
@@ -92,11 +186,13 @@ public final class IrisWorldGeneratorResolver {
|
||||
try {
|
||||
contentSnapshot = ServerConfigurator.computePackContentSnapshot(packsRoot);
|
||||
contextFingerprint = PackValidationCache.contextFingerprint();
|
||||
cached = PackValidationCache.load(
|
||||
cacheFile,
|
||||
contentSnapshot.content(),
|
||||
contextFingerprint,
|
||||
packNames);
|
||||
if (!externalContentChanged) {
|
||||
cached = PackValidationCache.load(
|
||||
cacheFile,
|
||||
contentSnapshot.content(),
|
||||
contextFingerprint,
|
||||
packNames);
|
||||
}
|
||||
} catch (RuntimeException exception) {
|
||||
Iris.reportError("Could not evaluate the persisted pack-validation cache", exception);
|
||||
}
|
||||
@@ -136,6 +232,12 @@ public final class IrisWorldGeneratorResolver {
|
||||
String compatSummary = PackValidator.compatSummary(result, minecraftVersion);
|
||||
String compatSuffix = compatSummary.isEmpty() ? "" : " " + compatSummary;
|
||||
if (!result.isLoadable()) {
|
||||
if (hasPendingExternalContent(result, IrisServices.getOrNull(ExternalDataSVC.class))
|
||||
&& packDirectory != null
|
||||
&& PackValidator.validateForDatapackBootstrap(packDirectory).isLoadable()) {
|
||||
Iris.info("Pack '" + result.getPackName() + "' is waiting for external block providers to initialize.");
|
||||
continue;
|
||||
}
|
||||
Iris.error("Pack '" + result.getPackName()
|
||||
+ "' FAILED validation - world and Studio creation with this pack will be refused. Reasons:");
|
||||
for (String reason : result.getBlockingErrors()) {
|
||||
@@ -226,6 +328,11 @@ public final class IrisWorldGeneratorResolver {
|
||||
}
|
||||
|
||||
static PackValidationResult requireSnapshotLoadable(File packRoot) {
|
||||
validateSnapshot(packRoot);
|
||||
return PackValidationRegistry.requireLoadable(packRoot.toPath());
|
||||
}
|
||||
|
||||
private static PackValidationResult validateSnapshot(File packRoot) {
|
||||
Path normalizedRoot = packRoot.toPath().toAbsolutePath().normalize();
|
||||
PackValidationResult result = PackValidationRegistry.get(normalizedRoot);
|
||||
if (result == null) {
|
||||
@@ -255,7 +362,7 @@ public final class IrisWorldGeneratorResolver {
|
||||
}
|
||||
}
|
||||
}
|
||||
return PackValidationRegistry.requireLoadable(normalizedRoot);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -493,6 +600,10 @@ public final class IrisWorldGeneratorResolver {
|
||||
long worldSeed = requireStoredWorldSeed(worldName, dimensionRoot);
|
||||
GenerationHistory history = requireGenerationHistory(dimensionRoot, id, worldSeed);
|
||||
File snapshotRoot = requireActivePack(history);
|
||||
PackValidationResult validation = validateSnapshot(snapshotRoot);
|
||||
if (hasPendingExternalContent(validation, IrisServices.getOrNull(ExternalDataSVC.class))) {
|
||||
return deferFrozenWorldGenerator(worldName, worldKey, dimensionRoot, snapshotRoot, id, history);
|
||||
}
|
||||
try {
|
||||
requireSnapshotLoadable(snapshotRoot);
|
||||
} catch (BrokenPackException exception) {
|
||||
@@ -522,6 +633,61 @@ public final class IrisWorldGeneratorResolver {
|
||||
return new BukkitChunkGenerator(world, false, snapshotRoot, dimension.getLoadKey(), history);
|
||||
}
|
||||
|
||||
static boolean hasPendingExternalContent(PackValidationResult validation, ExternalDataSVC external) {
|
||||
return validation != null && external != null && validation.getCompatFindings().stream()
|
||||
.anyMatch(finding -> finding.registry() == CompatRegistry.BLOCK
|
||||
&& external.hasPendingBlockProvider(finding.key()));
|
||||
}
|
||||
|
||||
private BukkitChunkGenerator deferFrozenWorldGenerator(
|
||||
String worldName,
|
||||
NamespacedKey worldKey,
|
||||
File dimensionRoot,
|
||||
File snapshotRoot,
|
||||
String dimensionKey,
|
||||
GenerationHistory history
|
||||
) {
|
||||
PackValidationResult structural = PackValidator.validateForDatapackBootstrap(snapshotRoot);
|
||||
if (!structural.isLoadable()) {
|
||||
throw new BrokenPackException(snapshotRoot.toString(), structural.getBlockingErrors());
|
||||
}
|
||||
IrisData data = IrisData.openDatapackCompiler(snapshotRoot);
|
||||
try {
|
||||
IrisDimension dimension = requireHistoricalDimension(history, data, dimensionKey);
|
||||
GenerationEpoch.DimensionContract contract = history.activeEpoch().dimensionContract();
|
||||
IrisWorld world = IrisWorld.builder()
|
||||
.platformIdentity(worldKey.toString())
|
||||
.name(worldName)
|
||||
.seed(history.activeEpoch().worldSeed())
|
||||
.worldFolder(dimensionRoot)
|
||||
.minHeight(contract.minHeight())
|
||||
.maxHeight(Math.addExact(contract.minHeight(), contract.height()))
|
||||
.build();
|
||||
Path snapshotPath = snapshotRoot.toPath().toAbsolutePath().normalize();
|
||||
CompletableFuture<Void> contentReady = new CompletableFuture<>();
|
||||
BukkitChunkGenerator generator = new BukkitChunkGenerator(
|
||||
world, false, snapshotRoot, dimensionKey, history);
|
||||
generator.deferStartup(new EngineTarget(world, dimension, data), contentReady);
|
||||
PendingSnapshot pending = new PendingSnapshot(history, dimensionKey, contentReady, generator.getStartupReady());
|
||||
if (pendingSnapshots.putIfAbsent(snapshotPath, pending) != null) {
|
||||
generator.closeAsync();
|
||||
throw new IllegalStateException("Iris snapshot already has a deferred startup: " + snapshotPath);
|
||||
}
|
||||
contentReady.orTimeout(EXTERNAL_CONTENT_STARTUP_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
pending.ready().whenComplete((ignored, failure) -> {
|
||||
if (failure == null) {
|
||||
pendingSnapshots.remove(snapshotPath, pending);
|
||||
}
|
||||
});
|
||||
Iris.info("Waiting for external block providers before initializing Iris world '" + worldName + "'.");
|
||||
requestExternalContentRefresh();
|
||||
return generator;
|
||||
} catch (RuntimeException | LinkageError failure) {
|
||||
data.close();
|
||||
throw failure;
|
||||
}
|
||||
}
|
||||
|
||||
private static GenerationHistory requireGenerationHistory(
|
||||
File dimensionRoot,
|
||||
String dimensionKey,
|
||||
@@ -587,6 +753,14 @@ public final class IrisWorldGeneratorResolver {
|
||||
GenerationHistory history,
|
||||
File packRoot,
|
||||
String dimensionKey
|
||||
) {
|
||||
return requireHistoricalDimension(history, IrisData.get(packRoot), dimensionKey);
|
||||
}
|
||||
|
||||
private static IrisDimension requireHistoricalDimension(
|
||||
GenerationHistory history,
|
||||
IrisData data,
|
||||
String dimensionKey
|
||||
) {
|
||||
GenerationEpoch epoch = history.activeEpoch();
|
||||
if (!epoch.dimensionContract().dimensionKey().equals(dimensionKey)) {
|
||||
@@ -594,21 +768,24 @@ public final class IrisWorldGeneratorResolver {
|
||||
+ " does not match active generation history dimension "
|
||||
+ epoch.dimensionContract().dimensionKey() + ".");
|
||||
}
|
||||
IrisData data = IrisData.get(packRoot);
|
||||
IrisDimension dimension = data.getDimensionLoader().load(dimensionKey, false);
|
||||
if (dimension == null) {
|
||||
throw new IllegalStateException("Immutable Iris generation pack at " + packRoot
|
||||
throw new IllegalStateException("Immutable Iris generation pack at " + data.getDataFolder()
|
||||
+ " does not contain dimension " + dimensionKey + ".");
|
||||
}
|
||||
GenerationEpoch.DimensionContract actualContract = GenerationEpochContractFactory.createForEpoch(
|
||||
dimension, dimensionTypeKey(data, dimension), epoch);
|
||||
if (!epoch.dimensionContract().equals(actualContract)) {
|
||||
throw new IllegalStateException("Immutable Iris generation pack dimension contract changed at "
|
||||
+ packRoot + ".");
|
||||
+ data.getDataFolder() + ".");
|
||||
}
|
||||
return dimension;
|
||||
}
|
||||
|
||||
private record PendingSnapshot(GenerationHistory history, String dimensionKey,
|
||||
CompletableFuture<Void> contentReady, CompletableFuture<Void> ready) {
|
||||
}
|
||||
|
||||
private static GenerationEpoch.DimensionContract captureDimensionContract(
|
||||
IrisData data,
|
||||
IrisDimension dimension
|
||||
|
||||
@@ -36,6 +36,10 @@ dependencies:
|
||||
load: BEFORE
|
||||
required: false
|
||||
join-classpath: true
|
||||
Oraxen:
|
||||
load: BEFORE
|
||||
required: false
|
||||
join-classpath: true
|
||||
ItemsAdder:
|
||||
load: BEFORE
|
||||
required: false
|
||||
|
||||
@@ -7,6 +7,7 @@ softdepend:
|
||||
- PlaceholderAPI
|
||||
- CraftEngine
|
||||
- Nexo
|
||||
- Oraxen
|
||||
- ItemsAdder
|
||||
- SCore
|
||||
- ExecutableItems
|
||||
|
||||
@@ -24,6 +24,7 @@ public class PaperPluginMetadataTest {
|
||||
"PlaceholderAPI",
|
||||
"CraftEngine",
|
||||
"Nexo",
|
||||
"Oraxen",
|
||||
"ItemsAdder",
|
||||
"SCore",
|
||||
"ExecutableItems",
|
||||
|
||||
+11
@@ -29,6 +29,17 @@ public class IrisStartupOrderingTest {
|
||||
"generatorResolver.validateAllPacks();");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void externalProvidersEnableBeforePackValidation() throws Exception {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.startupSource")));
|
||||
String enable = section(source, "private boolean enable()", "public void addShutdownHook()");
|
||||
|
||||
assertOrdered(enable,
|
||||
"service.onEnable();",
|
||||
"setContentChangeListener(generatorResolver::requestExternalContentRefresh)",
|
||||
"generatorResolver.validateAllPacks();");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restartRequiredStartupTerminatesAfterPluginInitialization() throws Exception {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.startupSource")));
|
||||
|
||||
+57
@@ -2,9 +2,15 @@ package art.arcane.iris.core;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.lifecycle.WorldLifecycleStaging;
|
||||
import art.arcane.iris.core.compat.CompatAction;
|
||||
import art.arcane.iris.core.compat.CompatFinding;
|
||||
import art.arcane.iris.core.compat.CompatRegistry;
|
||||
import art.arcane.iris.core.service.ExternalDataSVC;
|
||||
import art.arcane.iris.core.pack.BrokenPackException;
|
||||
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||
import art.arcane.iris.core.pack.PackValidationResult;
|
||||
import art.arcane.iris.util.common.plugin.VolmitPlugin;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import org.junit.After;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
@@ -22,7 +28,9 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -36,12 +44,31 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
|
||||
public class IrisWorldGeneratorResolverTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void snapshotDeferralRequiresAnUnresolvedBlockWithAPendingProvider() {
|
||||
ExternalDataSVC external = mock(ExternalDataSVC.class);
|
||||
when(external.hasPendingBlockProvider("itemsadder:example/stone")).thenReturn(true);
|
||||
PackValidationResult unavailable = new PackValidationResult("pack", List.of("unavailable"), List.of(), 1L,
|
||||
List.of(new CompatFinding(CompatRegistry.BLOCK, "itemsadder:example/stone",
|
||||
CompatAction.EXCLUDED, "biome", "main", "layers[0]")), "26.2");
|
||||
|
||||
assertTrue(IrisWorldGeneratorResolver.hasPendingExternalContent(unavailable, external));
|
||||
when(external.hasPendingBlockProvider("itemsadder:example/stone")).thenReturn(false);
|
||||
assertFalse(IrisWorldGeneratorResolver.hasPendingExternalContent(unavailable, external));
|
||||
assertFalse(IrisWorldGeneratorResolver.hasPendingExternalContent(unavailable, null));
|
||||
assertFalse(IrisWorldGeneratorResolver.hasPendingExternalContent(
|
||||
new PackValidationResult("vanilla", List.of(), List.of(), 1L), external));
|
||||
}
|
||||
|
||||
@After
|
||||
public void clearValidationState() {
|
||||
PackValidationRegistry.clear();
|
||||
@@ -83,6 +110,36 @@ public class IrisWorldGeneratorResolverTest {
|
||||
assertFalse(invalid.getBlockingErrors().toString(), invalid.isLoadable());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void providerEventsCoalesceAndRefreshAgainAfterAnInFlightChange() {
|
||||
IrisWorldGeneratorResolver resolver = spy(new IrisWorldGeneratorResolver(mock(VolmitPlugin.class)));
|
||||
List<Runnable> tasks = new ArrayList<>();
|
||||
AtomicInteger passes = new AtomicInteger();
|
||||
doAnswer(invocation -> {
|
||||
if (passes.incrementAndGet() == 1) {
|
||||
resolver.requestExternalContentRefresh();
|
||||
assertTrue(tasks.isEmpty());
|
||||
}
|
||||
return null;
|
||||
}).when(resolver).refreshExternalContent();
|
||||
try (MockedStatic<J> scheduler = mockStatic(J.class)) {
|
||||
scheduler.when(() -> J.a(any(Runnable.class))).thenAnswer(invocation -> {
|
||||
tasks.add(invocation.getArgument(0, Runnable.class));
|
||||
return null;
|
||||
});
|
||||
|
||||
resolver.requestExternalContentRefresh();
|
||||
resolver.requestExternalContentRefresh();
|
||||
assertEquals(1, tasks.size());
|
||||
tasks.removeFirst().run();
|
||||
assertEquals(1, passes.get());
|
||||
assertEquals(1, tasks.size());
|
||||
tasks.removeFirst().run();
|
||||
assertEquals(2, passes.get());
|
||||
assertTrue(tasks.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startupValidationPublishesFingerprintBoundExactRootResults() throws Exception {
|
||||
String source = Files.readString(Path.of(
|
||||
|
||||
Reference in new issue
Block a user