diff --git a/src/main/java/me/cortex/voxy/client/Voxy.java b/src/main/java/me/cortex/voxy/client/Voxy.java index 01cc453f..0eaa912c 100644 --- a/src/main/java/me/cortex/voxy/client/Voxy.java +++ b/src/main/java/me/cortex/voxy/client/Voxy.java @@ -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); } } diff --git a/src/main/java/me/cortex/voxy/client/core/DistanceTracker.java b/src/main/java/me/cortex/voxy/client/core/DistanceTracker.java index 073eade7..7b4007b5 100644 --- a/src/main/java/me/cortex/voxy/client/core/DistanceTracker.java +++ b/src/main/java/me/cortex/voxy/client/core/DistanceTracker.java @@ -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)); } } } diff --git a/src/main/java/me/cortex/voxy/client/core/VoxelCore.java b/src/main/java/me/cortex/voxy/client/core/VoxelCore.java index 6efbae99..8a4a6cd0 100644 --- a/src/main/java/me/cortex/voxy/client/core/VoxelCore.java +++ b/src/main/java/me/cortex/voxy/client/core/VoxelCore.java @@ -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); diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/RenderTracker.java b/src/main/java/me/cortex/voxy/client/core/rendering/RenderTracker.java index 869055cf..5d36d715 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/RenderTracker.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/RenderTracker.java @@ -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 activeSections = new Long2ObjectOpenHashMap<>(); - private final ConcurrentHashMap 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)); } } diff --git a/src/main/java/me/cortex/voxy/client/saver/SaveSelectionSystem.java b/src/main/java/me/cortex/voxy/client/saver/SaveSelectionSystem.java deleted file mode 100644 index 24d1eff1..00000000 --- a/src/main/java/me/cortex/voxy/client/saver/SaveSelectionSystem.java +++ /dev/null @@ -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 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; - } -} diff --git a/src/main/java/me/cortex/voxy/client/saver/WorldSelectionSystem.java b/src/main/java/me/cortex/voxy/client/saver/WorldSelectionSystem.java new file mode 100644 index 00000000..4988c51f --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/saver/WorldSelectionSystem.java @@ -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(); + } +} diff --git a/src/main/java/me/cortex/voxy/common/storage/StorageBackend.java b/src/main/java/me/cortex/voxy/common/storage/StorageBackend.java index 936514f5..235b7302 100644 --- a/src/main/java/me/cortex/voxy/common/storage/StorageBackend.java +++ b/src/main/java/me/cortex/voxy/common/storage/StorageBackend.java @@ -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 getChildBackends() { + return List.of(); + } + + public final List collectAllBackends() { + List backends = new ArrayList<>(); + backends.add(this); + for (var child : this.getChildBackends()) { + backends.addAll(child.collectAllBackends()); + } + return backends; + } } diff --git a/src/main/java/me/cortex/voxy/common/storage/config/ConfigBuildCtx.java b/src/main/java/me/cortex/voxy/common/storage/config/ConfigBuildCtx.java index 5c1d75c4..bf35f9ff 100644 --- a/src/main/java/me/cortex/voxy/common/storage/config/ConfigBuildCtx.java +++ b/src/main/java/me/cortex/voxy/common/storage/config/ConfigBuildCtx.java @@ -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 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; } /** diff --git a/src/main/java/me/cortex/voxy/common/storage/config/Serialization.java b/src/main/java/me/cortex/voxy/common/storage/config/Serialization.java index 7d8a552e..38b61aa0 100644 --- a/src/main/java/me/cortex/voxy/common/storage/config/Serialization.java +++ b/src/main/java/me/cortex/voxy/common/storage/config/Serialization.java @@ -98,4 +98,6 @@ public class Serialization { } }).collect(Collectors.toList()); } + + public static void init() {} } diff --git a/src/main/java/me/cortex/voxy/common/storage/config/StorageConfig.java b/src/main/java/me/cortex/voxy/common/storage/config/StorageConfig.java index 1160638c..48371a76 100644 --- a/src/main/java/me/cortex/voxy/common/storage/config/StorageConfig.java +++ b/src/main/java/me/cortex/voxy/common/storage/config/StorageConfig.java @@ -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 getChildStorageConfigs() { + return List.of(); + } + + public final List collectStorageConfigs() { + List configs = new ArrayList<>(); + configs.add(this); + for (var child : this.getChildStorageConfigs()) { + configs.addAll(child.collectStorageConfigs()); + } + return configs; + } } diff --git a/src/main/java/me/cortex/voxy/common/storage/other/CompressionStorageAdaptor.java b/src/main/java/me/cortex/voxy/common/storage/other/CompressionStorageAdaptor.java index 8de682dc..3b0a2736 100644 --- a/src/main/java/me/cortex/voxy/common/storage/other/CompressionStorageAdaptor.java +++ b/src/main/java/me/cortex/voxy/common/storage/other/CompressionStorageAdaptor.java @@ -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 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 getChildStorageConfigs() { + return List.of(this.delegate); } public static String getConfigTypeName() { diff --git a/src/main/java/me/cortex/voxy/common/storage/other/DelegatingStorageAdaptor.java b/src/main/java/me/cortex/voxy/common/storage/other/DelegatingStorageAdaptor.java new file mode 100644 index 00000000..43ac97c5 --- /dev/null +++ b/src/main/java/me/cortex/voxy/common/storage/other/DelegatingStorageAdaptor.java @@ -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 getIdMappingsData() { + return this.delegate.getIdMappingsData(); + } + + @Override + public void flush() { + this.delegate.flush(); + } + + @Override + public void close() { + this.delegate.close(); + } + + @Override + public List getChildBackends() { + return List.of(this.delegate); + } +} diff --git a/src/main/java/me/cortex/voxy/common/storage/other/FragmentedStorageBackendAdaptor.java b/src/main/java/me/cortex/voxy/common/storage/other/FragmentedStorageBackendAdaptor.java index a5b56d5f..e22553bf 100644 --- a/src/main/java/me/cortex/voxy/common/storage/other/FragmentedStorageBackendAdaptor.java +++ b/src/main/java/me/cortex/voxy/common/storage/other/FragmentedStorageBackendAdaptor.java @@ -131,9 +131,19 @@ public class FragmentedStorageBackendAdaptor extends StorageBackend { } } + @Override + public List getChildBackends() { + return List.of(this.backends); + } + public static class Config extends StorageConfig { public List backends = new ArrayList<>(); + @Override + public List getChildStorageConfigs() { + return new ArrayList<>(this.backends); + } + @Override public StorageBackend build(ConfigBuildCtx ctx) { StorageBackend[] builtBackends = new StorageBackend[this.backends.size()]; diff --git a/src/main/java/me/cortex/voxy/common/storage/other/TranslocatingStorageAdaptor.java b/src/main/java/me/cortex/voxy/common/storage/other/TranslocatingStorageAdaptor.java new file mode 100644 index 00000000..1cfb83dd --- /dev/null +++ b/src/main/java/me/cortex/voxy/common/storage/other/TranslocatingStorageAdaptor.java @@ -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 transforms = new ArrayList<>(); + + + @Override + public StorageBackend build(ConfigBuildCtx ctx) { + return new TranslocatingStorageAdaptor(this.delegate.build(ctx), this.transforms.toArray(BoxTransform[]::new)); + } + + @Override + public List getChildStorageConfigs() { + return List.of(this.delegate); + } + + public static String getConfigTypeName() { + return "TranslocatingAdaptor"; + } + } +} diff --git a/src/main/java/me/cortex/voxy/common/world/SaveLoadSystem.java b/src/main/java/me/cortex/voxy/common/world/SaveLoadSystem.java index 95b7c1e0..af812bcf 100644 --- a/src/main/java/me/cortex/voxy/common/world/SaveLoadSystem.java +++ b/src/main/java/me/cortex/voxy/common/world/SaveLoadSystem.java @@ -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(); diff --git a/src/main/java/me/cortex/voxy/common/world/WorldEngine.java b/src/main/java/me/cortex/voxy/common/world/WorldEngine.java index 061e3e42..e0a04418 100644 --- a/src/main/java/me/cortex/voxy/common/world/WorldEngine.java +++ b/src/main/java/me/cortex/voxy/common/world/WorldEngine.java @@ -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) { diff --git a/src/main/java/me/cortex/voxy/common/world/other/Mipper.java b/src/main/java/me/cortex/voxy/common/world/other/Mipper.java index 9e1da49c..1bbae44c 100644 --- a/src/main/java/me/cortex/voxy/common/world/other/Mipper.java +++ b/src/main/java/me/cortex/voxy/common/world/other/Mipper.java @@ -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)+