Skip to content

Commit 08dbf37

Browse files
committed
caches chunks
1 parent 202a9f0 commit 08dbf37

File tree

16 files changed

+106
-29
lines changed

16 files changed

+106
-29
lines changed

src/main/java/net/potato/tuff/TuffX.java

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
import java.util.concurrent.atomic.AtomicInteger;
3838
import java.util.concurrent.ConcurrentLinkedQueue;
3939

40+
import com.google.common.cache.Cache;
41+
import com.google.common.cache.CacheBuilder;
42+
4043
import java.util.logging.Level;
4144

4245
public class TuffX extends JavaPlugin implements Listener, PluginMessageListener {
@@ -54,6 +57,8 @@ public class TuffX extends JavaPlugin implements Listener, PluginMessageListener
5457

5558
private BukkitTask processorTask;
5659

60+
private Cache<WorldChunkKey, List<byte[]>> chunkPayloadCache;
61+
5762
private boolean debug;
5863

5964
private ExecutorService chunkProcessorPool;
@@ -62,6 +67,8 @@ private void logDebug(String message) {
6267
if (debug) getLogger().log(Level.INFO, "[TuffX-Debug] " + message);
6368
}
6469

70+
public record WorldChunkKey(String worldName, int x, int z) {}
71+
6572
@Override
6673
public void onEnable() {
6774
saveDefaultConfig();
@@ -71,6 +78,11 @@ public void onEnable() {
7178

7279
logDebug("TuffX will be active in the following worlds: " + String.join(", ", this.enabledWorlds));
7380

81+
this.chunkPayloadCache = CacheBuilder.newBuilder()
82+
.maximumSize(getConfig().getInt("cache-size", 512))
83+
.expireAfterAccess(getConfig().getInt("cache-expiration", 5), TimeUnit.MINUTES)
84+
.build();
85+
7486
getServer().getMessenger().registerOutgoingPluginChannel(this, CHANNEL);
7587
getServer().getMessenger().registerIncomingPluginChannel(this, CHANNEL, this);
7688
getServer().getPluginManager().registerEvents(this, this);
@@ -223,17 +235,33 @@ public void run() {
223235
}
224236

225237
Queue<Vector> queue = requestQueue.get(playerUUID);
226-
if (queue != null && !queue.isEmpty()) {
227-
for (int i = 0; i < CHUNKS_PER_TICK && !queue.isEmpty(); i++) {
228-
Vector vec = queue.poll();
229-
if (vec != null) {
230-
World world = player.getWorld();
231-
if (world.isChunkLoaded(vec.getBlockX(), vec.getBlockZ())) {
232-
processAndSendChunk(player, world.getChunkAt(vec.getBlockX(), vec.getBlockZ()));
233-
} else {
234-
world.loadChunk(vec.getBlockX(), vec.getBlockZ(), true);
235-
queue.add(vec);
236-
}
238+
if (queue == null) continue;
239+
240+
for (int i = 0; i < CHUNKS_PER_TICK && !queue.isEmpty(); i++) {
241+
Vector vec = queue.poll();
242+
if (vec != null) {
243+
World world = player.getWorld();
244+
WorldChunkKey key = new WorldChunkKey(world.getName(), vec.getBlockX(), vec.getBlockZ());
245+
246+
List<byte[]> cachedData = chunkPayloadCache.getIfPresent(key);
247+
248+
if (cachedData != null) {
249+
logDebug("Cache HIT for chunk: " + key);
250+
sendPayloadsToPlayer(player, cachedData);
251+
checkIfInitialLoadComplete(player);
252+
} else {
253+
logDebug("Cache MISS for chunk: " + key + ". Generating...");
254+
new BukkitRunnable() {
255+
@Override
256+
public void run() {
257+
if (world.isChunkLoaded(key.x(), key.z())) {
258+
processAndSendChunk(player, world.getChunkAt(key.x(), key.z()));
259+
} else {
260+
world.loadChunk(key.x(), key.z(), true);
261+
processAndSendChunk(player, world.getChunkAt(key.x(), key.z()));
262+
}
263+
}
264+
}.runTaskAsynchronously(TuffX.this);
237265
}
238266
}
239267
}
@@ -242,33 +270,48 @@ public void run() {
242270
}.runTaskTimer(this, 0L, 1L);
243271
}
244272

273+
private void sendPayloadsToPlayer(Player player, List<byte[]> payloads) {
274+
new BukkitRunnable() {
275+
@Override
276+
public void run() {
277+
if (player.isOnline()) {
278+
for (byte[] payload : payloads) {
279+
player.sendPluginMessage(TuffX.this, CHANNEL, payload);
280+
}
281+
}
282+
}
283+
}.runTask(this);
284+
}
285+
245286
@EventHandler(priority = EventPriority.MONITOR)
246287
public void onPlayerChangeWorld(PlayerChangedWorldEvent event) {
247288
Player player = event.getPlayer();
248289
UUID playerId = player.getUniqueId();
249290

250291
Queue<Vector> playerQueue = requestQueue.get(playerId);
251292
if (playerQueue != null && !playerQueue.isEmpty()) {
252-
logDebug("Player " + player.getName() + " changed worlds. Clearing " + playerQueue.size() + " pending chunk requests.");
253-
playerQueue.clear();
254-
}
255-
256-
if (initialChunksToProcess.remove(playerId) != null) {
257-
logDebug("Player " + player.getName() + " was in the middle of an initial chunk load. The process has been cancelled.");
258-
awaitingInitialBatch.remove(playerId);
259-
player.sendPluginMessage(this, CHANNEL, createLoadFinishedPayload());
260-
}
293+
logDebug("Player " + player.getName() + " changed worlds. Clearing " + playerQueue.size() + " pending chunk requests.");
294+
playerQueue.clear();
295+
}
296+
297+
if (initialChunksToProcess.remove(playerId) != null) {
298+
logDebug("Player " + player.getName() + " was in the middle of an initial chunk load. The process has been cancelled.");
299+
awaitingInitialBatch.remove(playerId);
300+
player.sendPluginMessage(this, CHANNEL, createLoadFinishedPayload());
301+
}
261302

262-
player.sendPluginMessage(this, CHANNEL, createDimensionPayload());
303+
player.sendPluginMessage(this, CHANNEL, createDimensionPayload());
263304

264-
player.sendPluginMessage(this, CHANNEL, createBelowY0StatusPayload(enabledWorlds.contains(player.getWorld().getName())));
265-
}
305+
player.sendPluginMessage(this, CHANNEL, createBelowY0StatusPayload(enabledWorlds.contains(player.getWorld().getName())));
306+
}
266307

267-
private void processAndSendChunk(final Player player, final Chunk chunk) {
308+
private void processAndSendChunk(final Player player, final Chunk chunk) {
268309
if (chunk == null || !player.isOnline() || chunkProcessorPool.isShutdown()) {
269310
return;
270311
}
271312

313+
final WorldChunkKey key = new WorldChunkKey(chunk.getWorld().getName(), chunk.getX(), chunk.getZ());
314+
272315
chunkProcessorPool.submit(() -> {
273316
final List<byte[]> processedPayloads = new ArrayList<>();
274317
final ChunkSnapshot snapshot = chunk.getChunkSnapshot(true, false, false);
@@ -288,6 +331,8 @@ private void processAndSendChunk(final Player player, final Chunk chunk) {
288331
}
289332
}
290333

334+
chunkPayloadCache.put(key, processedPayloads);
335+
291336
new BukkitRunnable() {
292337
@Override
293338
public void run() {
@@ -302,6 +347,12 @@ public void run() {
302347
});
303348
}
304349

350+
private void invalidateChunkCache(World world, int blockX, int blockZ) {
351+
WorldChunkKey key = new WorldChunkKey(world.getName(), blockX >> 4, blockZ >> 4);
352+
chunkPayloadCache.invalidate(key);
353+
logDebug("Invalidated cache for chunk: " + key);
354+
}
355+
305356
private void checkIfInitialLoadComplete(Player player) {
306357
UUID playerId = player.getUniqueId();
307358
AtomicInteger counter = initialChunksToProcess.get(playerId);
@@ -405,11 +456,29 @@ private byte[] createSectionPayload(ChunkSnapshot snapshot, int cx, int cz, int
405456

406457

407458
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
408-
public void onBlockBreak(BlockBreakEvent event) { if (event.getBlock().getY() < 0) handleBlockChange(event.getBlock().getLocation(), event.getBlock().getBlockData(), Material.AIR.createBlockData()); }
459+
public void onBlockBreak(BlockBreakEvent event) {
460+
if (event.getBlock().getY() < 0) {
461+
handleBlockChange(event.getBlock().getLocation(), event.getBlock().getBlockData(), Material.AIR.createBlockData());
462+
invalidateChunkCache(event.getBlock().getWorld(), event.getBlock().getX(), event.getBlock().getZ());
463+
}
464+
}
465+
409466
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
410-
public void onBlockPlace(BlockPlaceEvent event) { if (event.getBlock().getY() < 0) handleBlockChange(event.getBlock().getLocation(), event.getBlockReplacedState().getBlockData(), event.getBlock().getBlockData()); }
467+
public void onBlockPlace(BlockPlaceEvent event) {
468+
if (event.getBlock().getY() < 0) {
469+
handleBlockChange(event.getBlock().getLocation(), event.getBlockReplacedState().getBlockData(), event.getBlock().getBlockData());
470+
invalidateChunkCache(event.getBlock().getWorld(), event.getBlock().getX(), event.getBlock().getZ());
471+
}
472+
}
473+
411474
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
412-
public void onBlockPhysics(BlockPhysicsEvent event) { Block block = event.getBlock(); if (block.getY() < 0) sendSingleBlockUpdate(block.getLocation(), block.getBlockData());sendLightingUpdate(block.getLocation()); }
475+
public void onBlockPhysics(BlockPhysicsEvent event) {
476+
Block block = event.getBlock();
477+
if (block.getY() < 0) {
478+
sendSingleBlockUpdate(block.getLocation(), block.getBlockData());sendLightingUpdate(block.getLocation());
479+
invalidateChunkCache(block.getWorld(), block.getX(), block.getZ());
480+
}
481+
}
413482

414483
private void sendSingleBlockUpdate(Location loc, BlockData data) {
415484
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(64);

src/main/resources/config.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,9 @@ enabled-worlds:
1010

1111
#the amount of threads for the chunk processor to use - using more threads processes chunks faster, but increases CPU usage
1212
#-1 is automatic
13-
chunk-processor-threads: -1
13+
chunk-processor-threads: -1
14+
15+
#the size of the cache
16+
cache-size: 512
17+
#how long until the cache expires (minutes)
18+
cache-expiration: 5

target/TuffX.jar

3.89 KB
Binary file not shown.
0 Bytes
Binary file not shown.
1.39 KB
Binary file not shown.
1.23 KB
Binary file not shown.
-97 Bytes
Binary file not shown.
-134 Bytes
Binary file not shown.
-692 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)