Skip to content

Instantly share code, notes, and snippets.

@IllusionTheDev
Created February 6, 2024 23:47
Show Gist options
  • Save IllusionTheDev/7c3a524d75677a5523f38a176a710dcc to your computer and use it in GitHub Desktop.
Save IllusionTheDev/7c3a524d75677a5523f38a176a710dcc to your computer and use it in GitHub Desktop.
World storage example
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public interface BlockStorage<T extends WorldBlock> extends Storage {
CompletableFuture<List<T>> fetchChunkData(UUID worldId, Long chunkId);
CompletableFuture<Void> insertData(UUID worldId, Long chunkId, T data);
CompletableFuture<Void> deleteChunkData(UUID worldId, Long chunkId);
CompletableFuture<Void> deleteChunkData(UUID worldId, Long chunkId, T data);
CompletableFuture<Void> deleteChunkData(UUID worldId);
}
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
public class BlockStorageListener implements Listener {
private final WorldBlockStorage<?> blockStorage;
public BlockStorageListener(WorldBlockStorage<?> blockStorage) {
this.blockStorage = blockStorage;
}
@EventHandler(priority = EventPriority.MONITOR)
private void onBreak(BlockBreakEvent event) {
blockStorage.removeData(event.getBlock().getLocation());
}
@EventHandler
private void onLoad(ChunkLoadEvent event) {
blockStorage.loadChunk(event.getChunk());
}
@EventHandler
private void onUnload(ChunkUnloadEvent event) {
blockStorage.unloadChunk(event.getChunk());
}
@EventHandler
private void onWorldUnload(WorldUnloadEvent event) {
blockStorage.unloadWorld(event.getWorld().getUID());
}
}
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class ChunkDataContainer<T> {
private final Map<Integer, T> storage = new ConcurrentHashMap<>();
public T getAt(int x, int y, int z) {
return storage.get(getBlockKey(x, y, z));
}
public void setAt(int x, int y, int z, T data) {
storage.put(getBlockKey(x, y, z), data);
}
public T removeAt(int x, int y, int z) {
return storage.remove(getBlockKey(x, y, z));
}
public static int getBlockKey(int x, int y, int z) {
// x and z are 4 bits
// y is 8 bits
return ((x & 0xf) << 12) | ((y & 0xff) << 4) | (z & 0xf);
}
public Collection<T> getAll() {
return Set.copyOf(storage.values());
}
}
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Location;
import org.bukkit.util.BoundingBox;
public class ChunkDataStorage<T extends WorldBlock> {
private final Map<Long, ChunkDataContainer<T>> storage = new ConcurrentHashMap<>();
public ChunkDataContainer<T> getAt(Location location) {
return getAt(location.getChunk().getX(), location.getChunk().getZ());
}
public ChunkDataContainer<T> getAt(int x, int z) {
return storage.computeIfAbsent(getChunkKey(x, z), key -> new ChunkDataContainer<>());
}
public ChunkDataContainer<T> getAt(long chunkKey) {
return storage.computeIfAbsent(chunkKey, key -> new ChunkDataContainer<>());
}
public void unloadChunk(int x, int z) {
storage.remove(getChunkKey(x, z));
}
public void unloadChunk(Location location) {
unloadChunk(location.getChunk().getX(), location.getChunk().getZ());
}
public void unloadChunk(long chunkKey) {
storage.remove(chunkKey);
}
public Collection<T> getWithin(BoundingBox box) {
return getWithin(box.getMinX(), box.getMinY(), box.getMinZ(), box.getMaxX(), box.getMaxY(), box.getMaxZ());
}
public Collection<T> getWithin(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
return getWithin((int) minX, (int) minY, (int) minZ, (int) maxX, (int) maxY, (int) maxZ);
}
public Collection<T> getWithin(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
int minChunkX = minX >> 4;
int minChunkZ = minZ >> 4;
int maxChunkX = maxX >> 4;
int maxChunkZ = maxZ >> 4;
Set<T> data = ConcurrentHashMap.newKeySet();
for (int x = minChunkX; x <= maxChunkX; x++) {
for (int z = minChunkZ; z <= maxChunkZ; z++) {
ChunkDataContainer<T> chunkDataContainer = getAt(x, z);
if (chunkDataContainer == null) {
continue;
}
for (T block : chunkDataContainer.getAll()) {
Location location = block.getLocation().toCenterLocation();
if (location.getX() >= minX && location.getX() <= maxX
&& location.getY() >= minY && location.getY() <= maxY
&& location.getZ() >= minZ && location.getZ() <= maxZ) {
data.add(block);
}
}
}
}
return data;
}
public static long getChunkKey(int x, int z) {
return ((long) x << 32) | (z & 0xffffffffL);
}
}
import org.bukkit.Location;
public interface WorldBlock {
Location getLocation();
default int getX() {
return getLocation().getBlockX();
}
default int getY() {
return getLocation().getBlockY();
}
default int getZ() {
return getLocation().getBlockZ();
}
}
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.plugin.java.JavaPlugin;
public class WorldBlockStorage<T extends WorldBlock> {
private final Map<UUID, ChunkDataStorage<T>> storage = new ConcurrentHashMap<>();
private final BlockStorage<T> blockStorage;
public WorldBlockStorage(JavaPlugin plugin, BlockStorage<T> blockStorage) {
this.blockStorage = blockStorage;
Bukkit.getPluginManager().registerEvents(new BlockStorageListener(this), plugin);
this.loadAll();
}
public void loadAll() {
for(World world : Bukkit.getWorlds()) {
for (Chunk chunk : world.getLoadedChunks()) {
loadChunk(chunk);
}
}
}
public ChunkDataStorage<T> getChunkDataStorage(UUID worldId) {
return storage.computeIfAbsent(worldId, (key) -> new ChunkDataStorage<>());
}
public ChunkDataStorage<T> getChunkDataStorage(World world) {
return getChunkDataStorage(world.getUID());
}
private ChunkDataStorage<T> getChunkDataStorage(Location location) {
World world = location.getWorld();
if (world == null) {
return null;
}
return getChunkDataStorage(world);
}
private ChunkDataContainer<T> getChunkDataContainer(Location location) {
ChunkDataStorage<T> chunkDataStorage = getChunkDataStorage(location);
if (chunkDataStorage == null) {
return null;
}
return chunkDataStorage.getAt(location);
}
public T getData(Location location) {
ChunkDataContainer<T> chunkDataContainer = getChunkDataContainer(location);
if (chunkDataContainer == null) {
return null;
}
return chunkDataContainer.getAt(location.getBlockX(), location.getBlockY(), location.getBlockZ());
}
public void setData(T data) {
Location location = data.getLocation();
ChunkDataContainer<T> chunkDataContainer = getChunkDataContainer(location);
if (chunkDataContainer == null) {
return;
}
chunkDataContainer.setAt(location.getBlockX(), location.getBlockY(), location.getBlockZ(), data);
UUID worldId = location.getWorld().getUID();
Long chunkId = ChunkDataStorage.getChunkKey(location.getChunk().getX(), location.getChunk().getZ());
blockStorage.insertData(worldId, chunkId, data);
}
public void removeData(Location location) {
ChunkDataContainer<T> chunkDataContainer = getChunkDataContainer(location);
if (chunkDataContainer == null) {
return;
}
T removed = chunkDataContainer.removeAt(location.getBlockX(), location.getBlockY(), location.getBlockZ());
if (removed == null) {
return;
}
UUID worldId = location.getWorld().getUID();
Long chunkId = ChunkDataStorage.getChunkKey(location.getChunk().getX(), location.getChunk().getZ());
blockStorage.deleteChunkData(worldId, chunkId);
}
public void unloadWorld(UUID worldId) {
storage.remove(worldId);
}
public void loadChunk(Chunk chunk) {
UUID worldId = chunk.getWorld().getUID();
Long chunkId = ChunkDataStorage.getChunkKey(chunk.getX(), chunk.getZ());
if (blockStorage == null) {
return;
}
blockStorage.fetchChunkData(worldId, chunkId).thenAccept((data) -> {
if (data.isEmpty()) {
return;
}
ChunkDataStorage<T> chunkDataStorage = getChunkDataStorage(worldId);
ChunkDataContainer<T> chunkDataContainer = chunkDataStorage.getAt(chunk.getX(), chunk.getZ());
for (T block : data) {
chunkDataContainer.setAt(block.getX(), block.getY(), block.getZ(), block);
}
});
}
public void unloadChunk(Chunk chunk) {
UUID worldId = chunk.getWorld().getUID();
long chunkId = ChunkDataStorage.getChunkKey(chunk.getX(), chunk.getZ());
getChunkDataStorage(worldId).unloadChunk(chunkId);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment