Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
fcdc8db
Folia Support
R00tB33rMan Feb 20, 2025
1a3385b
Cleanup unused code and unneeded changes
R00tB33rMan Feb 20, 2025
d73a7aa
Upstream
R00tB33rMan Mar 7, 2025
5fc9a0b
Upstream
R00tB33rMan Mar 10, 2025
cba1ea7
Upstream
R00tB33rMan Mar 24, 2025
566b810
Upstream
R00tB33rMan Apr 5, 2025
7e8f400
Upstream
R00tB33rMan Apr 8, 2025
5e09c42
Upstream
R00tB33rMan Apr 11, 2025
57eb415
Upstream
R00tB33rMan Apr 22, 2025
40af10e
Upstream
R00tB33rMan May 9, 2025
b6ba699
Upstream
R00tB33rMan May 20, 2025
7475e63
Upstream
R00tB33rMan May 30, 2025
d73b7be
Upstream
R00tB33rMan Jun 13, 2025
c2d1a67
Cleanup code and implement proper handling for WorldEdit utilization.…
R00tB33rMan Jun 14, 2025
f679b6b
Upstream
R00tB33rMan Jun 22, 2025
e0c0b81
Upstream
R00tB33rMan Jun 25, 2025
63b2a41
Upstream
R00tB33rMan Jul 1, 2025
0392172
Upstream
R00tB33rMan Jul 3, 2025
ad35b48
Upstream
R00tB33rMan Jul 8, 2025
35ee5b9
Upstream
R00tB33rMan Jul 12, 2025
8fc0c35
Upstream
R00tB33rMan Jul 19, 2025
cbbd7e7
Upstream
R00tB33rMan Aug 4, 2025
0bcfa78
Upstream
R00tB33rMan Aug 9, 2025
f0eb6cf
Upstream
R00tB33rMan Aug 16, 2025
23d85e0
Upstream
R00tB33rMan Aug 19, 2025
5a90627
Upstream
R00tB33rMan Aug 19, 2025
3e4d18e
Upstream
R00tB33rMan Aug 22, 2025
0a17d20
Upstream
R00tB33rMan Sep 8, 2025
0e9d7db
Upstream
R00tB33rMan Sep 18, 2025
5813d2e
Upstream
R00tB33rMan Oct 7, 2025
8728c8e
Upstream
R00tB33rMan Oct 7, 2025
ea238c1
Upstream
R00tB33rMan Oct 8, 2025
dbc13e2
Reimplement the butcher command (of all types) and even //regen, alon…
R00tB33rMan Oct 24, 2025
3956a69
Cleanup
R00tB33rMan Oct 24, 2025
96caf7c
Part One: Cleanup & Refactor Scheduling
R00tB33rMan Oct 24, 2025
96f44fa
Synchronize singular class
R00tB33rMan Oct 24, 2025
c2ab291
Remove isPrimaryThread checks
R00tB33rMan Oct 24, 2025
bdb6823
Upstream
R00tB33rMan Oct 25, 2025
6b4cd88
Upstream
R00tB33rMan Oct 26, 2025
50767e7
fix: code refactor
bennycallanan Nov 3, 2025
bfa8efa
Improve and refactor regen implementations in other NMS classes
R00tB33rMan Nov 3, 2025
fc34db3
Not sure how this slipped through
R00tB33rMan Nov 3, 2025
d33f501
How the hell did I make this mistake?
R00tB33rMan Nov 3, 2025
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,15 +25,16 @@
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;
import com.sk89q.worldedit.WorldEditException;
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 @@ -699,6 +700,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 @@ -708,6 +713,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 @@ -781,6 +794,244 @@ 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()
),
new NoOpWorldLoadListener(),
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, 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) {
}
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));
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) {
net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId(serverWorld.registryAccess());
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")
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));
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) {
ResourceLocation key = serverWorld.registryAccess().lookupOrThrow(Registries.BIOME).getKey(origBiome);
if (key == null) {
Expand All @@ -801,7 +1052,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