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