Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,3 @@ jobs:
with:
name: logs for ${{ matrix.os }}
path: '**/*.log'

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.mojang.serialization.Codec;
import com.mojang.serialization.Lifecycle;
import com.sk89q.worldedit.EditSession;
Expand All @@ -34,7 +33,9 @@
import com.sk89q.worldedit.blocks.BaseItem;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.folia.FoliaScheduler;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extension.platform.Watchdog;
import com.sk89q.worldedit.extent.Extent;
Expand Down Expand Up @@ -711,6 +712,10 @@ public boolean canPlaceAt(World world, BlockVector3 position, BlockState blockSt

@Override
public boolean regenerate(World bukkitWorld, Region region, Extent extent, RegenOptions options) {
if (FoliaScheduler.isFolia()) {
return regenerateFolia(bukkitWorld, region, extent, options);
}

try {
doRegen(bukkitWorld, region, extent, options);
} catch (Exception e) {
Expand All @@ -720,6 +725,14 @@ public boolean regenerate(World bukkitWorld, Region region, Extent extent, Regen
return true;
}

private boolean regenerateFolia(World bukkitWorld, Region region, Extent extent, RegenOptions options) {
try {
return doRegenFolia(bukkitWorld, region, extent, options);
} catch (Exception e) {
throw new IllegalStateException("Regen failed on Folia.", e);
}
}

private void doRegen(World bukkitWorld, Region region, Extent extent, RegenOptions options) throws Exception {
Environment env = bukkitWorld.getEnvironment();
ChunkGenerator gen = bukkitWorld.getGenerator();
Expand Down Expand Up @@ -793,6 +806,248 @@ private void doRegen(World bukkitWorld, Region region, Extent extent, RegenOptio
}
}

private boolean doRegenFolia(World bukkitWorld, Region region, Extent extent, RegenOptions options) throws Exception {
Environment env = bukkitWorld.getEnvironment();
ChunkGenerator gen = bukkitWorld.getGenerator();

Path tempDir = Files.createTempDirectory("WorldEditWorldGen");
LevelStorageSource levelStorage = LevelStorageSource.createDefault(tempDir);
ResourceKey<LevelStem> worldDimKey = getWorldDimKey(env);
try (LevelStorageSource.LevelStorageAccess session = levelStorage.createAccess("worldeditregentempworld", worldDimKey)) {
ServerLevel originalWorld = ((CraftWorld) bukkitWorld).getHandle();
PrimaryLevelData levelProperties = (PrimaryLevelData) originalWorld.getServer()
.getWorldData().overworldData();
WorldOptions originalOpts = levelProperties.worldGenOptions();

long seed = options.getSeed().orElse(originalWorld.getSeed());
WorldOptions newOpts = options.getSeed().isPresent()
? originalOpts.withSeed(OptionalLong.of(seed))
: originalOpts;

LevelSettings newWorldSettings = new LevelSettings(
"worldeditregentempworld",
levelProperties.settings.gameType(),
levelProperties.settings.hardcore(),
levelProperties.settings.difficulty(),
levelProperties.settings.allowCommands(),
levelProperties.settings.gameRules(),
levelProperties.settings.getDataConfiguration()
);

@SuppressWarnings("deprecation")
PrimaryLevelData.SpecialWorldProperty specialWorldProperty =
levelProperties.isFlatWorld()
? PrimaryLevelData.SpecialWorldProperty.FLAT
: levelProperties.isDebugWorld()
? PrimaryLevelData.SpecialWorldProperty.DEBUG
: PrimaryLevelData.SpecialWorldProperty.NONE;

PrimaryLevelData newWorldData = new PrimaryLevelData(newWorldSettings, newOpts, specialWorldProperty, Lifecycle.stable());

ServerLevel freshWorld = new ServerLevel(
originalWorld.getServer(),
originalWorld.getServer().executor,
session, newWorldData,
originalWorld.dimension(),
new LevelStem(
originalWorld.dimensionTypeRegistration(),
originalWorld.getChunkSource().getGenerator()
),
originalWorld.isDebug(),
seed,
ImmutableList.of(),
false,
originalWorld.getRandomSequences(),
env,
gen,
bukkitWorld.getBiomeProvider()
);

try {
ChunkPos spawnChunk = new ChunkPos(
freshWorld.getChunkSource().randomState().sampler().findSpawnPosition()
);

try {
Field randomSpawnField = ServerLevel.class.getDeclaredField("randomSpawnSelection");
randomSpawnField.setAccessible(true);
randomSpawnField.set(freshWorld, spawnChunk);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to set spawn chunk for Folia", e);
}

MinecraftServer console = originalWorld.getServer();
CompletableFuture<Void> initFuture = new CompletableFuture<>();

FoliaScheduler.getRegionScheduler().run(
WorldEditPlugin.getInstance(),
freshWorld.getWorld(),
spawnChunk.x, spawnChunk.z,
o -> {
try {
console.initWorld(freshWorld, newWorldData, newWorldData.worldGenOptions());
initFuture.complete(null);
} catch (Exception e) {
initFuture.completeExceptionally(e);
}
}
);

initFuture.get();

regenForWorldFolia(region, extent, freshWorld, options);
} finally {
freshWorld.getChunkSource().close(false);
}
} finally {
try {
@SuppressWarnings("unchecked")
Map<String, World> map = (Map<String, World>) serverWorldsField.get(Bukkit.getServer());
map.remove("worldeditregentempworld");
} catch (IllegalAccessException ignored) {
// It's fine if we couldn't remove it
}
SafeFiles.tryHardToDeleteDir(tempDir);
}

return true;
}

@SuppressWarnings("unchecked")
private void regenForWorldFolia(Region region, Extent extent, ServerLevel serverWorld, RegenOptions options) throws WorldEditException {
Map<BlockVector3, BlockStateHolder<?>> blockStates = new HashMap<>();
Map<BlockVector3, BiomeType> biomes = new HashMap<>();
Map<ChunkPos, List<BlockVector3>> blocksByChunk = new HashMap<>();

for (BlockVector3 vec : region) {
ChunkPos chunkPos = new ChunkPos(vec.x() >> 4, vec.z() >> 4);
blocksByChunk.computeIfAbsent(chunkPos, k -> new ArrayList<>()).add(vec);
}

World bukkitWorld = serverWorld.getWorld();
List<CompletableFuture<Void>> extractionFutures = new ArrayList<>();

for (Map.Entry<ChunkPos, List<BlockVector3>> entry : blocksByChunk.entrySet()) {
ChunkPos chunkPos = entry.getKey();
List<BlockVector3> blocks = entry.getValue();
CompletableFuture<Void> future = new CompletableFuture<>();

FoliaScheduler.getRegionScheduler().execute(
WorldEditPlugin.getInstance(),
bukkitWorld,
chunkPos.x,
chunkPos.z,
() -> {
try {
ServerChunkCache chunkManager = serverWorld.getChunkSource();
CompletableFuture<ChunkResult<ChunkAccess>> chunkFuture =
((CompletableFuture<ChunkResult<ChunkAccess>>)
getChunkFutureMethod.invoke(chunkManager, chunkPos.x, chunkPos.z, ChunkStatus.FEATURES, true));
@SuppressWarnings({"FutureReturnValueIgnored", "unused"})
var ignored = chunkFuture.thenApply(either -> either.orElse(null))
.whenComplete((chunkAccess, throwable) -> {
if (throwable != null) {
future.completeExceptionally(new IllegalStateException("Couldn't load chunk for regen.", throwable));
} else if (chunkAccess != null) {
try {
for (BlockVector3 vec : blocks) {
BlockPos pos = new BlockPos(vec.x(), vec.y(), vec.z());
final net.minecraft.world.level.block.state.BlockState blockData = chunkAccess.getBlockState(pos);
int internalId = Block.getId(blockData);
BlockStateHolder<?> state = BlockStateIdAccess.getBlockStateById(internalId);
Objects.requireNonNull(state);
BlockEntity blockEntity = chunkAccess.getBlockEntity(pos);
if (blockEntity != null) {
var tagValueOutput = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, serverWorld.registryAccess());
blockEntity.saveWithId(tagValueOutput);
net.minecraft.nbt.CompoundTag tag = tagValueOutput.buildResult();
state = state.toBaseBlock(LazyReference.from(() -> (LinCompoundTag) toNative(tag)));
}
synchronized (blockStates) {
blockStates.put(vec, state);
}
if (options.shouldRegenBiomes()) {
Biome origBiome = chunkAccess.getNoiseBiome(vec.x(), vec.y(), vec.z()).value();
BiomeType adaptedBiome = adapt(serverWorld, origBiome);
if (adaptedBiome != null) {
synchronized (biomes) {
biomes.put(vec, adaptedBiome);
}
}
}
}
future.complete(null);
} catch (Exception e) {
future.completeExceptionally(e);
}
} else {
future.completeExceptionally(new IllegalStateException("Failed to generate a chunk, regen failed."));
}
});
} catch (IllegalAccessException | InvocationTargetException e) {
future.completeExceptionally(new IllegalStateException("Couldn't load chunk for regen.", e));
}
}
);
extractionFutures.add(future);
}

CompletableFuture.allOf(extractionFutures.toArray(new CompletableFuture<?>[0])).join();

for (BlockVector3 vec : region) {
BlockStateHolder<?> state = blockStates.get(vec);
if (state != null) {
extent.setBlock(vec, state.toBaseBlock());
if (options.shouldRegenBiomes()) {
BiomeType biome = biomes.get(vec);
if (biome != null) {
extent.setBiome(vec, biome);
}
}
}
}
}

@SuppressWarnings({"unchecked", "unused"})
private List<CompletableFuture<ChunkAccess>> submitChunkLoadTasksFolia(Region region, ServerLevel serverWorld) {
ServerChunkCache chunkManager = serverWorld.getChunkSource();
List<CompletableFuture<ChunkAccess>> chunkLoadings = new ArrayList<>();
World bukkitWorld = serverWorld.getWorld();

for (BlockVector2 chunk : region.getChunks()) {
CompletableFuture<ChunkAccess> future = new CompletableFuture<>();
final int chunkX = chunk.x();
final int chunkZ = chunk.z();

FoliaScheduler.getRegionScheduler().execute(
WorldEditPlugin.getInstance(),
bukkitWorld,
chunkX,
chunkZ,
() -> {
try {
CompletableFuture<ChunkResult<ChunkAccess>> chunkFuture =
((CompletableFuture<ChunkResult<ChunkAccess>>)
getChunkFutureMethod.invoke(chunkManager, chunkX, chunkZ, ChunkStatus.FEATURES, true));
@SuppressWarnings({"FutureReturnValueIgnored", "unused"})
var ignored = chunkFuture.thenApply(either -> either.orElse(null))
.whenComplete((chunkAccess, throwable) -> {
if (throwable != null) {
future.completeExceptionally(throwable);
} else {
future.complete(chunkAccess);
}
});
} catch (IllegalAccessException | InvocationTargetException e) {
future.completeExceptionally(new IllegalStateException("Couldn't load chunk for regen.", e));
}
}
);
chunkLoadings.add(future);
}
return chunkLoadings;
}

private BiomeType adapt(ServerLevel serverWorld, Biome origBiome) {
Identifier key = serverWorld.registryAccess().lookupOrThrow(Registries.BIOME).getKey(origBiome);
if (key == null) {
Expand All @@ -813,7 +1068,7 @@ private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld
executor.managedBlock(() -> {
// bail out early if a future fails
if (chunkLoadings.stream().anyMatch(ftr ->
ftr.isDone() && Futures.getUnchecked(ftr) == null
ftr.isDone() && ftr.getNow(null) == null
)) {
return false;
}
Expand Down
Loading