Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-11-17 00:20:09 +01:00
initial work on reworked regeneration logic
Dieser Commit ist enthalten in:
Ursprung
8e2691c613
Commit
8f3f0fae4b
@ -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())
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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,25 +58,26 @@ 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();
|
||||
for (CuboidRegion region : regions) {
|
||||
region.setPos1(region.getPos1().withY(minY));
|
||||
region.setPos2(region.getPos2().withY(maxY));
|
||||
session.setBlocks((Region) region, blocks);
|
||||
}
|
||||
try {
|
||||
session.flushQueue();
|
||||
try(final EditSession session = WorldEdit.getInstance().newEditSessionBuilder().world(world).checkMemory(false).
|
||||
fastMode(true).limitUnlimited().changeSetNull().build()) {
|
||||
for (CuboidRegion region : regions) {
|
||||
FaweAPI.fixLighting(world, region, null,
|
||||
RelightMode.valueOf(com.fastasyncworldedit.core.configuration.Settings.settings().LIGHTING.MODE)
|
||||
);
|
||||
region.setPos1(region.getPos1().withY(minY));
|
||||
region.setPos2(region.getPos2().withY(maxY));
|
||||
session.setBlocks((Region) region, blocks);
|
||||
}
|
||||
} catch (MaxChangedBlocksException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (whenDone != null) {
|
||||
TaskManager.taskManager().task(whenDone);
|
||||
try {
|
||||
session.flushQueue();
|
||||
for (CuboidRegion region : regions) {
|
||||
FaweAPI.fixLighting(world, region, null,
|
||||
RelightMode.valueOf(com.fastasyncworldedit.core.configuration.Settings.settings().LIGHTING.MODE)
|
||||
);
|
||||
}
|
||||
} catch (MaxChangedBlocksException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (whenDone != null) {
|
||||
TaskManager.taskManager().task(whenDone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren