Added experimental TranslocatingStorageAdaptor

This commit is contained in:
mcrcortex
2024-02-12 10:52:05 +10:00
parent 57cd448520
commit 0c866c8af6
17 changed files with 414 additions and 122 deletions

View File

@@ -2,6 +2,7 @@ package me.cortex.voxy.client;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.VoxelCore;
import me.cortex.voxy.client.saver.WorldSelectionSystem;
import me.cortex.voxy.client.terrain.WorldImportCommand;
import me.cortex.voxy.common.storage.config.Serialization;
import me.cortex.voxy.common.storage.other.CompressionStorageAdaptor;
@@ -14,27 +15,21 @@ import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.minecraft.client.world.ClientWorld;
import java.io.File;
public class Voxy implements ClientModInitializer {
@Override
public void onInitializeClient() {
//var cfg = new CompressionStorageAdaptor.Config();
//cfg.compressor = new ZSTDCompressor.Config();
//cfg.backend = new FragmentedStorageBackendAdaptor.Config();
//((FragmentedStorageBackendAdaptor.Config)cfg.backend).backends.add(new RocksDBStorageBackend.Config());
//System.out.println(Serialization.GSON.toJson(cfg));
Serialization.init();
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
dispatcher.register(WorldImportCommand.register());
});
}
private static final WorldSelectionSystem selector = new WorldSelectionSystem();
public static VoxelCore createVoxelCore(ClientWorld world) {
StorageBackend storage = new RocksDBStorageBackend(VoxyConfig.CONFIG.storagePath);
//StorageBackend storage = new FragmentedStorageBackendAdaptor(new File(VoxyConfig.CONFIG.storagePath));
storage = new CompressionStorageAdaptor(new ZSTDCompressor(VoxyConfig.CONFIG.savingCompressionLevel), storage);
var engine = new WorldEngine(storage, VoxyConfig.CONFIG.ingestThreads, VoxyConfig.CONFIG.savingThreads, 5);
return new VoxelCore(engine);
var selection = selector.getBestSelectionOrCreate(world);
return new VoxelCore(selection);
}
}

View File

