Last active
January 13, 2024 03:49
-
-
Save jpenilla/5890fe2065bb9b199f6049962dbd2c00 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// licensed CC0 (except for iterator class, and Bukkit is GPL, so, IANAL, do what you want) | |
package xyz.jpenilla.gist; | |
import it.unimi.dsi.fastutil.ints.IntIntPair; | |
import java.util.Iterator; | |
import java.util.function.Consumer; | |
import net.kyori.adventure.key.Key; | |
import org.bukkit.World; | |
import org.bukkit.event.EventHandler; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.world.WorldLoadEvent; | |
import org.bukkit.plugin.java.JavaPlugin; | |
import org.bukkit.scheduler.BukkitTask; | |
// Example of how to load a large amount of chunks quickly/efficiently, by keeping the chunk system saturated with up to MAXIMUM_OUTSTANDING_REQUESTS active chunk requests at a time | |
public final class SpiralOutChunksPlugin extends JavaPlugin implements Listener { | |
@Override | |
public void onEnable() { | |
this.getServer().getPluginManager().registerEvents(this, this); | |
} | |
@EventHandler | |
public void worldLoad(final WorldLoadEvent event) { | |
if (event.getWorld().key().equals(Key.key("overworld"))) { | |
this.getServer().getScheduler().runTaskTimer(this, new ChunkTask(this, event.getWorld(), 0, 0, 50), 0L, 1L); | |
} | |
} | |
private static final class ChunkTask implements Consumer<BukkitTask> { | |
private static final int MAXIMUM_OUTSTANDING_REQUESTS = 1024; | |
private final JavaPlugin plugin; | |
private final World world; | |
private final SpiralIterator<IntIntPair> iterator; | |
private long submitted = 0; | |
private long executed = 0; | |
private ChunkTask(final JavaPlugin plugin, final World world, final int centerChunkX, final int centerChunkZ, final int chunksRadius) { | |
this.plugin = plugin; | |
this.world = world; | |
// If you're loading a chunk radius that spans many regions, consider implementing something to work on X regions at a time, | |
// as a chunk spiral will become less efficient as the region radius goes past 0. | |
this.iterator = SpiralIterator.create(IntIntPair::of, centerChunkX, centerChunkZ, chunksRadius); | |
} | |
@Override | |
public void accept(final BukkitTask task) { | |
while ((this.submitted - this.executed) < MAXIMUM_OUTSTANDING_REQUESTS && this.iterator.hasNext()) { | |
final IntIntPair coord = this.iterator.next(); | |
this.submitted++; | |
this.world.getChunkAtAsync(coord.firstInt(), coord.secondInt(), false).whenComplete((chunk, thr) -> { | |
if (chunk != null) { | |
// this.plugin.getSLF4JLogger().info("Processing chunk at {}...", pos(this.world, coord)); | |
// doStuff(chunk); | |
// this.plugin.getSLF4JLogger().info("Done processing chunk at {}.", pos(this.world, coord)); | |
} else if (thr != null) { | |
this.plugin.getSLF4JLogger().warn("Exception loading chunk at {}", pos(this.world, coord), thr); | |
} else { | |
// this.plugin.getSLF4JLogger().info("Chunk not generated at {}", pos(this.world, coord)); | |
} | |
this.executed++; | |
}); | |
} | |
if (!this.iterator.hasNext()) { | |
if (this.submitted - this.executed == 0) { | |
this.plugin.getSLF4JLogger().info("Task done for world {}.", this.world.key()); | |
task.cancel(); | |
return; | |
} | |
// Every five seconds print a message when we are done submitting but are waiting on completion | |
if (this.plugin.getServer().getCurrentTick() % (20 * 5) == 0) { | |
this.plugin.getSLF4JLogger().info("Done submitting tasks for world {}, waiting on completion...", this.world.key()); | |
} | |
} else if (this.plugin.getServer().getCurrentTick() % 20 == 0) { | |
this.plugin.getSLF4JLogger().info("Processed {}/{} chunks for world {}", this.executed, this.iterator.totalSteps(), this.world.key()); | |
} | |
} | |
private static String pos(final World world, final IntIntPair pos) { | |
return world.key().asString() + '[' + pos.firstInt() + ", " + pos.secondInt() + ']'; | |
} | |
} | |
// Copied from squaremap, see the license there https://github.com/jpenilla/squaremap | |
public static final class SpiralIterator<T> implements Iterator<T> { | |
private final long totalSteps; | |
private final CoordinateMapper<T> coordinateMapper; | |
private int x; | |
private int z; | |
private long legAxis; | |
private long stepCount; | |
private long stepLeg; | |
private long layer; | |
private Direction direction = Direction.RIGHT; | |
private SpiralIterator(final CoordinateMapper<T> coordinateMapper, final int x, final int z, final int radius) { | |
this.coordinateMapper = coordinateMapper; | |
this.x = x; | |
this.z = z; | |
this.totalSteps = (radius * 2L + 1) * (radius * 2L + 1); | |
} | |
@Override | |
public boolean hasNext() { | |
return this.stepCount < this.totalSteps; | |
} | |
@Override | |
public T next() { | |
final T t = this.coordinateMapper.create(this.x, this.z); | |
if (!this.hasNext()) { | |
return t; | |
} | |
switch (this.direction) { | |
case DOWN -> this.z++; | |
case LEFT -> this.x--; | |
case UP -> this.z--; | |
case RIGHT -> this.x++; | |
} | |
this.stepCount++; | |
this.stepLeg++; | |
if (this.stepLeg > this.layer) { | |
this.direction = this.direction.next(); | |
this.stepLeg = 0; | |
this.legAxis++; | |
if (this.legAxis > 1) { | |
this.legAxis = 0; | |
this.layer++; | |
} | |
} | |
return t; | |
} | |
public long totalSteps() { | |
return this.totalSteps; | |
} | |
public static <T> SpiralIterator<T> create(final CoordinateMapper<T> coordinateMapper, final int x, final int z, final int radius) { | |
return new SpiralIterator<>(coordinateMapper, x, z, radius); | |
} | |
@FunctionalInterface | |
public interface CoordinateMapper<T> { | |
T create(int x, int z); | |
} | |
private enum Direction { | |
RIGHT, | |
DOWN, | |
LEFT, | |
UP; | |
private static final Direction[] VALUES = values(); | |
public Direction next() { | |
return VALUES[(this.ordinal() + 1) % VALUES.length]; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment