mirror of
https://github.com/Samsuik/Sakura.git
synced 2026-01-04 15:31:43 +00:00
Relocate local config package
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
package me.samsuik.sakura.configuration.local;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
||||
import me.samsuik.sakura.local.LocalRegion;
|
||||
import me.samsuik.sakura.local.storage.LocalStorageHandler;
|
||||
import me.samsuik.sakura.local.storage.LocalValueStorage;
|
||||
import me.samsuik.sakura.utils.TickExpiry;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
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 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 final Long2ObjectMap<Pair<LocalValueConfig, TickExpiry>> chunkConfigCache = new Long2ObjectOpenHashMap<>();
|
||||
private final Level level;
|
||||
private long expirationTick = 0L;
|
||||
|
||||
public LocalConfigManager(Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized @NonNull Optional<LocalRegion> locate(int x, int z) {
|
||||
int regionX = x >> this.regionExponent;
|
||||
int regionZ = z >> this.regionExponent;
|
||||
long regionPos = ChunkPos.asLong(regionX, regionZ);
|
||||
List<LocalRegion> regions = this.smallRegions.getOrDefault(regionPos, List.of());
|
||||
for (LocalRegion region : Iterables.concat(regions, this.largeRegions)) {
|
||||
if (region.contains(x, z)) {
|
||||
return Optional.of(region);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized @Nullable LocalValueStorage get(@NonNull LocalRegion region) {
|
||||
return this.storageMap.get(region);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean has(@NonNull LocalRegion region) {
|
||||
return this.storageMap.containsKey(region);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void put(@NonNull LocalRegion region, @NonNull LocalValueStorage storage) {
|
||||
this.ensureNotOverlapping(region);
|
||||
int shift = this.regionExponent;
|
||||
int regionChunks = regionChunks(region, shift);
|
||||
|
||||
if (regionChunks <= SMALL_REGION_SIZE) {
|
||||
this.forEachRegionChunks(region, pos -> {
|
||||
this.smallRegions.computeIfAbsent(pos, k -> new ArrayList<>())
|
||||
.add(region);
|
||||
});
|
||||
} else {
|
||||
this.largeRegions.add(region);
|
||||
// The region exponent might be too small
|
||||
if (this.largeRegions.size() % 24 == 0) {
|
||||
this.resizeRegions();
|
||||
}
|
||||
}
|
||||
|
||||
this.chunkConfigCache.clear();
|
||||
this.storageMap.put(region, storage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void remove(@NonNull LocalRegion region) {
|
||||
this.forEachRegionChunks(region, pos -> {
|
||||
List<LocalRegion> regions = this.smallRegions.get(pos);
|
||||
if (regions != null) {
|
||||
regions.remove(region);
|
||||
if (regions.isEmpty()) {
|
||||
this.smallRegions.remove(pos);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.chunkConfigCache.clear();
|
||||
this.storageMap.remove(region);
|
||||
}
|
||||
|
||||
private void forEachRegionChunks(LocalRegion region, LongConsumer chunkConsumer) {
|
||||
int exponent = this.regionExponent;
|
||||
int minX = region.minX() >> exponent;
|
||||
int minZ = region.minZ() >> exponent;
|
||||
int maxX = region.maxX() >> exponent;
|
||||
int maxZ = region.maxZ() >> exponent;
|
||||
|
||||
for (int x = minX; x <= maxX; ++x) {
|
||||
for (int z = minZ; z <= maxZ; ++z) {
|
||||
chunkConsumer.accept(ChunkPos.asLong(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resizeRegions() {
|
||||
List<LocalRegion> regions = this.regions();
|
||||
int newExponent = this.calculateRegionExponent(regions);
|
||||
if (newExponent == this.regionExponent) {
|
||||
return; // nothing has changed
|
||||
}
|
||||
|
||||
this.regionExponent = newExponent;
|
||||
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 {
|
||||
this.largeRegions.add(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateRegionExponent(List<LocalRegion> regions) {
|
||||
int regionChunks = 0;
|
||||
for (LocalRegion region : regions) {
|
||||
regionChunks += regionChunks(region, 0);
|
||||
}
|
||||
regionChunks /= regions.size();
|
||||
|
||||
int exponent = 4;
|
||||
while (true) {
|
||||
if ((regionChunks >> exponent++) <= SMALL_REGION_SIZE / 2) {
|
||||
return exponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int regionChunks(LocalRegion region, int exponent) {
|
||||
int sizeX = region.maxX() - region.minX() >> exponent;
|
||||
int sizeZ = region.maxZ() - region.minZ() >> exponent;
|
||||
return (sizeX + 1) * (sizeZ + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized @NonNull List<LocalRegion> regions() {
|
||||
return new ArrayList<>(this.storageMap.keySet());
|
||||
}
|
||||
|
||||
public synchronized LocalValueConfig config(BlockPos position) {
|
||||
long gameTime = this.level.getGameTime();
|
||||
long ticks = this.expirationTick - gameTime;
|
||||
if (ticks >= CONFIG_CACHE_EXPIRATION / 3) {
|
||||
this.chunkConfigCache.values().removeIf(pair -> pair.value().isExpired(gameTime));
|
||||
this.expirationTick = gameTime;
|
||||
}
|
||||
|
||||
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.value().refresh(gameTime);
|
||||
return pair.key();
|
||||
}
|
||||
|
||||
private LocalValueConfig createLocalChunkConfig(BlockPos position) {
|
||||
// uses defaults from the sakura config
|
||||
LocalValueConfig config = new LocalValueConfig(this.level);
|
||||
this.locate(position.getX(), position.getZ()).ifPresent(region -> {
|
||||
config.loadFromStorage(this.storageMap.get(region));
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
private void ensureNotOverlapping(LocalRegion region) {
|
||||
Set<LocalRegion> nearbyRegions = new ReferenceOpenHashSet<>();
|
||||
this.forEachRegionChunks(region, pos -> {
|
||||
nearbyRegions.addAll(this.smallRegions.getOrDefault(pos, List.of()));
|
||||
});
|
||||
for (LocalRegion present : Iterables.concat(nearbyRegions, this.largeRegions)) {
|
||||
if (present != region && present.intersects(region)) {
|
||||
throw new UnsupportedOperationException("overlapping region (%s, %s)".formatted(present, region));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package me.samsuik.sakura.configuration.local;
|
||||
|
||||
import io.papermc.paper.configuration.WorldConfiguration.Misc.RedstoneImplementation;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import me.samsuik.sakura.explosion.durable.DurableMaterial;
|
||||
import me.samsuik.sakura.local.LocalValueKeys;
|
||||
import me.samsuik.sakura.local.storage.LocalValueStorage;
|
||||
import me.samsuik.sakura.physics.PhysicsVersion;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public final class LocalValueConfig {
|
||||
public Map<Block, DurableMaterial> durableMaterials;
|
||||
public RedstoneImplementation redstoneImplementation;
|
||||
public PhysicsVersion physicsVersion;
|
||||
public boolean consistentRadius;
|
||||
public boolean redstoneCache;
|
||||
public int lavaFlowSpeed = -1;
|
||||
|
||||
LocalValueConfig(Level level) {
|
||||
this.durableMaterials = new Reference2ObjectOpenHashMap<>(level.sakuraConfig().cannons.explosion.durableMaterials);
|
||||
this.redstoneImplementation = level.paperConfig().misc.redstoneImplementation;
|
||||
this.physicsVersion = level.sakuraConfig().cannons.mechanics.physicsVersion;
|
||||
this.consistentRadius = level.sakuraConfig().cannons.explosion.consistentRadius;
|
||||
this.redstoneCache = level.sakuraConfig().technical.redstone.redstoneCache;
|
||||
}
|
||||
|
||||
void loadFromStorage(LocalValueStorage storage) {
|
||||
storage.get(LocalValueKeys.DURABLE_MATERIALS).ifPresent(materials -> {
|
||||
materials.forEach((materialType, materialProperties) -> {
|
||||
Block nmsBlock = CraftMagicNumbers.getBlock(materialType);
|
||||
DurableMaterial durableMaterial = new DurableMaterial(materialProperties.getKey(), materialProperties.getValue());
|
||||
this.durableMaterials.put(nmsBlock, durableMaterial);
|
||||
});
|
||||
});
|
||||
storage.get(LocalValueKeys.REDSTONE_IMPLEMENTATION).ifPresent(implementation -> {
|
||||
this.redstoneImplementation = RedstoneImplementation.values()[implementation.ordinal()];
|
||||
});
|
||||
this.physicsVersion = storage.getOrDefault(LocalValueKeys.PHYSICS_VERSION, this.physicsVersion);
|
||||
this.consistentRadius = storage.getOrDefault(LocalValueKeys.CONSISTENT_EXPLOSION_RADIUS, this.consistentRadius);
|
||||
this.redstoneCache = storage.getOrDefault(LocalValueKeys.REDSTONE_CACHE, this.redstoneCache);
|
||||
this.lavaFlowSpeed = storage.getOrDefault(LocalValueKeys.LAVA_FLOW_SPEED, this.lavaFlowSpeed);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user