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..2b77b942b1733380c566683ba0516a74ee5c6389 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/local/config/LocalConfigManager.java @@ -0,0 +1,141 @@ +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 chunkConfigs = 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.chunkConfigs.computeIfAbsent(chunkKey, key -> { + LocalValueConfig config = new LocalValueConfig(new Expiry(MinecraftServer.currentTick, 600)); + + // defaults from sakura config + config.loadDefaults(this.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.loadFromStorage(this.storageMap.get(region)); + } + + return config; + }); + + local.expiry().refresh(MinecraftServer.currentTick); + return local; + } + + public synchronized void expire(int tick) { + if (tick % 200 != 0) return; + + // remove expired + this.chunkConfigs.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..b445166f38202ec250bcaf124b88746b3559a952 --- /dev/null +++ b/src/main/java/me/samsuik/sakura/local/config/LocalValueConfig.java @@ -0,0 +1,55 @@ +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.LocalValueKeys; +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 RedstoneImplementation redstoneImplementation; + public PhysicsVersion physicsVersion; + public boolean consistentRadius; + public boolean redstoneCache; + + LocalValueConfig(Expiry expiry) { + this.expiry = expiry; + } + + void loadDefaults(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); + } + + Expiry expiry() { + return this.expiry; + } +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index a39b00ebb37a0bf041bc7b9861fb7f9ebd962c40..1f9f023a43c16cc472deea5285b01c19136e019f 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1890,6 +1890,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop