From 3c6280b11eb7050e07dde6432ee773e5f50ba988 Mon Sep 17 00:00:00 2001 From: Aurora <21148213+aurorasmiles@users.noreply.github.com> Date: Sat, 10 Oct 2020 11:57:12 +0200 Subject: [PATCH] Re-Implement //regen (#598) * start reimplementing regen command * start reimplementing regen command * Formatting and logic tweaks. Regen will now throw exceptions but they are not caught yet. ConversionSessions will now be closed when no longer being used instead of left open. * fix //regen crashing server * added //regen support for 1.16.1 and 1.15.2 * cleanup * Update the issue template * improve performance of regen by a factor of 40, approx 1.2 millon blocks/second * Update the issue template * Update the issue template & add a config (#640) * Update the issue template * Add a config.yml to the issue template * improve performance of regen by a factor of 40, approx 1.2 millon blocks/second * Fix entity rotation (#642) * fix entity rotation fixes #577 Co-authored-by: wea_ondara * Fix toggle permission (#644) * Fixes #529 * fix superperms perm toggling Co-authored-by: @weaondara * Fix #647 * Squash errors and debug to aid fixing #652 properly * cleanup imports * cleanup imports 2 * cleanup imports 3 * cleanup imports 4 * add patch by @SirYwell * aysnc world gen with features and stuff and some minor issues * optimizations, full chunkstatus, block populators * optimizations, cleanup * optimizations * fix feature regeneration, fix temp folder deletion * cleanup * make chunk gen multithreaded * fix precomputation of chunk lists for RegionLimitedWorldAccess again * added regenerator abstraction, fix aioobe while running through chunk stati * remove override for getChunkAt in freshnmsworld * don't use concurrent chunk gen if custom chunk generators do not support it * distinct between generator types * improve regen speed for overworlds * mix * Add message that regen might take a while * use a shared map for FastAreaLazy, cleanup imports * use custom concurrency levels for chunk stati and process accordingly * implement new regen in 1.15.2 and 1.16.1 as well Conflicts: worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java * woops * further abstraction, finalized regen impl * Formatting * Fix some typos, remove debug * replace wildcard imports * cleanup debug * braces * serr -> logger * move regen impls to seperate classes * fix world init for 1.16.1 * fix world init for 1.15.2 * fix world init for 1.15.2 #2 * use original world name for regeneration * Update Regen_v1_15_R2.java * Update worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java Co-authored-by: Matt <4009945+MattBDev@users.noreply.github.com> * Update worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java Co-authored-by: Matt <4009945+MattBDev@users.noreply.github.com> * Update worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java Co-authored-by: Matt <4009945+MattBDev@users.noreply.github.com> * improve documentation, use parallel task count for fawe settings * fix compile Co-authored-by: wea_ondara Co-authored-by: MattBDev <4009945+MattBDev@users.noreply.github.com> Co-authored-by: dordsor21 --- .../sk89q/worldedit/bukkit/BukkitWorld.java | 51 +- .../bukkit/adapter/BukkitImplAdapter.java | 10 +- .../worldedit/bukkit/adapter/Regenerator.java | 545 ++++++++++++++++++ .../adapter/impl/FAWE_Spigot_v1_15_R2.java | 107 +--- .../adapter/impl/FAWE_Spigot_v1_16_R1.java | 89 +-- .../adapter/impl/FAWE_Spigot_v1_16_R2.java | 87 +-- .../adapter/impl/regen/Regen_v1_15_R2.java | 478 +++++++++++++++ .../adapter/impl/regen/Regen_v1_16_R1.java | 519 +++++++++++++++++ .../adapter/impl/regen/Regen_v1_16_R2.java | 529 +++++++++++++++++ .../worldedit/command/RegionCommands.java | 1 + .../com/sk89q/worldedit/extent/Extent.java | 2 +- .../com/sk89q/worldedit/world/NullWorld.java | 5 + .../java/com/sk89q/worldedit/world/World.java | 41 +- .../src/main/resources/lang/strings.json | 2 + 14 files changed, 2155 insertions(+), 311 deletions(-) create mode 100644 worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/Regenerator.java create mode 100644 worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java create mode 100644 worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R1.java create mode 100644 worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R2.java diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index c88de2a76..f282901c1 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -32,6 +32,7 @@ import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; @@ -42,6 +43,7 @@ import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.world.AbstractWorld; +import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockStateHolder; @@ -202,61 +204,18 @@ public class BukkitWorld extends AbstractWorld { } @Override - public boolean regenerate(Region region, EditSession editSession) { + public boolean regenerate(Region region, Extent extent, RegenOptions options) { BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); try { if (adapter != null) { - return adapter.regenerate(getWorld(), region, editSession); + return adapter.regenerate(getWorld(), region, extent, options); } else { throw new UnsupportedOperationException("Missing BukkitImplAdapater for this version."); } } catch (Exception e) { logger.warn("Regeneration via adapter failed.", e); + return false; } - /* - BaseBlock[] history = new BaseBlock[16 * 16 * (getMaxY() + 1)]; - - for (BlockVector2 chunk : region.getChunks()) { - BlockVector3 min = BlockVector3.at(chunk.getBlockX() * 16, 0, chunk.getBlockZ() * 16); - - // First save all the blocks inside - for (int x = 0; x < 16; ++x) { - for (int y = 0; y < (getMaxY() + 1); ++y) { - for (int z = 0; z < 16; ++z) { - BlockVector3 pt = min.add(x, y, z); - int index = y * 16 * 16 + z * 16 + x; - history[index] = editSession.getFullBlock(pt); - } - } - } - - try { - getWorld().regenerateChunk(chunk.getBlockX(), chunk.getBlockZ()); - } catch (Throwable t) { - logger.warn("Chunk generation via Bukkit raised an error", t); - } - - // Then restore - for (int x = 0; x < 16; ++x) { - for (int y = 0; y < (getMaxY() + 1); ++y) { - for (int z = 0; z < 16; ++z) { - BlockVector3 pt = min.add(x, y, z); - int index = y * 16 * 16 + z * 16 + x; - - // We have to restore the block if it was outside - if (!region.contains(pt)) { - editSession.smartSetBlock(pt, history[index]); - } else { // Otherwise fool with history - editSession.getChangeSet().add(new BlockChange(pt, history[index], editSession.getFullBlock(pt))); - } - } - } - } - } - - return true; - */ - return editSession.regenerate(region); } /** diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index f94dc5e12..cd3e86499 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -25,11 +25,11 @@ import com.boydti.fawe.beta.implementation.packet.ChunkPacket; import com.boydti.fawe.bukkit.FaweBukkit; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; -import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -37,6 +37,7 @@ import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.world.DataFixer; +import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; @@ -236,11 +237,12 @@ public interface BukkitImplAdapter extends IBukkitAdapter { * Regenerate a region in the given world, so it appears "as new". * @param world the world to regen in * @param region the region to regen - * @param session the session to use for setting blocks + * @param extent the extent to use for setting blocks + * @param options the regeneration options * @return true on success, false on failure */ - default boolean regenerate(org.bukkit.World world, Region region, EditSession session) { - return session.regenerate(region); + default boolean regenerate(World world, Region region, Extent extent, RegenOptions options) throws Exception{ + throw new UnsupportedOperationException("This adapter does not support regeneration."); } default IChunkGet get(World world, int chunkX, int chunkZ) { diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/Regenerator.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/Regenerator.java new file mode 100644 index 000000000..cbf099142 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/Regenerator.java @@ -0,0 +1,545 @@ +package com.sk89q.worldedit.bukkit.adapter; + +import com.boydti.fawe.beta.IChunkCache; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.beta.implementation.queue.SingleThreadQueueExtent; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.util.MathMan; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.world.RegenOptions; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; +import org.bukkit.generator.BlockPopulator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents an abstract regeneration handler. + * @param the type of the {@Code IChunkAccess} of the current Minecraft implementation + * @param the type of the {@Code ProtoChunk} of the current Minecraft implementation + * @param the type of the {@Code Chunk} of the current Minecraft implementation + * @param the type of the {@Code ChunkStatusWrapper} wrapping the {@Code ChunkStatus} enum + */ +public abstract class Regenerator> { + + public static final Logger logger = LoggerFactory.getLogger(Regenerator.class); + + protected final org.bukkit.World originalBukkitWorld; + protected final Region region; + protected final Extent target; + protected final RegenOptions options; + + //runtime + protected final Map chunkStati = new LinkedHashMap<>(); + protected boolean generateConcurrent = true; + protected long seed; + + private final Long2ObjectLinkedOpenHashMap protoChunks = new Long2ObjectLinkedOpenHashMap<>(); + private final Long2ObjectOpenHashMap chunks = new Long2ObjectOpenHashMap<>(); + private ExecutorService executor; + private SingleThreadQueueExtent source; + + /** + * Initializes an abstract regeneration handler. + * @param originalBukkitWorld the Bukkit world containing all the information on how to regenerate the {code Region} + * @param region the selection to regenerate + * @param target the target {@code Extent} to paste the regenerated blocks into + * @param options the options to used while regenerating and pasting into the target {@code Extent} + */ + public Regenerator(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) { + this.originalBukkitWorld = originalBukkitWorld; + this.region = region; + this.target = target; + this.options = options; + } + + /** + * Regenerates the selected {@code Region}. + * @return whether or not the regeneration process was successful + * @throws Exception when something goes terribly wrong + */ + public boolean regenerate() throws Exception { + if (!prepare()) { + return false; + } + + try { + if (!initNewWorld()) { + cleanup0(); + return false; + } + } catch (Exception e) { + cleanup0(); + throw e; + } + + try { + if (!generate()) { + cleanup0(); + return false; + } + } catch (Exception e) { + cleanup0(); + throw e; + } + + try { + copyToWorld(); + } catch (Exception e) { + cleanup0(); + throw e; + } + + cleanup0(); + return true; + } + + /** + * Returns the {@code ProtoChunk} at the given chunk coordinates. + * @param x the chunk x coordinate + * @param z the chunk z coordinate + * @return the {@code ProtoChunk} at the given chunk coordinates or null if it is not part of the regeneration process or has not been initialized yet. + */ + protected ProtoChunk getProtoChunkAt(int x, int z) { + return protoChunks.get(MathMan.pairInt(x, z)); + } + + /** + * Returns the {@code Chunk} at the given chunk coordinates. + * @param x the chunk x coordinate + * @param z the chunk z coordinate + * @return the {@code Chunk} at the given chunk coordinates or null if it is not part of the regeneration process or has not been converted yet. + */ + protected Chunk getChunkAt(int x, int z) { + return chunks.get(MathMan.pairInt(x, z)); + } + + private boolean generate() throws Exception { + if (generateConcurrent) { + //Using concurrent chunk generation + executor = Executors.newFixedThreadPool(Settings.IMP.QUEUE.PARALLEL_THREADS); + } // else using sequential chunk generation, concurrent not supported + + //TODO: can we get that required radius down without affecting chunk generation (e.g. strucures, features, ...)? + //for now it is working well and fast, if we are bored in the future we could do the research (a lot of it) to reduce the border radius + + //generate chunk coords lists with a certain radius + Int2ObjectOpenHashMap> chunkCoordsForRadius = new Int2ObjectOpenHashMap<>(); + chunkStati.keySet().stream().map(ChunkStatusWrapper::requiredNeigborChunkRadius0).distinct().forEach(radius -> { + if (radius == -1) //ignore ChunkStatus.EMPTY + return; + int border = 16 - radius; //9 = 8 + 1, 8: max border radius used in chunk stages, 1: need 1 extra chunk for chunk features to generate at the border of the region + chunkCoordsForRadius.put(radius, getChunkCoordsRegen(region, border)); + }); + + //create chunks + for (Long xz : chunkCoordsForRadius.get(0)) { + ProtoChunk chunk = createProtoChunk(MathMan.unpairIntX(xz), MathMan.unpairIntY(xz)); + protoChunks.put(xz, chunk); + } + + //generate lists for RegionLimitedWorldAccess, need to be square with odd length (e.g. 17x17), 17 = 1 middle chunk + 8 border chunks * 2 + Int2ObjectOpenHashMap>> worldlimits = new Int2ObjectOpenHashMap<>(); + chunkStati.keySet().stream().map(ChunkStatusWrapper::requiredNeigborChunkRadius0).distinct().forEach(radius -> { + if (radius == -1) //ignore ChunkStatus.EMPTY + return; + Long2ObjectOpenHashMap> map = new Long2ObjectOpenHashMap<>(); + for (Long xz : chunkCoordsForRadius.get(radius)) { + int x = MathMan.unpairIntX(xz); + int z = MathMan.unpairIntY(xz); + List l = new ArrayList<>((radius + 1 + radius) * (radius + 1 + radius)); + for (int zz = z - radius; zz <= z + radius; zz++) { //order is important, first z then x + for (int xx = x - radius; xx <= x + radius; xx++) { + l.add(protoChunks.get(MathMan.pairInt(xx, zz))); + } + } + map.put(xz, l); + } + worldlimits.put(radius, map); + }); + + //run generation tasks exluding FULL chunk status + for (Map.Entry entry : chunkStati.entrySet()) { + ChunkStatus chunkStatus = entry.getKey(); + int radius = chunkStatus.requiredNeigborChunkRadius0(); + + List coords = chunkCoordsForRadius.get(radius); + if (this.generateConcurrent && entry.getValue() == Concurrency.RADIUS) { + SequentialTasks>> tasks = getChunkStatusTaskRows(coords, radius); + for (ConcurrentTasks> para : tasks) { + List scheduled = new ArrayList<>(tasks.size()); + for (SequentialTasks row : para) { + scheduled.add((Callable) () -> { + for (Long xz : row) { + chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); + } + return null; + }); + } + try { + List futures = executor.invokeAll(scheduled); + for (Future future : futures) { + future.get(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } else if (this.generateConcurrent && entry.getValue() == Concurrency.FULL) { + // every chunk can be processed individually + List scheduled = new ArrayList(coords.size()); + for (long xz : coords) { + scheduled.add((Callable) () -> { + chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); + return null; + }); + } + try { + List futures = executor.invokeAll(scheduled); + for (Future future : futures) { + future.get(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } else { // Concurrency.NONE or generateConcurrent == false + // run sequential + for (long xz : coords) { + chunkStatus.processChunkSave(xz, worldlimits.get(radius).get(xz)); + } + } + } + + //convert to proper chunks + for (Long xz : chunkCoordsForRadius.get(0)) { + ProtoChunk proto = protoChunks.get(xz); + chunks.put(xz, createChunk(proto)); + } + + //final chunkstatus + ChunkStatus FULL = getFullChunkStatus(); + for (Long xz : chunkCoordsForRadius.get(0)) { //FULL.requiredNeighbourChunkRadius() == 0! + Chunk chunk = chunks.get(xz); + FULL.processChunkSave(xz, Arrays.asList(chunk)); + } + + //populate + List populators = getBlockPopulators(); + for (Long xz : chunkCoordsForRadius.get(0)) { + int x = MathMan.unpairIntX(xz); + int z = MathMan.unpairIntY(xz); + + //prepare chunk seed + Random random = getChunkRandom(seed, x, z); + + //actually populate + Chunk c = chunks.get(xz); + populators.forEach(pop -> { + populate(c, random, pop); + }); + } + + source = new SingleThreadQueueExtent(); + source.init(null, initSourceQueueCache(), null); + return true; + } + + private void copyToWorld() { + //Setting Blocks + long start = System.currentTimeMillis(); + boolean genbiomes = options.shouldRegenBiomes(); + for (BlockVector3 vec : region) { + target.setBlock(vec, source.getBlock(vec)); + if (genbiomes) { + target.setBiome(vec, source.getBiome(vec)); + } +// realExtent.setSkyLight(vec, extent.getSkyLight(vec)); +// realExtent.setBlockLight(vec, extent.getBrightness(vec)); + } + } + + private void cleanup0() { + if (executor != null) { + executor.shutdownNow(); + } + cleanup(); + } + + //functions to be implemented by sub class + /** + *

