From 4392a39783a63566c288ed576578b83db93f5359 Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Mon, 12 Feb 2024 15:07:23 +1000 Subject: [PATCH] Added basic world seperation --- build.gradle | 2 + src/main/java/me/cortex/voxy/client/Voxy.java | 11 +- .../config/VoxyConfigScreenFactory.java | 18 +- .../config/screens/StorageConfigScreen.java | 9 + .../me/cortex/voxy/client/core/VoxelCore.java | 4 +- .../client/core/rendering/RenderTracker.java | 10 +- .../client/saver/ContextSelectionSystem.java | 168 ++++++++++++++++++ .../client/saver/WorldSelectionSystem.java | 70 -------- .../common/storage/config/ConfigBuildCtx.java | 22 ++- .../storage/lmdb/LMDBStorageBackend.java | 4 +- .../rocksdb/RocksDBStorageBackend.java | 4 +- src/main/resources/voxy.accesswidener | 1 + 12 files changed, 221 insertions(+), 102 deletions(-) create mode 100644 src/main/java/me/cortex/voxy/client/config/screens/StorageConfigScreen.java create mode 100644 src/main/java/me/cortex/voxy/client/saver/ContextSelectionSystem.java delete mode 100644 src/main/java/me/cortex/voxy/client/saver/WorldSelectionSystem.java diff --git a/build.gradle b/build.gradle index 3c25ff10..a849c6a0 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,8 @@ repositories { includeGroup "maven.modrinth" } } + maven { url "https://maven.shedaniel.me/" } + maven { url "https://maven.terraformersmc.com/releases/" } } processResources { diff --git a/src/main/java/me/cortex/voxy/client/Voxy.java b/src/main/java/me/cortex/voxy/client/Voxy.java index 0eaa912c..9680dcde 100644 --- a/src/main/java/me/cortex/voxy/client/Voxy.java +++ b/src/main/java/me/cortex/voxy/client/Voxy.java @@ -1,16 +1,9 @@ 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.saver.ContextSelectionSystem; import me.cortex.voxy.client.terrain.WorldImportCommand; import me.cortex.voxy.common.storage.config.Serialization; -import me.cortex.voxy.common.storage.other.CompressionStorageAdaptor; -import me.cortex.voxy.common.storage.StorageBackend; -import me.cortex.voxy.common.storage.compressors.ZSTDCompressor; -import me.cortex.voxy.common.storage.other.FragmentedStorageBackendAdaptor; -import me.cortex.voxy.common.storage.rocksdb.RocksDBStorageBackend; -import me.cortex.voxy.common.world.WorldEngine; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.minecraft.client.world.ClientWorld; @@ -26,7 +19,7 @@ public class Voxy implements ClientModInitializer { } - private static final WorldSelectionSystem selector = new WorldSelectionSystem(); + private static final ContextSelectionSystem selector = new ContextSelectionSystem(); public static VoxelCore createVoxelCore(ClientWorld world) { var selection = selector.getBestSelectionOrCreate(world); diff --git a/src/main/java/me/cortex/voxy/client/config/VoxyConfigScreenFactory.java b/src/main/java/me/cortex/voxy/client/config/VoxyConfigScreenFactory.java index aef588ce..42ca0453 100644 --- a/src/main/java/me/cortex/voxy/client/config/VoxyConfigScreenFactory.java +++ b/src/main/java/me/cortex/voxy/client/config/VoxyConfigScreenFactory.java @@ -3,6 +3,7 @@ package me.cortex.voxy.client.config; import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; import me.cortex.voxy.client.core.IGetVoxelCore; +import me.shedaniel.clothconfig2.ClothConfigDemo; import me.shedaniel.clothconfig2.api.ConfigBuilder; import me.shedaniel.clothconfig2.api.ConfigCategory; import me.shedaniel.clothconfig2.api.ConfigEntryBuilder; @@ -10,6 +11,8 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import net.minecraft.text.Text; +import java.util.List; + public class VoxyConfigScreenFactory implements ModMenuApi { private static final VoxyConfig DEFAULT = new VoxyConfig(); @Override @@ -36,13 +39,26 @@ public class VoxyConfigScreenFactory implements ModMenuApi { VoxyConfig.CONFIG.save(); }); - return builder.build(); + return ClothConfigDemo.getConfigBuilderWithDemo().build(); } private static void addGeneralCategory(ConfigBuilder builder, VoxyConfig config) { + + ConfigCategory category = builder.getOrCreateCategory(Text.translatable("voxy.config.general")); ConfigEntryBuilder entryBuilder = builder.entryBuilder(); + category.addEntry(entryBuilder.startSubCategory(Text.translatable("aaa"), List.of(entryBuilder.startBooleanToggle(Text.translatable("voxy.config.general.enabled"), config.enabled) + .setTooltip(Text.translatable("voxy.config.general.enabled.tooltip")) + .setSaveConsumer(val -> config.enabled = val) + .setDefaultValue(DEFAULT.enabled) + .build(), entryBuilder.startSubCategory(Text.translatable("bbb"), List.of(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.geometryBuffer"), config.geometryBufferSize, (1<<27)/8, ((1<<31)-1)/8) + .setTooltip(Text.translatable("voxy.config.general.geometryBuffer.tooltip")) + .setSaveConsumer(val -> config.geometryBufferSize = val) + .setDefaultValue(DEFAULT.geometryBufferSize) + .build())).build() + )).build()); + category.addEntry(entryBuilder.startBooleanToggle(Text.translatable("voxy.config.general.enabled"), config.enabled) .setTooltip(Text.translatable("voxy.config.general.enabled.tooltip")) diff --git a/src/main/java/me/cortex/voxy/client/config/screens/StorageConfigScreen.java b/src/main/java/me/cortex/voxy/client/config/screens/StorageConfigScreen.java new file mode 100644 index 00000000..808d27b1 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/config/screens/StorageConfigScreen.java @@ -0,0 +1,9 @@ +package me.cortex.voxy.client.config.screens; + +import me.shedaniel.clothconfig2.api.AbstractConfigListEntry; + +public class StorageConfigScreen { + public static AbstractConfigListEntry makeScreen() { + return null; + } +} 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 8a4a6cd0..373b2f62 100644 --- a/src/main/java/me/cortex/voxy/client/core/VoxelCore.java +++ b/src/main/java/me/cortex/voxy/client/core/VoxelCore.java @@ -6,7 +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.client.saver.ContextSelectionSystem; import me.cortex.voxy.common.world.WorldEngine; import me.cortex.voxy.client.importers.WorldImporter; import net.minecraft.client.MinecraftClient; @@ -47,7 +47,7 @@ public class VoxelCore { //private final Thread shutdownThread = new Thread(this::shutdown); - public VoxelCore(WorldSelectionSystem.Selection worldSelection) { + public VoxelCore(ContextSelectionSystem.Selection worldSelection) { this.world = worldSelection.createEngine(); System.out.println("Initializing voxy core"); 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 5d36d715..0f51fad4 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 @@ -159,16 +159,16 @@ public class RenderTracker { // (due to block occlusion) //TODO: FIXME: REBUILDING THE ENTIRE NEIGHBORS when probably only the internal layout changed is NOT SMART - this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z, this::shouldStillBuild); - this.renderGen.enqueueTask(section.lvl, section.x-1, section.y, section.z, this::shouldStillBuild); - this.renderGen.enqueueTask(section.lvl, section.x+1, section.y, section.z, this::shouldStillBuild); - this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z-1, this::shouldStillBuild); - this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z+1, this::shouldStillBuild); this.renderGen.clearCache(section.lvl, section.x, section.y, section.z); this.renderGen.clearCache(section.lvl, section.x-1, section.y, section.z); this.renderGen.clearCache(section.lvl, section.x+1, section.y, section.z); this.renderGen.clearCache(section.lvl, section.x, section.y, section.z-1); this.renderGen.clearCache(section.lvl, section.x, section.y, section.z+1); + this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z, this::shouldStillBuild); + this.renderGen.enqueueTask(section.lvl, section.x-1, section.y, section.z, this::shouldStillBuild); + this.renderGen.enqueueTask(section.lvl, section.x+1, section.y, section.z, this::shouldStillBuild); + this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z-1, this::shouldStillBuild); + this.renderGen.enqueueTask(section.lvl, section.x, section.y, section.z+1, this::shouldStillBuild); } //this.renderGen.enqueueTask(section); } diff --git a/src/main/java/me/cortex/voxy/client/saver/ContextSelectionSystem.java b/src/main/java/me/cortex/voxy/client/saver/ContextSelectionSystem.java new file mode 100644 index 00000000..b3f7aa34 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/saver/ContextSelectionSystem.java @@ -0,0 +1,168 @@ +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.config.Serialization; +import me.cortex.voxy.common.storage.config.StorageConfig; +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.MinecraftClient; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.WorldSavePath; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +//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 ContextSelectionSystem { + public static class Selection { + private final Path selectionFolder; + private final String worldId; + + private StorageConfig storageConfig; + + public Selection(Path selectionFolder, String worldId) { + this.selectionFolder = selectionFolder; + this.worldId = worldId; + loadStorageConfigOrDefault(); + } + + private void loadStorageConfigOrDefault() { + var json = this.selectionFolder.resolve("storage_config.json"); + + if (Files.exists(json)) { + try { + this.storageConfig = Serialization.GSON.fromJson(Files.readString(json), StorageConfig.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + //Load the default config + var baseDB = new RocksDBStorageBackend.Config(); + + var compressor = new ZSTDCompressor.Config(); + compressor.compressionLevel = 7; + + var compression = new CompressionStorageAdaptor.Config(); + compression.delegate = baseDB; + compression.compressor = compressor; + + this.storageConfig = compression; + + this.save(); + } + } + + public StorageBackend createStorageBackend() { + var ctx = new ConfigBuildCtx(); + ctx.setProperty(ConfigBuildCtx.BASE_SAVE_PATH, this.selectionFolder.toString()); + ctx.setProperty(ConfigBuildCtx.WORLD_IDENTIFIER, this.worldId); + ctx.pushPath(ConfigBuildCtx.DEFAULT_STORAGE_PATH); + return this.storageConfig.build(ctx); + + /* + var translocator = new TranslocatingStorageAdaptor.Config(); + translocator.delegate = compression; + translocator.transforms.add(new TranslocatingStorageAdaptor.BoxTransform(0,5,0, 200, 64, 200, 0, -5, 0)); + */ + + + //StorageBackend storage = new RocksDBStorageBackend(VoxyConfig.CONFIG.storagePath); + ////StorageBackend storage = new FragmentedStorageBackendAdaptor(new File(VoxyConfig.CONFIG.storagePath)); + //return new CompressionStorageAdaptor(new ZSTDCompressor(VoxyConfig.CONFIG.savingCompressionLevel), storage); + } + + public WorldEngine createEngine() { + return new WorldEngine(this.createStorageBackend(), 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() { + var file = this.selectionFolder.resolve("storage_config.json"); + var json = Serialization.GSON.toJson(this.storageConfig); + try { + Files.writeString(file, json); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + //Gets dimension independent base world, if singleplayer, its the world name, if multiplayer, its the server ip + private static Path getBasePath(ClientWorld world) { + //TODO: improve this + Path basePath = MinecraftClient.getInstance().runDirectory.toPath().resolve(".voxy").resolve("saves"); + var iserver = MinecraftClient.getInstance().getServer(); + if (iserver != null) { + basePath = iserver.getSavePath(WorldSavePath.ROOT).resolve("voxy"); + } else { + var netHandle = MinecraftClient.getInstance().getNetworkHandler(); + if (netHandle == null) { + System.err.println("Network handle null"); + basePath = basePath.resolve("UNKNOWN"); + } else { + var info = netHandle.getServerInfo(); + if (info == null) { + System.err.println("Server info null"); + basePath = basePath.resolve("UNKNOWN"); + } else { + if (info.isRealm()) { + basePath = basePath.resolve("realms"); + } else { + basePath = basePath.resolve(info.address.replace(":", "_")); + } + } + } + } + return basePath; + } + + private static String bytesToHex(byte[] hash) { + StringBuilder hexString = new StringBuilder(2 * hash.length); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } + + private static String getWorldId(ClientWorld world) { + String data = world.getBiomeAccess().seed + world.getRegistryKey().toString(); + try { + return bytesToHex(MessageDigest.getInstance("SHA-256").digest(data.getBytes())).substring(0, 32); + } catch ( + NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + //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 ContextSelectionSystem() { + } + + + public Selection getBestSelectionOrCreate(ClientWorld world) { + var path = getBasePath(world); + try { + Files.createDirectories(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + return new Selection(path, getWorldId(world)); + } +} diff --git a/src/main/java/me/cortex/voxy/client/saver/WorldSelectionSystem.java b/src/main/java/me/cortex/voxy/client/saver/WorldSelectionSystem.java deleted file mode 100644 index fc593688..00000000 --- a/src/main/java/me/cortex/voxy/client/saver/WorldSelectionSystem.java +++ /dev/null @@ -1,70 +0,0 @@ -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.config.Serialization; -import me.cortex.voxy.common.storage.config.StorageConfig; -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 { - private VoxyConfig config; - - public StorageBackend createStorageBackend() { - 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(); - return translocator.build(ctx); - - //StorageBackend storage = new RocksDBStorageBackend(VoxyConfig.CONFIG.storagePath); - ////StorageBackend storage = new FragmentedStorageBackendAdaptor(new File(VoxyConfig.CONFIG.storagePath)); - //return new CompressionStorageAdaptor(new ZSTDCompressor(VoxyConfig.CONFIG.savingCompressionLevel), storage); - } - - public WorldEngine createEngine() { - return new WorldEngine(this.createStorageBackend(), 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/config/ConfigBuildCtx.java b/src/main/java/me/cortex/voxy/common/storage/config/ConfigBuildCtx.java index 8601d79c..9411622d 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 @@ -9,7 +9,9 @@ import java.util.Stack; public class ConfigBuildCtx { //List of tokens - public static final String BASE_LEVEL_PATH = "{base_level_path}"; + public static final String BASE_SAVE_PATH = "{base_save_path}"; + public static final String WORLD_IDENTIFIER = "{world_identifier}"; + public static final String DEFAULT_STORAGE_PATH = BASE_SAVE_PATH+"/"+WORLD_IDENTIFIER+"/storage/"; private final Map properties = new HashMap<>(); @@ -74,17 +76,19 @@ public class ConfigBuildCtx { } /** - * Resolves a path with the current build context path - * @param other path to resolve against + * Resolves the current path stack recursively * @return resolved path */ - public String resolvePath(String other) { - this.pathStack.push(other); + public String resolvePath() { + String prev = ""; String path = ""; - for (var part : this.pathStack) { - path = concatPath(path, part); - } - this.pathStack.pop(); + do { + prev = path; + path = ""; + for (var part : this.pathStack) { + path = concatPath(path, part); + } + } while (!prev.equals(path)); return path; } diff --git a/src/main/java/me/cortex/voxy/common/storage/lmdb/LMDBStorageBackend.java b/src/main/java/me/cortex/voxy/common/storage/lmdb/LMDBStorageBackend.java index d8774d7b..c41259b1 100644 --- a/src/main/java/me/cortex/voxy/common/storage/lmdb/LMDBStorageBackend.java +++ b/src/main/java/me/cortex/voxy/common/storage/lmdb/LMDBStorageBackend.java @@ -159,11 +159,9 @@ public class LMDBStorageBackend extends StorageBackend { } public static class Config extends StorageConfig { - public String path; - @Override public StorageBackend build(ConfigBuildCtx ctx) { - return new LMDBStorageBackend(ctx.ensurePathExists(ctx.substituteString(ctx.resolvePath(this.path)))); + return new LMDBStorageBackend(ctx.ensurePathExists(ctx.substituteString(ctx.resolvePath()))); } public static String getConfigTypeName() { diff --git a/src/main/java/me/cortex/voxy/common/storage/rocksdb/RocksDBStorageBackend.java b/src/main/java/me/cortex/voxy/common/storage/rocksdb/RocksDBStorageBackend.java index d190f7f1..c23e3049 100644 --- a/src/main/java/me/cortex/voxy/common/storage/rocksdb/RocksDBStorageBackend.java +++ b/src/main/java/me/cortex/voxy/common/storage/rocksdb/RocksDBStorageBackend.java @@ -166,11 +166,9 @@ public class RocksDBStorageBackend extends StorageBackend { } public static class Config extends StorageConfig { - public String path; - @Override public StorageBackend build(ConfigBuildCtx ctx) { - return new RocksDBStorageBackend(ctx.ensurePathExists(ctx.substituteString(ctx.resolvePath(this.path)))); + return new RocksDBStorageBackend(ctx.ensurePathExists(ctx.substituteString(ctx.resolvePath()))); } public static String getConfigTypeName() { diff --git a/src/main/resources/voxy.accesswidener b/src/main/resources/voxy.accesswidener index 6e0d3862..bd50e6e8 100644 --- a/src/main/resources/voxy.accesswidener +++ b/src/main/resources/voxy.accesswidener @@ -8,5 +8,6 @@ accessible field net/minecraft/client/render/GameRenderer zoomX F accessible field net/minecraft/client/render/GameRenderer zoomY F accessible field net/minecraft/client/render/GameRenderer zoom F accessible field net/minecraft/client/world/ClientWorld worldRenderer Lnet/minecraft/client/render/WorldRenderer; +accessible field net/minecraft/world/biome/source/BiomeAccess seed J accessible method net/minecraft/client/render/GameRenderer getFov (Lnet/minecraft/client/render/Camera;FZ)D \ No newline at end of file