Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: initial work on reworked regeneration logic #2483

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ tasks {
}
}
runServer {
minecraftVersion("1.20.2")
minecraftVersion(supportedVersions.last())
pluginJars(*project(":worldedit-bukkit").getTasksByName("shadowJar", false).map { (it as Jar).archiveFile }
.toTypedArray())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.mojang.datafixers.util.Either;
Expand Down Expand Up @@ -393,7 +392,7 @@ private static net.minecraft.core.Direction adapt(Direction face) {
}

@SuppressWarnings({"rawtypes", "unchecked"})
private net.minecraft.world.level.block.state.BlockState applyProperties(
public net.minecraft.world.level.block.state.BlockState applyProperties(
StateDefinition<Block, net.minecraft.world.level.block.state.BlockState> stateContainer,
net.minecraft.world.level.block.state.BlockState newState,
Map<Property<?>, Object> states
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ private static net.minecraft.core.Direction adapt(Direction face) {
}

@SuppressWarnings({"rawtypes", "unchecked"})
private net.minecraft.world.level.block.state.BlockState applyProperties(
public net.minecraft.world.level.block.state.BlockState applyProperties(
StateDefinition<Block, net.minecraft.world.level.block.state.BlockState> stateContainer,
net.minecraft.world.level.block.state.BlockState newState,
Map<Property<?>, Object> states
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.VersionedRegenerator;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
Expand Down Expand Up @@ -570,7 +570,9 @@ public net.minecraft.nbt.Tag fromNative(Tag foreign) {

@Override
public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception {
return new PaperweightRegen(bukkitWorld, region, target, options).regenerate();
try (final VersionedRegenerator regenerator = new VersionedRegenerator(bukkitWorld, region, options, target)) {
return regenerator.regenerate().join();
}
}

@Override
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator;

import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.level.FaweProtoChunkAccess;
import com.sk89q.worldedit.world.RegenOptions;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.QuartPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.util.Mth;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import org.jetbrains.annotations.NotNull;

import java.util.List;

public class DelegatingWorldGenRegion extends WorldGenRegion {

private final RegenOptions regenOptions;

public DelegatingWorldGenRegion(
final ServerLevel world,
final List<ChunkAccess> chunkAccesses,
final ChunkStatus status,
final int placementRadius,
final RegenOptions regenOptions
) {
super(world, chunkAccesses, status, placementRadius);
this.regenOptions = regenOptions;
}


@Override
public boolean ensureCanWrite(final BlockPos pos) {
return this.hasChunk(pos.getX() >> 4, pos.getZ() >> 4) &&
pos.getY() >= getMinBuildHeight() && pos.getY() <= getMaxBuildHeight();
}

@Override
public boolean isOldChunkAround(final @NotNull ChunkPos chunkPos, final int checkRadius) {
return false; // We don't migrate old worlds, won't be properly blended either way
}

/**
* Don't notify ServerLevel on place and don't set chunk for postprocessing
*/
@Override
public boolean setBlock(
final @NotNull BlockPos pos,
final @NotNull BlockState state,
final int flags,
final int maxUpdateDepth
) {
if (!this.ensureCanWrite(pos)) {
return false;
}
ChunkAccess chunk = this.getChunk(pos);
BlockState oldState = chunk.setBlockState(pos, state, false);
if (state.hasBlockEntity()) {
BlockEntity tileEntity = ((EntityBlock) state.getBlock()).newBlockEntity(pos, state);
if (tileEntity != null) {
chunk.setBlockEntity(tileEntity);
} else {
chunk.removeBlockEntity(pos);
}
} else if (oldState != null && oldState.hasBlockEntity()) {
chunk.removeBlockEntity(pos);
}
return true;
}

@Override
public @NotNull Holder<Biome> getNoiseBiome(final int biomeX, final int biomeY, final int biomeZ) {
FaweProtoChunkAccess chunkAccess = (FaweProtoChunkAccess) this.getChunk(
QuartPos.toSection(biomeX), QuartPos.toSection(biomeZ), ChunkStatus.EMPTY, false
);
if (chunkAccess == null) {
return DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.BIOME)
.getHolderOrThrow(Biomes.PLAINS);
}
int l = QuartPos.fromBlock(this.getMinBuildHeight());
int i1 = l + QuartPos.fromBlock(this.getHeight()) - 1;
int j1 = Mth.clamp(biomeY, l, i1);
int k1 = this.getSectionIndex(QuartPos.toBlock(j1));
return chunkAccess.getSection(k1).getNoiseBiome(biomeX & 3, j1 & 3, biomeZ & 3);
}

@Override
public long getSeed() {
return this.regenOptions.getSeed().orElse(super.getSeed());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator;

import com.fastasyncworldedit.bukkit.adapter.regeneration.ChunkWorker;
import com.fastasyncworldedit.bukkit.adapter.regeneration.Regenerator;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.PaperweightFaweAdapter;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.level.FaweChunkSource;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker.BiomesWorker;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker.CarversWorker;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker.FeaturesWorker;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker.NoiseWorker;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker.SurfaceWorker;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.concurrency.LazyReference;
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.levelgen.Heightmap;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class VersionedRegenerator extends Regenerator<ServerLevel, WorldGenRegion, ChunkAccess, ChunkGenerator, ChunkStatus> {

/**
* Each chunk has their own ChunkSource as chunks are worked on in parallel and therefor neighbour chunks may differ in status
*/
private final Long2ObjectLinkedOpenHashMap<FaweChunkSource> chunkSources;

public VersionedRegenerator(
final World world, final Region region, final RegenOptions options,
final Extent extent
) {
super(world, region, options, extent);
this.chunkSources = new Long2ObjectLinkedOpenHashMap<>(region.getChunks().size());

for (final BlockVector2 chunk : region.getChunks()) {
FaweChunkSource source = new FaweChunkSource(
region,
serverLevel().registryAccess().registryOrThrow(Registries.BIOME),
options,
serverLevel()
);
this.chunkSources.put(ChunkPos.asLong(chunk.getX(), chunk.getZ()), source);
}
}

@Override
public CompletableFuture<Boolean> flushPalettesIntoWorld(final ChunkAccess chunkAccess, final WorldGenRegion worldGenRegion) {
return CompletableFuture.supplyAsync(() -> {
boolean result = false;
final int minX = Math.max(chunkAccess.getPos().getMinBlockX(), region.getMinimumPoint().getBlockX());
final int minY = Math.max(chunkAccess.getMinBuildHeight(), region.getMinimumY());
final int minZ = Math.max(chunkAccess.getPos().getMinBlockZ(), region.getMinimumPoint().getBlockZ());
final int maxX = Math.min(chunkAccess.getPos().getMaxBlockX(), region.getMaximumPoint().getBlockX());
final int maxY = Math.min(chunkAccess.getMaxBuildHeight(), region.getMaximumY());
final int maxZ = Math.min(chunkAccess.getPos().getMaxBlockZ(), region.getMaximumPoint().getBlockZ());
for (final BlockPos blockPos : BlockPos.MutableBlockPos.betweenClosed(
minX, minY, minZ,
maxX, maxY, maxZ
)) {
// We still have to validate boundaries, as region may not be a cuboid
if (!(region.contains(blockPos.getX(), blockPos.getY(), blockPos.getZ()))) {
continue;
}
if ((options.shouldRegenBiomes() || options.hasBiomeType()) &&
(blockPos.getX() % 4 == 0 && blockPos.getY() % 4 == 0 && blockPos.getZ() % 4 == 0)) {
result |= this.extent.setBiome(
blockPos.getX(), blockPos.getY(), blockPos.getZ(),
BiomeTypes.get(worldGenRegion.getBiome(blockPos).unwrapKey()
.orElseThrow().location().toString())
);
}
final BlockEntity blockEntity = chunkAccess.getBlockEntity(blockPos);
final com.sk89q.worldedit.world.block.BlockState blockState = ((PaperweightFaweAdapter) WorldEditPlugin
.getInstance()
.getBukkitImplAdapter())
.adapt(blockEntity != null ? blockEntity.getBlockState() : chunkAccess.getBlockState(blockPos));
if (blockEntity != null) {
result |= this.extent.setBlock(
blockPos.getX(), blockPos.getY(), blockPos.getZ(),
blockState.toBaseBlock(LazyReference.from(() -> {
//noinspection unchecked
if (!(WorldEditPlugin.getInstance().getBukkitImplAdapter()
.toNativeBinary(blockEntity.saveWithId()) instanceof CompoundBinaryTag tag)) {
throw new IllegalStateException("Entity Binary-Tag is no CompoundBinaryTag");
}
return tag;
}))
);
continue;
}
result |= this.extent.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), blockState);
}
this.chunkSources.remove(chunkAccess.getPos().toLong());
return result;
}, provideExecutor().get());
}

@Override
public @Nullable ChunkAccess toNativeChunkAccess(
final ServerLevel level,
final BlockVector2 chunk,
final int x,
final int z,
final ChunkStatus leastStatus
) {
return this.chunkSources.get(ChunkPos.asLong(chunk.getX(), chunk.getZ()))
.getChunk(x, z, leastStatus, true);
}

@Override
protected @NonNull CompletableFuture<@Nullable Void> primeHeightmaps(final ChunkAccess chunk) {
return CompletableFuture.runAsync(
() -> Heightmap.primeHeightmaps(chunk, ChunkStatus.POST_FEATURES),
provideExecutor().get()
);
}

@Override
public WorldGenRegion worldGenRegionForRegion(
final ServerLevel level,
final List<ChunkAccess> chunkAccesses,
final Region region,
final ChunkStatus status
) {
return new DelegatingWorldGenRegion(
level, chunkAccesses,
status,
PLACEMENT_RADII[status.getIndex() - ChunkStatus.BIOMES.getIndex()],
options
);
}

@Override
public ChunkGenerator generatorFromLevel(final ServerLevel serverLevel) {
return serverLevel.chunkSource.getGenerator();
}

@Override
protected void setChunkStatus(final List<ChunkAccess> chunkAccesses, final ChunkStatus status) {
chunkAccesses.forEach(chunkAccess -> ((ProtoChunk) chunkAccess).setStatus(status));
}

@Override
public @NonNull List<@NonNull ChunkWorker<ServerLevel, ChunkAccess, WorldGenRegion, ChunkGenerator, ChunkStatus>> workers() {
return List.of(
BiomesWorker.INSTANCE,
NoiseWorker.INSTANCE,
SurfaceWorker.INSTANCE,
CarversWorker.INSTANCE,
FeaturesWorker.INSTANCE
);
}

@Override
public @NonNull ServerLevel serverLevel() {
return ((CraftWorld) this.world).getHandle();
}

@Override
public void close() throws IOException {
this.chunkSources.clear();
super.close();
}

}
Loading
Loading