@@ -87,7 +87,7 @@ public class DistanceTracker {
public void init(int x, int z) {
//Radius of chunks to enqueue
int SIZE = 64;
int SIZE = 128;
//Insert highest LOD level
for (int ox = -SIZE; ox <= SIZE; ox++) {
for (int oz = -SIZE; oz <= SIZE; oz++) {
@@ -265,8 +265,8 @@ public class DistanceTracker {
this.currentX = cx;
this.currentZ = cz;
this.lastUpdateX = x;
this.lastUpdateZ = z;
this.lastUpdateX = x + (((int)(Math.random()*4))<<(this.shiftSize-4));
this.lastUpdateZ = z + (((int)(Math.random()*4))<<(this.shiftSize-4));
}
}
}

View File

@@ -6,6 +6,7 @@ import me.cortex.voxy.client.core.rendering.*;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
import me.cortex.voxy.client.core.util.DebugUtil;
import me.cortex.voxy.client.saver.WorldSelectionSystem;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.client.importers.WorldImporter;
import net.minecraft.client.MinecraftClient;
@@ -46,8 +47,8 @@ public class VoxelCore {
//private final Thread shutdownThread = new Thread(this::shutdown);
public VoxelCore(WorldEngine engine) {
this.world = engine;
public VoxelCore(WorldSelectionSystem.Selection worldSelection) {
this.world = worldSelection.createEngine();
System.out.println("Initializing voxy core");
//Trigger the shared index buffer loading
@@ -106,6 +107,7 @@ public class VoxelCore {
}
private Matrix4f getProjectionMatrix() {
//TODO: use the existing projection matrix use mulLocal by the inverse of the projection and then mulLocal our projection
var projection = new Matrix4f();
var client = MinecraftClient.getInstance();
@@ -115,7 +117,7 @@ public class VoxelCore {
projection.setPerspective(fov * 0.01745329238474369f,
(float) client.getWindow().getFramebufferWidth() / (float)client.getWindow().getFramebufferHeight(),
64F, 16 * 3000f);
16F, 16 * 3000f);
var transform = new Matrix4f().identity();
transform.translate(gameRenderer.zoomX, -gameRenderer.zoomY, 0.0F);
transform.scale(gameRenderer.zoom, gameRenderer.zoom, 1.0F);

View File

@@ -1,5 +1,7 @@
package me.cortex.voxy.client.core.rendering;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.common.world.WorldEngine;
@@ -14,45 +16,77 @@ public class RenderTracker {
private final WorldEngine world;
private RenderGenerationService renderGen;
private final AbstractFarWorldRenderer renderer;
private final LongSet[] sets;
//private final Long2ObjectOpenHashMap<Object> activeSections = new Long2ObjectOpenHashMap<>();
private final ConcurrentHashMap<Long,Object> activeSections = new ConcurrentHashMap<>(50000,0.75f, 16);
private static final Object O = new Object();
public RenderTracker(WorldEngine world, AbstractFarWorldRenderer renderer) {
this.world = world;
this.renderer = renderer;
this.sets = new LongSet[1<<4];
for (int i = 0; i < this.sets.length; i++) {
this.sets[i] = new LongOpenHashSet();
}
}
public void setRenderGen(RenderGenerationService renderGen) {
this.renderGen = renderGen;
}
public RenderTracker(WorldEngine world, AbstractFarWorldRenderer renderer) {
this.world = world;
this.renderer = renderer;
public static long mixStafford13(long seed) {
seed = (seed ^ seed >>> 30) * -4658895280553007687L;
seed = (seed ^ seed >>> 27) * -7723592293110705685L;
return seed ^ seed >>> 31;
}
private LongSet getSet(long key) {
return this.sets[(int) (mixStafford13(key) & (this.sets.length-1))];
}
private void put(long key) {
var set = this.getSet(key);
synchronized (set) {
set.add(key);
}
}
private void remove(long key) {
var set = this.getSet(key);
synchronized (set) {
set.remove(key);
}
}
private boolean contains(long key) {
var set = this.getSet(key);
synchronized (set) {
return set.contains(key);
}
}
//Adds a lvl 0 section into the world renderer
public void addLvl0(int x, int y, int z) {
this.activeSections.put(WorldEngine.getWorldSectionId(0, x, y, z), O);
this.put(WorldEngine.getWorldSectionId(0, x, y, z));
this.renderGen.enqueueTask(0, x, y, z, this::shouldStillBuild);
}
//Removes a lvl 0 section from the world renderer
public void remLvl0(int x, int y, int z) {
this.activeSections.remove(WorldEngine.getWorldSectionId(0, x, y, z));
this.remove(WorldEngine.getWorldSectionId(0, x, y, z));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(0, x, y, z)));
this.renderGen.removeTask(0, x, y, z);
}
//Increases from lvl-1 to lvl at the coordinates (which are in lvl space)
public void inc(int lvl, int x, int y, int z) {
this.activeSections.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)));
this.activeSections.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1));
this.activeSections.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)));
this.activeSections.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1));
this.activeSections.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)));
this.activeSections.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1));
this.activeSections.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)));
this.activeSections.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1));
this.activeSections.put(WorldEngine.getWorldSectionId(lvl, x, y, z), O);
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)));
this.remove(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1));
this.put(WorldEngine.getWorldSectionId(lvl, x, y, z));
//TODO: make a seperate object to hold the build data and link it with the location in a
// concurrent hashmap or something, this is so that e.g. the build data position
@@ -82,15 +116,15 @@ public class RenderTracker {
//Decreases from lvl to lvl-1 at the coordinates (which are in lvl space)
public void dec(int lvl, int x, int y, int z) {
this.activeSections.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)), O);
this.activeSections.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1), O);
this.activeSections.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)), O);
this.activeSections.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1), O);
this.activeSections.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)), O);
this.activeSections.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1), O);
this.activeSections.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)), O);
this.activeSections.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1), O);
this.activeSections.remove(WorldEngine.getWorldSectionId(lvl, x, y, z));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1), (z<<1)+1));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1), (y<<1)+1, (z<<1)+1));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1), (z<<1)+1));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)));
this.put(WorldEngine.getWorldSectionId(lvl-1, (x<<1)+1, (y<<1)+1, (z<<1)+1));
this.remove(WorldEngine.getWorldSectionId(lvl, x, y, z));
this.renderer.enqueueResult(new BuiltSection(WorldEngine.getWorldSectionId(lvl, x, y, z)));
this.renderGen.removeTask(lvl, x, y, z);
@@ -120,7 +154,7 @@ public class RenderTracker {
//Called by the world engine when a section gets dirtied
public void sectionUpdated(WorldSection section) {
if (this.activeSections.containsKey(section.key)) {
if (this.contains(section.key)) {
//TODO:FIXME: if the section gets updated, that means that its neighbors might need to be updated aswell
// (due to block occlusion)
@@ -144,7 +178,7 @@ public class RenderTracker {
// it also batch collects the geometry sections until all the geometry for an operation is collected, then it executes the operation, its removes flickering
public void processBuildResult(BuiltSection section) {
//Check that we still want the section
if (this.activeSections.containsKey(section.position)) {
if (this.contains(section.position)) {
this.renderer.enqueueResult(section);
} else {
section.free();
@@ -152,6 +186,6 @@ public class RenderTracker {
}
public boolean shouldStillBuild(int lvl, int x, int y, int z) {
return this.activeSections.containsKey(WorldEngine.getWorldSectionId(lvl, x, y, z));
return this.contains(WorldEngine.getWorldSectionId(lvl, x, y, z));
}
}

View File

@@ -1,34 +0,0 @@
package me.cortex.voxy.client.saver;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.common.storage.other.CompressionStorageAdaptor;
import me.cortex.voxy.common.storage.other.FragmentedStorageBackendAdaptor;
import me.cortex.voxy.common.storage.compressors.ZSTDCompressor;
import me.cortex.voxy.common.world.WorldEngine;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
//Sets up a world engine with respect to the world the client is currently loaded into
// this is a bit tricky as each world has its own config, e.g. storage configuration
public class SaveSelectionSystem {
//The way this works is saves are segmented into base worlds, e.g. server ip, local save etc
// these are then segmented into subsaves for different worlds within the parent
public SaveSelectionSystem(List<Path> storagePaths) {
}
public WorldEngine createWorldEngine() {
//TODO: have basicly a recursive config tree for StorageBackend
// with a .build() method
// also have a way for the config to specify and create a config "screen"
// e.g. CompressionStorageAdaptorConfig(StorageCompressorConfig, StorageBackendConfig)
// FragmentedStorageBackendAdaptorConfig(File)
// RocksDBStorageBackendConfig(File)
// RedisStorageBackendConfig(String, int, String)
return null;
}
}

View File

@@ -0,0 +1,64 @@
package me.cortex.voxy.client.saver;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.common.storage.StorageBackend;
import me.cortex.voxy.common.storage.compressors.ZSTDCompressor;
import me.cortex.voxy.common.storage.config.ConfigBuildCtx;
import me.cortex.voxy.common.storage.other.CompressionStorageAdaptor;
import me.cortex.voxy.common.storage.other.TranslocatingStorageAdaptor;
import me.cortex.voxy.common.storage.rocksdb.RocksDBStorageBackend;
import me.cortex.voxy.common.world.WorldEngine;
import net.minecraft.client.world.ClientWorld;
import java.nio.file.Path;
import java.util.List;
//Sets up a world engine with respect to the world the client is currently loaded into
// this is a bit tricky as each world has its own config, e.g. storage configuration
public class WorldSelectionSystem {
public static class Selection {
public WorldEngine createEngine() {
var baseDB = new RocksDBStorageBackend.Config();
baseDB.path = VoxyConfig.CONFIG.storagePath;
var compressor = new ZSTDCompressor.Config();
compressor.compressionLevel = VoxyConfig.CONFIG.savingCompressionLevel;
var compression = new CompressionStorageAdaptor.Config();
compression.delegate = baseDB;
compression.compressor = compressor;
var translocator = new TranslocatingStorageAdaptor.Config();
translocator.delegate = compression;
translocator.transforms.add(new TranslocatingStorageAdaptor.BoxTransform(0,5,0, 200, 64, 200, 0, -5, 0));
var ctx = new ConfigBuildCtx();
var storage = translocator.build(ctx);
return new WorldEngine(storage, VoxyConfig.CONFIG.ingestThreads, VoxyConfig.CONFIG.savingThreads, 5);
//StorageBackend storage = new RocksDBStorageBackend(VoxyConfig.CONFIG.storagePath);
////StorageBackend storage = new FragmentedStorageBackendAdaptor(new File(VoxyConfig.CONFIG.storagePath));
//storage = new CompressionStorageAdaptor(new ZSTDCompressor(VoxyConfig.CONFIG.savingCompressionLevel), storage);
//return new WorldEngine(storage, VoxyConfig.CONFIG.ingestThreads, VoxyConfig.CONFIG.savingThreads, 5);
}
//Saves the config for the world selection or something, need to figure out how to make it work with dimensional configs maybe?
// or just have per world config, cause when creating the world engine doing the string substitution would
// make it automatically select the right id
public void save() {
}
}
//The way this works is saves are segmented into base worlds, e.g. server ip, local save etc
// these are then segmented into subsaves for different worlds within the parent
public WorldSelectionSystem() {
}
public Selection getBestSelectionOrCreate(ClientWorld world) {
return new Selection();
}
}

View File

@@ -3,6 +3,8 @@ package me.cortex.voxy.common.storage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public abstract class StorageBackend {
@@ -19,4 +21,17 @@ public abstract class StorageBackend {
public abstract void flush();
public abstract void close();
public List<StorageBackend> getChildBackends() {
return List.of();
}
public final List<StorageBackend> collectAllBackends() {
List<StorageBackend> backends = new ArrayList<>();
backends.add(this);
for (var child : this.getChildBackends()) {
backends.addAll(child.collectAllBackends());
}
return backends;
}
}

View File

@@ -3,27 +3,71 @@ package me.cortex.voxy.common.storage.config;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Stack;
public class ConfigBuildCtx {
//List of tokens
public static final String BASE_LEVEL_PATH = "{base_level_path}";
//Pushes a path to the BuildCtx path stack so that when resolving with resolvePath it uses the entire path stack
private final Stack<String> pathStack = new Stack<>();
/**
* Pushes a path to the build context so that when resolvePath is called it is with respect to the added path
* @param path the path to add to the stack
* @return the build context
*/
public ConfigBuildCtx pushPath(String path) {
this.pathStack.push(path);
return this;
}
/**
* Pops a path from the build context path stack
* @return the build context
*/
public ConfigBuildCtx popPath() {
this.pathStack.pop();
return this;
}
//TODO: FINISH THIS and check and test
private static String concatPath(String a, String b) {
if (b.contains("..")) {
throw new IllegalStateException("Relative resolving not supported");
}
if ((!a.isBlank()) && !a.endsWith("/")) {
a += "/";
}
if (b.startsWith("/")) {//Absolute path
return b;
}
if (b.startsWith("./")) {
b = b.substring(2);
}
if (b.startsWith(":", 1)) {//Drive path
return b;
}
return a+b;
}
/**
* Resolves a path with the current build context path
* @param other path to resolve against
* @return resolved path
*/
public String resolvePath(String other) {
return null;
this.pathStack.push(other);
String path = "";
for (var part : this.pathStack) {
path = concatPath(path, part);
}
this.pathStack.pop();
return path;
}
/**
@@ -32,8 +76,8 @@ public class ConfigBuildCtx {
* @return substituted string
*/
public String substituteString(String string) {
//This is e.g. so you can have dbs spread across multiple disks if you want
return null;
//TODO: this
return string;
}
/**

View File

@@ -98,4 +98,6 @@ public class Serialization {
}
}).collect(Collectors.toList());
}
public static void init() {}
}

View File

@@ -2,10 +2,26 @@ package me.cortex.voxy.common.storage.config;
import me.cortex.voxy.common.storage.StorageBackend;
import java.util.ArrayList;
import java.util.List;
public abstract class StorageConfig {
static {
Serialization.CONFIG_TYPES.add(StorageConfig.class);
}
public abstract StorageBackend build(ConfigBuildCtx ctx);
public List<StorageConfig> getChildStorageConfigs() {
return List.of();
}
public final List<StorageConfig> collectStorageConfigs() {
List<StorageConfig> configs = new ArrayList<>();
configs.add(this);
for (var child : this.getChildStorageConfigs()) {
configs.addAll(child.collectStorageConfigs());
}
return configs;
}
}

View File

@@ -9,19 +9,19 @@ import me.cortex.voxy.common.storage.config.StorageConfig;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.List;
//Compresses the section data
public class CompressionStorageAdaptor extends StorageBackend {
public class CompressionStorageAdaptor extends DelegatingStorageAdaptor {
private final StorageCompressor compressor;
private final StorageBackend child;
public CompressionStorageAdaptor(StorageCompressor compressor, StorageBackend child) {
public CompressionStorageAdaptor(StorageCompressor compressor, StorageBackend delegate) {
super(delegate);
this.compressor = compressor;
this.child = child;
}
@Override
public ByteBuffer getSectionData(long key) {
var data = this.child.getSectionData(key);
var data = this.delegate.getSectionData(key);
if (data == null) {
return null;
}
@@ -33,43 +33,28 @@ public class CompressionStorageAdaptor extends StorageBackend {
@Override
public void setSectionData(long key, ByteBuffer data) {
var cdata = this.compressor.compress(data);
this.child.setSectionData(key, cdata);
this.delegate.setSectionData(key, cdata);
MemoryUtil.memFree(cdata);
}
@Override
public void deleteSectionData(long key) {
this.child.deleteSectionData(key);
}
@Override
public void putIdMapping(int id, ByteBuffer data) {
this.child.putIdMapping(id, data);
}
@Override
public Int2ObjectOpenHashMap<byte[]> getIdMappingsData() {
return this.child.getIdMappingsData();
}
@Override
public void flush() {
this.child.flush();
}
@Override
public void close() {
this.compressor.close();
this.child.close();
super.close();
}
public static class Config extends StorageConfig {
public CompressorConfig compressor;
public StorageConfig backend;
public StorageConfig delegate;
@Override
public StorageBackend build(ConfigBuildCtx ctx) {
return new CompressionStorageAdaptor(this.compressor.build(ctx), this.backend.build(ctx));
return new CompressionStorageAdaptor(this.compressor.build(ctx), this.delegate.build(ctx));
}
@Override
public List<StorageConfig> getChildStorageConfigs() {
return List.of(this.delegate);
}
public static String getConfigTypeName() {

View File

@@ -0,0 +1,59 @@
package me.cortex.voxy.common.storage.other;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import me.cortex.voxy.common.storage.StorageBackend;
import me.cortex.voxy.common.storage.StorageCompressor;
import me.cortex.voxy.common.storage.config.CompressorConfig;
import me.cortex.voxy.common.storage.config.ConfigBuildCtx;
import me.cortex.voxy.common.storage.config.StorageConfig;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.List;
public class DelegatingStorageAdaptor extends StorageBackend {
protected final StorageBackend delegate;
public DelegatingStorageAdaptor(StorageBackend delegate) {
this.delegate = delegate;
}
@Override
public ByteBuffer getSectionData(long key) {
return this.delegate.getSectionData(key);
}
@Override
public void setSectionData(long key, ByteBuffer data) {
this.delegate.setSectionData(key, data);
}
@Override
public void deleteSectionData(long key) {
this.delegate.deleteSectionData(key);
}
@Override
public void putIdMapping(int id, ByteBuffer data) {
this.delegate.putIdMapping(id, data);
}
@Override
public Int2ObjectOpenHashMap<byte[]> getIdMappingsData() {
return this.delegate.getIdMappingsData();
}
@Override
public void flush() {
this.delegate.flush();
}
@Override
public void close() {
this.delegate.close();
}
@Override
public List<StorageBackend> getChildBackends() {
return List.of(this.delegate);
}
}

View File

@@ -131,9 +131,19 @@ public class FragmentedStorageBackendAdaptor extends StorageBackend {
}
}
@Override
public List<StorageBackend> getChildBackends() {
return List.of(this.backends);
}
public static class Config extends StorageConfig {
public List<StorageConfig> backends = new ArrayList<>();
@Override
public List<StorageConfig> getChildStorageConfigs() {
return new ArrayList<>(this.backends);
}
@Override
public StorageBackend build(ConfigBuildCtx ctx) {
StorageBackend[] builtBackends = new StorageBackend[this.backends.size()];

View File

@@ -0,0 +1,85 @@
package me.cortex.voxy.common.storage.other;
import me.cortex.voxy.common.storage.StorageBackend;
import me.cortex.voxy.common.storage.config.ConfigBuildCtx;
import me.cortex.voxy.common.storage.config.StorageConfig;
import me.cortex.voxy.common.world.WorldEngine;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class TranslocatingStorageAdaptor extends DelegatingStorageAdaptor {
public record BoxTransform(int x1, int y1, int z1, int x2, int y2, int z2, int dx, int dy, int dz) {
public long transformIfInBox(long pos) {
int lvl = WorldEngine.getLevel(pos);
int x = WorldEngine.getX(pos);
int y = WorldEngine.getY(pos);
int z = WorldEngine.getZ(pos);
//TODO: FIXME this might need to be the other way around, as in shift x,y,z instead of x1 etc
if (!((this.x1>>lvl) <= x && x <= (this.x2>>lvl) &&
(this.y1>>lvl) <= y && y <= (this.y2>>lvl) &&
(this.z1>>lvl) <= z && z <= (this.z2>>lvl))) {
return -1;
}
return WorldEngine.getWorldSectionId(lvl,
x + (this.dx>>lvl),
y + (this.dy>>lvl),
z + (this.dz>>lvl)
);
}
}
private final BoxTransform[] transforms;
public TranslocatingStorageAdaptor(StorageBackend delegate, BoxTransform... transforms) {
super(delegate);
this.transforms = transforms;
}
private long transformPosition(long pos) {
for (var transform : this.transforms) {
long tpos = transform.transformIfInBox(pos);
if (tpos != -1) {
return tpos;
}
}
return pos;
}
@Override
public ByteBuffer getSectionData(long key) {
return super.getSectionData(this.transformPosition(key));
}
@Override
public void setSectionData(long key, ByteBuffer data) {
super.setSectionData(this.transformPosition(key), data);
}
@Override
public void deleteSectionData(long key) {
super.deleteSectionData(this.transformPosition(key));
}
public static class Config extends StorageConfig {
public StorageConfig delegate;
public List<BoxTransform> transforms = new ArrayList<>();
@Override
public StorageBackend build(ConfigBuildCtx ctx) {
return new TranslocatingStorageAdaptor(this.delegate.build(ctx), this.transforms.toArray(BoxTransform[]::new));
}
@Override
public List<StorageConfig> getChildStorageConfigs() {
return List.of(this.delegate);
}
public static String getConfigTypeName() {
return "TranslocatingAdaptor";
}
}
}

View File

@@ -66,11 +66,11 @@ public class SaveLoadSystem {
hash ^= lut[i];
}
if (section.key != key) {
//throw new IllegalStateException("Decompressed section not the same as requested. got: " + key + " expected: " + section.key);
System.err.println("Decompressed section not the same as requested. got: " + key + " expected: " + section.key);
return false;
}
//if (section.key != key) {
// //throw new IllegalStateException("Decompressed section not the same as requested. got: " + key + " expected: " + section.key);
// System.err.println("Decompressed section not the same as requested. got: " + key + " expected: " + section.key);
// return false;
//}
for (int i = 0; i < section.data.length; i++) {
short lutId = data.getShort();

View File

@@ -49,8 +49,6 @@ public class WorldEngine {
//TODO: regenerate the section from children
Arrays.fill(into.data, Mapper.AIR);
System.err.println("Section " + into.lvl + ", " + into.x + ", " + into.y + ", " + into.z + " was unable to load, removing");
this.storage.deleteSectionData(into.key);
return -1;
} else {
return 0;
@@ -80,6 +78,23 @@ public class WorldEngine {
return ((long)lvl<<60)|((long)(y&0xFF)<<52)|((long)(z&((1<<24)-1))<<28)|((long)(x&((1<<24)-1))<<4);//NOTE: 4 bits spare for whatever
}
public static int getLevel(long id) {
return (int) ((id>>60)&0xf);
}
//TODO: check these shifts are correct for all the gets
public static int getX(long id) {
return (int) ((id<<36)>>40);
}
public static int getY(long id) {
return (int) ((id<<4)>>56);
}
public static int getZ(long id) {
return (int) ((id<<12)>>40);
}
//Marks a section as dirty, enqueuing it for saving and or render data rebuilding
public void markDirty(WorldSection section) {
if (this.dirtyCallback != null) {

View File

@@ -36,7 +36,7 @@ public class Mipper {
if (!Mapper.isAir(I000)) {
return I000;
}
//TODO: need to account for different light levels of "air"
int blockLight = (Mapper.getLightId(I000)&0xF0)+(Mapper.getLightId(I001)&0xF0)+(Mapper.getLightId(I010)&0xF0)+(Mapper.getLightId(I011)&0xF0)+
(Mapper.getLightId(I100)&0xF0)+(Mapper.getLightId(I101)&0xF0)+(Mapper.getLightId(I110)&0xF0)+(Mapper.getLightId(I111)&0xF0);
int skyLight = (Mapper.getLightId(I000)&0x0F)+(Mapper.getLightId(I001)&0x0F)+(Mapper.getLightId(I010)&0x0F)+(Mapper.getLightId(I011)&0x0F)+