From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Samsuik <40902469+Samsuik@users.noreply.github.com> Date: Tue, 21 Nov 2023 23:24:24 +0000 Subject: [PATCH] Local Config and Value Storage API diff --git a/src/main/java/me/samsuik/sakura/local/config/LocalConfigManager.java b/src/main/java/me/samsuik/sakura/local/config/LocalConfigManager.java new file mode 100644 index 0000000000000000000000000000000000000000..a3a09b8d58589883c7c465597bc64502bbfa0d88 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/local/config/LocalConfigManager.java @@ -0,0 +1,143 @@ +package me.samsuik.sakura.local.config; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectRBTreeMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectSortedMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +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.objects.Expiry; +import net.minecraft.core.BlockPos; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import org.bukkit.Location; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public final class LocalConfigManager implements LocalStorageHandler { + + private final Map storageMap = new Object2ObjectOpenHashMap<>(); + // tree is a tree. it may not be correct but it works. + private final Long2ObjectRBTreeMap regionTree = new Long2ObjectRBTreeMap<>(); + private final Long2ObjectMap configMap = new Long2ObjectOpenHashMap<>(); + private final Level level; + + public LocalConfigManager(Level level) { + this.level = level; + } + + @Override + public LocalRegion locate(Location location, int searchDistance) { + return this.locate(location.getBlockX(), location.getBlockZ(), searchDistance); + } + + @Override + public synchronized LocalRegion locate(int x, int z, int searchDistance) { + long search = (long) searchDistance << 32; + long coordinate = ChunkPos.asLong(x, z); + + // all regions below the coordinate (they're stored with the minX, minZ) + Long2ObjectSortedMap head = this.regionTree.headMap(coordinate); + + if (head.isEmpty()) return null; + + for (Long2ObjectMap.Entry entry : head.long2ObjectEntrySet()) { + // this has no respect for the x coordinate, but it works + if (coordinate - entry.getLongKey() > search) { + break; + } + + // check if the region we found contains the position + if (entry.getValue().contains(x, z)) { + return entry.getValue(); + } + } + + return null; + } + + @Override + public synchronized LocalValueStorage get(LocalRegion region) { + return this.storageMap.get(region); + } + + @Override + public synchronized boolean has(LocalRegion region) { + return this.storageMap.containsKey(region); + } + + @Override + public synchronized void put(LocalRegion region, LocalValueStorage storage) { + assert region != null : "region cannot be null"; + assert storage != null : "storage cannot be null"; + + long base = ChunkPos.asLong(region.minX(), region.minZ()); + this.ensureNotOverlapping(region); + + this.storageMap.put(region, storage); + this.regionTree.put(base, region); + } + + @Override + public synchronized void remove(LocalRegion region) { + assert region != null : "region cannot be null"; + + long base = ChunkPos.asLong(region.minX(), region.minZ()); + + this.storageMap.remove(region); + this.regionTree.remove(base); + } + + @Override + public synchronized List regions() { + return new ArrayList<>(this.storageMap.keySet()); + } + + public synchronized LocalValueConfig config(BlockPos position) { + long chunkKey = ChunkPos.asLong(position.getX() >> 4, position.getZ() >> 4); + + LocalValueConfig local = this.configMap.computeIfAbsent(chunkKey, (key) -> { + LocalValueConfig config = new LocalValueConfig(new Expiry(MinecraftServer.currentTickLong, 600)); + + // defaults from sakura config + config.init(level); + + // search the entire map if we have to for a region + LocalRegion region = this.locate(position.getX(), position.getZ(), Integer.MAX_VALUE); + + if (region != null) { + // load local values + config.load(this.storageMap.get(region)); + } + + return config; + }); + + local.expiry().refresh(MinecraftServer.currentTickLong); + return local; + } + + public synchronized void expire(long tick) { + if (tick % 200 != 0) return; + + // remove expired + this.configMap.values().removeIf(obj -> obj.expiry().isExpired(tick)); + } + + private void ensureNotOverlapping(LocalRegion region) { + long top = ChunkPos.asLong(region.maxX(), region.maxZ()); + Long2ObjectSortedMap head = this.regionTree.headMap(top); + + for (LocalRegion present : head.values()) { + if (present != region && present.intersects(region)) { + throw new UnsupportedOperationException("overlapping regions"); + } + } + } + +} diff --git a/src/main/java/me/samsuik/sakura/local/config/LocalValueConfig.java b/src/main/java/me/samsuik/sakura/local/config/LocalValueConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..dd61d4e8811ffb1e8a842df1db6ac6ce5a8258c6 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/local/config/LocalValueConfig.java @@ -0,0 +1,68 @@ +package me.samsuik.sakura.local.config; + +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.LocalValueKey; +import me.samsuik.sakura.local.storage.LocalValueStorage; +import me.samsuik.sakura.physics.PhysicsVersion; +import me.samsuik.sakura.utils.objects.Expiry; +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 { + + private final Expiry expiry; + public Map durableMaterials; + public PhysicsVersion physicsVersion; + public RedstoneImplementation redstoneImplementation; + public boolean consistentRadius; + + LocalValueConfig(Expiry expiry) { + this.expiry = expiry; + } + + void init(Level level) { + // default materials + this.durableMaterials = new Reference2ObjectOpenHashMap<>(level.sakuraConfig().cannons.explosion.durableMaterials); + + // physics version + this.physicsVersion = level.sakuraConfig().cannons.mechanics.physicsVersion; + + // redstone implementation + this.redstoneImplementation = level.paperConfig().misc.redstoneImplementation; + + // consistent explosion radius + this.consistentRadius = level.sakuraConfig().cannons.explosion.consistentRadius; + } + + void load(LocalValueStorage storage) { + // local materials + storage.value(LocalValueKey.DURABLE_MATERIALS, true).forEach(((material, entry) -> { + this.durableMaterials.put(CraftMagicNumbers.getBlock(material), new DurableMaterial(entry.getKey(), entry.getValue())); + })); + + // physics version + if (storage.exists(LocalValueKey.PHYSICS_VERSION)) { + this.physicsVersion = storage.value(LocalValueKey.PHYSICS_VERSION); + } + + // redstone implementation + if (storage.exists(LocalValueKey.REDSTONE_IMPLEMENTATION)) { + this.redstoneImplementation = RedstoneImplementation.values()[storage.value(LocalValueKey.REDSTONE_IMPLEMENTATION).ordinal()]; + } + + // consistent explosion radius + if (storage.exists(LocalValueKey.CONSISTENT_EXPLOSION_RADIUS)) { + this.consistentRadius = storage.value(LocalValueKey.CONSISTENT_EXPLOSION_RADIUS); + } + } + + Expiry expiry() { + return expiry; + } + +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 91d3224cb30f62008a116fa4f913b06928d43c0a..6aacf5ff736bcbbb8582b6ae11e7dfc6ff265c08 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1802,6 +1802,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop