9
0
mirror of https://github.com/Samsuik/Sakura.git synced 2025-12-19 14:59:30 +00:00

Fix integer overflow in regionChunks and some other changes

This commit is contained in:
Samsuik
2025-06-23 10:36:40 +01:00
parent 5b8cf57b90
commit 4413574030
2 changed files with 58 additions and 47 deletions

View File

@@ -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) {

View File

@@ -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<LocalRegion, LocalValueStorage> storageMap = new Object2ObjectOpenHashMap<>();
private final List<LocalRegion> largeRegions = new ObjectArrayList<>();
private final Long2ObjectMap<List<LocalRegion>> smallRegions = new Long2ObjectOpenHashMap<>();
private int regionExponent = 0;
private int regionExponent = 4;
private final Long2ObjectMap<Pair<LocalValueConfig, TickExpiry>> 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<LocalRegion> 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<LocalRegion> 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<LocalRegion> 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<LocalRegion> 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<LocalRegion> 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<LocalRegion> 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<LocalValueConfig, TickExpiry> pair = this.chunkConfigCache.computeIfAbsent(chunkKey, k -> {
return Pair.of(this.createLocalChunkConfig(position), new TickExpiry(gameTime, CONFIG_CACHE_EXPIRATION));
});
Pair<LocalValueConfig, TickExpiry> 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<LocalValueConfig, TickExpiry> 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<LocalRegion> 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);