diff --git a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalRegion.java b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalRegion.java index e9c29d2..6a792f5 100644 --- a/sakura-api/src/main/java/me/samsuik/sakura/local/LocalRegion.java +++ b/sakura-api/src/main/java/me/samsuik/sakura/local/LocalRegion.java @@ -31,8 +31,8 @@ public record LocalRegion(int minX, int minZ, int maxX, int maxZ) { } public boolean intersects(LocalRegion region) { - return (this.minX < region.minX() && this.maxX > region.minX() || this.maxX > region.maxX() && this.minX < region.maxX()) - && (this.minZ < region.minZ() && this.maxZ > region.minZ() || this.maxZ > region.maxZ() && this.minZ < region.maxZ()); + return (this.minX <= region.minX() && this.maxX >= region.minX() || this.maxX >= region.maxX() && this.minX < region.maxX()) + && (this.minZ <= region.minZ() && this.maxZ >= region.minZ() || this.maxZ >= region.maxZ() && this.minZ < region.maxZ()); } public boolean contains(LocalRegion region) { diff --git a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java index 288307c..864bf4c 100644 --- a/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java +++ b/sakura-server/src/main/java/me/samsuik/sakura/configuration/local/LocalConfigManager.java @@ -1,5 +1,6 @@ package me.samsuik.sakura.configuration.local; +import ca.spottedleaf.concurrentutil.function.BiLongObjectConsumer; import com.google.common.collect.Iterables; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -18,16 +19,16 @@ import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import java.util.*; -import java.util.function.LongConsumer; public final class LocalConfigManager implements LocalStorageHandler { + private static final long MASSIVE_REGION_SIZE = 0x10000000000L; private static final int SMALL_REGION_SIZE = 12; private static final int CONFIG_CACHE_EXPIRATION = 600; private final Map storageMap = new Object2ObjectOpenHashMap<>(); private final List largeRegions = new ObjectArrayList<>(); private final Long2ObjectMap> smallRegions = new Long2ObjectOpenHashMap<>(); - private int regionExponent = 0; + private int regionExponent = 4; private final Long2ObjectMap> chunkConfigCache = new Long2ObjectOpenHashMap<>(); private final Level level; private long expirationTick = 0L; @@ -36,10 +37,14 @@ public final class LocalConfigManager implements LocalStorageHandler { this.level = level; } + private int regionChunkCoord(int n) { + return n >> this.regionExponent; + } + @Override public synchronized @NonNull Optional locate(int x, int z) { - int regionX = x >> this.regionExponent; - int regionZ = z >> this.regionExponent; + int regionX = this.regionChunkCoord(x); + int regionZ = this.regionChunkCoord(z); long regionPos = ChunkPos.asLong(regionX, regionZ); List regions = this.smallRegions.getOrDefault(regionPos, List.of()); for (LocalRegion region : Iterables.concat(regions, this.largeRegions)) { @@ -62,24 +67,18 @@ public final class LocalConfigManager implements LocalStorageHandler { @Override public synchronized void put(@NonNull LocalRegion region, @NonNull LocalValueStorage storage) { - int shift = this.regionExponent; - int regionChunks = regionChunks(region, shift); + boolean smallRegion = this.isSmallRegion(region); + this.ensureRegionIsNotOverlapping(region, smallRegion); - // make sure there's no overlapping regions - this.ensureRegionIsNotOverlapping(region, regionChunks); - - if (regionChunks <= SMALL_REGION_SIZE) { - this.forEachRegionChunks(region, pos -> { - this.smallRegions.computeIfAbsent(pos, k -> new ArrayList<>()) - .add(region); - }); - } else { + if (!smallRegion) { this.largeRegions.add(region); // The region exponent may be too small if ((this.largeRegions.size() & 15) == 0) { this.resizeRegions(); } + } else { + this.forEachRegionChunks(region, this::addSmallRegion); } this.chunkConfigCache.clear(); @@ -88,7 +87,7 @@ public final class LocalConfigManager implements LocalStorageHandler { @Override public synchronized void remove(@NonNull LocalRegion region) { - this.forEachRegionChunks(region, pos -> { + this.forEachRegionChunks(region, (pos, r) -> { List regions = this.smallRegions.get(pos); if (regions != null) { regions.remove(region); @@ -100,9 +99,15 @@ public final class LocalConfigManager implements LocalStorageHandler { this.chunkConfigCache.clear(); this.storageMap.remove(region); + this.largeRegions.remove(region); } - private void forEachRegionChunks(LocalRegion region, LongConsumer chunkConsumer) { + private void addSmallRegion(long pos, LocalRegion region) { + this.smallRegions.computeIfAbsent(pos, k -> new ArrayList<>()) + .add(region); + } + + private void forEachRegionChunks(LocalRegion region, BiLongObjectConsumer chunkConsumer) { int exponent = this.regionExponent; int minX = region.minX() >> exponent; int minZ = region.minZ() >> exponent; @@ -111,14 +116,13 @@ public final class LocalConfigManager implements LocalStorageHandler { for (int x = minX; x <= maxX; ++x) { for (int z = minZ; z <= maxZ; ++z) { - chunkConsumer.accept(ChunkPos.asLong(x, z)); + chunkConsumer.accept(ChunkPos.asLong(x, z), region); } } } private void resizeRegions() { - List regions = this.regions(); - int newExponent = this.calculateRegionExponent(regions); + int newExponent = this.calculateRegionExponent(); if (newExponent == this.regionExponent) { return; // nothing has changed } @@ -127,38 +131,42 @@ public final class LocalConfigManager implements LocalStorageHandler { this.largeRegions.clear(); this.smallRegions.clear(); - for (LocalRegion region : regions) { - int regionChunks = regionChunks(region, newExponent); - if (regionChunks <= SMALL_REGION_SIZE) { - this.forEachRegionChunks(region, pos -> { - this.smallRegions.computeIfAbsent(pos, k -> new ArrayList<>()) - .add(region); - }); - } else { + for (LocalRegion region : this.storageMap.keySet()) { + if (!this.isSmallRegion(region)) { this.largeRegions.add(region); + } else { + this.forEachRegionChunks(region, this::addSmallRegion); } } } - private int calculateRegionExponent(List regions) { - int regionChunks = 0; - for (LocalRegion region : regions) { - regionChunks += regionChunks(region, 0); + private int calculateRegionExponent() { + long totalRegionChunks = 0; + for (LocalRegion region : this.storageMap.keySet()) { + long chunks = regionChunks(region, 0); + if (chunks >= MASSIVE_REGION_SIZE) { + continue; + } + totalRegionChunks += chunks; } - regionChunks /= regions.size(); + totalRegionChunks /= this.storageMap.size(); int exponent = 4; while (true) { - if ((regionChunks >> exponent++) <= SMALL_REGION_SIZE / 2) { + if ((totalRegionChunks >> exponent++) <= SMALL_REGION_SIZE / 2) { return exponent; } } } - private static int regionChunks(LocalRegion region, int exponent) { + private boolean isSmallRegion(LocalRegion region) { + return regionChunks(region, this.regionExponent) <= SMALL_REGION_SIZE; + } + + private static long regionChunks(LocalRegion region, int exponent) { int sizeX = region.maxX() - region.minX() >> exponent; int sizeZ = region.maxZ() - region.minZ() >> exponent; - return (sizeX + 1) * (sizeZ + 1); + return (long) (sizeX + 1) * (long) (sizeZ + 1); } @Override @@ -175,32 +183,35 @@ public final class LocalConfigManager implements LocalStorageHandler { } long chunkKey = ChunkPos.asLong(position.getX() >> 4, position.getZ() >> 4); - Pair pair = this.chunkConfigCache.computeIfAbsent(chunkKey, k -> { - return Pair.of(this.createLocalChunkConfig(position), new TickExpiry(gameTime, CONFIG_CACHE_EXPIRATION)); - }); + Pair pair = this.chunkConfigCache.computeIfAbsent(chunkKey, + k -> this.createLocalChunkConfigWithExpiry(position, gameTime)); pair.value().refresh(gameTime); return pair.key(); } - private LocalValueConfig createLocalChunkConfig(BlockPos position) { - // uses defaults from the sakura config + private Pair createLocalChunkConfigWithExpiry(BlockPos position, long gameTime) { + // uses defaults from the sakura and paper config LocalValueConfig config = new LocalValueConfig(this.level); this.locate(position.getX(), position.getZ()).ifPresent(region -> { config.loadFromStorage(this.storageMap.get(region)); }); - return config; + + TickExpiry expiry = new TickExpiry(gameTime, CONFIG_CACHE_EXPIRATION); + return Pair.of(config, expiry); } - private void ensureRegionIsNotOverlapping(LocalRegion region, int regionChunks) { + private void ensureRegionIsNotOverlapping(LocalRegion region, boolean smallRegion) { Set nearbyRegions = new ReferenceOpenHashSet<>(); - if (regionChunks > SMALL_REGION_SIZE) { + if (!smallRegion) { nearbyRegions.addAll(this.storageMap.keySet()); } else { - this.forEachRegionChunks(region, pos -> { + this.forEachRegionChunks(region, (pos, r) -> { nearbyRegions.addAll(this.smallRegions.getOrDefault(pos, List.of())); }); } + + // Throw if any of the nearby regions are overlapping for (LocalRegion present : Iterables.concat(nearbyRegions, this.largeRegions)) { if (present != region && present.intersects(region)) { throw new OverlappingRegionException(present, region);