Added basic world seperation

This commit is contained in:
mcrcortex
2024-02-12 15:07:23 +10:00
parent ada7af983f
commit 4392a39783
12 changed files with 221 additions and 102 deletions

View File

@@ -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);

View File

@@ -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"))

View File

@@ -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;
}
}

View File

@@ -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");

View File

@@ -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);
}

View File

@@ -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));
}
}

View File

@@ -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();
}
}

View File

@@ -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<String, String> 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;
}

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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