Implement the preparation process in here. DO NOT instanciate any variable here that require the cleanup function. This function is for gathering further information before initializing a new + * world.

+ * + *

Fields required to be initialized: chunkStati, seed

+ *

For chunkStati also see {code ChunkStatusWrapper}.

+ * + * @return whether or not the preparation process was successful + */ + protected abstract boolean prepare(); + + /** + * Implement the creation of the seperate world in here. + * + * Fields required to be initialized: generateConcurrent + * + * @return true if everything went fine, otherwise false. When false is returned the Regenerator halts the regeneration process and calls the cleanup function. + * @throws java.lang.Exception When the implementation of this method throws and exception the Regenerator halts the regeneration process and calls the cleanup function. + */ + protected abstract boolean initNewWorld() throws Exception; + + /** + * Implement the cleanup of all the mess that is created during the regeneration process (initNewWorld() and generate()).This function must not throw any exceptions. + */ + protected abstract void cleanup(); + + //functions to implement by sub class - regenate related + /** + * Implement the initialization of a {@code ProtoChunk} here. + * + * @param x the x coorinate of the {@code ProtoChunk} to create + * @param z the z coorinate of the {@code ProtoChunk} to create + * @return an initialized {@code ProtoChunk} + */ + protected abstract ProtoChunk createProtoChunk(int x, int z); + + /** + * Implement the convertion of a {@code ProtoChunk} to a {@code Chunk} here. + * + * @param protoChunk the {@code ProtoChunk} to be converted to a {@code Chunk} + * @return the converted {@code Chunk} + */ + protected abstract Chunk createChunk(ProtoChunk protoChunk); + + /** + * Return the {@code ChunkStatus.FULL} here. + * ChunkStatus.FULL is the last step of vanilla chunk generation. + * + * @return {@code ChunkStatus.FULL} + */ + protected abstract ChunkStatus getFullChunkStatus(); + + /** + * Return a list of {@code BlockPopulator} used to populate the original world here. + * + * @return {@code ChunkStatus.FULL} + */ + protected abstract List getBlockPopulators(); + + /** + * Implement the population of the {@code Chunk} with the given chunk random and {@code BlockPopulator} here. + * + * @param chunk the {@code Chunk} to populate + * @param random the chunk random to use for population + * @param pop the {@code BlockPopulator} to use + */ + protected abstract void populate(Chunk chunk, Random random, BlockPopulator pop); + + /** + * Implement the initialization an {@code IChunkCache} here. Use will need the {@code getChunkAt} function + * @return an initialized {@code IChunkCache} + */ + protected abstract IChunkCache initSourceQueueCache(); + + //algorithms + private List getChunkCoordsRegen(Region region, int border) { //needs to be square num of chunks + BlockVector3 oldMin = region.getMinimumPoint(); + BlockVector3 newMin = BlockVector3.at((oldMin.getX() >> 4 << 4) - border * 16, oldMin.getY(), (oldMin.getZ() >> 4 << 4) - border * 16); + BlockVector3 oldMax = region.getMaximumPoint(); + BlockVector3 newMax = BlockVector3.at((oldMax.getX() >> 4 << 4) + (border + 1) * 16 - 1, oldMax.getY(), (oldMax.getZ() >> 4 << 4) + (border + 1) * 16 - 1); + Region adjustedRegion = new CuboidRegion(newMin, newMax); + return adjustedRegion.getChunks().stream() + .map(c -> BlockVector2.at(c.getX(), c.getZ())) + .sorted(Comparator.comparingInt(c -> c.getZ()).thenComparingInt(c -> c.getX())) //needed for RegionLimitedWorldAccess + .map(c -> MathMan.pairInt(c.getX(), c.getZ())) + .collect(Collectors.toList()); + } + + /** + * Creates a list of chunkcoord rows that may be executed concurrently + * + * @param allcoords the coords that should be sorted into rows, must be sorted by z and x + * @param requiredNeighborChunkRadius the radius of neighbor chunks that may not be written to conccurently (ChunkStatus.requiredNeighborRadius) + * @return a list of chunkcoords rows that may be executed concurrently + */ + private SequentialTasks>> getChunkStatusTaskRows(List allcoords, int requiredNeighborChunkRadius) { + int requiredneighbors = Math.max(0, requiredNeighborChunkRadius); + + int minx = allcoords.isEmpty() ? 0 : MathMan.unpairIntX(allcoords.get(0)); + int maxx = allcoords.isEmpty() ? 0 : MathMan.unpairIntX(allcoords.get(allcoords.size() - 1)); + int minz = allcoords.isEmpty() ? 0 : MathMan.unpairIntY(allcoords.get(0)); + int maxz = allcoords.isEmpty() ? 0 : MathMan.unpairIntY(allcoords.get(allcoords.size() - 1)); + SequentialTasks>> tasks; + if (maxz - minz > maxx - minx) { + int numlists = Math.min(requiredneighbors * 2 + 1, maxx - minx + 1); + + Int2ObjectOpenHashMap> byx = new Int2ObjectOpenHashMap(); + int expectedListLength = (allcoords.size() + 1) / (maxx - minx); + + //init lists + for (int i = minx; i <= maxx; i++) { + byx.put(i, new SequentialTasks(expectedListLength)); + } + + //sort into lists by x coord + for (Long xz : allcoords) { + byx.get(MathMan.unpairIntX(xz)).add(xz); + } + + //create parallel tasks + tasks = new SequentialTasks(numlists); + for (int offset = 0; offset < numlists; offset++) { + ConcurrentTasks> para = new ConcurrentTasks((maxz - minz + 1) / numlists + 1); + for (int i = 0; minx + i * numlists + offset <= maxx; i++) + para.add(byx.get(minx + i * numlists + offset)); + tasks.add(para); + } + } else { + int numlists = Math.min(requiredneighbors * 2 + 1, maxz - minz + 1); + + Int2ObjectOpenHashMap> byz = new Int2ObjectOpenHashMap(); + int expectedListLength = (allcoords.size() + 1) / (maxz - minz); + + //init lists + for (int i = minz; i <= maxz; i++) { + byz.put(i, new SequentialTasks(expectedListLength)); + } + + //sort into lists by x coord + for (Long xz : allcoords) { + byz.get(MathMan.unpairIntY(xz)).add(xz); + } + + //create parallel tasks + tasks = new SequentialTasks(numlists); + for (int offset = 0; offset < numlists; offset++) { + ConcurrentTasks> para = new ConcurrentTasks((maxx - minx + 1) / numlists + 1); + for (int i = 0; minz + i * numlists + offset <= maxz; i++) + para.add(byz.get(minz + i * numlists + offset)); + tasks.add(para); + } + } + + return tasks; + } + + private static Random getChunkRandom(long worldseed, int x, int z) { + Random random = new Random(); + random.setSeed(worldseed); + long xRand = random.nextLong() / 2L * 2L + 1L; + long zRand = random.nextLong() / 2L * 2L + 1L; + random.setSeed((long) x * xRand + (long) z * zRand ^ worldseed); + return random; + } + + //classes + /** + * This class is used to wrap the ChunkStatus of the current Minecraft implementation and as the implementation to execute a chunk generation step. + * @param the IChunkAccess class of the current Minecraft implementation + */ + public static abstract class ChunkStatusWrapper { + + /** + * Return the required neighbor chunk radius the wrapped {@code ChunkStatus} requires. + * + * @return the radius of required neighbor chunks + */ + public abstract int requiredNeigborChunkRadius(); + + int requiredNeigborChunkRadius0() { + return Math.max(0, requiredNeigborChunkRadius()); + } + + /** + * Return the name of the wrapped {@code ChunkStatus}. + * + * @return the radius of required neighbor chunks + */ + public abstract String name(); + + /** + * Return the name of the wrapped {@code ChunkStatus}. + * + * @param xz represents the chunk coordinates of the chunk to process as denoted by {@code MathMan} + * @param accessibleChunks a list of chunks that will be used during the execution of the wrapped {@code ChunkStatus}. + * This list is order in the correct order required by the {@code ChunkStatus}, unless Mojang suddenly decides to do things differently. + */ + public abstract void processChunk(Long xz, List accessibleChunks); + + void processChunkSave(Long xz, List accessibleChunks) { + try { + processChunk(xz, accessibleChunks); + } catch (Exception e) { + logger.error("Error while running " + name() + " on chunk " + MathMan.unpairIntX(xz) + "/" + MathMan.unpairIntY(xz), e); + } + } + } + + public enum Concurrency { + FULL, + RADIUS, + NONE + } + + public static class SequentialTasks extends Tasks { + + public SequentialTasks(int expectedsize) { + super(expectedsize); + } + } + + public static class ConcurrentTasks extends Tasks { + + public ConcurrentTasks(int expectedsize) { + super(expectedsize); + } + } + + public static class Tasks implements Iterable { + + private final List tasks; + + public Tasks(int expectedsize) { + tasks = new ArrayList(expectedsize); + } + + public void add(T task) { + tasks.add(task); + } + + public List list() { + return tasks; + } + + public int size() { + return tasks.size(); + } + + @Override + public Iterator iterator() { + return tasks.iterator(); + } + + @Override + public String toString() { + return tasks.toString(); + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java index 232e22c51..e97f894cd 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_15_R2.java @@ -22,36 +22,33 @@ package com.sk89q.worldedit.bukkit.adapter.impl; import com.boydti.fawe.Fawe; import com.boydti.fawe.FaweCache; import com.boydti.fawe.beta.IChunkGet; -import com.boydti.fawe.beta.IQueueChunk; -import com.boydti.fawe.beta.IQueueExtent; import com.boydti.fawe.beta.implementation.packet.ChunkPacket; -import com.boydti.fawe.beta.implementation.queue.SingleThreadQueueExtent; import com.boydti.fawe.bukkit.adapter.mc1_15_2.BlockMaterial_1_15_2; import com.boydti.fawe.bukkit.adapter.mc1_15_2.BukkitAdapter_1_15_2; import com.boydti.fawe.bukkit.adapter.mc1_15_2.BukkitGetBlocks_1_15_2; import com.boydti.fawe.bukkit.adapter.mc1_15_2.FAWEWorldNativeAccess_1_15_2; import com.boydti.fawe.bukkit.adapter.mc1_15_2.MapChunkUtil_1_15_2; import com.boydti.fawe.bukkit.adapter.mc1_15_2.nbt.LazyCompoundTag_1_15_2; -import com.google.common.io.Files; +import com.google.common.base.Preconditions; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; -import com.sk89q.worldedit.EditSession; -import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.TileEntityBlock; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.CachedBukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.IDelegateBukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.regen.Regen_v1_15_R2; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.LazyBaseEntity; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; -import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; @@ -66,7 +63,6 @@ import net.minecraft.server.v1_15_R1.Block; import net.minecraft.server.v1_15_R1.BlockPosition; import net.minecraft.server.v1_15_R1.Chunk; import net.minecraft.server.v1_15_R1.ChunkCoordIntPair; -import net.minecraft.server.v1_15_R1.ChunkProviderServer; import net.minecraft.server.v1_15_R1.ChunkSection; import net.minecraft.server.v1_15_R1.Entity; import net.minecraft.server.v1_15_R1.EntityPlayer; @@ -75,7 +71,6 @@ import net.minecraft.server.v1_15_R1.IBlockData; import net.minecraft.server.v1_15_R1.IRegistry; import net.minecraft.server.v1_15_R1.ItemStack; import net.minecraft.server.v1_15_R1.MinecraftKey; -import net.minecraft.server.v1_15_R1.MinecraftServer; import net.minecraft.server.v1_15_R1.NBTBase; import net.minecraft.server.v1_15_R1.NBTTagCompound; import net.minecraft.server.v1_15_R1.NBTTagInt; @@ -83,12 +78,9 @@ import net.minecraft.server.v1_15_R1.PacketPlayOutMapChunk; import net.minecraft.server.v1_15_R1.PlayerChunk; import net.minecraft.server.v1_15_R1.TileEntity; import net.minecraft.server.v1_15_R1.World; -import net.minecraft.server.v1_15_R1.WorldData; -import net.minecraft.server.v1_15_R1.WorldNBTStorage; import net.minecraft.server.v1_15_R1.WorldServer; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.World.Environment; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_15_R1.CraftChunk; import org.bukkit.craftbukkit.v1_15_R1.CraftWorld; @@ -98,17 +90,11 @@ import org.bukkit.craftbukkit.v1_15_R1.entity.CraftEntity; import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_15_R1.inventory.CraftItemStack; import org.bukkit.entity.Player; -import org.bukkit.generator.ChunkGenerator; -import java.io.File; -import java.io.IOException; import java.lang.ref.WeakReference; import java.util.Map; import java.util.OptionalInt; import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.function.Supplier; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -119,7 +105,6 @@ import static org.slf4j.LoggerFactory.getLogger; public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements IDelegateBukkitImplAdapter { private final Spigot_v1_15_R2 parent; private char[] ibdToStateOrdinal; - // ------------------------------------------------------------------------ // Code that may break between versions of Minecraft // ------------------------------------------------------------------------ @@ -157,8 +142,6 @@ public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements I public BlockMaterial getMaterial(BlockState state) { IBlockData bs = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState(); return new BlockMaterial_1_15_2(bs.getBlock(), bs); - - } public Block getBlock(BlockType blockType) { @@ -168,7 +151,7 @@ public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements I @SuppressWarnings("deprecation") @Override public BaseBlock getBlock(Location location) { - checkNotNull(location); + Preconditions.checkNotNull(location); CraftWorld craftWorld = ((CraftWorld) location.getWorld()); int x = location.getBlockX(); @@ -272,7 +255,7 @@ public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements I @Override public BaseEntity getEntity(org.bukkit.entity.Entity entity) { - checkNotNull(entity); + Preconditions.checkNotNull(entity); CraftEntity craftEntity = ((CraftEntity) entity); Entity mcEntity = craftEntity.getHandle(); @@ -417,83 +400,9 @@ public final class FAWE_Spigot_v1_15_R2 extends CachedBukkitAdapter implements I } return parent.fromNative(foreign); } - @Override - public boolean regenerate(org.bukkit.World world, Region region, EditSession editSession) { - WorldServer originalWorld = ((CraftWorld) world).getHandle(); - ChunkProviderServer provider = originalWorld.getChunkProvider(); - if (!(provider instanceof ChunkProviderServer)) { - return false; - } - - File saveFolder = Files.createTempDir(); - // register this just in case something goes wrong - // normally it should be deleted at the end of this method - saveFolder.deleteOnExit(); - try { - MinecraftServer server = originalWorld.getServer().getServer(); - WorldNBTStorage originalDataManager = originalWorld.getDataManager(); - WorldNBTStorage saveHandler = new WorldNBTStorage(saveFolder, originalDataManager.getDirectory().getName(), server, originalDataManager.getDataFixer()); - WorldData newWorldData = new WorldData(originalWorld.worldData.a((NBTTagCompound) null), - server.dataConverterManager, getDataVersion(), null); - newWorldData.setName(UUID.randomUUID().toString()); - - ChunkGenerator gen = world.getGenerator(); - Environment env = world.getEnvironment(); - try (WorldServer freshWorld = new WorldServer(server, - server.executorService, saveHandler, - newWorldData, - originalWorld.worldProvider.getDimensionManager(), - originalWorld.getMethodProfiler(), - server.worldLoadListenerFactory.create(11), - env, - gen) { - @Override - public boolean addEntityChunk(net.minecraft.server.v1_15_R1.Entity entity) { - //Fixes #320; Prevent adding entities so we aren't attempting to spawn them asynchronously - return false; - } - }) { - - // Pre-gen all the chunks - // We need to also pull one more chunk in every direction - Fawe.get().getQueueHandler().startSet(true); - try { - IQueueExtent extent = new SingleThreadQueueExtent(); - extent.init(null, (x, z) -> new BukkitGetBlocks_1_15_2(freshWorld, x, z) { - @Override - public Chunk ensureLoaded(World nmsWorld, int chunkX, int chunkZ) { - Chunk cached = nmsWorld.getChunkIfLoaded(chunkX, chunkZ); - if (cached != null) { - return cached; - } - Future future = Fawe.get().getQueueHandler().sync((Supplier) () -> freshWorld.getChunkAt(chunkX, chunkZ)); - while (!future.isDone()) { - // this feels so dirty - freshWorld.getChunkProvider().runTasks(); - } - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - }, null); - for (BlockVector3 vec : region) { - editSession.setBlock(vec, extent.getFullBlock(vec)); - } - } finally { - Fawe.get().getQueueHandler().endSet(true); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } catch (MaxChangedBlocksException e) { - throw new RuntimeException(e); - } finally { - saveFolder.delete(); - } - return true; + public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception { + return new Regen_v1_15_R2(bukkitWorld, region, target, options).regenerate(); } @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java index 44e42d099..cda1690cd 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R1.java @@ -28,23 +28,26 @@ import com.boydti.fawe.bukkit.adapter.mc1_16_1.BukkitGetBlocks_1_16_1; import com.boydti.fawe.bukkit.adapter.mc1_16_1.FAWEWorldNativeAccess_1_16; import com.boydti.fawe.bukkit.adapter.mc1_16_1.MapChunkUtil_1_16_1; import com.boydti.fawe.bukkit.adapter.mc1_16_1.nbt.LazyCompoundTag_1_16_1; +import com.google.common.base.Preconditions; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; -import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.TileEntityBlock; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.CachedBukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.IDelegateBukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.regen.Regen_v1_16_R1; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.LazyBaseEntity; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; @@ -101,7 +104,6 @@ import static org.slf4j.LoggerFactory.getLogger; public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements IDelegateBukkitImplAdapter { private final Spigot_v1_16_R1 parent; private char[] ibdToStateOrdinal; - // ------------------------------------------------------------------------ // Code that may break between versions of Minecraft // ------------------------------------------------------------------------ @@ -139,8 +141,6 @@ public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements I public BlockMaterial getMaterial(BlockState state) { IBlockData bs = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState(); return new BlockMaterial_1_16_1(bs.getBlock(), bs); - - } public Block getBlock(BlockType blockType) { @@ -150,7 +150,7 @@ public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements I @SuppressWarnings("deprecation") @Override public BaseBlock getBlock(Location location) { - checkNotNull(location); + Preconditions.checkNotNull(location); CraftWorld craftWorld = ((CraftWorld) location.getWorld()); int x = location.getBlockX(); @@ -254,7 +254,7 @@ public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements I @Override public BaseEntity getEntity(org.bukkit.entity.Entity entity) { - checkNotNull(entity); + Preconditions.checkNotNull(entity); CraftEntity craftEntity = ((CraftEntity) entity); Entity mcEntity = craftEntity.getHandle(); @@ -401,81 +401,8 @@ public final class FAWE_Spigot_v1_16_R1 extends CachedBukkitAdapter implements I } @Override - public boolean regenerate(org.bukkit.World world, Region region, EditSession editSession) { -// WorldServer originalWorld = ((CraftWorld) world).getHandle(); -// ChunkProviderServer provider = originalWorld.getChunkProvider(); -// if (!(provider instanceof ChunkProviderServer)) { -// return false; -// } -// -// File saveFolder = Files.createTempDir(); -// // register this just in case something goes wrong -// // normally it should be deleted at the end of this method -// saveFolder.deleteOnExit(); -// try { -// MinecraftServer server = originalWorld.getServer().getServer(); -// Convertable.ConversionSession originalDataManager = server.convertable; -//// Convertable.ConversionSession saveHandler = new Convertable.ConversionSession(world.getName(), world.); -// WorldData newWorldData = new WorldData(originalWorld.worldData.a((NBTTagCompound) null), -// server.dataConverterManager, getDataVersion(), null); -// newWorldData.setName(UUID.randomUUID().toString()); -// -// ChunkGenerator gen = world.getGenerator(); -// Environment env = world.getEnvironment(); -// try (WorldServer freshWorld = new WorldServer(server, -// server.executorService, originalDataManager, -// newWorldData, -// originalWorld.worldProvider.getDimensionManager(), -// originalWorld.getMethodProfiler(), -// server.worldLoadListenerFactory.create(11), -// env, -// gen) { -// @Override -// public boolean addEntityChunk(Entity entity) { -// //Fixes #320; Prevent adding entities so we aren't attempting to spawn them asynchronously -// return false; -// } -// }) { -// -// // Pre-gen all the chunks -// // We need to also pull one more chunk in every direction -// Fawe.get().getQueueHandler().startSet(true); -// try { -// IQueueExtent extent = new SingleThreadQueueExtent(); -// extent.init(null, (x, z) -> new BukkitGetBlocks_1_16_1(freshWorld, x, z) { -// @Override -// public Chunk ensureLoaded(World nmsWorld, int X, int Z) { -// Chunk cached = nmsWorld.getChunkIfLoaded(X, Z); -// if (cached != null) return cached; -// Future future = Fawe.get().getQueueHandler().sync((Supplier) () -> freshWorld.getChunkAt(X, Z)); -// while (!future.isDone()) { -// // this feels so dirty -// freshWorld.getChunkProvider().runTasks(); -// } -// try { -// return future.get(); -// } catch (InterruptedException | ExecutionException e) { -// throw new RuntimeException(e); -// } -// } -// }, null); -// for (BlockVector3 vec : region) { -// editSession.setBlock(vec, extent.getFullBlock(vec)); -// } -// } finally { -// Fawe.get().getQueueHandler().endSet(true); -// } -// } catch (IOException e) { -// throw new RuntimeException(e); -// } -// } catch (MaxChangedBlocksException e) { -// throw new RuntimeException(e); -// } finally { -// saveFolder.delete(); -// } -// return true; - - return false; //TODO: rework or remove for 1.16 + public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception { + return new Regen_v1_16_R1(bukkitWorld, region, target, options).regenerate(); } @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java index 303c405d7..056988439 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/FAWE_Spigot_v1_16_R2.java @@ -28,23 +28,26 @@ import com.boydti.fawe.bukkit.adapter.mc1_16_2.BukkitGetBlocks_1_16_2; import com.boydti.fawe.bukkit.adapter.mc1_16_2.FAWEWorldNativeAccess_1_16; import com.boydti.fawe.bukkit.adapter.mc1_16_2.MapChunkUtil_1_16_2; import com.boydti.fawe.bukkit.adapter.mc1_16_2.nbt.LazyCompoundTag_1_16_2; +import com.google.common.base.Preconditions; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; -import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.TileEntityBlock; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.CachedBukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.IDelegateBukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.regen.Regen_v1_16_R2; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.LazyBaseEntity; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.internal.wna.WorldNativeAccess; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; @@ -87,7 +90,6 @@ import org.bukkit.craftbukkit.v1_16_R2.entity.CraftEntity; import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_16_R2.inventory.CraftItemStack; import org.bukkit.entity.Player; - import java.lang.ref.WeakReference; import java.util.Map; import java.util.OptionalInt; @@ -149,7 +151,7 @@ public final class FAWE_Spigot_v1_16_R2 extends CachedBukkitAdapter implements I @SuppressWarnings("deprecation") @Override public BaseBlock getBlock(Location location) { - checkNotNull(location); + Preconditions.checkNotNull(location); CraftWorld craftWorld = ((CraftWorld) location.getWorld()); int x = location.getBlockX(); @@ -253,7 +255,7 @@ public final class FAWE_Spigot_v1_16_R2 extends CachedBukkitAdapter implements I @Override public BaseEntity getEntity(org.bukkit.entity.Entity entity) { - checkNotNull(entity); + Preconditions.checkNotNull(entity); CraftEntity craftEntity = ((CraftEntity) entity); Entity mcEntity = craftEntity.getHandle(); @@ -400,81 +402,8 @@ public final class FAWE_Spigot_v1_16_R2 extends CachedBukkitAdapter implements I } @Override - public boolean regenerate(org.bukkit.World world, Region region, EditSession editSession) { -// WorldServer originalWorld = ((CraftWorld) world).getHandle(); -// ChunkProviderServer provider = originalWorld.getChunkProvider(); -// if (!(provider instanceof ChunkProviderServer)) { -// return false; -// } -// -// File saveFolder = Files.createTempDir(); -// // register this just in case something goes wrong -// // normally it should be deleted at the end of this method -// saveFolder.deleteOnExit(); -// try { -// MinecraftServer server = originalWorld.getServer().getServer(); -// Convertable.ConversionSession originalDataManager = server.convertable; -//// Convertable.ConversionSession saveHandler = new Convertable.ConversionSession(world.getName(), world.); -// WorldData newWorldData = new WorldData(originalWorld.worldData.a((NBTTagCompound) null), -// server.dataConverterManager, getDataVersion(), null); -// newWorldData.setName(UUID.randomUUID().toString()); -// -// ChunkGenerator gen = world.getGenerator(); -// Environment env = world.getEnvironment(); -// try (WorldServer freshWorld = new WorldServer(server, -// server.executorService, originalDataManager, -// newWorldData, -// originalWorld.worldProvider.getDimensionManager(), -// originalWorld.getMethodProfiler(), -// server.worldLoadListenerFactory.create(11), -// env, -// gen) { -// @Override -// public boolean addEntityChunk(Entity entity) { -// //Fixes #320; Prevent adding entities so we aren't attempting to spawn them asynchronously -// return false; -// } -// }) { -// -// // Pre-gen all the chunks -// // We need to also pull one more chunk in every direction -// Fawe.get().getQueueHandler().startSet(true); -// try { -// IQueueExtent extent = new SingleThreadQueueExtent(); -// extent.init(null, (x, z) -> new BukkitGetBlocks_1_16_2(freshWorld, x, z) { -// @Override -// public Chunk ensureLoaded(World nmsWorld, int X, int Z) { -// Chunk cached = nmsWorld.getChunkIfLoaded(X, Z); -// if (cached != null) return cached; -// Future future = Fawe.get().getQueueHandler().sync((Supplier) () -> freshWorld.getChunkAt(X, Z)); -// while (!future.isDone()) { -// // this feels so dirty -// freshWorld.getChunkProvider().runTasks(); -// } -// try { -// return future.get(); -// } catch (InterruptedException | ExecutionException e) { -// throw new RuntimeException(e); -// } -// } -// }, null); -// for (BlockVector3 vec : region) { -// editSession.setBlock(vec, extent.getFullBlock(vec)); -// } -// } finally { -// Fawe.get().getQueueHandler().endSet(true); -// } -// } catch (IOException e) { -// throw new RuntimeException(e); -// } -// } catch (MaxChangedBlocksException e) { -// throw new RuntimeException(e); -// } finally { -// saveFolder.delete(); -// } -// return true; - - return false; //TODO: rework or remove for 1.16 + public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception { + return new Regen_v1_16_R2(bukkitWorld, region, target, options).regenerate(); } @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java new file mode 100644 index 000000000..3f9958194 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_15_R2.java @@ -0,0 +1,478 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.regen; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.beta.IChunkCache; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.bukkit.adapter.mc1_15_2.BukkitGetBlocks_1_15_2; +import com.mojang.datafixers.util.Either; +import com.sk89q.worldedit.bukkit.adapter.Regenerator; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.world.RegenOptions; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BooleanSupplier; +import java.util.function.LongFunction; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import net.minecraft.server.v1_15_R1.Area; +import net.minecraft.server.v1_15_R1.AreaContextTransformed; +import net.minecraft.server.v1_15_R1.AreaFactory; +import net.minecraft.server.v1_15_R1.AreaTransformer8; +import net.minecraft.server.v1_15_R1.BiomeBase; +import net.minecraft.server.v1_15_R1.BiomeLayoutOverworldConfiguration; +import net.minecraft.server.v1_15_R1.Biomes; +import net.minecraft.server.v1_15_R1.Chunk; +import net.minecraft.server.v1_15_R1.ChunkConverter; +import net.minecraft.server.v1_15_R1.ChunkCoordIntPair; +import net.minecraft.server.v1_15_R1.ChunkGenerator; +import net.minecraft.server.v1_15_R1.ChunkProviderFlat; +import net.minecraft.server.v1_15_R1.ChunkProviderGenerate; +import net.minecraft.server.v1_15_R1.ChunkProviderHell; +import net.minecraft.server.v1_15_R1.ChunkProviderServer; +import net.minecraft.server.v1_15_R1.ChunkProviderTheEnd; +import net.minecraft.server.v1_15_R1.ChunkStatus; +import net.minecraft.server.v1_15_R1.DefinedStructureManager; +import net.minecraft.server.v1_15_R1.GenLayer; +import net.minecraft.server.v1_15_R1.GenLayers; +import net.minecraft.server.v1_15_R1.GeneratorSettingsEnd; +import net.minecraft.server.v1_15_R1.GeneratorSettingsFlat; +import net.minecraft.server.v1_15_R1.GeneratorSettingsNether; +import net.minecraft.server.v1_15_R1.GeneratorSettingsOverworld; +import net.minecraft.server.v1_15_R1.IChunkAccess; +import net.minecraft.server.v1_15_R1.IRegistry; +import net.minecraft.server.v1_15_R1.LightEngineThreaded; +import net.minecraft.server.v1_15_R1.LinearCongruentialGenerator; +import net.minecraft.server.v1_15_R1.MinecraftServer; +import net.minecraft.server.v1_15_R1.NBTTagCompound; +import net.minecraft.server.v1_15_R1.NoiseGeneratorPerlin; +import net.minecraft.server.v1_15_R1.ProtoChunk; +import net.minecraft.server.v1_15_R1.World; +import net.minecraft.server.v1_15_R1.WorldChunkManager; +import net.minecraft.server.v1_15_R1.WorldChunkManagerOverworld; +import net.minecraft.server.v1_15_R1.WorldData; +import net.minecraft.server.v1_15_R1.WorldLoadListener; +import net.minecraft.server.v1_15_R1.WorldNBTStorage; +import net.minecraft.server.v1_15_R1.WorldServer; +import net.minecraft.server.v1_15_R1.WorldType; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_15_R1.CraftServer; +import org.bukkit.craftbukkit.v1_15_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_15_R1.generator.CustomChunkGenerator; +import org.bukkit.craftbukkit.v1_15_R1.util.CraftMagicNumbers; +import org.bukkit.generator.BlockPopulator; + +public class Regen_v1_15_R2 extends Regenerator { + + private static final Field serverWorldsField; + private static final Field worldPaperConfigField; + private static final Field flatBedrockField; + private static final Field delegateField; + private static final Field chunkProviderField; + + //list of chunk stati in correct order without FULL + private static final Map chunkStati = new LinkedHashMap<>(); + + static { + chunkStati.put(ChunkStatus.EMPTY, Regenerator.Concurrency.FULL); // radius -1, does nothing + chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Regenerator.Concurrency.NONE); // uses unsynchronized maps + chunkStati.put(ChunkStatus.STRUCTURE_REFERENCES, Regenerator.Concurrency.FULL); // radius 8, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.BIOMES, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.NOISE, Regenerator.Concurrency.RADIUS); // radius 8 + chunkStati.put(ChunkStatus.SURFACE, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.LIQUID_CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.FEATURES, Regenerator.Concurrency.NONE); // uses unsynchronized maps + chunkStati.put(ChunkStatus.LIGHT, Regenerator.Concurrency.FULL); // radius 1, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.SPAWN, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.HEIGHTMAPS, Regenerator.Concurrency.FULL); // radius 0 + + try { + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + Field tmpPaperConfigField = null; + Field tmpFlatBedrockField = null; + try { //only present on paper + tmpPaperConfigField = World.class.getDeclaredField("paperConfig"); + tmpPaperConfigField.setAccessible(true); + + tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock"); + tmpFlatBedrockField.setAccessible(true); + } catch (Exception e) { + tmpPaperConfigField = null; + tmpFlatBedrockField = null; + } + worldPaperConfigField = tmpPaperConfigField; + flatBedrockField = tmpFlatBedrockField; + + delegateField = CustomChunkGenerator.class.getDeclaredField("delegate"); + delegateField.setAccessible(true); + + chunkProviderField = World.class.getDeclaredField("chunkProvider"); + chunkProviderField.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + //runtime + private WorldServer originalNMSWorld; + private ChunkProviderServer originalChunkProvider; + private WorldServer freshNMSWorld; + private ChunkProviderServer freshChunkProvider; + private DefinedStructureManager structureManager; + private LightEngineThreaded lightEngine; + private ChunkGenerator generator; + + private Path tempDir; + + private boolean generateFlatBedrock = false; + + public Regen_v1_15_R2(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) { + super(originalBukkitWorld, region, target, options); + } + + @Override + protected boolean prepare() { + this.originalNMSWorld = ((CraftWorld) originalBukkitWorld).getHandle(); + originalChunkProvider = originalNMSWorld.getChunkProvider(); + if (!(originalChunkProvider instanceof ChunkProviderServer)) { + return false; + } + + //flat bedrock? (only on paper) + try { + generateFlatBedrock = flatBedrockField.getBoolean(worldPaperConfigField.get(originalNMSWorld)); + } catch (Exception ignored) { + } + + seed = options.getSeed().orElse(originalNMSWorld.getSeed()); + chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c)); + + return true; + } + + @Override + protected boolean initNewWorld() throws Exception { + //world folder + tempDir = java.nio.file.Files.createTempDirectory("WorldEditWorldGen"); + + //prepare for world init (see upstream implementation for reference) + org.bukkit.World.Environment env = originalBukkitWorld.getEnvironment(); + org.bukkit.generator.ChunkGenerator gen = originalBukkitWorld.getGenerator(); + + MinecraftServer server = originalNMSWorld.getServer().getServer(); + WorldData newWorldData = new WorldData(originalNMSWorld.worldData.a((NBTTagCompound) null), server.dataConverterManager, CraftMagicNumbers.INSTANCE.getDataVersion(), (NBTTagCompound) null); + newWorldData.setName("worldeditregentempworld"); + WorldNBTStorage saveHandler = new WorldNBTStorage(new File(tempDir.toUri()), originalNMSWorld.getDataManager().getDirectory().getName(), server, server.dataConverterManager); + + //init world + freshNMSWorld = Fawe.get().getQueueHandler().sync((Supplier) () -> new WorldServer(server, server.executorService, saveHandler, newWorldData, originalNMSWorld.worldProvider.getDimensionManager(), originalNMSWorld.getMethodProfiler(), new RegenNoOpWorldLoadListener(), env, gen) { + @Override + public void doTick(BooleanSupplier booleansupplier) { //no ticking + } + }).get(); + freshNMSWorld.savingDisabled = true; + removeWorldFromWorldsMap(); + newWorldData.checkName(originalNMSWorld.getWorldData().getName()); //rename to original world name + + try { //flat bedrock (paper only) + Object paperconf = worldPaperConfigField.get(freshNMSWorld); + flatBedrockField.setBoolean(paperconf, generateFlatBedrock); + } catch (Exception e) { + } + + DefinedStructureManager tmpStructureManager = saveHandler.f(); + freshChunkProvider = new ChunkProviderServer(freshNMSWorld, saveHandler.getDirectory(), server.aC(), tmpStructureManager, server.executorService, originalChunkProvider.chunkGenerator, freshNMSWorld.spigotConfig.viewDistance, new RegenNoOpWorldLoadListener(), () -> freshNMSWorld.getWorldPersistentData()) { + // redirect to our protoChunks list + @Override + public IChunkAccess getChunkAt(int x, int z, ChunkStatus chunkstatus, boolean flag) { + return getProtoChunkAt(x, z); + } + }; + chunkProviderField.set(freshNMSWorld, freshChunkProvider); + + //generator + if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderFlat) { + GeneratorSettingsFlat generatorSettingFlat = (GeneratorSettingsFlat) originalChunkProvider.getChunkGenerator().getSettings(); + generator = new ChunkProviderFlat(freshNMSWorld, originalChunkProvider.getChunkGenerator().getWorldChunkManager(), generatorSettingFlat); + } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderGenerate) { //overworld + GeneratorSettingsOverworld settings = (GeneratorSettingsOverworld) originalChunkProvider.getChunkGenerator().getSettings(); + WorldChunkManager chunkManager = originalChunkProvider.getChunkGenerator().getWorldChunkManager(); + if (chunkManager instanceof WorldChunkManagerOverworld) { //should always be true + chunkManager = fastOverWorldChunkManager(chunkManager); + } + generator = new ChunkProviderGenerate(freshNMSWorld, chunkManager, settings); + } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderHell) { //nether + GeneratorSettingsNether settings = (GeneratorSettingsNether) originalChunkProvider.getChunkGenerator().getSettings(); + generator = new ChunkProviderHell(freshNMSWorld, originalChunkProvider.getChunkGenerator().getWorldChunkManager(), settings); + } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderTheEnd) { //end + GeneratorSettingsEnd settings = (GeneratorSettingsEnd) originalChunkProvider.getChunkGenerator().getSettings(); + generator = new ChunkProviderTheEnd(freshNMSWorld, originalChunkProvider.getChunkGenerator().getWorldChunkManager(), settings); + } else if (originalChunkProvider.getChunkGenerator() instanceof CustomChunkGenerator) { + ChunkGenerator delegate = (ChunkGenerator) delegateField.get(originalChunkProvider.getChunkGenerator()); + generator = delegate; + } else { + System.out.println("Unsupported generator type " + originalChunkProvider.getChunkGenerator().getClass().getName()); + return false; + } + if (originalNMSWorld.generator != null) { + // wrap custom world generator + generator = new CustomChunkGenerator(freshNMSWorld, originalNMSWorld.generator); + generateConcurrent = originalNMSWorld.generator.isParallelCapable(); + } + + //lets start then + structureManager = tmpStructureManager; + lightEngine = freshChunkProvider.getLightEngine(); + + return true; + } + + @Override + protected void cleanup() { + //shutdown chunk provider + try { + Fawe.get().getQueueHandler().sync(() -> { + try { + freshChunkProvider.close(false); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (Exception e) { + } + + //remove world from server + try { + removeWorldFromWorldsMap(); + } catch (Exception e) { + } + + //delete directory + try { + SafeFiles.tryHardToDeleteDir(tempDir); + } catch (Exception e) { + } + } + + @Override + protected ProtoChunk createProtoChunk(int x, int z) { + return new ProtoChunk(new ChunkCoordIntPair(x, z), ChunkConverter.a) { + public boolean generateFlatBedrock() { + return generateFlatBedrock; + } + }; + } + + @Override + protected Chunk createChunk(ProtoChunk protoChunk) { + return new Chunk(freshNMSWorld, protoChunk); + } + + @Override + protected ChunkStatusWrap getFullChunkStatus() { + return new ChunkStatusWrap(ChunkStatus.FULL); + } + + @Override + protected List getBlockPopulators() { + return originalNMSWorld.getWorld().getPopulators(); + } + + @Override + protected void populate(Chunk chunk, Random random, BlockPopulator pop) { + pop.populate(freshNMSWorld.getWorld(), random, chunk.bukkitChunk); + } + + @Override + protected IChunkCache initSourceQueueCache() { + return (chunkX, chunkZ) -> new BukkitGetBlocks_1_15_2(freshNMSWorld, chunkX, chunkZ) { + @Override + public Chunk ensureLoaded(World nmsWorld, int x, int z) { + return getChunkAt(x, z); + } + }; + } + + protected class ChunkStatusWrap extends Regenerator.ChunkStatusWrapper { + + private final ChunkStatus chunkStatus; + + public ChunkStatusWrap(ChunkStatus chunkStatus) { + this.chunkStatus = chunkStatus; + } + + @Override + public int requiredNeigborChunkRadius() { + return chunkStatus.f(); + } + + @Override + public String name() { + return chunkStatus.d(); + } + + @Override + public void processChunk(Long xz, List accessibleChunks) { + chunkStatus.a(freshNMSWorld, + generator, + structureManager, + lightEngine, + c -> CompletableFuture.completedFuture(Either.left(c)), + accessibleChunks); + } + } + + //util + private void removeWorldFromWorldsMap() { + Fawe.get().getQueueHandler().sync(() -> { + try { + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove("worldeditregentempworld"); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } + + private WorldChunkManager fastOverWorldChunkManager(WorldChunkManager chunkManager) throws Exception { + Field genLayerField = WorldChunkManagerOverworld.class.getDeclaredField("d"); + genLayerField.setAccessible(true); + Field areaLazyField = GenLayer.class.getDeclaredField("b"); + areaLazyField.setAccessible(true); + Method initAreaFactoryMethod = GenLayers.class.getDeclaredMethod("a", WorldType.class, GeneratorSettingsOverworld.class, LongFunction.class); + initAreaFactoryMethod.setAccessible(true); + + //init new WorldChunkManagerOverworld + BiomeLayoutOverworldConfiguration biomeconfig = new BiomeLayoutOverworldConfiguration(freshNMSWorld.getWorldData()) + .a((GeneratorSettingsOverworld) originalChunkProvider.getChunkGenerator().getSettings()); + chunkManager = new WorldChunkManagerOverworld(biomeconfig); + + //replace genLayer + AreaFactory factory = (AreaFactory) initAreaFactoryMethod.invoke(null, biomeconfig.b(), biomeconfig.c(), (LongFunction) (l -> new FastWorldGenContextArea(seed, l))); + genLayerField.set(chunkManager, new FastGenLayer(factory)); + + return chunkManager; + } + + private static class FastWorldGenContextArea implements AreaContextTransformed { + + private final ConcurrentHashMap sharedAreaMap = new ConcurrentHashMap<>(); + private final NoiseGeneratorPerlin perlinNoise; + private final long magicrandom; + private final ConcurrentHashMap map = new ConcurrentHashMap<>(); //needed for multithreaded generation + + public FastWorldGenContextArea(long seed, long lconst) { + this.magicrandom = mix(seed, lconst); + this.perlinNoise = new NoiseGeneratorPerlin(new Random(seed)); + } + + @Override + public FastAreaLazy a(AreaTransformer8 var0) { + return new FastAreaLazy(sharedAreaMap, var0); + } + + @Override + public void a(long x, long z) { + long l = this.magicrandom; + l = LinearCongruentialGenerator.a(l, x); + l = LinearCongruentialGenerator.a(l, z); + l = LinearCongruentialGenerator.a(l, x); + l = LinearCongruentialGenerator.a(l, z); + this.map.put(Thread.currentThread().getId(), l); + } + + @Override + public int a(int y) { + long tid = Thread.currentThread().getId(); + long e = this.map.computeIfAbsent(tid, i -> 0L); + int mod = (int) Math.floorMod(e >> 24L, (long) y); + this.map.put(tid, LinearCongruentialGenerator.a(e, this.magicrandom)); + return mod; + } + + @Override + public NoiseGeneratorPerlin b() { + return this.perlinNoise; + } + + private static long mix(long seed, long lconst) { + long l1 = lconst; + l1 = LinearCongruentialGenerator.a(l1, lconst); + l1 = LinearCongruentialGenerator.a(l1, lconst); + l1 = LinearCongruentialGenerator.a(l1, lconst); + long l2 = seed; + l2 = LinearCongruentialGenerator.a(l2, l1); + l2 = LinearCongruentialGenerator.a(l2, l1); + l2 = LinearCongruentialGenerator.a(l2, l1); + return l2; + } + } + + private static class FastGenLayer extends GenLayer { + + private final FastAreaLazy areaLazy; + + public FastGenLayer(AreaFactory factory) throws Exception { + super(() -> null); + this.areaLazy = factory.make(); + } + + @Override + public BiomeBase a(int x, int z) { + BiomeBase biome = IRegistry.BIOME.fromId(this.areaLazy.a(x, z)); + if (biome == null) + return Biomes.b; + return biome; + } + } + + private static class FastAreaLazy implements Area { + + private final AreaTransformer8 transformer; + //ConcurrentHashMap is 50% faster that Long2IntLinkedOpenHashMap in a syncronized context + //using a map for each thread worsens the performance significantly due to cache misses (factor 5) + private final ConcurrentHashMap sharedMap; + + public FastAreaLazy(ConcurrentHashMap sharedMap, AreaTransformer8 transformer) { + this.sharedMap = sharedMap; + this.transformer = transformer; + } + + @Override + public int a(int x, int z) { + long zx = ChunkCoordIntPair.pair(x, z); + return this.sharedMap.computeIfAbsent(zx, i -> this.transformer.apply(x, z)); + } + } + + private static class RegenNoOpWorldLoadListener implements WorldLoadListener { + + private RegenNoOpWorldLoadListener() { + } + + @Override + public void a(ChunkCoordIntPair chunkCoordIntPair) { + } + + @Override + public void a(ChunkCoordIntPair chunkCoordIntPair, @Nullable ChunkStatus chunkStatus) { + } + + @Override + public void b() { + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R1.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R1.java new file mode 100644 index 000000000..af66ef955 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R1.java @@ -0,0 +1,519 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.regen; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.beta.IChunkCache; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.bukkit.adapter.mc1_16_1.BukkitGetBlocks_1_16_1; +import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.Lifecycle; +import com.sk89q.worldedit.bukkit.adapter.Regenerator; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.world.RegenOptions; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BooleanSupplier; +import java.util.function.LongFunction; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import net.minecraft.server.v1_16_R1.Area; +import net.minecraft.server.v1_16_R1.AreaContextTransformed; +import net.minecraft.server.v1_16_R1.AreaFactory; +import net.minecraft.server.v1_16_R1.AreaTransformer8; +import net.minecraft.server.v1_16_R1.BiomeBase; +import net.minecraft.server.v1_16_R1.Biomes; +import net.minecraft.server.v1_16_R1.Chunk; +import net.minecraft.server.v1_16_R1.ChunkConverter; +import net.minecraft.server.v1_16_R1.ChunkCoordIntPair; +import net.minecraft.server.v1_16_R1.ChunkGenerator; +import net.minecraft.server.v1_16_R1.ChunkGeneratorAbstract; +import net.minecraft.server.v1_16_R1.ChunkProviderFlat; +import net.minecraft.server.v1_16_R1.ChunkProviderServer; +import net.minecraft.server.v1_16_R1.ChunkStatus; +import net.minecraft.server.v1_16_R1.Convertable; +import net.minecraft.server.v1_16_R1.DefinedStructureManager; +import net.minecraft.server.v1_16_R1.DynamicOpsNBT; +import net.minecraft.server.v1_16_R1.GenLayer; +import net.minecraft.server.v1_16_R1.GenLayers; +import net.minecraft.server.v1_16_R1.GeneratorSettingBase; +import net.minecraft.server.v1_16_R1.GeneratorSettings; +import net.minecraft.server.v1_16_R1.GeneratorSettingsFlat; +import net.minecraft.server.v1_16_R1.IChunkAccess; +import net.minecraft.server.v1_16_R1.IRegistry; +import net.minecraft.server.v1_16_R1.IRegistryCustom; +import net.minecraft.server.v1_16_R1.LightEngineThreaded; +import net.minecraft.server.v1_16_R1.LinearCongruentialGenerator; +import net.minecraft.server.v1_16_R1.MinecraftServer; +import net.minecraft.server.v1_16_R1.NBTBase; +import net.minecraft.server.v1_16_R1.NBTTagCompound; +import net.minecraft.server.v1_16_R1.NoiseGeneratorPerlin; +import net.minecraft.server.v1_16_R1.ProtoChunk; +import net.minecraft.server.v1_16_R1.RegistryReadOps; +import net.minecraft.server.v1_16_R1.ResourceKey; +import net.minecraft.server.v1_16_R1.World; +import net.minecraft.server.v1_16_R1.WorldChunkManager; +import net.minecraft.server.v1_16_R1.WorldChunkManagerOverworld; +import net.minecraft.server.v1_16_R1.WorldDataServer; +import net.minecraft.server.v1_16_R1.WorldDimension; +import net.minecraft.server.v1_16_R1.WorldLoadListener; +import net.minecraft.server.v1_16_R1.WorldServer; +import net.minecraft.server.v1_16_R1.WorldSettings; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_16_R1.CraftServer; +import org.bukkit.craftbukkit.v1_16_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R1.generator.CustomChunkGenerator; +import org.bukkit.generator.BlockPopulator; + +public class Regen_v1_16_R1 extends Regenerator { + + private static final Field serverWorldsField; + private static final Field worldPaperConfigField; + private static final Field flatBedrockField; + private static final Field generatorSettingBaseField; + private static final Field generatorSettingFlatField; + private static final Field delegateField; + private static final Field chunkProviderField; + + //list of chunk stati in correct order without FULL + private static final Map chunkStati = new LinkedHashMap<>(); + + static { + chunkStati.put(ChunkStatus.EMPTY, Regenerator.Concurrency.FULL); // radius -1, does nothing + chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Regenerator.Concurrency.NONE); // uses unsynchronized maps + chunkStati.put(ChunkStatus.STRUCTURE_REFERENCES, Regenerator.Concurrency.FULL); // radius 8, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.BIOMES, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.NOISE, Regenerator.Concurrency.RADIUS); // radius 8 + chunkStati.put(ChunkStatus.SURFACE, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.LIQUID_CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.FEATURES, Regenerator.Concurrency.NONE); // uses unsynchronized maps + chunkStati.put(ChunkStatus.LIGHT, Regenerator.Concurrency.FULL); // radius 1, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.SPAWN, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.HEIGHTMAPS, Regenerator.Concurrency.FULL); // radius 0 + + try { + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + Field tmpPaperConfigField = null; + Field tmpFlatBedrockField = null; + try { //only present on paper + tmpPaperConfigField = World.class.getDeclaredField("paperConfig"); + tmpPaperConfigField.setAccessible(true); + + tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock"); + tmpFlatBedrockField.setAccessible(true); + } catch (Exception e) { + tmpPaperConfigField = null; + tmpFlatBedrockField = null; + } + worldPaperConfigField = tmpPaperConfigField; + flatBedrockField = tmpFlatBedrockField; + + generatorSettingBaseField = ChunkGeneratorAbstract.class.getDeclaredField("h"); + generatorSettingBaseField.setAccessible(true); + + generatorSettingFlatField = ChunkProviderFlat.class.getDeclaredField("e"); + generatorSettingFlatField.setAccessible(true); + + delegateField = CustomChunkGenerator.class.getDeclaredField("delegate"); + delegateField.setAccessible(true); + + chunkProviderField = WorldServer.class.getDeclaredField("chunkProvider"); + chunkProviderField.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + //runtime + private WorldServer originalNMSWorld; + private ChunkProviderServer originalChunkProvider; + private WorldServer freshNMSWorld; + private ChunkProviderServer freshChunkProvider; + private Convertable.ConversionSession session; + private DefinedStructureManager structureManager; + private LightEngineThreaded lightEngine; + private ChunkGenerator generator; + + private Path tempDir; + + private boolean generateFlatBedrock = false; + + public Regen_v1_16_R1(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) { + super(originalBukkitWorld, region, target, options); + } + + @Override + protected boolean prepare() { + this.originalNMSWorld = ((CraftWorld) originalBukkitWorld).getHandle(); + originalChunkProvider = originalNMSWorld.getChunkProvider(); + if (!(originalChunkProvider instanceof ChunkProviderServer)) { + return false; + } + + //flat bedrock? (only on paper) + try { + generateFlatBedrock = flatBedrockField.getBoolean(worldPaperConfigField.get(originalNMSWorld)); + } catch (Exception ignored) { + } + + seed = options.getSeed().orElse(originalNMSWorld.getSeed()); + chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c)); + + return true; + } + + @Override + protected boolean initNewWorld() throws Exception { + //world folder + tempDir = java.nio.file.Files.createTempDirectory("WorldEditWorldGen"); + + //prepare for world init (see upstream implementation for reference) + org.bukkit.World.Environment env = originalBukkitWorld.getEnvironment(); + org.bukkit.generator.ChunkGenerator gen = originalBukkitWorld.getGenerator(); + Convertable convertable = Convertable.a(tempDir); + ResourceKey worldDimKey = getWorldDimKey(env); + session = convertable.c("worldeditregentempworld", worldDimKey); + WorldDataServer originalWorldData = originalNMSWorld.worldDataServer; + + MinecraftServer server = originalNMSWorld.getServer().getServer(); + WorldDataServer levelProperties = (WorldDataServer) server.getSaveData(); + GeneratorSettings newOpts = GeneratorSettings.a.encodeStart(DynamicOpsNBT.a, levelProperties.getGeneratorSettings()).flatMap(tag -> GeneratorSettings.a.parse(this.recursivelySetSeed(new Dynamic<>(DynamicOpsNBT.a, tag), seed, new HashSet<>()))).result().orElseThrow(() -> new IllegalStateException("Unable to map GeneratorOptions")); + WorldSettings newWorldSettings = new WorldSettings("worldeditregentempworld", originalWorldData.b.getGameType(), originalWorldData.b.hardcore, originalWorldData.b.getDifficulty(), originalWorldData.b.e(), originalWorldData.b.getGameRules(), originalWorldData.b.g()); + WorldDataServer newWorldData = new WorldDataServer(newWorldSettings, newOpts, Lifecycle.stable()); + + //init world + freshNMSWorld = Fawe.get().getQueueHandler().sync((Supplier) () -> new WorldServer(server, server.executorService, session, newWorldData, originalNMSWorld.getDimensionKey(), originalNMSWorld.getTypeKey(), originalNMSWorld.getDimensionManager(), new RegenNoOpWorldLoadListener(), ((WorldDimension) newOpts.e().a(worldDimKey)).c(), originalNMSWorld.isDebugWorld(), seed, ImmutableList.of(), false, env, gen) { + @Override + public void doTick(BooleanSupplier booleansupplier) { //no ticking + } + }).get(); + freshNMSWorld.savingDisabled = true; + removeWorldFromWorldsMap(); + newWorldData.checkName(originalNMSWorld.worldDataServer.getName()); //rename to original world name + + freshChunkProvider = new ChunkProviderServer(freshNMSWorld, session, server.getDataFixer(), server.getDefinedStructureManager(), server.executorService, originalChunkProvider.chunkGenerator, freshNMSWorld.spigotConfig.viewDistance, server.isSyncChunkWrites(), new RegenNoOpWorldLoadListener(), () -> server.D().getWorldPersistentData()) { + // redirect to our protoChunks list + @Override + public IChunkAccess getChunkAt(int x, int z, ChunkStatus chunkstatus, boolean flag) { + return getProtoChunkAt(x, z); + } + }; + chunkProviderField.set(freshNMSWorld, freshChunkProvider); + + //generator + if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderFlat) { + GeneratorSettingsFlat generatorSettingFlat = (GeneratorSettingsFlat) generatorSettingFlatField.get(originalChunkProvider.getChunkGenerator()); + generator = new ChunkProviderFlat(generatorSettingFlat); + } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkGeneratorAbstract) { + GeneratorSettingBase generatorSettingBase = (GeneratorSettingBase) generatorSettingBaseField.get(originalChunkProvider.getChunkGenerator()); + WorldChunkManager chunkManager = originalChunkProvider.getChunkGenerator().getWorldChunkManager(); + if (chunkManager instanceof WorldChunkManagerOverworld) { + chunkManager = fastOverWorldChunkManager(chunkManager); + } + generator = new ChunkGeneratorAbstract(chunkManager, seed, generatorSettingBase); + } else if (originalChunkProvider.getChunkGenerator() instanceof CustomChunkGenerator) { + ChunkGenerator delegate = (ChunkGenerator) delegateField.get(originalChunkProvider.getChunkGenerator()); + generator = delegate; + } else { + System.out.println("Unsupported generator type " + originalChunkProvider.getChunkGenerator().getClass().getName()); + return false; + } + if (originalNMSWorld.generator != null) { + // wrap custom world generator + generator = new CustomChunkGenerator(freshNMSWorld, generator, originalNMSWorld.generator); + generateConcurrent = originalNMSWorld.generator.isParallelCapable(); + } + + //lets start then + structureManager = server.getDefinedStructureManager(); + lightEngine = freshChunkProvider.getLightEngine(); + + return true; + } + + @Override + protected void cleanup() { + try { + session.close(); + } catch (Exception e) { + } + + //shutdown chunk provider + try { + Fawe.get().getQueueHandler().sync(() -> { + try { + freshChunkProvider.close(false); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (Exception e) { + } + + //remove world from server + try { + removeWorldFromWorldsMap(); + } catch (Exception e) { + } + + //delete directory + try { + SafeFiles.tryHardToDeleteDir(tempDir); + } catch (Exception e) { + } + } + + @Override + protected ProtoChunk createProtoChunk(int x, int z) { + return new ProtoChunk(new ChunkCoordIntPair(x, z), ChunkConverter.a) { + public boolean generateFlatBedrock() { + return generateFlatBedrock; + } + }; + } + + @Override + protected Chunk createChunk(ProtoChunk protoChunk) { + return new Chunk(freshNMSWorld, protoChunk); + } + + @Override + protected ChunkStatusWrap getFullChunkStatus() { + return new ChunkStatusWrap(ChunkStatus.FULL); + } + + @Override + protected List getBlockPopulators() { + return originalNMSWorld.getWorld().getPopulators(); + } + + @Override + protected void populate(Chunk chunk, Random random, BlockPopulator pop) { + pop.populate(freshNMSWorld.getWorld(), random, chunk.bukkitChunk); + } + + @Override + protected IChunkCache initSourceQueueCache() { + return (chunkX, chunkZ) -> new BukkitGetBlocks_1_16_1(freshNMSWorld, chunkX, chunkZ) { + @Override + public Chunk ensureLoaded(World nmsWorld, int x, int z) { + return getChunkAt(x, z); + } + }; + } + + protected class ChunkStatusWrap extends Regenerator.ChunkStatusWrapper { + + private final ChunkStatus chunkStatus; + + public ChunkStatusWrap(ChunkStatus chunkStatus) { + this.chunkStatus = chunkStatus; + } + + @Override + public int requiredNeigborChunkRadius() { + return chunkStatus.f(); + } + + @Override + public String name() { + return chunkStatus.d(); + } + + @Override + public void processChunk(Long xz, List accessibleChunks) { + chunkStatus.a(freshNMSWorld, + generator, + structureManager, + lightEngine, + c -> CompletableFuture.completedFuture(Either.left(c)), + accessibleChunks); + } + } + + //util + private void removeWorldFromWorldsMap() { + Fawe.get().getQueueHandler().sync(() -> { + try { + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove("worldeditregentempworld"); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } + + private ResourceKey getWorldDimKey(org.bukkit.World.Environment env) { + switch (env) { + case NETHER: + return WorldDimension.THE_NETHER; + case THE_END: + return WorldDimension.THE_END; + case NORMAL: + default: + return WorldDimension.OVERWORLD; + } + } + + private Dynamic recursivelySetSeed(Dynamic dynamic, long seed, Set> seen) { + return !seen.add(dynamic) ? dynamic : dynamic.updateMapValues((pair) -> { + if (((Dynamic) pair.getFirst()).asString("").equals("seed")) { + return pair.mapSecond((v) -> { + return v.createLong(seed); + }); + } else { + return ((Dynamic) pair.getSecond()).getValue() instanceof NBTTagCompound ? pair.mapSecond((v) -> { + return this.recursivelySetSeed((Dynamic) v, seed, seen); + }) : pair; + } + }); + } + + private WorldChunkManager fastOverWorldChunkManager(WorldChunkManager chunkManager) throws Exception { + Field legacyBiomeInitLayerField = WorldChunkManagerOverworld.class.getDeclaredField("i"); + legacyBiomeInitLayerField.setAccessible(true); + Field largeBiomesField = WorldChunkManagerOverworld.class.getDeclaredField("j"); + largeBiomesField.setAccessible(true); + Field genLayerField = WorldChunkManagerOverworld.class.getDeclaredField("f"); + genLayerField.setAccessible(true); + Field areaLazyField = GenLayer.class.getDeclaredField("b"); + areaLazyField.setAccessible(true); + Method initAreaFactoryMethod = GenLayers.class.getDeclaredMethod("a", boolean.class, int.class, int.class, LongFunction.class); + initAreaFactoryMethod.setAccessible(true); + + //init new WorldChunkManagerOverworld + boolean legacyBiomeInitLayer = legacyBiomeInitLayerField.getBoolean(chunkManager); + boolean largebiomes = largeBiomesField.getBoolean(chunkManager); + chunkManager = new WorldChunkManagerOverworld(seed, legacyBiomeInitLayer, largebiomes); + + //replace genLayer + AreaFactory factory = (AreaFactory) initAreaFactoryMethod.invoke(null, legacyBiomeInitLayer, largebiomes ? 6 : 4, 4, (LongFunction) (l -> new FastWorldGenContextArea(seed, l))); + genLayerField.set(chunkManager, new FastGenLayer(factory)); + + return chunkManager; + } + + private static class FastWorldGenContextArea implements AreaContextTransformed { + + private final ConcurrentHashMap sharedAreaMap = new ConcurrentHashMap<>(); + private final NoiseGeneratorPerlin perlinNoise; + private final long magicrandom; + private final ConcurrentHashMap map = new ConcurrentHashMap<>(); //needed for multithreaded generation + + public FastWorldGenContextArea(long seed, long lconst) { + this.magicrandom = mix(seed, lconst); + this.perlinNoise = new NoiseGeneratorPerlin(new Random(seed)); + } + + @Override + public FastAreaLazy a(AreaTransformer8 var0) { + return new FastAreaLazy(sharedAreaMap, var0); + } + + @Override + public void a(long x, long z) { + long l = this.magicrandom; + l = LinearCongruentialGenerator.a(l, x); + l = LinearCongruentialGenerator.a(l, z); + l = LinearCongruentialGenerator.a(l, x); + l = LinearCongruentialGenerator.a(l, z); + this.map.put(Thread.currentThread().getId(), l); + } + + @Override + public int a(int y) { + long tid = Thread.currentThread().getId(); + long e = this.map.computeIfAbsent(tid, i -> 0L); + int mod = (int) Math.floorMod(e >> 24L, (long) y); + this.map.put(tid, LinearCongruentialGenerator.a(e, this.magicrandom)); + return mod; + } + + @Override + public NoiseGeneratorPerlin b() { + return this.perlinNoise; + } + + private static long mix(long seed, long lconst) { + long l1 = lconst; + l1 = LinearCongruentialGenerator.a(l1, lconst); + l1 = LinearCongruentialGenerator.a(l1, lconst); + l1 = LinearCongruentialGenerator.a(l1, lconst); + long l2 = seed; + l2 = LinearCongruentialGenerator.a(l2, l1); + l2 = LinearCongruentialGenerator.a(l2, l1); + l2 = LinearCongruentialGenerator.a(l2, l1); + return l2; + } + } + + private static class FastGenLayer extends GenLayer { + + private final FastAreaLazy areaLazy; + + public FastGenLayer(AreaFactory factory) throws Exception { + super(() -> null); + this.areaLazy = factory.make(); + } + + @Override + public BiomeBase a(int x, int z) { + BiomeBase biome = IRegistry.BIOME.fromId(this.areaLazy.a(x, z)); + if (biome == null) + return Biomes.b; + return biome; + } + } + + private static class FastAreaLazy implements Area { + + private final AreaTransformer8 transformer; + //ConcurrentHashMap is 50% faster that Long2IntLinkedOpenHashMap in a syncronized context + //using a map for each thread worsens the performance significantly due to cache misses (factor 5) + private final ConcurrentHashMap sharedMap; + + public FastAreaLazy(ConcurrentHashMap sharedMap, AreaTransformer8 transformer) { + this.sharedMap = sharedMap; + this.transformer = transformer; + } + + @Override + public int a(int x, int z) { + long zx = ChunkCoordIntPair.pair(x, z); + return this.sharedMap.computeIfAbsent(zx, i -> this.transformer.apply(x, z)); + } + } + + private static class RegenNoOpWorldLoadListener implements WorldLoadListener { + + private RegenNoOpWorldLoadListener() { + } + + @Override + public void a(ChunkCoordIntPair chunkCoordIntPair) { + } + + @Override + public void a(ChunkCoordIntPair chunkCoordIntPair, @Nullable ChunkStatus chunkStatus) { + } + + @Override + public void b() { + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R2.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R2.java new file mode 100644 index 000000000..af58d56cf --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/regen/Regen_v1_16_R2.java @@ -0,0 +1,529 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.regen; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.beta.IChunkCache; +import com.boydti.fawe.beta.IChunkGet; +import com.boydti.fawe.bukkit.adapter.mc1_16_2.BukkitGetBlocks_1_16_2; +import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.Lifecycle; +import com.sk89q.worldedit.bukkit.adapter.Regenerator; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.world.RegenOptions; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BooleanSupplier; +import java.util.function.LongFunction; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import net.minecraft.server.v1_16_R2.Area; +import net.minecraft.server.v1_16_R2.AreaContextTransformed; +import net.minecraft.server.v1_16_R2.AreaFactory; +import net.minecraft.server.v1_16_R2.AreaTransformer8; +import net.minecraft.server.v1_16_R2.BiomeBase; +import net.minecraft.server.v1_16_R2.BiomeRegistry; +import net.minecraft.server.v1_16_R2.Chunk; +import net.minecraft.server.v1_16_R2.ChunkConverter; +import net.minecraft.server.v1_16_R2.ChunkCoordIntPair; +import net.minecraft.server.v1_16_R2.ChunkGenerator; +import net.minecraft.server.v1_16_R2.ChunkGeneratorAbstract; +import net.minecraft.server.v1_16_R2.ChunkProviderFlat; +import net.minecraft.server.v1_16_R2.ChunkProviderServer; +import net.minecraft.server.v1_16_R2.ChunkStatus; +import net.minecraft.server.v1_16_R2.Convertable; +import net.minecraft.server.v1_16_R2.DefinedStructureManager; +import net.minecraft.server.v1_16_R2.DynamicOpsNBT; +import net.minecraft.server.v1_16_R2.GenLayer; +import net.minecraft.server.v1_16_R2.GenLayers; +import net.minecraft.server.v1_16_R2.GeneratorSettingBase; +import net.minecraft.server.v1_16_R2.GeneratorSettings; +import net.minecraft.server.v1_16_R2.GeneratorSettingsFlat; +import net.minecraft.server.v1_16_R2.IChunkAccess; +import net.minecraft.server.v1_16_R2.IRegistry; +import net.minecraft.server.v1_16_R2.IRegistryCustom; +import net.minecraft.server.v1_16_R2.LightEngineThreaded; +import net.minecraft.server.v1_16_R2.LinearCongruentialGenerator; +import net.minecraft.server.v1_16_R2.MinecraftServer; +import net.minecraft.server.v1_16_R2.NBTBase; +import net.minecraft.server.v1_16_R2.NBTTagCompound; +import net.minecraft.server.v1_16_R2.NoiseGeneratorPerlin; +import net.minecraft.server.v1_16_R2.ProtoChunk; +import net.minecraft.server.v1_16_R2.RegistryReadOps; +import net.minecraft.server.v1_16_R2.ResourceKey; +import net.minecraft.server.v1_16_R2.World; +import net.minecraft.server.v1_16_R2.WorldChunkManager; +import net.minecraft.server.v1_16_R2.WorldChunkManagerOverworld; +import net.minecraft.server.v1_16_R2.WorldDataServer; +import net.minecraft.server.v1_16_R2.WorldDimension; +import net.minecraft.server.v1_16_R2.WorldLoadListener; +import net.minecraft.server.v1_16_R2.WorldServer; +import net.minecraft.server.v1_16_R2.WorldSettings; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_16_R2.CraftServer; +import org.bukkit.craftbukkit.v1_16_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_16_R2.generator.CustomChunkGenerator; +import org.bukkit.generator.BlockPopulator; + +public class Regen_v1_16_R2 extends Regenerator { + + private static final Field serverWorldsField; + private static final Field worldPaperConfigField; + private static final Field flatBedrockField; + private static final Field generatorSettingBaseSupplierField; + private static final Field generatorSettingFlatField; + private static final Field delegateField; + private static final Field chunkProviderField; + + //list of chunk stati in correct order without FULL + private static final Map chunkStati = new LinkedHashMap<>(); + + static { + chunkStati.put(ChunkStatus.EMPTY, Regenerator.Concurrency.FULL); // radius -1, does nothing + chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Regenerator.Concurrency.NONE); // uses unsynchronized maps + chunkStati.put(ChunkStatus.STRUCTURE_REFERENCES, Regenerator.Concurrency.FULL); // radius 8, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.BIOMES, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.NOISE, Regenerator.Concurrency.RADIUS); // radius 8 + chunkStati.put(ChunkStatus.SURFACE, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.LIQUID_CARVERS, Regenerator.Concurrency.NONE); // radius 0, but RADIUS and FULL change results + chunkStati.put(ChunkStatus.FEATURES, Regenerator.Concurrency.NONE); // uses unsynchronized maps + chunkStati.put(ChunkStatus.LIGHT, Regenerator.Concurrency.FULL); // radius 1, but no writes to other chunks, only current chunk + chunkStati.put(ChunkStatus.SPAWN, Regenerator.Concurrency.FULL); // radius 0 + chunkStati.put(ChunkStatus.HEIGHTMAPS, Regenerator.Concurrency.FULL); // radius 0 + + try { + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + Field tmpPaperConfigField = null; + Field tmpFlatBedrockField = null; + try { //only present on paper + tmpPaperConfigField = World.class.getDeclaredField("paperConfig"); + tmpPaperConfigField.setAccessible(true); + + tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock"); + tmpFlatBedrockField.setAccessible(true); + } catch (Exception e) { + tmpPaperConfigField = null; + tmpFlatBedrockField = null; + } + worldPaperConfigField = tmpPaperConfigField; + flatBedrockField = tmpFlatBedrockField; + + generatorSettingBaseSupplierField = ChunkGeneratorAbstract.class.getDeclaredField("h"); + generatorSettingBaseSupplierField.setAccessible(true); + + generatorSettingFlatField = ChunkProviderFlat.class.getDeclaredField("e"); + generatorSettingFlatField.setAccessible(true); + + delegateField = CustomChunkGenerator.class.getDeclaredField("delegate"); + delegateField.setAccessible(true); + + chunkProviderField = WorldServer.class.getDeclaredField("chunkProvider"); + chunkProviderField.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + //runtime + private WorldServer originalNMSWorld; + private ChunkProviderServer originalChunkProvider; + private WorldServer freshNMSWorld; + private ChunkProviderServer freshChunkProvider; + private Convertable.ConversionSession session; + private DefinedStructureManager structureManager; + private LightEngineThreaded lightEngine; + private ChunkGenerator generator; + + private Path tempDir; + + private boolean generateFlatBedrock = false; + + public Regen_v1_16_R2(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) { + super(originalBukkitWorld, region, target, options); + } + + @Override + protected boolean prepare() { + this.originalNMSWorld = ((CraftWorld) originalBukkitWorld).getHandle(); + originalChunkProvider = originalNMSWorld.getChunkProvider(); + if (!(originalChunkProvider instanceof ChunkProviderServer)) { + return false; + } + + //flat bedrock? (only on paper) + try { + generateFlatBedrock = flatBedrockField.getBoolean(worldPaperConfigField.get(originalNMSWorld)); + } catch (Exception ignored) { + } + + seed = options.getSeed().orElse(originalNMSWorld.getSeed()); + chunkStati.forEach((s, c) -> super.chunkStati.put(new ChunkStatusWrap(s), c)); + + return true; + } + + @Override + protected boolean initNewWorld() throws Exception { + //world folder + tempDir = java.nio.file.Files.createTempDirectory("WorldEditWorldGen"); + + //prepare for world init (see upstream implementation for reference) + org.bukkit.World.Environment env = originalBukkitWorld.getEnvironment(); + org.bukkit.generator.ChunkGenerator gen = originalBukkitWorld.getGenerator(); + Convertable convertable = Convertable.a(tempDir); + ResourceKey worldDimKey = getWorldDimKey(env); + session = convertable.c("worldeditregentempworld", worldDimKey); + WorldDataServer originalWorldData = originalNMSWorld.worldDataServer; + + MinecraftServer server = originalNMSWorld.getServer().getServer(); + WorldDataServer levelProperties = (WorldDataServer) server.getSaveData(); + RegistryReadOps nbtRegOps = RegistryReadOps.a(DynamicOpsNBT.a, server.dataPackResources.h(), IRegistryCustom.b()); + GeneratorSettings newOpts = GeneratorSettings.a.encodeStart(nbtRegOps, levelProperties.getGeneratorSettings()).flatMap(tag -> GeneratorSettings.a.parse(this.recursivelySetSeed(new Dynamic<>(nbtRegOps, tag), seed, new HashSet<>()))).result().orElseThrow(() -> new IllegalStateException("Unable to map GeneratorOptions")); + WorldSettings newWorldSettings = new WorldSettings("worldeditregentempworld", originalWorldData.b.getGameType(), originalWorldData.b.hardcore, originalWorldData.b.getDifficulty(), originalWorldData.b.e(), originalWorldData.b.getGameRules(), originalWorldData.b.g()); + WorldDataServer newWorldData = new WorldDataServer(newWorldSettings, newOpts, Lifecycle.stable()); + + //init world + freshNMSWorld = Fawe.get().getQueueHandler().sync((Supplier) () -> new WorldServer(server, server.executorService, session, newWorldData, originalNMSWorld.getDimensionKey(), originalNMSWorld.getDimensionManager(), new RegenNoOpWorldLoadListener(), ((WorldDimension) newOpts.d().a(worldDimKey)).c(), originalNMSWorld.isDebugWorld(), seed, ImmutableList.of(), false, env, gen) { + @Override + public void doTick(BooleanSupplier booleansupplier) { //no ticking + } + }).get(); + freshNMSWorld.savingDisabled = true; + removeWorldFromWorldsMap(); + newWorldData.checkName(originalNMSWorld.worldDataServer.getName()); //rename to original world name + + freshChunkProvider = new ChunkProviderServer(freshNMSWorld, session, server.getDataFixer(), server.getDefinedStructureManager(), server.executorService, originalChunkProvider.chunkGenerator, freshNMSWorld.spigotConfig.viewDistance, server.isSyncChunkWrites(), new RegenNoOpWorldLoadListener(), () -> server.E().getWorldPersistentData()) { + // redirect to our protoChunks list + @Override + public IChunkAccess getChunkAt(int x, int z, ChunkStatus chunkstatus, boolean flag) { + return getProtoChunkAt(x, z); + } + }; + chunkProviderField.set(freshNMSWorld, freshChunkProvider); + + //generator + if (originalChunkProvider.getChunkGenerator() instanceof ChunkProviderFlat) { + GeneratorSettingsFlat generatorSettingFlat = (GeneratorSettingsFlat) generatorSettingFlatField.get(originalChunkProvider.getChunkGenerator()); + generator = new ChunkProviderFlat(generatorSettingFlat); + } else if (originalChunkProvider.getChunkGenerator() instanceof ChunkGeneratorAbstract) { + Supplier generatorSettingBaseSupplier = (Supplier) generatorSettingBaseSupplierField.get(originalChunkProvider.getChunkGenerator()); + WorldChunkManager chunkManager = originalChunkProvider.getChunkGenerator().getWorldChunkManager(); + if (chunkManager instanceof WorldChunkManagerOverworld) { + chunkManager = fastOverWorldChunkManager(chunkManager); + } + generator = new ChunkGeneratorAbstract(chunkManager, seed, generatorSettingBaseSupplier); + } else if (originalChunkProvider.getChunkGenerator() instanceof CustomChunkGenerator) { + ChunkGenerator delegate = (ChunkGenerator) delegateField.get(originalChunkProvider.getChunkGenerator()); + generator = delegate; + } else { + System.out.println("Unsupported generator type " + originalChunkProvider.getChunkGenerator().getClass().getName()); + return false; + } + if (originalNMSWorld.generator != null) { + // wrap custom world generator + generator = new CustomChunkGenerator(freshNMSWorld, generator, originalNMSWorld.generator); + generateConcurrent = originalNMSWorld.generator.isParallelCapable(); + } + + //lets start then + structureManager = server.getDefinedStructureManager(); + lightEngine = freshChunkProvider.getLightEngine(); + + return true; + } + + @Override + protected void cleanup() { + try { + session.close(); + } catch (Exception e) { + } + + //shutdown chunk provider + try { + Fawe.get().getQueueHandler().sync(() -> { + try { + freshChunkProvider.close(false); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (Exception e) { + } + + //remove world from server + try { + Fawe.get().getQueueHandler().sync(() -> { + removeWorldFromWorldsMap(); + }); + } catch (Exception e) { + } + + //delete directory + try { + SafeFiles.tryHardToDeleteDir(tempDir); + } catch (Exception e) { + } + } + + @Override + protected ProtoChunk createProtoChunk(int x, int z) { + return new ProtoChunk(new ChunkCoordIntPair(x, z), ChunkConverter.a) { + public boolean generateFlatBedrock() { + return generateFlatBedrock; + } + }; + } + + @Override + protected Chunk createChunk(ProtoChunk protoChunk) { + return new Chunk(freshNMSWorld, protoChunk); + } + + @Override + protected ChunkStatusWrap getFullChunkStatus() { + return new ChunkStatusWrap(ChunkStatus.FULL); + } + + @Override + protected List getBlockPopulators() { + return originalNMSWorld.getWorld().getPopulators(); + } + + @Override + protected void populate(Chunk chunk, Random random, BlockPopulator pop) { + pop.populate(freshNMSWorld.getWorld(), random, chunk.bukkitChunk); + } + + @Override + protected IChunkCache initSourceQueueCache() { + return (chunkX, chunkZ) -> new BukkitGetBlocks_1_16_2(freshNMSWorld, chunkX, chunkZ) { + @Override + public Chunk ensureLoaded(World nmsWorld, int x, int z) { + return getChunkAt(x, z); + } + }; + } + + protected class ChunkStatusWrap extends Regenerator.ChunkStatusWrapper { + + private final ChunkStatus chunkStatus; + + public ChunkStatusWrap(ChunkStatus chunkStatus) { + this.chunkStatus = chunkStatus; + } + + @Override + public int requiredNeigborChunkRadius() { + return chunkStatus.f(); + } + + @Override + public String name() { + return chunkStatus.d(); + } + + @Override + public void processChunk(Long xz, List accessibleChunks) { + chunkStatus.a(freshNMSWorld, + generator, + structureManager, + lightEngine, + c -> CompletableFuture.completedFuture(Either.left(c)), + accessibleChunks); + } + } + + //util + private void removeWorldFromWorldsMap() { + Fawe.get().getQueueHandler().sync(() -> { + try { + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove("worldeditregentempworld"); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } + + private ResourceKey getWorldDimKey(org.bukkit.World.Environment env) { + switch (env) { + case NETHER: + return WorldDimension.THE_NETHER; + case THE_END: + return WorldDimension.THE_END; + case NORMAL: + default: + return WorldDimension.OVERWORLD; + } + } + + private Dynamic recursivelySetSeed(Dynamic dynamic, long seed, Set> seen) { + return !seen.add(dynamic) ? dynamic : dynamic.updateMapValues((pair) -> { + if (((Dynamic) pair.getFirst()).asString("").equals("seed")) { + return pair.mapSecond((v) -> { + return v.createLong(seed); + }); + } else { + return ((Dynamic) pair.getSecond()).getValue() instanceof NBTTagCompound ? pair.mapSecond((v) -> { + return this.recursivelySetSeed((Dynamic) v, seed, seen); + }) : pair; + + } + }); + } + + private WorldChunkManager fastOverWorldChunkManager(WorldChunkManager chunkManager) throws Exception { + Field legacyBiomeInitLayerField = WorldChunkManagerOverworld.class.getDeclaredField("i"); + legacyBiomeInitLayerField.setAccessible(true); + Field largeBiomesField = WorldChunkManagerOverworld.class.getDeclaredField("j"); + largeBiomesField.setAccessible(true); + Field biomeRegistryField = WorldChunkManagerOverworld.class.getDeclaredField("k"); + biomeRegistryField.setAccessible(true); + Field genLayerField = WorldChunkManagerOverworld.class.getDeclaredField("f"); + genLayerField.setAccessible(true); + Field areaLazyField = GenLayer.class.getDeclaredField("b"); + areaLazyField.setAccessible(true); + Method initAreaFactoryMethod = GenLayers.class.getDeclaredMethod("a", boolean.class, int.class, int.class, LongFunction.class); + initAreaFactoryMethod.setAccessible(true); + + //init new WorldChunkManagerOverworld + boolean legacyBiomeInitLayer = legacyBiomeInitLayerField.getBoolean(chunkManager); + boolean largebiomes = largeBiomesField.getBoolean(chunkManager); + IRegistry biomeRegistry = (IRegistry) biomeRegistryField.get(chunkManager); + chunkManager = new WorldChunkManagerOverworld(seed, legacyBiomeInitLayer, largebiomes, biomeRegistry); + + //replace genLayer + AreaFactory factory = (AreaFactory) initAreaFactoryMethod.invoke(null, legacyBiomeInitLayer, largebiomes ? 6 : 4, 4, (LongFunction) (l -> new FastWorldGenContextArea(seed, l))); + genLayerField.set(chunkManager, new FastGenLayer(factory)); + + return chunkManager; + } + + private static class FastWorldGenContextArea implements AreaContextTransformed { + + private final ConcurrentHashMap sharedAreaMap = new ConcurrentHashMap<>(); + private final NoiseGeneratorPerlin perlinNoise; + private final long magicrandom; + private final ConcurrentHashMap map = new ConcurrentHashMap<>(); //needed for multithreaded generation + + public FastWorldGenContextArea(long seed, long lconst) { + this.magicrandom = mix(seed, lconst); + this.perlinNoise = new NoiseGeneratorPerlin(new Random(seed)); + } + + @Override + public FastAreaLazy a(AreaTransformer8 var0) { + return new FastAreaLazy(sharedAreaMap, var0); + } + + @Override + public void a(long x, long z) { + long l = this.magicrandom; + l = LinearCongruentialGenerator.a(l, x); + l = LinearCongruentialGenerator.a(l, z); + l = LinearCongruentialGenerator.a(l, x); + l = LinearCongruentialGenerator.a(l, z); + this.map.put(Thread.currentThread().getId(), l); + } + + @Override + public int a(int y) { + long tid = Thread.currentThread().getId(); + long e = this.map.computeIfAbsent(tid, i -> 0L); + int mod = (int) Math.floorMod(e >> 24L, (long) y); + this.map.put(tid, LinearCongruentialGenerator.a(e, this.magicrandom)); + return mod; + } + + @Override + public NoiseGeneratorPerlin b() { + return this.perlinNoise; + } + + private static long mix(long seed, long lconst) { + long l1 = lconst; + l1 = LinearCongruentialGenerator.a(l1, lconst); + l1 = LinearCongruentialGenerator.a(l1, lconst); + l1 = LinearCongruentialGenerator.a(l1, lconst); + long l2 = seed; + l2 = LinearCongruentialGenerator.a(l2, l1); + l2 = LinearCongruentialGenerator.a(l2, l1); + l2 = LinearCongruentialGenerator.a(l2, l1); + return l2; + } + } + + private static class FastGenLayer extends GenLayer { + + private final FastAreaLazy areaLazy; + + public FastGenLayer(AreaFactory factory) throws Exception { + super(() -> null); + this.areaLazy = factory.make(); + } + + @Override + public BiomeBase a(IRegistry registry, int x, int z) { + ResourceKey key = BiomeRegistry.a(this.areaLazy.a(x, z)); + if (key == null) + return registry.a(BiomeRegistry.a(0)); + BiomeBase biome = registry.a(key); + if (biome == null) + return registry.a(BiomeRegistry.a(0)); + return biome; + } + } + + private static class FastAreaLazy implements Area { + + private final AreaTransformer8 transformer; + //ConcurrentHashMap is 50% faster that Long2IntLinkedOpenHashMap in a syncronized context + //using a map for each thread worsens the performance significantly due to cache misses (factor 5) + private final ConcurrentHashMap sharedMap; + + public FastAreaLazy(ConcurrentHashMap sharedMap, AreaTransformer8 transformer) { + this.sharedMap = sharedMap; + this.transformer = transformer; + } + + @Override + public int a(int x, int z) { + long zx = ChunkCoordIntPair.pair(x, z); + return this.sharedMap.computeIfAbsent(zx, i -> this.transformer.apply(x, z)); + } + } + + private static class RegenNoOpWorldLoadListener implements WorldLoadListener { + + private RegenNoOpWorldLoadListener() { + } + + @Override + public void a(ChunkCoordIntPair chunkCoordIntPair) { + } + + @Override + public void a(ChunkCoordIntPair chunkCoordIntPair, @Nullable ChunkStatus chunkStatus) { + } + + @Override + public void b() { + } + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index a5c184fb1..d357d111d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -616,6 +616,7 @@ public class RegionCommands { try { session.setMask((Mask) null); session.setSourceMask((Mask) null); + actor.printInfo(TranslatableComponent.of("fawe.regen.time")); success = world.regenerate(region, editSession); } finally { session.setMask(mask); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java index 21b2cd39f..1c9348fa9 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java @@ -186,7 +186,7 @@ public interface Extent extends InputExtent, OutputExtent { } default boolean regenerateChunk(int x, int z, @Nullable BiomeType type, @Nullable Long seed) { - throw new UnsupportedOperationException("TODO NOT IMPLEMENTED: " + isWorld()); + return false; } /* diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java index 1d2ff9e97..747675232 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java @@ -31,6 +31,7 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.regions.Region; @@ -226,6 +227,10 @@ public class NullWorld extends AbstractWorld { @Override public void sendFakeChunk(@Nullable Player player, ChunkPacket packet) { + } + @Override + public boolean regenerate(Region region, Extent extent, RegenOptions options) { + return false; } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java index f0771063a..05ef1e5c7 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java @@ -31,6 +31,8 @@ import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Platform; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.internal.util.DeprecationUtil; +import com.sk89q.worldedit.internal.util.NonAbstractForCompatibility; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; @@ -218,7 +220,44 @@ public interface World extends Extent, Keyed, IChunkCache { * @param editSession the {@link EditSession} * @return true if re-generation was successful */ - boolean regenerate(Region region, EditSession editSession); + default boolean regenerate(Region region, EditSession editSession) { + return regenerate(region, editSession, RegenOptions.builder().build()); + } + + /** + * Regenerate an area. + * + * @param region the region + * @param extent the {@link Extent} + * @return true if re-generation was successful + */ + default boolean regenerate(Region region, Extent extent) { + return regenerate(region, extent, RegenOptions.builder().build()); + } + + /** + * Regenerate an area. + * + * @param region the region + * @param extent the {@link Extent} + * @param options the regeneration options + * @return true if regeneration was successful + * @apiNote This must be overridden by new subclasses. See {@link NonAbstractForCompatibility} + * for details + */ + @NonAbstractForCompatibility( + delegateName = "regenerate", + delegateParams = { Region.class, EditSession.class } + ) + default boolean regenerate(Region region, Extent extent, RegenOptions options) { + DeprecationUtil.checkDelegatingOverride(getClass()); + if (extent instanceof EditSession) { + return regenerate(region, (EditSession) extent); + } + throw new UnsupportedOperationException("This World class (" + + getClass().getName() + + ") does not implement the general Extent variant of this method"); + } /** * Generate a tree at the given position. diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index de060a1ed..a074f2ed0 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -165,6 +165,8 @@ "fawe.tips.tip.biome.pattern": "Tip: The #biome[forest] pattern can be used in any command", "fawe.tips.tip.biome.mask": "Tip: Restrict to a biome with the `$jungle` mask", + "fawe.regen.time": "Regenerating region, this might take a while!", + "worldedit.expand.description.vert": "Vertically expand the selection to world limits.", "worldedit.expand.expanded": "Region expanded {0} blocks", "worldedit.expand.expanded.vert": "Region expanded {0} blocks (top-to-bottom).",