Skip to content

Commit a8c3fa7

Browse files
committed
Optimize chunk PDC refresh with lightweight tile-state path
1 parent fc9fba3 commit a8c3fa7

File tree

2 files changed

+154
-23
lines changed

2 files changed

+154
-23
lines changed

src/main/java/me/crafter/mc/lockettepro/ContainerPdcLockManager.java

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -418,26 +418,16 @@ private static LockData readLockData(Block block) {
418418
for (Block containerBlock : getLinkedContainerBlocks(block)) {
419419
BlockState blockState = containerBlock.getState();
420420
if (!(blockState instanceof Container container)) continue;
421-
PersistentDataContainer pdc = container.getPersistentDataContainer();
422-
423-
boolean hasLockedKey = pdc.has(lockedKey(), PersistentDataType.BYTE);
424-
if (hasLockedKey) {
421+
LockData partial = readLockDataFromPdc(container.getPersistentDataContainer());
422+
if (partial.hasPdcData()) {
425423
hasPdc = true;
426-
Byte lockedByte = pdc.get(lockedKey(), PersistentDataType.BYTE);
427-
if (lockedByte != null && lockedByte != 0) {
428-
locked = true;
429-
}
430424
}
431-
432-
for (NamespacedKey key : pdc.getKeys()) {
433-
if (!isPermissionKey(key)) continue;
434-
hasPdc = true;
435-
String subject = decodeSubject(key.getKey().substring(PERMISSION_KEY_PATH_PREFIX.length()));
436-
if (subject == null || subject.isBlank()) continue;
437-
438-
String token = pdc.get(key, PersistentDataType.STRING);
439-
PermissionAccess access = PermissionAccess.parseToken(token);
440-
if (access == null || access == PermissionAccess.NONE) continue;
425+
if (partial.isLocked()) {
426+
locked = true;
427+
}
428+
for (Map.Entry<String, PermissionAccess> entry : partial.permissions().entrySet()) {
429+
String subject = entry.getKey();
430+
PermissionAccess access = entry.getValue();
441431
PermissionAccess old = permissions.get(subject);
442432
if (old == null || access.power > old.power) {
443433
permissions.put(subject, access);
@@ -448,6 +438,38 @@ private static LockData readLockData(Block block) {
448438
return new LockData(hasPdc, locked, permissions);
449439
}
450440

441+
private static LockData readLockDataFromPdc(PersistentDataContainer pdc) {
442+
boolean hasPdc = false;
443+
boolean locked = false;
444+
LinkedHashMap<String, PermissionAccess> permissions = new LinkedHashMap<>();
445+
446+
boolean hasLockedKey = pdc.has(lockedKey(), PersistentDataType.BYTE);
447+
if (hasLockedKey) {
448+
hasPdc = true;
449+
Byte lockedByte = pdc.get(lockedKey(), PersistentDataType.BYTE);
450+
if (lockedByte != null && lockedByte != 0) {
451+
locked = true;
452+
}
453+
}
454+
455+
for (NamespacedKey key : pdc.getKeys()) {
456+
if (!isPermissionKey(key)) continue;
457+
hasPdc = true;
458+
String subject = decodeSubject(key.getKey().substring(PERMISSION_KEY_PATH_PREFIX.length()));
459+
if (subject == null || subject.isBlank()) continue;
460+
461+
String token = pdc.get(key, PersistentDataType.STRING);
462+
PermissionAccess access = PermissionAccess.parseToken(token);
463+
if (access == null || access == PermissionAccess.NONE) continue;
464+
PermissionAccess old = permissions.get(subject);
465+
if (old == null || access.power > old.power) {
466+
permissions.put(subject, access);
467+
}
468+
}
469+
470+
return new LockData(hasPdc, locked, permissions);
471+
}
472+
451473
private static boolean isPermissionKey(NamespacedKey key) {
452474
if (!Objects.equals(key.getNamespace(), LOCKETTE_NAMESPACE)) return false;
453475
return key.getKey().startsWith(PERMISSION_KEY_PATH_PREFIX);
@@ -548,6 +570,68 @@ public static boolean refreshLockedContainerTag(Block block) {
548570
return true;
549571
}
550572

573+
/**
574+
* Lightweight path for chunk-scan maintenance. Operates on the provided tile state
575+
* and only writes when the locked_container tag value actually changes.
576+
*/
577+
public static boolean refreshLockedContainerTag(BlockState blockState) {
578+
return refreshLockedContainerTag(blockState, null);
579+
}
580+
581+
/**
582+
* Lightweight path for chunk-scan maintenance with optional linked state (double chest pair).
583+
*/
584+
public static boolean refreshLockedContainerTag(BlockState blockState, BlockState linkedState) {
585+
if (!(blockState instanceof Container container)) return false;
586+
PersistentDataContainer pdc = container.getPersistentDataContainer();
587+
LockData data = readLockDataFromPdc(pdc);
588+
LockData linkedData = null;
589+
Container linkedContainer = null;
590+
PersistentDataContainer linkedPdc = null;
591+
592+
if (linkedState instanceof Container containerState) {
593+
linkedContainer = containerState;
594+
linkedPdc = linkedContainer.getPersistentDataContainer();
595+
linkedData = readLockDataFromPdc(linkedPdc);
596+
}
597+
598+
if (linkedData != null && linkedData.hasPdcData()) {
599+
boolean mergedLocked = data.isLocked() || linkedData.isLocked();
600+
LinkedHashMap<String, PermissionAccess> mergedPermissions = new LinkedHashMap<>(data.permissions());
601+
for (Map.Entry<String, PermissionAccess> entry : linkedData.permissions().entrySet()) {
602+
PermissionAccess existing = mergedPermissions.get(entry.getKey());
603+
if (existing == null || entry.getValue().power > existing.power) {
604+
mergedPermissions.put(entry.getKey(), entry.getValue());
605+
}
606+
}
607+
data = new LockData(true, mergedLocked, mergedPermissions);
608+
}
609+
610+
if (!data.hasPdcData()) return false;
611+
612+
boolean shouldHaveTag = data.isLocked() && shouldSetLockedContainerTag(data.permissions);
613+
NamespacedKey key = lockedContainerKey();
614+
applyLockedContainerTag(container, pdc, key, shouldHaveTag);
615+
if (linkedContainer != null && linkedPdc != null) {
616+
applyLockedContainerTag(linkedContainer, linkedPdc, key, shouldHaveTag);
617+
}
618+
return true;
619+
}
620+
621+
private static boolean applyLockedContainerTag(Container container, PersistentDataContainer pdc, NamespacedKey key, boolean shouldHaveTag) {
622+
boolean hasTag = pdc.has(key, PersistentDataType.BYTE);
623+
if (hasTag == shouldHaveTag) {
624+
return false;
625+
}
626+
if (shouldHaveTag) {
627+
pdc.set(key, PersistentDataType.BYTE, (byte) 1);
628+
} else {
629+
pdc.remove(key);
630+
}
631+
container.update(true, false);
632+
return true;
633+
}
634+
551635
private static boolean shouldSetLockedContainerTag(Map<String, PermissionAccess> permissions) {
552636
if (permissions == null || permissions.isEmpty()) return false;
553637
return !isOpenToEveryone(permissions) && !hasBypassTag(permissions);

src/main/java/me/crafter/mc/lockettepro/Utils.java

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.bukkit.block.Sign;
1818
import org.bukkit.block.data.BlockData;
1919
import org.bukkit.block.data.Directional;
20+
import org.bukkit.block.data.type.Chest;
2021
import org.bukkit.block.sign.Side;
2122
import org.bukkit.command.CommandSender;
2223
import org.bukkit.entity.Player;
@@ -257,8 +258,10 @@ public static void refreshLockedContainerPdcTagLater(Block block) {
257258

258259
public static void refreshLockedContainerPdcTagsInChunk(Chunk chunk) {
259260
if (chunk == null) return;
260-
for (BlockState blockState : chunk.getTileEntities()) {
261-
refreshLockedContainerPdcTagByTileState(blockState);
261+
BlockState[] tileEntities = chunk.getTileEntities();
262+
Map<Long, BlockState> tileLookup = buildTileStateLookup(tileEntities);
263+
for (BlockState blockState : tileEntities) {
264+
refreshLockedContainerPdcTagByTileState(blockState, tileLookup);
262265
}
263266
}
264267

@@ -319,17 +322,59 @@ private static void drainQueuedPdcTagRefreshTasks() {
319322
}
320323
}
321324

322-
private static void refreshLockedContainerPdcTagByTileState(BlockState blockState) {
325+
private static void refreshLockedContainerPdcTagByTileState(BlockState blockState, @Nullable Map<Long, BlockState> tileLookup) {
323326
if (blockState == null) return;
324327
if (blockState instanceof Container) {
325-
ContainerPdcLockManager.refreshLockedContainerTag(blockState.getBlock());
328+
BlockState linkedState = findLinkedContainerTileState(blockState, tileLookup);
329+
ContainerPdcLockManager.refreshLockedContainerTag(blockState, linkedState);
326330
}
327331
if (!(blockState instanceof Sign)) return;
328332
Block signBlock = blockState.getBlock();
329333
if (!LocketteProAPI.isLockSign(signBlock)) return;
330334
refreshLockedContainerPdcTag(LocketteProAPI.getAttachedBlock(signBlock));
331335
}
332336

337+
private static Map<Long, BlockState> buildTileStateLookup(BlockState[] tileEntities) {
338+
HashMap<Long, BlockState> map = new HashMap<>(Math.max(16, tileEntities.length * 2));
339+
for (BlockState tileEntity : tileEntities) {
340+
if (tileEntity == null) continue;
341+
Block block = tileEntity.getBlock();
342+
map.put(blockPositionKey(block.getX(), block.getY(), block.getZ()), tileEntity);
343+
}
344+
return map;
345+
}
346+
347+
@Nullable
348+
private static BlockState findLinkedContainerTileState(BlockState blockState, @Nullable Map<Long, BlockState> tileLookup) {
349+
if (!(blockState instanceof Container)) return null;
350+
if (!(blockState.getBlockData() instanceof Chest chestData)) return null;
351+
if (chestData.getType() == Chest.Type.SINGLE) return null;
352+
353+
Block block = blockState.getBlock();
354+
Chest.Type expected = chestData.getType() == Chest.Type.LEFT ? Chest.Type.RIGHT : Chest.Type.LEFT;
355+
BlockFace facing = chestData.getFacing();
356+
357+
for (BlockFace face : new BlockFace[]{BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST}) {
358+
Block neighbor = block.getRelative(face);
359+
if (neighbor.getType() != block.getType()) continue;
360+
361+
if (tileLookup == null) continue;
362+
BlockState candidate = tileLookup.get(blockPositionKey(neighbor.getX(), neighbor.getY(), neighbor.getZ()));
363+
if (!(candidate instanceof Container)) continue;
364+
if (!(candidate.getBlockData() instanceof Chest candidateData)) continue;
365+
if (candidateData.getType() != expected) continue;
366+
if (candidateData.getFacing() != facing) continue;
367+
return candidate;
368+
}
369+
return null;
370+
}
371+
372+
private static long blockPositionKey(int x, int y, int z) {
373+
return ((long) (x & 0x3FFFFFF) << 38)
374+
| ((long) (z & 0x3FFFFFF) << 12)
375+
| (y & 0xFFFL);
376+
}
377+
333378
private static String toChunkQueueKey(UUID worldId, int chunkX, int chunkZ) {
334379
return worldId + ":" + chunkX + ":" + chunkZ;
335380
}
@@ -340,6 +385,7 @@ private static final class ChunkTagRefreshTask {
340385
private final int chunkZ;
341386
private final String key;
342387
private BlockState[] tileEntities;
388+
private Map<Long, BlockState> tileStatesByPosition;
343389
private int cursor;
344390
private boolean done;
345391

@@ -356,7 +402,7 @@ private int process(int budget) {
356402

357403
int processed = 0;
358404
while (cursor < tileEntities.length && processed < budget) {
359-
refreshLockedContainerPdcTagByTileState(tileEntities[cursor]);
405+
refreshLockedContainerPdcTagByTileState(tileEntities[cursor], tileStatesByPosition);
360406
cursor++;
361407
processed++;
362408
}
@@ -377,6 +423,7 @@ private boolean ensureTileEntitiesLoaded() {
377423
}
378424
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
379425
tileEntities = chunk.getTileEntities();
426+
tileStatesByPosition = buildTileStateLookup(tileEntities);
380427
cursor = 0;
381428
if (tileEntities.length == 0) {
382429
done = true;

0 commit comments

Comments
 (0)