3
0
Mirror von https://github.com/IntellectualSites/FastAsyncWorldEdit.git synchronisiert 2024-11-20 01:40:06 +01:00

initial work on reworked regeneration logic

Dieser Commit ist enthalten in:
Pierre Maurice Schwang 2023-10-13 20:54:07 +02:00
Ursprung 8e2691c613
Commit 8f3f0fae4b
29 geänderte Dateien mit 1630 neuen und 626 gelöschten Zeilen

Datei anzeigen

@ -97,7 +97,7 @@ tasks {
}
}
runServer {
minecraftVersion("1.20.2")
minecraftVersion(supportedVersions.last())
pluginJars(*project(":worldedit-bukkit").getTasksByName("shadowJar", false).map { (it as Jar).archiveFile }
.toTypedArray())

Datei anzeigen

@ -24,7 +24,6 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.mojang.datafixers.util.Either;
@ -393,7 +392,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
}
@SuppressWarnings({"rawtypes", "unchecked"})
private net.minecraft.world.level.block.state.BlockState applyProperties(
public net.minecraft.world.level.block.state.BlockState applyProperties(
StateDefinition<Block, net.minecraft.world.level.block.state.BlockState> stateContainer,
net.minecraft.world.level.block.state.BlockState newState,
Map<Property<?>, Object> states

Datei anzeigen

@ -378,7 +378,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
}
@SuppressWarnings({"rawtypes", "unchecked"})
private net.minecraft.world.level.block.state.BlockState applyProperties(
public net.minecraft.world.level.block.state.BlockState applyProperties(
StateDefinition<Block, net.minecraft.world.level.block.state.BlockState> stateContainer,
net.minecraft.world.level.block.state.BlockState newState,
Map<Property<?>, Object> states

Datei anzeigen

@ -21,7 +21,7 @@ import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.VersionedRegenerator;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
@ -570,7 +570,9 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
@Override
public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception {
return new PaperweightRegen(bukkitWorld, region, target, options).regenerate();
try (final VersionedRegenerator regenerator = new VersionedRegenerator(bukkitWorld, region, options, target)) {
return regenerator.regenerate().join();
}
}
@Override

Datei anzeigen

@ -1,591 +0,0 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen;
import com.fastasyncworldedit.bukkit.adapter.Regenerator;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.queue.IChunkCache;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager;
import com.google.common.collect.ImmutableList;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Lifecycle;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.Refraction;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.PaperweightGetBlocks;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.io.file.SafeFiles;
import com.sk89q.worldedit.world.RegenOptions;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter.Message;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ThreadedLevelLightEngine;
import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.thread.ProcessorHandle;
import net.minecraft.util.thread.ProcessorMailbox;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.LevelSettings;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.FixedBiomeSource;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import net.minecraft.world.level.levelgen.WorldOptions;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.craftbukkit.v1_20_R2.CraftServer;
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R2.generator.CustomChunkGenerator;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.BlockPopulator;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.OptionalLong;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import static net.minecraft.core.registries.Registries.BIOME;
public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, LevelChunk, PaperweightRegen.ChunkStatusWrap> {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private static final Field serverWorldsField;
private static final Field paperConfigField;
private static final Field flatBedrockField;
private static final Field generatorSettingFlatField;
private static final Field generatorSettingBaseSupplierField;
private static final Field delegateField;
private static final Field chunkSourceField;
private static final Field generatorStructureStateField;
private static final Field ringPositionsField;
private static final Field hasGeneratedPositionsField;
//list of chunk stati in correct order without FULL
private static final Map<ChunkStatus, Concurrency> chunkStati = new LinkedHashMap<>();
static {
chunkStati.put(ChunkStatus.EMPTY, Concurrency.FULL); // empty: radius -1, does nothing
chunkStati.put(ChunkStatus.STRUCTURE_STARTS, Concurrency.NONE); // structure starts: uses unsynchronized maps
chunkStati.put(
ChunkStatus.STRUCTURE_REFERENCES,
Concurrency.FULL
); // structure refs: radius 8, but only writes to current chunk
chunkStati.put(ChunkStatus.BIOMES, Concurrency.FULL); // biomes: radius 0
chunkStati.put(ChunkStatus.NOISE, Concurrency.RADIUS); // noise: radius 8
chunkStati.put(ChunkStatus.SURFACE, Concurrency.NONE); // surface: radius 0, requires NONE
chunkStati.put(ChunkStatus.CARVERS, Concurrency.NONE); // carvers: radius 0, but RADIUS and FULL change results
/*chunkStati.put(
ChunkStatus.LIQUID_CARVERS,
Concurrency.NONE
); // liquid carvers: radius 0, but RADIUS and FULL change results*/
chunkStati.put(ChunkStatus.FEATURES, Concurrency.NONE); // features: uses unsynchronized maps
chunkStati.put(
ChunkStatus.LIGHT,
Concurrency.FULL
); // light: radius 1, but no writes to other chunks, only current chunk
chunkStati.put(ChunkStatus.SPAWN, Concurrency.FULL); // spawn: radius 0
// chunkStati.put(ChunkStatus.HEIGHTMAPS, Concurrency.FULL); // heightmaps: radius 0
try {
serverWorldsField = CraftServer.class.getDeclaredField("worlds");
serverWorldsField.setAccessible(true);
Field tmpPaperConfigField;
Field tmpFlatBedrockField;
try { //only present on paper
tmpPaperConfigField = Level.class.getDeclaredField("paperConfig");
tmpPaperConfigField.setAccessible(true);
tmpFlatBedrockField = tmpPaperConfigField.getType().getDeclaredField("generateFlatBedrock");
tmpFlatBedrockField.setAccessible(true);
} catch (Exception e) {
tmpPaperConfigField = null;
tmpFlatBedrockField = null;
}
paperConfigField = tmpPaperConfigField;
flatBedrockField = tmpFlatBedrockField;
generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName(
"settings", "e"));
generatorSettingBaseSupplierField.setAccessible(true);
generatorSettingFlatField = FlatLevelSource.class.getDeclaredField(Refraction.pickName("settings", "d"));
generatorSettingFlatField.setAccessible(true);
delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
delegateField.setAccessible(true);
chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "I"));
chunkSourceField.setAccessible(true);
generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "v"));
generatorStructureStateField.setAccessible(true);
ringPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(Refraction.pickName("ringPositions", "g"));
ringPositionsField.setAccessible(true);
hasGeneratedPositionsField = ChunkGeneratorStructureState.class.getDeclaredField(
Refraction.pickName("hasGeneratedPositions", "h")
);
hasGeneratedPositionsField.setAccessible(true);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//runtime
private ServerLevel originalServerWorld;
private ServerChunkCache originalChunkProvider;
private ServerLevel freshWorld;
private ServerChunkCache freshChunkProvider;
private LevelStorageSource.LevelStorageAccess session;
private StructureTemplateManager structureTemplateManager;
private ThreadedLevelLightEngine threadedLevelLightEngine;
private ChunkGenerator chunkGenerator;
private Path tempDir;
private boolean generateFlatBedrock = false;
public PaperweightRegen(org.bukkit.World originalBukkitWorld, Region region, Extent target, RegenOptions options) {
super(originalBukkitWorld, region, target, options);
}
@Override
protected boolean prepare() {
this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle();
originalChunkProvider = originalServerWorld.getChunkSource();
//flat bedrock? (only on paper)
if (paperConfigField != null) {
try {
generateFlatBedrock = flatBedrockField.getBoolean(paperConfigField.get(originalServerWorld));
} catch (Exception ignored) {
}
}
seed = options.getSeed().orElse(originalServerWorld.getSeed());
chunkStati.forEach((s, c) -> super.chunkStatuses.put(new ChunkStatusWrap(s), c));
return true;
}
@Override
@SuppressWarnings("unchecked")
protected boolean initNewWorld() throws Exception {
//world folder
tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen");
//prepare for world init (see upstream implementation for reference)
org.bukkit.World.Environment environment = originalBukkitWorld.getEnvironment();
org.bukkit.generator.ChunkGenerator generator = originalBukkitWorld.getGenerator();
LevelStorageSource levelStorageSource = LevelStorageSource.createDefault(tempDir);
ResourceKey<LevelStem> levelStemResourceKey = getWorldDimKey(environment);
session = levelStorageSource.createAccess("faweregentempworld", levelStemResourceKey);
PrimaryLevelData originalWorldData = originalServerWorld.serverLevelData;
MinecraftServer server = originalServerWorld.getCraftServer().getServer();
WorldOptions originalOpts = originalWorldData.worldGenOptions();
WorldOptions newOpts = options.getSeed().isPresent()
? originalOpts.withSeed(OptionalLong.of(seed))
: originalOpts;
LevelSettings newWorldSettings = new LevelSettings(
"faweregentempworld",
originalWorldData.settings.gameType(),
originalWorldData.settings.hardcore(),
originalWorldData.settings.difficulty(),
originalWorldData.settings.allowCommands(),
originalWorldData.settings.gameRules(),
originalWorldData.settings.getDataConfiguration()
);
PrimaryLevelData.SpecialWorldProperty specialWorldProperty =
originalWorldData.isFlatWorld()
? PrimaryLevelData.SpecialWorldProperty.FLAT
: originalWorldData.isDebugWorld()
? PrimaryLevelData.SpecialWorldProperty.DEBUG
: PrimaryLevelData.SpecialWorldProperty.NONE;
PrimaryLevelData newWorldData = new PrimaryLevelData(newWorldSettings, newOpts, specialWorldProperty, Lifecycle.stable());
BiomeProvider biomeProvider = getBiomeProvider();
//init world
freshWorld = Fawe.instance().getQueueHandler().sync((Supplier<ServerLevel>) () -> new ServerLevel(
server,
server.executor,
session,
newWorldData,
originalServerWorld.dimension(),
DedicatedServer.getServer().registryAccess().registry(Registries.LEVEL_STEM).orElseThrow()
.getOrThrow(levelStemResourceKey),
new RegenNoOpWorldLoadListener(),
originalServerWorld.isDebug(),
seed,
ImmutableList.of(),
false,
originalServerWorld.getRandomSequences(),
environment,
generator,
biomeProvider
) {
private final Holder<Biome> singleBiome = options.hasBiomeType() ? DedicatedServer.getServer().registryAccess()
.registryOrThrow(BIOME).asHolderIdMap().byIdOrThrow(
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
) : null;
@Override
public void tick(BooleanSupplier shouldKeepTicking) { //no ticking
}
@Override
public Holder<Biome> getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) {
if (options.hasBiomeType()) {
return singleBiome;
}
return PaperweightRegen.this.chunkGenerator.getBiomeSource().getNoiseBiome(
biomeX, biomeY, biomeZ, getChunkSource().randomState().sampler()
);
}
}).get();
freshWorld.noSave = true;
removeWorldFromWorldsMap();
newWorldData.checkName(originalServerWorld.serverLevelData.getLevelName()); //rename to original world name
if (paperConfigField != null) {
paperConfigField.set(freshWorld, originalServerWorld.paperConfig());
}
ChunkGenerator originalGenerator = originalChunkProvider.getGenerator();
if (originalGenerator instanceof FlatLevelSource flatLevelSource) {
FlatLevelGeneratorSettings generatorSettingFlat = flatLevelSource.settings();
chunkGenerator = new FlatLevelSource(generatorSettingFlat);
} else if (originalGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
Holder<NoiseGeneratorSettings> generatorSettingBaseSupplier = (Holder<NoiseGeneratorSettings>) generatorSettingBaseSupplierField.get(
originalGenerator);
BiomeSource biomeSource;
if (options.hasBiomeType()) {
biomeSource = new FixedBiomeSource(
DedicatedServer.getServer().registryAccess()
.registryOrThrow(BIOME).asHolderIdMap().byIdOrThrow(
WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType())
)
);
} else {
biomeSource = originalGenerator.getBiomeSource();
}
chunkGenerator = new NoiseBasedChunkGenerator(
biomeSource,
generatorSettingBaseSupplier
);
} else if (originalGenerator instanceof CustomChunkGenerator customChunkGenerator) {
chunkGenerator = customChunkGenerator.getDelegate();
} else {
LOGGER.error("Unsupported generator type {}", originalGenerator.getClass().getName());
return false;
}
if (generator != null) {
chunkGenerator = new CustomChunkGenerator(freshWorld, chunkGenerator, generator);
generateConcurrent = generator.isParallelCapable();
}
// chunkGenerator.conf = freshWorld.spigotConfig; - Does not exist anymore, may need to be re-addressed
freshChunkProvider = new ServerChunkCache(
freshWorld,
session,
server.getFixerUpper(),
server.getStructureManager(),
server.executor,
chunkGenerator,
freshWorld.spigotConfig.viewDistance,
freshWorld.spigotConfig.simulationDistance,
server.forceSynchronousWrites(),
new RegenNoOpWorldLoadListener(),
(chunkCoordIntPair, state) -> {
},
() -> server.overworld().getDataStorage()
) {
// redirect to LevelChunks created in #createChunks
@Override
public ChunkAccess getChunk(int x, int z, ChunkStatus chunkstatus, boolean create) {
ChunkAccess chunkAccess = getChunkAt(x, z);
if (chunkAccess == null && create) {
chunkAccess = createChunk(getProtoChunkAt(x, z));
}
return chunkAccess;
}
};
if (seed == originalOpts.seed() && !options.hasBiomeType()) {
// Optimisation for needless ring position calculation when the seed and biome is the same.
ChunkGeneratorStructureState state = (ChunkGeneratorStructureState) generatorStructureStateField.get(originalChunkProvider.chunkMap);
boolean hasGeneratedPositions = hasGeneratedPositionsField.getBoolean(state);
if (hasGeneratedPositions) {
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> origPositions =
(Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>>) ringPositionsField.get(state);
Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> copy = new Object2ObjectArrayMap<>(
origPositions);
ChunkGeneratorStructureState newState = (ChunkGeneratorStructureState) generatorStructureStateField.get(freshChunkProvider.chunkMap);
ringPositionsField.set(newState, copy);
hasGeneratedPositionsField.setBoolean(newState, true);
}
}
chunkSourceField.set(freshWorld, freshChunkProvider);
//let's start then
structureTemplateManager = server.getStructureManager();
threadedLevelLightEngine = new NoOpLightEngine(freshChunkProvider);
return true;
}
@Override
protected void cleanup() {
try {
session.close();
} catch (Exception ignored) {
}
//shutdown chunk provider
try {
Fawe.instance().getQueueHandler().sync(() -> {
try {
freshChunkProvider.close(false);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
} catch (Exception ignored) {
}
//remove world from server
try {
Fawe.instance().getQueueHandler().sync(this::removeWorldFromWorldsMap);
} catch (Exception ignored) {
}
//delete directory
try {
SafeFiles.tryHardToDeleteDir(tempDir);
} catch (Exception ignored) {
}
}
@Override
protected ProtoChunk createProtoChunk(int x, int z) {
return new FastProtoChunk(new ChunkPos(x, z), UpgradeData.EMPTY, freshWorld,
this.freshWorld.registryAccess().registryOrThrow(BIOME), null
);
}
@Override
protected LevelChunk createChunk(ProtoChunk protoChunk) {
return new LevelChunk(
freshWorld,
protoChunk,
null // we don't want to add entities
);
}
@Override
protected ChunkStatusWrap getFullChunkStatus() {
return new ChunkStatusWrap(ChunkStatus.FULL);
}
@Override
protected List<BlockPopulator> getBlockPopulators() {
return originalServerWorld.getWorld().getPopulators();
}
@Override
protected void populate(LevelChunk levelChunk, Random random, BlockPopulator blockPopulator) {
// BlockPopulator#populate has to be called synchronously for TileEntity access
TaskManager.taskManager().task(() -> {
final CraftWorld world = freshWorld.getWorld();
final Chunk chunk = world.getChunkAt(levelChunk.locX, levelChunk.locZ);
blockPopulator.populate(world, random, chunk);
});
}
@Override
protected IChunkCache<IChunkGet> initSourceQueueCache() {
return (chunkX, chunkZ) -> new PaperweightGetBlocks(freshWorld, chunkX, chunkZ) {
@Override
public LevelChunk ensureLoaded(ServerLevel nmsWorld, int x, int z) {
return getChunkAt(x, z);
}
};
}
//util
@SuppressWarnings("unchecked")
private void removeWorldFromWorldsMap() {
Fawe.instance().getQueueHandler().sync(() -> {
try {
Map<String, org.bukkit.World> map = (Map<String, org.bukkit.World>) serverWorldsField.get(Bukkit.getServer());
map.remove("faweregentempworld");
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
}
private ResourceKey<LevelStem> getWorldDimKey(org.bukkit.World.Environment env) {
return switch (env) {
case NETHER -> LevelStem.NETHER;
case THE_END -> LevelStem.END;
default -> LevelStem.OVERWORLD;
};
}
private static class RegenNoOpWorldLoadListener implements ChunkProgressListener {
private RegenNoOpWorldLoadListener() {
}
@Override
public void updateSpawnPos(ChunkPos spawnPos) {
}
@Override
public void onStatusChange(ChunkPos pos, @Nullable ChunkStatus status) {
}
@Override
public void start() {
}
@Override
public void stop() {
}
// TODO Paper only(?) @Override
public void setChunkRadius(int radius) {
}
}
private class FastProtoChunk extends ProtoChunk {
public FastProtoChunk(
final ChunkPos pos,
final UpgradeData upgradeData,
final LevelHeightAccessor world,
final Registry<Biome> biomeRegistry,
@Nullable final BlendingData blendingData
) {
super(pos, upgradeData, world, biomeRegistry, blendingData);
}
// avoid warning on paper
// compatibility with spigot
public boolean generateFlatBedrock() {
return generateFlatBedrock;
}
// no one will ever see the entities!
@Override
public List<CompoundTag> getEntities() {
return Collections.emptyList();
}
}
protected class ChunkStatusWrap extends ChunkStatusWrapper<ChunkAccess> {
private final ChunkStatus chunkStatus;
public ChunkStatusWrap(ChunkStatus chunkStatus) {
this.chunkStatus = chunkStatus;
}
@Override
public int requiredNeighborChunkRadius() {
return chunkStatus.getRange();
}
@Override
public String name() {
return chunkStatus.toString();
}
@Override
public CompletableFuture<?> processChunk(List<ChunkAccess> accessibleChunks) {
return chunkStatus.generate(
Runnable::run, // TODO revisit, we might profit from this somehow?
freshWorld,
chunkGenerator,
structureTemplateManager,
threadedLevelLightEngine,
c -> CompletableFuture.completedFuture(Either.left(c)),
accessibleChunks
);
}
}
/**
* A light engine that does nothing. As light is calculated after pasting anyway, we can avoid
* work this way.
*/
static class NoOpLightEngine extends ThreadedLevelLightEngine {
private static final ProcessorMailbox<Runnable> MAILBOX = ProcessorMailbox.create(task -> {
}, "fawe-no-op");
private static final ProcessorHandle<Message<Runnable>> HANDLE = ProcessorHandle.of("fawe-no-op", m -> {
});
public NoOpLightEngine(final ServerChunkCache chunkProvider) {
super(chunkProvider, chunkProvider.chunkMap, false, MAILBOX, HANDLE);
}
@Override
public CompletableFuture<ChunkAccess> lightChunk(final ChunkAccess chunk, final boolean excludeBlocks) {
return CompletableFuture.completedFuture(chunk);
}
}
}

Datei anzeigen

@ -0,0 +1,101 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.level.FaweProtoChunkAccess;
import com.sk89q.worldedit.world.RegenOptions;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.QuartPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.util.Mth;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class DelegatingWorldGenRegion extends WorldGenRegion {
private final RegenOptions regenOptions;
public DelegatingWorldGenRegion(
final ServerLevel world,
final List<ChunkAccess> chunkAccesses,
final ChunkStatus status,
final int placementRadius,
final RegenOptions regenOptions
) {
super(world, chunkAccesses, status, placementRadius);
this.regenOptions = regenOptions;
}
@Override
public boolean ensureCanWrite(final BlockPos pos) {
return this.hasChunk(pos.getX() >> 4, pos.getZ() >> 4) &&
pos.getY() >= getMinBuildHeight() && pos.getY() <= getMaxBuildHeight();
}
@Override
public boolean isOldChunkAround(final @NotNull ChunkPos chunkPos, final int checkRadius) {
return false; // We don't migrate old worlds, won't be properly blended either way
}
/**
* Don't notify ServerLevel on place and don't set chunk for postprocessing
*/
@Override
public boolean setBlock(
final @NotNull BlockPos pos,
final @NotNull BlockState state,
final int flags,
final int maxUpdateDepth
) {
if (!this.ensureCanWrite(pos)) {
return false;
}
ChunkAccess chunk = this.getChunk(pos);
BlockState oldState = chunk.setBlockState(pos, state, false);
if (state.hasBlockEntity()) {
BlockEntity tileEntity = ((EntityBlock) state.getBlock()).newBlockEntity(pos, state);
if (tileEntity != null) {
chunk.setBlockEntity(tileEntity);
} else {
chunk.removeBlockEntity(pos);
}
} else if (oldState != null && oldState.hasBlockEntity()) {
chunk.removeBlockEntity(pos);
}
return true;
}
@Override
public @NotNull Holder<Biome> getNoiseBiome(final int biomeX, final int biomeY, final int biomeZ) {
FaweProtoChunkAccess chunkAccess = (FaweProtoChunkAccess) this.getChunk(
QuartPos.toSection(biomeX), QuartPos.toSection(biomeZ), ChunkStatus.EMPTY, false
);
if (chunkAccess == null) {
return DedicatedServer.getServer().registryAccess().registryOrThrow(Registries.BIOME)
.getHolderOrThrow(Biomes.PLAINS);
}
int l = QuartPos.fromBlock(this.getMinBuildHeight());
int i1 = l + QuartPos.fromBlock(this.getHeight()) - 1;
int j1 = Mth.clamp(biomeY, l, i1);
int k1 = this.getSectionIndex(QuartPos.toBlock(j1));
return chunkAccess.getSection(k1).getNoiseBiome(biomeX & 3, j1 & 3, biomeZ & 3);
}
@Override
public long getSeed() {
return this.regenOptions.getSeed().orElse(super.getSeed());
}
}

Datei anzeigen

@ -0,0 +1,185 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator;
import com.fastasyncworldedit.bukkit.adapter.regeneration.ChunkWorker;
import com.fastasyncworldedit.bukkit.adapter.regeneration.Regenerator;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.PaperweightFaweAdapter;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.level.FaweChunkSource;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker.BiomesWorker;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker.CarversWorker;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker.FeaturesWorker;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker.NoiseWorker;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker.SurfaceWorker;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.concurrency.LazyReference;
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.levelgen.Heightmap;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class VersionedRegenerator extends Regenerator<ServerLevel, WorldGenRegion, ChunkAccess, ChunkGenerator, ChunkStatus> {
/**
* Each chunk has their own ChunkSource as chunks are worked on in parallel and therefor neighbour chunks may differ in status
*/
private final Long2ObjectLinkedOpenHashMap<FaweChunkSource> chunkSources;
public VersionedRegenerator(
final World world, final Region region, final RegenOptions options,
final Extent extent
) {
super(world, region, options, extent);
this.chunkSources = new Long2ObjectLinkedOpenHashMap<>(region.getChunks().size());
for (final BlockVector2 chunk : region.getChunks()) {
FaweChunkSource source = new FaweChunkSource(
region,
serverLevel().registryAccess().registryOrThrow(Registries.BIOME),
options,
serverLevel()
);
this.chunkSources.put(ChunkPos.asLong(chunk.getX(), chunk.getZ()), source);
}
}
@Override
public CompletableFuture<Boolean> flushPalettesIntoWorld(final ChunkAccess chunkAccess, final WorldGenRegion worldGenRegion) {
return CompletableFuture.supplyAsync(() -> {
boolean result = false;
final int minX = Math.max(chunkAccess.getPos().getMinBlockX(), region.getMinimumPoint().getBlockX());
final int minY = Math.max(chunkAccess.getMinBuildHeight(), region.getMinimumY());
final int minZ = Math.max(chunkAccess.getPos().getMinBlockZ(), region.getMinimumPoint().getBlockZ());
final int maxX = Math.min(chunkAccess.getPos().getMaxBlockX(), region.getMaximumPoint().getBlockX());
final int maxY = Math.min(chunkAccess.getMaxBuildHeight(), region.getMaximumY());
final int maxZ = Math.min(chunkAccess.getPos().getMaxBlockZ(), region.getMaximumPoint().getBlockZ());
for (final BlockPos blockPos : BlockPos.MutableBlockPos.betweenClosed(
minX, minY, minZ,
maxX, maxY, maxZ
)) {
// We still have to validate boundaries, as region may not be a cuboid
if (!(region.contains(blockPos.getX(), blockPos.getY(), blockPos.getZ()))) {
continue;
}
if ((options.shouldRegenBiomes() || options.hasBiomeType()) &&
(blockPos.getX() % 4 == 0 && blockPos.getY() % 4 == 0 && blockPos.getZ() % 4 == 0)) {
result |= this.extent.setBiome(
blockPos.getX(), blockPos.getY(), blockPos.getZ(),
BiomeTypes.get(worldGenRegion.getBiome(blockPos).unwrapKey()
.orElseThrow().location().toString())
);
}
final BlockEntity blockEntity = chunkAccess.getBlockEntity(blockPos);
final com.sk89q.worldedit.world.block.BlockState blockState = ((PaperweightFaweAdapter) WorldEditPlugin
.getInstance()
.getBukkitImplAdapter())
.adapt(blockEntity != null ? blockEntity.getBlockState() : chunkAccess.getBlockState(blockPos));
if (blockEntity != null) {
result |= this.extent.setBlock(
blockPos.getX(), blockPos.getY(), blockPos.getZ(),
blockState.toBaseBlock(LazyReference.from(() -> {
//noinspection unchecked
if (!(WorldEditPlugin.getInstance().getBukkitImplAdapter()
.toNativeBinary(blockEntity.saveWithId()) instanceof CompoundBinaryTag tag)) {
throw new IllegalStateException("Entity Binary-Tag is no CompoundBinaryTag");
}
return tag;
}))
);
continue;
}
result |= this.extent.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), blockState);
}
this.chunkSources.remove(chunkAccess.getPos().toLong());
return result;
}, provideExecutor().get());
}
@Override
public @Nullable ChunkAccess toNativeChunkAccess(
final ServerLevel level,
final BlockVector2 chunk,
final int x,
final int z,
final ChunkStatus leastStatus
) {
return this.chunkSources.get(ChunkPos.asLong(chunk.getX(), chunk.getZ()))
.getChunk(x, z, leastStatus, true);
}
@Override
protected @NonNull CompletableFuture<@Nullable Void> primeHeightmaps(final ChunkAccess chunk) {
return CompletableFuture.runAsync(
() -> Heightmap.primeHeightmaps(chunk, ChunkStatus.POST_FEATURES),
provideExecutor().get()
);
}
@Override
public WorldGenRegion worldGenRegionForRegion(
final ServerLevel level,
final List<ChunkAccess> chunkAccesses,
final Region region,
final ChunkStatus status
) {
return new DelegatingWorldGenRegion(
level, chunkAccesses,
status,
PLACEMENT_RADII[status.getIndex() - ChunkStatus.BIOMES.getIndex()],
options
);
}
@Override
public ChunkGenerator generatorFromLevel(final ServerLevel serverLevel) {
return serverLevel.chunkSource.getGenerator();
}
@Override
protected void setChunkStatus(final List<ChunkAccess> chunkAccesses, final ChunkStatus status) {
chunkAccesses.forEach(chunkAccess -> ((ProtoChunk) chunkAccess).setStatus(status));
}
@Override
public @NonNull List<@NonNull ChunkWorker<ServerLevel, ChunkAccess, WorldGenRegion, ChunkGenerator, ChunkStatus>> workers() {
return List.of(
BiomesWorker.INSTANCE,
NoiseWorker.INSTANCE,
SurfaceWorker.INSTANCE,
CarversWorker.INSTANCE,
FeaturesWorker.INSTANCE
);
}
@Override
public @NonNull ServerLevel serverLevel() {
return ((CraftWorld) this.world).getHandle();
}
@Override
public void close() throws IOException {
this.chunkSources.clear();
super.close();
}
}

Datei anzeigen

@ -0,0 +1,65 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.level;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class FaweBlockGetter implements BlockGetter {
private final FaweChunkSource chunkSource;
public FaweBlockGetter(final FaweChunkSource chunkSource) {
this.chunkSource = chunkSource;
}
@Nullable
@Override
public BlockEntity getBlockEntity(final BlockPos pos) {
final ChunkAccess chunkAccess = this.chunkSource.getChunk(pos.getX() >> 4, pos.getZ() >> 4, false);
return chunkAccess == null ? null : chunkAccess.getBlockEntity(pos);
}
@Override
public @NotNull BlockState getBlockState(final BlockPos pos) {
final ChunkAccess chunkAccess = this.chunkSource.getChunk(pos.getX() >> 4, pos.getZ() >> 4, false);
return chunkAccess == null ? Blocks.VOID_AIR.defaultBlockState() : chunkAccess.getBlockState(pos);
}
@Nullable
@Override
public BlockState getBlockStateIfLoaded(final @NotNull BlockPos pos) {
final ChunkAccess chunkAccess = this.chunkSource.getChunk(pos.getX() >> 4, pos.getZ() >> 4, false);
return chunkAccess == null ? Blocks.VOID_AIR.defaultBlockState() : chunkAccess.getBlockStateIfLoaded(pos);
}
@Nullable
@Override
public FluidState getFluidIfLoaded(final @NotNull BlockPos pos) {
final ChunkAccess chunkAccess = this.chunkSource.getChunk(pos.getX() >> 4, pos.getZ() >> 4, false);
return chunkAccess == null ? null : chunkAccess.getFluidIfLoaded(pos);
}
@Override
public @NotNull FluidState getFluidState(final @NotNull BlockPos pos) {
final ChunkAccess chunkAccess = this.chunkSource.getChunk(pos.getX() >> 4, pos.getZ() >> 4, false);
return chunkAccess == null ? Fluids.EMPTY.defaultFluidState() : chunkAccess.getFluidState(pos);
}
@Override
public int getHeight() {
return this.chunkSource.levelHeightAccessor().getHeight();
}
@Override
public int getMinBuildHeight() {
return this.chunkSource.levelHeightAccessor().getMinBuildHeight();
}
}

Datei anzeigen

@ -0,0 +1,105 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.level;
import com.fastasyncworldedit.bukkit.adapter.regeneration.Regenerator;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.noop.NoopLightEngine;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.RegenOptions;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import net.minecraft.core.Registry;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.function.BooleanSupplier;
public class FaweChunkSource extends ChunkSource {
private final Registry<Biome> biomeRegistry;
private final RegenOptions options;
private final LevelHeightAccessor levelHeightAccessor;
private final BlockGetter blockGetter;
private final Long2ObjectLinkedOpenHashMap<FaweProtoChunkAccess> cachedAccesses;
private final Region region;
public FaweChunkSource(
final Region region, final Registry<Biome> biomeRegistry, final RegenOptions options,
final LevelHeightAccessor levelHeightAccessor
) {
this.region = region;
this.biomeRegistry = biomeRegistry;
this.options = options;
this.levelHeightAccessor = levelHeightAccessor;
this.cachedAccesses = new Long2ObjectLinkedOpenHashMap<>();
this.blockGetter = new FaweBlockGetter(this);
}
@Nullable
@Override
public ChunkAccess getChunk(final int x, final int z, final @NotNull ChunkStatus leastStatus, final boolean create) {
if (x < (this.region.getMinimumPoint().getBlockX() >> 4) - Regenerator.TASK_MARGIN) {
return null;
}
if (x > (this.region.getMaximumPoint().getBlockX() >> 4) + Regenerator.TASK_MARGIN) {
return null;
}
if (z < (this.region.getMinimumPoint().getBlockZ() >> 4) - Regenerator.TASK_MARGIN) {
return null;
}
if (z > (this.region.getMaximumPoint().getBlockZ() >> 4) + Regenerator.TASK_MARGIN) {
return null;
}
FaweProtoChunkAccess chunkAccess = cachedAccesses.get(ChunkPos.asLong(x, z));
if (chunkAccess == null && create) {
cachedAccesses.put(ChunkPos.asLong(x, z), chunkAccess = new FaweProtoChunkAccess(this, new ChunkPos(x, z),
biomeRegistry, leastStatus, options
));
}
if (chunkAccess != null && !chunkAccess.getStatus().isOrAfter(leastStatus)) {
return null;
}
return chunkAccess;
}
@Override
public void tick(final @NotNull BooleanSupplier shouldKeepTicking, final boolean tickChunks) {
}
@Override
public @NotNull String gatherStats() {
return toString();
}
@Override
public int getLoadedChunksCount() {
return this.cachedAccesses.size();
}
@Override
public @NotNull LevelLightEngine getLightEngine() {
return new NoopLightEngine(this);
}
@Override
public @NotNull BlockGetter getLevel() {
return this.blockGetter;
}
@Override
public void close() throws IOException {
super.close();
this.cachedAccesses.clear();
}
public LevelHeightAccessor levelHeightAccessor() {
return levelHeightAccessor;
}
}

Datei anzeigen

@ -0,0 +1,108 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.level;
import com.sk89q.worldedit.world.RegenOptions;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* ChunkAccess which just delegates everything to an extent
*/
public class FaweProtoChunkAccess extends ProtoChunk {
private final int hashedCoords;
public FaweProtoChunkAccess(
final FaweChunkSource source,
final ChunkPos pos,
final Registry<Biome> biomeRegistry,
final ChunkStatus status,
final RegenOptions options
) {
super(
pos, UpgradeData.EMPTY, null,
NoopProtoChunkTick.BLOCKS, NoopProtoChunkTick.FLUIDS,
source.getLevel(), biomeRegistry, null
);
for (int i = 0; i < getSections().length; i++) {
getSections()[i] = new FaweProtoChunkSection(biomeRegistry, options);
}
setStatus(status);
this.hashedCoords = Objects.hash(getPos().x, getPos().z);
}
@Override
public @NotNull BlockState getBlockState(final int x, final int y, final int z) {
if (this.isOutsideBuildHeight(y)) {
return Blocks.VOID_AIR.defaultBlockState();
}
return getSection(getSectionIndex(y)).getBlockState(x & 15, y & 15, z & 15);
}
@Nullable
@Override
public BlockState setBlockState(final @NotNull BlockPos pos, final @NotNull BlockState state, final boolean moved) {
if (this.isOutsideBuildHeight(pos)) {
return null;
}
return getSection(getSectionIndex(pos.getY())).setBlockState(pos.getX() & 15, pos.getY() & 15, pos.getZ() & 15, state);
}
@Override
public void setBlockEntity(final @NotNull BlockEntity blockEntity) {
if (this.isOutsideBuildHeight(blockEntity.getBlockPos())) {
return;
}
super.setBlockEntity(blockEntity);
}
@Override
public void addEntity(final @NotNull Entity entity) {
if (this.isOutsideBuildHeight(entity.getBlockY())) {
return;
}
super.addEntity(entity);
}
@Override
public void removeBlockEntity(final @NotNull BlockPos pos) {
if (this.isOutsideBuildHeight(pos.getY())) {
return;
}
super.removeBlockEntity(pos);
}
@Override
public boolean equals(final Object other) {
if (!(other instanceof FaweProtoChunkAccess otherChunk)) {
return false;
}
return otherChunk.getPos().equals(this.getPos());
}
@Override
public int hashCode() {
return this.hashedCoords;
}
@Override
public String toString() {
return "FaweProtoChunkAccess{" +
"chunkPos=" + chunkPos +
'}';
}
}

Datei anzeigen

@ -0,0 +1,134 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.level;
import com.sk89q.worldedit.world.RegenOptions;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeResolver;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import org.jetbrains.annotations.NotNull;
public class FaweProtoChunkSection extends LevelChunkSection {
private final RegenOptions options;
public FaweProtoChunkSection(final Registry<Biome> biomeRegistry, final RegenOptions options) {
super(
new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(),
PalettedContainer.Strategy.SECTION_STATES, null
),
new PalettedContainer<>(
biomeRegistry.asHolderIdMap(),
options.hasBiomeType() && options.getBiomeType() != null ?
biomeRegistry.getHolderOrThrow(ResourceKey.create(
Registries.BIOME, ResourceLocation.of(options.getBiomeType().getId(), ':')
)) : biomeRegistry.getHolderOrThrow(Biomes.PLAINS),
PalettedContainer.Strategy.SECTION_BIOMES,
null
)
);
this.options = options;
}
@Override
public @NotNull BlockState setBlockState(final int x, final int y, final int z, final @NotNull BlockState state) {
return this.states.getAndSetUnchecked(x, y, z, state);
}
@Override
public @NotNull BlockState setBlockState(
final int x,
final int y,
final int z,
final @NotNull BlockState state,
final boolean lock
) {
return setBlockState(x, y, z, state);
}
/**
* We don't recalculate blocks after modification, just access every chunk
*
* @return always {@code false}
*/
@Override
public boolean hasOnlyAir() {
return false;
}
@Override
public boolean isRandomlyTicking() {
return false;
}
@Override
public boolean isRandomlyTickingBlocks() {
return false;
}
@Override
public boolean isRandomlyTickingFluids() {
return false;
}
/**
* We have no working TickingList (and don't need one) and don't recalculate block counts (see {@link #hasOnlyAir()})
*/
@Override
public void recalcBlockCounts() {
}
/**
* If a custom BiomeType has been supplied by the user, don't allow any biome writes into the palette
*/
@Override
public void setBiome(final int x, final int y, final int z, final @NotNull Holder<Biome> biome) {
if (options.hasBiomeType()) {
return;
}
super.setBiome(x, y, z, biome);
}
/**
* If a custom BiomeType has been supplied by the user, we don't write into the data palette as the default value is
* already the user defined biome type.
*/
@Override
public void fillBiomesFromNoise(
final @NotNull BiomeResolver biomeSupplier,
final Climate.@NotNull Sampler sampler,
final int x,
final int y,
final int z
) {
if (options.hasBiomeType()) {
// Don't populate anything - is handled by default palette value set in constructor
return;
}
super.fillBiomesFromNoise(biomeSupplier, sampler, x, y, z);
}
/**
* No need for locking
*/
@Override
public void acquire() {
}
/**
* No need for locking
*/
@Override
public void release() {
}
}

Datei anzeigen

@ -0,0 +1,45 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.level;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.ticks.ProtoChunkTicks;
import net.minecraft.world.ticks.SavedTick;
import net.minecraft.world.ticks.ScheduledTick;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.Function;
public class NoopProtoChunkTick<T> extends ProtoChunkTicks<T> {
public static ProtoChunkTicks<Block> BLOCKS = new NoopProtoChunkTick<>();
public static ProtoChunkTicks<Fluid> FLUIDS = new NoopProtoChunkTick<>();
@Override
public @NotNull List<SavedTick<T>> scheduledTicks() {
return List.of();
}
@Override
public void schedule(final @NotNull ScheduledTick<T> orderedTick) {
}
@Override
public boolean hasScheduledTick(final @NotNull BlockPos pos, final @NotNull T type) {
return false;
}
@Override
public int count() {
return 0;
}
@Override
public @NotNull Tag save(final long time, final @NotNull Function<T, String> typeToNameFunction) {
return new ListTag();
}
}

Datei anzeigen

@ -0,0 +1,33 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.noop;
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
import net.minecraft.server.level.ThreadedLevelLightEngine;
import net.minecraft.util.thread.ProcessorHandle;
import net.minecraft.util.thread.ProcessorMailbox;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LightChunkGetter;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
public class NoopLightEngine extends ThreadedLevelLightEngine {
private static final ProcessorMailbox<Runnable> MAILBOX = ProcessorMailbox.create(task -> {
}, "fawe-no-op");
private static final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> HANDLE = ProcessorHandle.of(
"fawe-no-op",
m -> {
}
);
public NoopLightEngine(final LightChunkGetter lightChunkGetter) {
//noinspection DataFlowIssue - nobody cares if the ChunkMap is null
super(lightChunkGetter, null, false, MAILBOX, HANDLE);
}
@Override
public @NotNull CompletableFuture<ChunkAccess> lightChunk(final @NotNull ChunkAccess chunk, final boolean excludeBlocks) {
return CompletableFuture.completedFuture(chunk);
}
}

Datei anzeigen

@ -0,0 +1,50 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.RegenOptions;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.blending.Blender;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class BiomesWorker extends VersionedChunkWorker {
public static final VersionedChunkWorker INSTANCE = new BiomesWorker();
public BiomesWorker() {
super(ChunkStatus.BIOMES, false);
}
@Override
public CompletableFuture<Boolean> work(
final ServerLevel level,
final List<ChunkAccess> chunks,
final WorldGenRegion worldGenRegion,
final ChunkGenerator chunkGenerator,
final RegenOptions regenOptions,
final Region region,
final Supplier<Executor> executor
) {
/*
* Clone the original RandomState into a new wrapper with the new seed provided by the options (if applicable)
*/
final RandomState seededRandomState = seededRandomState(
level.chunkSource.randomState(),
level,
chunkGenerator,
regenOptions
);
return chunkGenerator.createBiomes(
executor.get(), seededRandomState, Blender.empty(), level.structureManager(), chunks.get(chunks.size() / 2)
).thenApply(chunkAccess -> true);
}
}

Datei anzeigen

@ -0,0 +1,42 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.RegenOptions;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.levelgen.GenerationStep;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class CarversWorker extends VersionedChunkWorker {
public static final VersionedChunkWorker INSTANCE = new CarversWorker();
public CarversWorker() {
super(ChunkStatus.CARVERS, true);
}
@Override
public CompletableFuture<Boolean> work(
final ServerLevel level,
final List<ChunkAccess> chunks,
final WorldGenRegion worldGenRegion,
final ChunkGenerator chunkGenerator,
final RegenOptions regenOptions,
final Region region,
final Supplier<Executor> executor
) {
return CompletableFuture.runAsync(() -> chunkGenerator.applyCarvers(
worldGenRegion, regenOptions.getSeed().orElse(level.getSeed()),
seededRandomState(level.chunkSource.randomState(), level, chunkGenerator, regenOptions),
level.getBiomeManager(), level.structureManager(), chunks.get(chunks.size() / 2), GenerationStep.Carving.AIR
), executor.get()).thenApply(unused -> true);
}
}

Datei anzeigen

@ -0,0 +1,39 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.RegenOptions;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class FeaturesWorker extends VersionedChunkWorker {
public static final VersionedChunkWorker INSTANCE = new FeaturesWorker();
public FeaturesWorker() {
super(ChunkStatus.FEATURES, false);
}
@Override
public CompletableFuture<Boolean> work(
final ServerLevel level,
final List<ChunkAccess> chunks,
final WorldGenRegion worldGenRegion,
final ChunkGenerator chunkGenerator,
final RegenOptions regenOptions,
final Region region,
final Supplier<Executor> executor
) {
return CompletableFuture.runAsync(() -> chunkGenerator.applyBiomeDecoration(
worldGenRegion, chunks.get(chunks.size() / 2), level.structureManager()
), executor.get()).thenApply(unused -> true);
}
}

Datei anzeigen

@ -0,0 +1,44 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.RegenOptions;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.levelgen.blending.Blender;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class NoiseWorker extends VersionedChunkWorker {
public static final VersionedChunkWorker INSTANCE = new NoiseWorker();
public NoiseWorker() {
super(ChunkStatus.NOISE, true);
}
@Override
public CompletableFuture<Boolean> work(
final ServerLevel level,
final List<ChunkAccess> chunks,
final WorldGenRegion worldGenRegion,
final ChunkGenerator chunkGenerator,
final RegenOptions regenOptions,
final Region region,
final Supplier<Executor> executor
) {
return chunkGenerator.fillFromNoise(
executor.get(),
Blender.empty(),
seededRandomState(level.chunkSource.randomState(), level, chunkGenerator, regenOptions),
level.structureManager(),
chunks.get(chunks.size() / 2)
).thenApply(chunkAccess -> true);
}
}

Datei anzeigen

@ -0,0 +1,41 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.RegenOptions;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class SurfaceWorker extends VersionedChunkWorker {
public static final VersionedChunkWorker INSTANCE = new SurfaceWorker();
public SurfaceWorker() {
super(ChunkStatus.SURFACE, false);
}
@Override
public CompletableFuture<Boolean> work(
final ServerLevel level,
final List<ChunkAccess> chunks,
final WorldGenRegion worldGenRegion,
final ChunkGenerator chunkGenerator,
final RegenOptions regenOptions,
final Region region,
final Supplier<Executor> executor
) {
return CompletableFuture.runAsync(() -> chunkGenerator.buildSurface(
worldGenRegion, level.structureManager(),
seededRandomState(level.chunkSource.randomState(), level, chunkGenerator, regenOptions),
chunks.get(chunks.size() / 2)
), executor.get()).thenApply(unused -> true);
}
}

Datei anzeigen

@ -0,0 +1,68 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regenerator.worker;
import com.fastasyncworldedit.bukkit.adapter.regeneration.ChunkWorker;
import com.sk89q.worldedit.world.RegenOptions;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import net.minecraft.world.level.levelgen.RandomState;
public abstract class VersionedChunkWorker extends ChunkWorker<ServerLevel, ChunkAccess, WorldGenRegion, ChunkGenerator,
ChunkStatus> {
private final ChunkStatus status;
private final boolean primeHeightmapsAfter;
public VersionedChunkWorker(final ChunkStatus status, final boolean primeHeightmapsAfter) {
this.status = status;
this.primeHeightmapsAfter = primeHeightmapsAfter;
}
protected RandomState seededRandomState(
final RandomState originalState,
final ServerLevel level,
final ChunkGenerator chunkGenerator,
final RegenOptions regenOptions
) {
return regenOptions.getSeed().stream().mapToObj(value -> {
if (chunkGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
return RandomState.create(
noiseBasedChunkGenerator.generatorSettings().value(),
level.registryAccess().lookupOrThrow(Registries.NOISE),
value
);
}
return RandomState.create(
NoiseGeneratorSettings.dummy(),
level.registryAccess().lookupOrThrow(Registries.NOISE),
value
);
}).findFirst().orElse(originalState);
}
@Override
public ChunkStatus status() {
return this.status;
}
@Override
public ChunkStatus previousStatus() {
return status.getParent();
}
@Override
public ChunkStatus nextStatus() {
return status.getNextStatus();
}
@Override
public boolean shouldPrimeHeightmapsAfter() {
return this.primeHeightmapsAfter;
}
}

Datei anzeigen

@ -68,7 +68,7 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
protected boolean generateConcurrent = true;
protected long seed;
private ExecutorService executor;
private SingleThreadQueueExtent source;
protected SingleThreadQueueExtent source;
/**
* Initializes an abstract regeneration handler.
@ -83,6 +83,9 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
this.region = region;
this.target = target;
this.options = options;
this.source = new SingleThreadQueueExtent(BukkitWorld.HAS_MIN_Y ? originalBukkitWorld.getMinHeight() : 0,
BukkitWorld.HAS_MIN_Y ? originalBukkitWorld.getMaxHeight() : 256);
}
private static Random getChunkRandom(long worldSeed, int x, int z) {
@ -276,10 +279,6 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
populate(c, random, pop);
});
}
source = new SingleThreadQueueExtent(BukkitWorld.HAS_MIN_Y ? originalBukkitWorld.getMinHeight() : 0,
BukkitWorld.HAS_MIN_Y ? originalBukkitWorld.getMaxHeight() : 256);
source.init(target, initSourceQueueCache(), null);
return true;
}

Datei anzeigen

@ -0,0 +1,64 @@
package com.fastasyncworldedit.bukkit.adapter.regeneration;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.RegenOptions;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
/**
* A ChunkWorker represents a single regeneration step in a similar fashion as native ChunkStatuses do.
*
* @param <ServerLevel> The native (versioned) ServerLevel
* @param <WorldGenRegion> The native (versioned) ServerLevel
* @param <ChunkAccess> The native (versioned) ChunkAccess
* @param <ChunkGenerator> The native (versioned) ChunkGenerator (NMS, not CB)
* @param <ChunkStatus> The native (versioned) ChunkStatus
*/
public abstract class ChunkWorker<ServerLevel, ChunkAccess, WorldGenRegion, ChunkGenerator, ChunkStatus> {
/**
* Do the actual work by calling the native code for generation this specific step.
*
* @param level The level being assigned to the {@link ChunkAccess} for accessing original data like seeds.
* @param chunks The {@link ChunkAccess ChunkAccesses} to be worked on.
* @param worldGenRegion The assigned {@link WorldGenRegion} for the {@code chunks} and this specific step.
* @param chunkGenerator The original {@link ChunkGenerator} assigned to the {@link ServerLevel level}.
* @param regenOptions The possible options as passed by the api or command parameters.
* @param region The selected region by the user to be regenerated.
* @param executor The executor used to schedule asynchronous tasks.
* @return A future resolving when this step has finished containing its result (success / failure)
*/
public abstract CompletableFuture<Boolean> work(
final ServerLevel level,
final List<ChunkAccess> chunks,
final WorldGenRegion worldGenRegion,
final ChunkGenerator chunkGenerator,
final RegenOptions regenOptions,
final Region region,
final Supplier<Executor> executor
);
/**
* @return The native {@link ChunkStatus} assigned to this generation step.
*/
public abstract ChunkStatus status();
/**
* @return The previous {@link ChunkStatus} as defined by {@link #status()}.
*/
public abstract ChunkStatus previousStatus();
/**
* @return The following {@link ChunkStatus} as defined by {@link #status()}.
*/
public abstract ChunkStatus nextStatus();
/**
* @return {@code true} if heightmaps shall be primed after this step has finished.
*/
public abstract boolean shouldPrimeHeightmapsAfter();
}

Datei anzeigen

@ -0,0 +1,314 @@
package com.fastasyncworldedit.bukkit.adapter.regeneration;
import com.fastasyncworldedit.bukkit.adapter.regeneration.queue.QueuedRegenerationSection;
import com.fastasyncworldedit.core.util.TaskManager;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.world.RegenOptions;
import org.bukkit.World;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
/**
* Abstraction of World-Regeneration logic. Contains boilerplate code for faster versioned implementation.
* <p>
* Regeneration happens in batches of 4 chunks at a time to reduce memory overhead: When regenerating chunks some generation
* steps require adjacent chunks to be loaded and populated as well - whereas the maximum radius is 8. Therefor for each chunk
* generating, {@code 289} chunks have to be kept in memory each. Adjacent chunks may not be shared due to parallelism and
* potential different states and differing {@link ChunkStatus} which will lead to generation failure.
*
* @param <ServerLevel> The native (versioned) ServerLevel
* @param <WorldGenRegion> The native (versioned) ServerLevel
* @param <ChunkAccess> The native (versioned) ChunkAccess
* @param <ChunkGenerator> The native (versioned) ChunkGenerator (NMS, not CB)
* @param <ChunkStatus> The native (versioned) ChunkStatus
*/
public abstract class Regenerator<ServerLevel, WorldGenRegion, ChunkAccess, ChunkGenerator, ChunkStatus>
implements Closeable {
public static final int TASK_MARGIN = 8;
protected static final int[] PLACEMENT_RADII = new int[]{-1, 0, 0, 0, 1};
private static final short QUEUE_LIMIT = 4;
private static final int PER_CHUNK_CACHE_SIZE = (TASK_MARGIN + TASK_MARGIN + 1) * (TASK_MARGIN + TASK_MARGIN + 1);
protected final World world;
protected final Region region;
protected final RegenOptions options;
protected final Extent extent;
private final Queue<QueuedRegenerationSection> queue;
/**
* Instantiates a new Regeneration-Context for single-use only.
*
* @param world The world containing the regeneration selection.
* @param region The selected region by the user to be regenerated.
* @param options The possible options as passed by the api or command parameters.
* @param extent The outgoing extent to where the regenerated blocks shall be flushed.
*/
protected Regenerator(final World world, final Region region, final RegenOptions options, final Extent extent) {
this.world = world;
this.region = region;
this.options = options;
this.extent = extent;
this.queue = new ArrayDeque<>((int) Math.ceil((double) region.getChunks().size() / QUEUE_LIMIT));
this.populateQueue();
}
/**
* Simply triggers the initial {@link #poll()} which will also recursively trigger all other chunks.
*
* @return {@code true} if all chunks fully regenerated inside the selection.
*/
public CompletableFuture<Boolean> regenerate() {
return this.poll();
}
/**
* Regenerates a single chunk by populating the squared list containing the target chunk and their required adjacent chunks.
* Triggers {@link #runWorker(int, List)} with the index {@code 0} to start the worker chain.
*
* @param chunk The chunk position (absolute) to be regenerated.
* @return The Future completing when the chunk is regenerated.
* @see #runWorker(int, List)
*/
public CompletableFuture<Boolean> regenerateChunk(BlockVector2 chunk) {
final ServerLevel level = serverLevel();
// Has to be ordered, as generating chunks are often get by the index: size(list) / 2
final List<ChunkAccess> accesses = new ArrayList<>(PER_CHUNK_CACHE_SIZE);
for (int x = chunk.getX() - TASK_MARGIN; x <= chunk.getX() + TASK_MARGIN; x++) {
for (int z = chunk.getZ() - TASK_MARGIN; z <= chunk.getZ() + TASK_MARGIN; z++) {
accesses.add(toNativeChunkAccess(level, chunk, x, z, workers().get(0).previousStatus()));
}
}
return runWorker(0, accesses);
}
/**
* Polls the next {@link QueuedRegenerationSection} to be regenerated. If the queue is empty after
* {@link Queue#poll() polling} the next {@link QueuedRegenerationSection}, no further calls to this method are
* allowed.
* <br><br>
* Automatically calls this method again after regeneration the polled {@link QueuedRegenerationSection} if the queue
* contains further items. Their results are combined.
*
* @return A Future resolving when the polled {@link QueuedRegenerationSection} has finished regenerating.
* Contains {@code true} if <b>every section</b> has regenerated successfully. Otherwise {@code false}.
*/
private @NonNull CompletableFuture<@NonNull Boolean> poll() {
final QueuedRegenerationSection section = this.queue.poll();
if (section == null) {
throw new IllegalStateException("Attempted to call #poll() while Queue is empty");
}
CompletableFuture<Boolean> future = section.regenerate();
if (this.queue.peek() != null) {
// Basically, regenerate the polled section, then:
// If successful, call this method again after regeneration and return its result (as that would be the logical
// combination either way, as we know this step was successful)
// If failed, return false and don't continue regenerating. Nobody want's a half-cooked chicken.
future = future.thenCompose(thisResult -> thisResult ? poll() : CompletableFuture.completedFuture(false));
}
return future;
}
/**
* Executes a {@link ChunkWorker} based on their index by accessing {@link #workers()}.
* A {@link ChunkWorker} represents a generation step as defined in {@link ChunkStatus} as well.
*
* @param workerIndex The index of the {@link ChunkWorker} in the list to execute.
* @param chunkAccesses The list of {@link ChunkAccess ChunkAccesses} for regeneration.
* @return A future resolving when the generation step finished containing its result (success / failed).
*/
private CompletableFuture<Boolean> runWorker(
final int workerIndex,
final List<ChunkAccess> chunkAccesses
) {
final ServerLevel level = serverLevel();
final ChunkAccess center = chunkAccesses.get(chunkAccesses.size() / 2);
final ChunkWorker<ServerLevel, ChunkAccess, WorldGenRegion, ChunkGenerator, ChunkStatus> worker =
workers().get(workerIndex);
final WorldGenRegion worldGenRegion = worldGenRegionForRegion(level, chunkAccesses, region, worker.status());
CompletableFuture<Boolean> result = worker.work(
level, chunkAccesses, worldGenRegion, generatorFromLevel(level), this.options, this.region, provideExecutor()
).thenCompose(success -> {
// The ChunkStatus must always be set to the previously (finished) generation step after finished.
setChunkStatus(chunkAccesses, worker.status());
// If this worker step was successful, do post-processing (like priming heightmaps)
if (success) {
return postProcessChunkGeneration(worker, center).thenApply(unused -> true);
}
return CompletableFuture.completedFuture(false);
});
// If we finished the last worker step, flush all changes into the world (EditSession) and complete the future
if (workerIndex >= workers().size() - 1) {
return result.thenCompose(success -> success ? flushPalettesIntoWorld(center, worldGenRegion) :
CompletableFuture.completedFuture(false));
}
// If there are still more worker steps, enqueue the next one (if the current step finished successfully)
return result.thenCompose(success -> {
if (success) {
return runWorker(workerIndex + 1, chunkAccesses);
}
throw new CompletionException(
"Cant enqueue next worker",
new RuntimeException("Worker " + worker.status() + " returned non-true result for " + center)
);
});
}
/**
* Optional post-processing of a chunk after finishing each {@link ChunkWorker}.
* Currently only re-calculates heightmaps for the next {@link ChunkWorker} if
* {@link ChunkWorker#shouldPrimeHeightmapsAfter()} is {@code true}
*
* @param worker The recently finished {@link ChunkWorker}.
* @param chunkAccess The target {@link ChunkAccess} which was populated using the worker.
* @return A future resolving {@code null} when the heightmap calculation finished.
*/
private CompletableFuture<Void> postProcessChunkGeneration(
ChunkWorker<ServerLevel, ChunkAccess, WorldGenRegion, ChunkGenerator, ChunkStatus> worker,
ChunkAccess chunkAccess
) {
if (worker.shouldPrimeHeightmapsAfter()) {
return primeHeightmaps(chunkAccess);
}
return CompletableFuture.completedFuture(null);
}
/**
* Divides the Chunks inside the provided {@link Region} into smaller batches as limited by {@link #QUEUE_LIMIT}.
*/
private void populateQueue() {
final BlockVector2[] chunks = this.region.getChunks().toArray(BlockVector2[]::new);
// Sort, so that adjacent chunks are generated in batches
Arrays.sort(chunks, Comparator.comparingInt(BlockVector2::getX).thenComparing(BlockVector2::getZ));
final int queued = (int) Math.ceil((double) chunks.length / QUEUE_LIMIT);
final int divisor = chunks.length % QUEUE_LIMIT;
for (int i = 0; i < queued; i++) {
final boolean last = i == queued - 1;
final int start = i * QUEUE_LIMIT;
final int end = start + (last && divisor != 0 ? divisor : QUEUE_LIMIT);
this.queue.add(new QueuedRegenerationSection(this, Arrays.copyOfRange(chunks, start, end)));
}
}
@Override
public void close() throws IOException {
}
/**
* May be overridden in version specific code, if required.
*
* @return A supplier providing an executor for asynchronous tasks.
*/
public @NonNull Supplier<@NonNull Executor> provideExecutor() {
return () -> task -> TaskManager.taskManager().async(task);
}
/**
* Places all blocks, block entities and biomes into the actual work by accessing the block- and biome-palettes of the
* chunk and setting their native contents into the {@link #extent passed Extent}.
*
* @param chunkAccess The fully-regenerated chunk populated with blocks and biomes.
* @param worldGenRegion The associated {@link WorldGenRegion} to provide access to biomes.
* @return A future resolving when all changes are flushed containing its result (success / failure).
*/
public abstract CompletableFuture<Boolean> flushPalettesIntoWorld(
final ChunkAccess chunkAccess, final WorldGenRegion worldGenRegion
);
/**
* Attempts to either create a new native {@link ChunkAccess} or get a cached version of an already created one.
* If the cached {@link ChunkAccess} current {@link ChunkStatus} is less than {@code leastStatus} (as defined in the
* ordinal order in native code) it's expected to return {@code null}.
* <br>
* This method <b>must</b> return or create a chunk for out-of-bounds chunks (as defined by the selected {@link #region})
* as those are created for the required placement radii of certain generation stages. These {@link ChunkAccess accesses
* } shall <b>not flush or set their changes</b> into the {@link #extent outgoing Extent}.
*
* @param level The level containing the chunk (based on {@link #world} and {@link #serverLevel()}).
* @param contextChunk The working context for which the chunk should be converted (= working chunk coordinate).
* @param x The x coordinate of the native {@link ChunkAccess}.
* @param z The z coordinate of the native {@link ChunkAccess}.
* @param leastStatus The current status of the chunk to be set.
* @return The cached {@link ChunkAccess} if existing and applicable status, otherwise created or {@code null}.
*/
protected abstract @Nullable ChunkAccess toNativeChunkAccess(
final ServerLevel level, final BlockVector2 contextChunk, final int x, final int z, final ChunkStatus leastStatus
);
/**
* Prime (recalculate) heightmaps of noised {@link ChunkAccess ChunkAccesses}.
* This method must process all required heightmaps required by following steps.
* Results must be written back into the native {@link ChunkAccess} for access by following generation stages. (Extents
* don't have any functionality to store those results).
*
* @param chunk The {@link ChunkAccess} to prime the heightmaps for.
* @return A Future resolving when the priming / calculation finished.
*/
protected abstract @NonNull CompletableFuture<@Nullable Void> primeHeightmaps(ChunkAccess chunk);
/**
* Create a new native {@link WorldGenRegion} for the passed {@link ChunkStatus} (as those differ in {@code placement
* radii} spanning the passed {@link Region}.
*
* @param level The {@link ServerLevel} to be wrapped.
* @param chunkAccesses The containing {@link ChunkAccess ChunkAccesses} for the {@link WorldGenRegion}.
* @param region The {@link Region} to be contained by the {@link WorldGenRegion}.
* @param status The {@link ChunkStatus} for the {@link WorldGenRegion} to be created.
* @return The new {@link WorldGenRegion}.
*/
protected abstract WorldGenRegion worldGenRegionForRegion(
final ServerLevel level,
final List<ChunkAccess> chunkAccesses,
final Region region,
final ChunkStatus status
);
/**
* Getter-Abstraction to retrieve the native {@link ChunkGenerator} from a {@link ServerLevel}.
*
* @param level The {@link ServerLevel} to get the {@link ChunkGenerator} from.
* @return The original {@link ChunkGenerator} of the {@link ServerLevel}.
*/
protected abstract ChunkGenerator generatorFromLevel(ServerLevel level);
/**
* Set's the {@link ChunkStatus} for all {@link ChunkAccess ChunkAccesses} after finishing a generation step.
*
* @param chunkAccesses The {@link ChunkAccess ChunkAccesses} to be set the new {@link ChunkStatus}
* @param status The {@link ChunkStatus} by the recently finished {@link ChunkWorker} ({@link ChunkWorker#status()})
*/
protected abstract void setChunkStatus(List<ChunkAccess> chunkAccesses, ChunkStatus status);
/**
* A list of all versioned {@link ChunkWorker ChunkWorkers} to fully regenerate the selection. Must be in order.
*
* @return All {@link ChunkWorker ChunkWorkers}.
*/
protected abstract @NonNull List<@NonNull ChunkWorker<ServerLevel, ChunkAccess, WorldGenRegion, ChunkGenerator, ChunkStatus>> workers();
/**
* Getter-Abstraction to retrieve the native {@link ServerLevel} from the passed {@link #world World}.
*
* @return The native assigned {@link ServerLevel} for the {@link World Bukkit World}
*/
protected abstract @NonNull ServerLevel serverLevel();
}

Datei anzeigen

@ -0,0 +1,37 @@
package com.fastasyncworldedit.bukkit.adapter.regeneration.queue;
import com.fastasyncworldedit.bukkit.adapter.regeneration.Regenerator;
import com.sk89q.worldedit.math.BlockVector2;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
* A section of the to-regenerated selection.
*/
public class QueuedRegenerationSection {
private final Regenerator<?, ?, ?, ?, ?> regenerator;
private final BlockVector2[] chunks;
public QueuedRegenerationSection(final Regenerator<?, ?, ?, ?, ?> regenerator, final BlockVector2[] chunks) {
this.regenerator = regenerator;
this.chunks = chunks;
}
/**
* Start the regeneration process for this part of the selection.
* @return A future completing when all {@link #chunks} are regenerated including their logically combined results
* (successful / failed)
*/
public CompletableFuture<Boolean> regenerate() {
final Set<CompletableFuture<Boolean>> futures = Arrays.stream(chunks)
.map(this.regenerator::regenerateChunk)
.collect(Collectors.toUnmodifiableSet());
return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new))
.thenApply(unused -> futures.stream().allMatch(CompletableFuture::join));
}
}

