Skip to content

Commit

Permalink
Fix lag caused when generating augmented worlds with roads (#3614)
Browse files Browse the repository at this point in the history
 - Begin by implementing forceSync to the queue system as we know the chunk will be accessible to edits in some cases (i.e. population).
 - Also implement custom SideEffectSets to override any decided by the default chunk consumer, as we do NOT want to update neighbours in population (this caused infinite generation to be required causing the lag and server death). We also do not want to enqueue the QueueCoordinator in AugmentedUtils as this would write to the world and update neighbours before we might want to (plus it's just used to restrict and offset the blocks being set)
 - Then implement disabling any biomes from being saved/set to the queue to prevent augmented worlds having their biomes overridden in roads
 - Consequently fix ScopedQueueCoordinator, preventing the y value of blocks being set from needlessly being changed, fixing road heights in augmented worlds
 - Finally we do not need a method with chunkObject in the signature in AugmentedUtils as this is no longer used by the method
  • Loading branch information
dordsor21 committed May 16, 2022
1 parent 171d2e5 commit 96dfc27
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 60 deletions.
Expand Up @@ -25,7 +25,11 @@
*/
package com.plotsquared.bukkit.generator;

import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.generator.AugmentedUtils;
import com.plotsquared.core.queue.QueueCoordinator;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.util.SideEffectSet;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.generator.BlockPopulator;
Expand All @@ -52,7 +56,14 @@ public static BukkitAugmentedGenerator get(World world) {

@Override
public void populate(@NonNull World world, @NonNull Random random, @NonNull Chunk source) {
AugmentedUtils.generate(source, world.getName(), source.getX(), source.getZ(), null);
QueueCoordinator queue = PlotSquared.platform().globalBlockQueue().getNewQueue(BukkitAdapter.adapt(world));
// The chunk is already loaded and we do not want to load the chunk in "fully" by using any PaperLib methods.
queue.setForceSync(true);
queue.setSideEffectSet(SideEffectSet.none());
queue.setBiomesEnabled(false);
queue.setChunkObject(source);
AugmentedUtils.generateChunk(world.getName(), source.getX(), source.getZ(), queue);
queue.enqueue();
}

}
Expand Up @@ -76,10 +76,11 @@ public final class BukkitChunkCoordinator extends ChunkCoordinator {
private final int totalSize;
private final AtomicInteger expectedSize;
private final AtomicInteger loadingChunks = new AtomicInteger();
private final boolean forceSync;

private int batchSize;
private PlotSquaredTask task;
private boolean shouldCancel;
private volatile boolean shouldCancel;
private boolean finished;

@Inject
Expand All @@ -92,7 +93,8 @@ private BukkitChunkCoordinator(
@Assisted final @NonNull Runnable whenDone,
@Assisted final @NonNull Consumer<Throwable> throwableConsumer,
@Assisted final boolean unloadAfter,
@Assisted final @NonNull Collection<ProgressSubscriber> progressSubscribers
@Assisted final @NonNull Collection<ProgressSubscriber> progressSubscribers,
@Assisted final boolean forceSync
) {
this.requestedChunks = new LinkedBlockingQueue<>(requestedChunks);
this.availableChunks = new LinkedBlockingQueue<>();
Expand All @@ -107,14 +109,27 @@ private BukkitChunkCoordinator(
this.plugin = JavaPlugin.getPlugin(BukkitPlatform.class);
this.bukkitWorld = Bukkit.getWorld(world.getName());
this.progressSubscribers.addAll(progressSubscribers);
this.forceSync = forceSync;
}

@Override
public void start() {
// Request initial batch
this.requestBatch();
// Wait until next tick to give the chunks a chance to be loaded
TaskManager.runTaskLater(() -> task = TaskManager.runTaskRepeat(this, TaskTime.ticks(1)), TaskTime.ticks(1));
if (!forceSync) {
// Request initial batch
this.requestBatch();
// Wait until next tick to give the chunks a chance to be loaded
TaskManager.runTaskLater(() -> task = TaskManager.runTaskRepeat(this, TaskTime.ticks(1)), TaskTime.ticks(1));
} else {
try {
while (!shouldCancel && !requestedChunks.isEmpty()) {
chunkConsumer.accept(requestedChunks.poll());
}
} catch (Throwable t) {
throwableConsumer.accept(t);
} finally {
finish();
}
}
}

@Override
Expand All @@ -131,7 +146,9 @@ private void finish() {
for (final ProgressSubscriber subscriber : this.progressSubscribers) {
subscriber.notifyEnd();
}
task.cancel();
if (task != null) {
task.cancel();
}
finished = true;
}
}
Expand Down
Expand Up @@ -63,35 +63,39 @@

public class BukkitQueueCoordinator extends BasicQueueCoordinator {

private final SideEffectSet noSideEffectSet;
private final SideEffectSet lightingSideEffectSet;
private final SideEffectSet edgeSideEffectSet;
private final SideEffectSet edgeLightingSideEffectSet;
private org.bukkit.World bukkitWorld;
@Inject
private ChunkCoordinatorBuilderFactory chunkCoordinatorBuilderFactory;
@Inject
private ChunkCoordinatorFactory chunkCoordinatorFactory;
private ChunkCoordinator chunkCoordinator;
private static final SideEffectSet NO_SIDE_EFFECT_SET;
private static final SideEffectSet EDGE_SIDE_EFFECT_SET;
private static final SideEffectSet LIGHTING_SIDE_EFFECT_SET;
private static final SideEffectSet EDGE_LIGHTING_SIDE_EFFECT_SET;

@Inject
public BukkitQueueCoordinator(@NonNull World world) {
super(world);
noSideEffectSet = SideEffectSet.none().with(SideEffect.LIGHTING, SideEffect.State.OFF).with(
static {
NO_SIDE_EFFECT_SET = SideEffectSet.none().with(SideEffect.LIGHTING, SideEffect.State.OFF).with(
SideEffect.NEIGHBORS,
SideEffect.State.OFF
);
lightingSideEffectSet = SideEffectSet.none().with(SideEffect.NEIGHBORS, SideEffect.State.OFF);
edgeSideEffectSet = noSideEffectSet.with(SideEffect.UPDATE, SideEffect.State.ON).with(
EDGE_SIDE_EFFECT_SET = SideEffectSet.none().with(SideEffect.UPDATE, SideEffect.State.ON).with(
SideEffect.NEIGHBORS,
SideEffect.State.ON
);
edgeLightingSideEffectSet = noSideEffectSet.with(SideEffect.UPDATE, SideEffect.State.ON).with(
LIGHTING_SIDE_EFFECT_SET = SideEffectSet.none().with(SideEffect.NEIGHBORS, SideEffect.State.OFF);
EDGE_LIGHTING_SIDE_EFFECT_SET = SideEffectSet.none().with(SideEffect.UPDATE, SideEffect.State.ON).with(
SideEffect.NEIGHBORS,
SideEffect.State.ON
);
}

private org.bukkit.World bukkitWorld;
@Inject
private ChunkCoordinatorBuilderFactory chunkCoordinatorBuilderFactory;
@Inject
private ChunkCoordinatorFactory chunkCoordinatorFactory;
private ChunkCoordinator chunkCoordinator;

@Inject
public BukkitQueueCoordinator(@NonNull World world) {
super(world);
}

@Override
public BlockState getBlock(int x, int y, int z) {
Block block = getBukkitWorld().getBlockAt(x, y, z);
Expand Down Expand Up @@ -202,7 +206,7 @@ public boolean enqueue() {
localChunk.getTiles().forEach((blockVector3, tag) -> {
try {
BaseBlock block = getWorld().getBlock(blockVector3).toBaseBlock(tag);
getWorld().setBlock(blockVector3, block, noSideEffectSet);
getWorld().setBlock(blockVector3, block, getSideEffectSet(SideEffectState.NONE));
} catch (WorldEditException ignored) {
StateWrapper sw = new StateWrapper(tag);
sw.restoreTag(getWorld().getName(), blockVector3.getX(), blockVector3.getY(), blockVector3.getZ());
Expand Down Expand Up @@ -259,9 +263,9 @@ private void setWorldBlock(int x, int y, int z, @NonNull BaseBlock block, @NonNu
}
SideEffectSet sideEffectSet;
if (lighting) {
sideEffectSet = edge ? edgeLightingSideEffectSet : lightingSideEffectSet;
sideEffectSet = getSideEffectSet(edge ? SideEffectState.EDGE_LIGHTING : SideEffectState.LIGHTING);
} else {
sideEffectSet = edge ? edgeSideEffectSet : noSideEffectSet;
sideEffectSet = getSideEffectSet(edge ? SideEffectState.EDGE : SideEffectState.NONE);
}
getWorld().setBlock(loc, block, sideEffectSet);
} catch (WorldEditException ignored) {
Expand Down Expand Up @@ -382,4 +386,23 @@ private boolean isEdgeRegen(int x, int z, BlockVector2 blockVector2) {
return false;
}

private SideEffectSet getSideEffectSet(SideEffectState state) {
if (getSideEffectSet() != null) {
return getSideEffectSet();
}
return switch (state) {
case NONE -> NO_SIDE_EFFECT_SET;
case EDGE -> EDGE_SIDE_EFFECT_SET;
case LIGHTING -> LIGHTING_SIDE_EFFECT_SET;
case EDGE_LIGHTING -> EDGE_LIGHTING_SIDE_EFFECT_SET;
};
}

private enum SideEffectState {
NONE,
EDGE,
LIGHTING,
EDGE_LIGHTING
}

}
Expand Up @@ -54,12 +54,23 @@ public static void bypass(boolean bypass, Runnable run) {
enabled = true;
}

public static boolean generate(
@Nullable Object chunkObject,
/**
* Generate an augmented world chunk at the given location. If a queue is given, the data will be written to it, else a new
* queue will be created and written to world. Returns true if generation occurred.
*
* @param world World name to generate data for. Must be a PlotSquared world containing one or more areas else nothing will
* happen.
* @param chunkX Chunk X position
* @param chunkZ Chunk Z position
* @param queue Queue to write to, if desired.
* @return true if generation occurred.
* @since TODO
*/
public static boolean generateChunk(
final @NonNull String world,
final int chunkX,
final int chunkZ,
QueueCoordinator queue
@Nullable QueueCoordinator queue
) {
if (!enabled) {
return false;
Expand Down Expand Up @@ -97,9 +108,6 @@ public static boolean generate(
.platform()
.worldUtil()
.getWeWorld(world));
if (chunkObject != null) {
queue.setChunkObject(chunkObject);
}
}
QueueCoordinator primaryMask;
// coordinates
Expand Down Expand Up @@ -157,13 +165,9 @@ public static boolean generate(
}
generationResult = true;
}
if (chunkObject != null) {
primaryMask.setChunkObject(chunkObject);
}
if (chunkObject != null) {
secondaryMask.setChunkObject(chunkObject);
}

// This queue should not be enqueued as it is simply used to restrict block setting, and then delegate to the
// actual queue
ScopedQueueCoordinator scoped =
new ScopedQueueCoordinator(
secondaryMask,
Expand All @@ -172,13 +176,26 @@ public static boolean generate(
);
generator.generateChunk(scoped, area);
generator.populateChunk(scoped, area);
scoped.setForceSync(true);
scoped.enqueue();
}
if (enqueue) {
queue.enqueue();
}
return generationResult;
}

/**
* @deprecated Use {@link AugmentedUtils#generateChunk(String, int, int, QueueCoordinator)} as chunkObject is not required
* in the above method
*/
@Deprecated(forRemoval = true, since = "TODO")
public static boolean generate(
@Nullable Object chunkObject,
final @NonNull String world,
final int chunkX,
final int chunkZ,
QueueCoordinator queue
) {
return generateChunk(world, chunkX, chunkZ, queue);
}

}
Expand Up @@ -34,6 +34,7 @@
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
Expand Down Expand Up @@ -61,14 +62,16 @@ public abstract class BasicQueueCoordinator extends QueueCoordinator {
private int lastX = Integer.MIN_VALUE;
private int lastZ = Integer.MIN_VALUE;
private boolean settingBiomes = false;
private boolean disableBiomes = false;
private boolean settingTiles = false;
private boolean regen = false;
private int[] regenStart;
private int[] regenEnd;
private CuboidRegion regenRegion = null;
private Consumer<BlockVector2> consumer = null;
private boolean unloadAfter = true;
private Runnable whenDone;
private Runnable whenDone = null;
private SideEffectSet sideEffectSet = null;
@Nullable
private LightingMode lightingMode = LightingMode.valueOf(Settings.QUEUE.LIGHTING_MODE);

Expand Down Expand Up @@ -120,6 +123,9 @@ public boolean setBlock(int x, int y, int z, @NonNull BlockState id) {
@SuppressWarnings("removal")
@Override
public boolean setBiome(int x, int z, @NonNull BiomeType biomeType) {
if (disableBiomes) {
return false;
}
LocalChunk chunk = getChunk(x >> 4, z >> 4);
for (int y = world.getMinY(); y <= world.getMaxY(); y++) {
chunk.setBiome(x & 15, y, z & 15, biomeType);
Expand All @@ -130,6 +136,9 @@ public boolean setBiome(int x, int z, @NonNull BiomeType biomeType) {

@Override
public final boolean setBiome(int x, int y, int z, @NonNull BiomeType biomeType) {
if (disableBiomes) {
return false;
}
LocalChunk chunk = getChunk(x >> 4, z >> 4);
chunk.setBiome(x & 15, y, z & 15, biomeType);
settingBiomes = true;
Expand All @@ -141,6 +150,12 @@ public boolean isSettingBiomes() {
return this.settingBiomes;
}

@Override
public void setBiomesEnabled(boolean settingBiomes) {
this.settingBiomes = settingBiomes;
this.disableBiomes = true;
}

@Override
public boolean setTile(int x, int y, int z, @NonNull CompoundTag tag) {
LocalChunk chunk = getChunk(x >> 4, z >> 4);
Expand Down Expand Up @@ -315,6 +330,29 @@ public void setCompleteTask(Runnable whenDone) {
this.whenDone = whenDone;
}

@Override
public SideEffectSet getSideEffectSet() {
return sideEffectSet;
}

@Override
public void setSideEffectSet(SideEffectSet sideEffectSet) {
this.sideEffectSet = sideEffectSet;
}

// Don't ask about the @NonNull placement. That's how it needs to be else it errors.
@Override
public void setBiomeCuboid(
final com.plotsquared.core.location.@NonNull Location pos1,
final com.plotsquared.core.location.@NonNull Location pos2,
@NonNull final BiomeType biome
) {
if (disableBiomes) {
return;
}
super.setBiomeCuboid(pos1, pos2, biome);
}

/**
* Get the {@link LocalChunk} from the queue at the given chunk coordinates. Returns a new instance if one doesn't exist
*/
Expand Down

0 comments on commit 96dfc27

Please sign in to comment.