Datei anzeigen

@ -27,6 +27,7 @@ import com.sk89q.worldedit.function.visitor.FlatRegionVisitor;
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 com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BlockType;
@ -57,8 +58,8 @@ public class FaweDelegateRegionManager {
TaskManager.taskManager().async(() -> {
synchronized (FaweDelegateRegionManager.class) {
World world = BukkitAdapter.adapt(getWorld(area.getWorldName()));
EditSession session = WorldEdit.getInstance().newEditSessionBuilder().world(world).checkMemory(false).
fastMode(true).limitUnlimited().changeSetNull().build();
try(final EditSession session = WorldEdit.getInstance().newEditSessionBuilder().world(world).checkMemory(false).
fastMode(true).limitUnlimited().changeSetNull().build()) {
for (CuboidRegion region : regions) {
region.setPos1(region.getPos1().withY(minY));
region.setPos2(region.getPos2().withY(maxY));
@ -79,6 +80,7 @@ public class FaweDelegateRegionManager {
}
}
}
}
});
return true;
}
@ -358,19 +360,18 @@ public class FaweDelegateRegionManager {
public boolean regenerateRegion(final Location pos1, final Location pos2, boolean ignore, final Runnable whenDone) {
TaskManager.taskManager().async(() -> {
synchronized (FaweDelegateRegionManager.class) {
World pos1World = BukkitAdapter.adapt(getWorld(pos1.getWorldName()));
try (EditSession editSession = WorldEdit.getInstance().newEditSessionBuilder().world(pos1World)
World world = BukkitAdapter.adapt(getWorld(pos1.getWorldName()));
try (final EditSession editSession = WorldEdit.getInstance().newEditSessionBuilder().world(world)
.checkMemory(false)
.fastMode(true)
.limitUnlimited()
.changeSetNull()
.build()) {
CuboidRegion region = new CuboidRegion(
BlockVector3.at(pos1.getX(), pos1.getY(), pos1.getZ()),
BlockVector3.at(pos2.getX(), pos2.getY(), pos2.getZ())
world.regenerate(
new CuboidRegion(pos1.getBlockVector3(), pos2.getBlockVector3()),
editSession, RegenOptions.builder().regenBiomes(true).build()
);
editSession.regenerate(region);
editSession.flushQueue();
Operations.completeBlindly(editSession.commit());
}
if (whenDone != null) {
TaskManager.taskManager().task(whenDone);

Datei anzeigen

@ -98,6 +98,11 @@ public class PositionTransformExtent extends ResettableExtent {
return super.setBiome(getPos(mutable.getX(), mutable.getY(), mutable.getZ()), biome);
}
@Override
public boolean setBiome(final int x, final int y, final int z, final BiomeType biome) {
return super.setBiome(getPos(x, y, z), biome);
}
public void setTransform(Transform transform) {
this.transform = transform;
}

Datei anzeigen

@ -0,0 +1,37 @@
package com.fastasyncworldedit.core.util;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.function.Function;
public record Either<L, R>(@Nullable L left, @Nullable R right) {
public <NL, NR> Either<NL, NR> map(@NonNull Function<L, NL> leftMapper, @NonNull Function<R, NR> rightMapper) {
if (left() != null) {
return new Either<>(leftMapper.apply(left()), null);
}
if (right() != null) {
return new Either<>(null, rightMapper.apply(right()));
}
return new Either<>(null, null);
}
public <Result> Result accept(
@NonNull Function<L, Result> lConsumer, @NonNull Function<R, Result> rConsumer,
@NonNull Result nullFallback
) {
if (left() != null) {
return lConsumer.apply(left());
}
if (right() != null) {
return rConsumer.apply(right());
}
return nullFallback;
}
public static <SL, SR> Either<SL, SR> empty() {
return new Either<>(null, null);
}
}

Datei anzeigen

@ -8,9 +8,11 @@ import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
import com.sk89q.worldedit.util.nbt.IntBinaryTag;
import com.sk89q.worldedit.util.nbt.ShortBinaryTag;
import com.sk89q.worldedit.world.storage.InvalidFormatException;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class NbtUtils {
@ -80,4 +82,38 @@ public class NbtUtils {
return value;
}
/**
* Gets the uuid out of an {@link CompoundBinaryTag}
*
* @param tag The tag containing the potential UUID
* @return the UUID or {@code null} if no format was found
*/
public static @Nullable UUID getUuid(CompoundBinaryTag tag) {
final int[] uuidTag = tag.getIntArray("UUID");
if (uuidTag.length > 0) {
return new UUID(
(long) uuidTag[0] << 32 | (uuidTag[1] & 0xFFFFFFFFL),
(long) uuidTag[2] << 32 | (uuidTag[3] & 0xFFFFFFFFL)
);
}
final long uuidMost = tag.getLong("UUIDMost");
if (uuidMost != 0) {
return new UUID(uuidMost, tag.getLong("UUIDLeast"));
}
final long worldUuidMost = tag.getLong("WorldUUIDMost");
if (worldUuidMost != 0) {
return new UUID(uuidMost, tag.getLong("WorldUUIDLeast"));
}
final long persistentIdmsb = tag.getLong("PersistentIDMSB");
if (persistentIdmsb != 0) {
return new UUID(persistentIdmsb, tag.getLong("PersistentIDLSB"));
}
return null;
}
}

Datei anzeigen

@ -1,5 +1,6 @@
package com.fastasyncworldedit.core.util;
import org.checkerframework.checker.nullness.qual.NonNull;
import sun.misc.Unsafe;
import javax.annotation.Nonnull;
@ -10,6 +11,8 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* This is an internal class not meant to be used outside the FAWE internals.
@ -121,6 +124,32 @@ public class ReflectionUtils {
}
}
@SuppressWarnings("unchecked")
public static <T> T getFieldThrowing(@Nonnull Field field, Object instance) throws IllegalAccessException {
field.setAccessible(true);
return (T) field.get(instance);
}
@SuppressWarnings("unchecked")
public static <T> T getFieldOr(@Nonnull Field field, Object instance, @NonNull Supplier<T> fallback) {
field.setAccessible(true);
try {
return (T) field.get(instance);
} catch (IllegalAccessException e) {
return fallback.get();
}
}
@SuppressWarnings("unchecked")
public static <T> T getFieldOr(@Nonnull Field field, Object instance, @NonNull Function<IllegalAccessException, T> fallback) {
field.setAccessible(true);
try {
return (T) field.get(instance);
} catch (IllegalAccessException e) {
return fallback.apply(e);
}
}
public static Class<?> getClass(String name) {
try {
return Class.forName(name);

Datei anzeigen

@ -201,6 +201,9 @@ public class BlockArrayClipboard implements Clipboard {
//FAWE start
@Override
public boolean setTile(int x, int y, int z, CompoundTag tag) {
if (!region.contains(x, y, z)) {
return false;
}
x -= offset.getX();
y -= offset.getY();
z -= offset.getZ();
@ -214,6 +217,9 @@ public class BlockArrayClipboard implements Clipboard {
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block) throws WorldEditException {
if (!region.contains(x, y, z)) {
return false;
}
x -= offset.getX();
y -= offset.getY();
z -= offset.getZ();
@ -238,6 +244,9 @@ public class BlockArrayClipboard implements Clipboard {
@Override
public boolean setBiome(BlockVector3 position, BiomeType biome) {
if (!region.contains(position)) {
return false;
}
int x = position.getBlockX() - offset.getX();
int y = position.getBlockY() - offset.getY();
int z = position.getBlockZ() - offset.getZ();
@ -246,6 +255,9 @@ public class BlockArrayClipboard implements Clipboard {
@Override
public boolean setBiome(int x, int y, int z, BiomeType biome) {
if (!region.contains(x, y, z)) {
return false;
}
x -= offset.getX();
y -= offset.getY();
z -= offset.getZ();