Added config

This commit is contained in:
mcrcortex
2024-01-21 18:56:57 +10:00
parent 7ead4a9f96
commit 44236cb865
9 changed files with 205 additions and 39 deletions

View File

@@ -4,4 +4,6 @@ import me.cortex.zenith.client.core.VoxelCore;
public interface IGetVoxelCore { public interface IGetVoxelCore {
VoxelCore getVoxelCore(); VoxelCore getVoxelCore();
void reloadVoxelCore();
} }

View File

@@ -1,15 +1,67 @@
package me.cortex.zenith.client.config; package me.cortex.zenith.client.config;
public class ZenithConfig { import com.google.gson.FieldNamingPolicy;
int qualityScale; import com.google.gson.Gson;
int ingestThreads; import com.google.gson.GsonBuilder;
int savingThreads; import net.fabricmc.loader.api.FabricLoader;
int renderThreads;
int savingCompressionLevel;
StorageConfig storageConfig;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
public class ZenithConfig {
private static final Gson GSON = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setPrettyPrinting()
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.create();
public static ZenithConfig CONFIG = loadOrCreate();
public boolean enabled = true;
public int qualityScale = 20;
public int ingestThreads = 2;
public int savingThreads = 10;
public int renderThreads = 5;
public int savingCompressionLevel = 7;
public String storagePath = "zenith_db";
transient StorageConfig storageConfig;
public static abstract class StorageConfig { } public static abstract class StorageConfig { }
public static class FragmentedStorageConfig extends StorageConfig { } public static class FragmentedStorageConfig extends StorageConfig { }
public static class LmdbStorageConfig extends StorageConfig { } public static class LmdbStorageConfig extends StorageConfig { }
public static ZenithConfig loadOrCreate() {
var path = getConfigPath();
if (Files.exists(path)) {
try (FileReader reader = new FileReader(path.toFile())) {
return GSON.fromJson(reader, ZenithConfig.class);
} catch (IOException e) {
System.err.println("Could not parse config");
e.printStackTrace();
}
}
return new ZenithConfig();
}
public void save() {
//Unsafe, todo: fixme! needs to be atomic!
try {
Files.writeString(getConfigPath(), GSON.toJson(this));
} catch (IOException e) {
System.err.println("Failed to write config file");
e.printStackTrace();
}
}
private static Path getConfigPath() {
return FabricLoader.getInstance()
.getConfigDir()
.resolve("zenith-config.json");
}
} }

View File

@@ -2,11 +2,105 @@ package me.cortex.zenith.client.config;
import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi; import com.terraformersmc.modmenu.api.ModMenuApi;
import me.cortex.zenith.client.IGetVoxelCore;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
import me.shedaniel.clothconfig2.api.Requirement;
import me.shedaniel.clothconfig2.gui.entries.BooleanListEntry;
import me.shedaniel.clothconfig2.gui.entries.IntegerListEntry;
import me.shedaniel.clothconfig2.gui.entries.IntegerSliderEntry;
import me.shedaniel.clothconfig2.impl.builders.DropdownMenuBuilder;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.text.Text;
public class ZenithConfigScreenFactory implements ModMenuApi { public class ZenithConfigScreenFactory implements ModMenuApi {
private static final ZenithConfig DEFAULT = new ZenithConfig();
@Override @Override
public ConfigScreenFactory<?> getModConfigScreenFactory() { public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> null; return parent -> buildConfigScreen(parent, ZenithConfig.CONFIG);
} }
private static Screen buildConfigScreen(Screen parent, ZenithConfig config) {
ConfigBuilder builder = ConfigBuilder.create()
.setParentScreen(parent)
.setTitle(Text.translatable("title.zenith.config"));
addGeneralCategory(builder, config);
addThreadsCategory(builder, config);
addStorageCategory(builder, config);
builder.setSavingRunnable(() -> {
//After saving the core should be reloaded/reset
var world = (IGetVoxelCore)MinecraftClient.getInstance().worldRenderer;
if (world != null) {
world.reloadVoxelCore();
}
ZenithConfig.CONFIG.save();
});
return builder.build();
}
private static void addGeneralCategory(ConfigBuilder builder, ZenithConfig config) {
ConfigCategory category = builder.getOrCreateCategory(Text.translatable("zenith.config.general"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
category.addEntry(entryBuilder.startBooleanToggle(Text.translatable("zenith.config.general.enabled"), config.enabled)
.setTooltip(Text.translatable("zenith.config.general.enabled.tooltip"))
.setSaveConsumer(val -> config.enabled = val)
.setDefaultValue(DEFAULT.enabled)
.build());
category.addEntry(entryBuilder.startIntSlider(Text.translatable("zenith.config.general.quality"), config.qualityScale, 16, 64)
.setTooltip(Text.translatable("zenith.config.general.quality.tooltip"))
.setSaveConsumer(val -> config.qualityScale = val)
.setDefaultValue(DEFAULT.qualityScale)
.build());
category.addEntry(entryBuilder.startIntSlider(Text.translatable("zenith.config.general.compression"), config.savingCompressionLevel, 1, 21)
.setTooltip(Text.translatable("zenith.config.general.compression.tooltip"))
.setSaveConsumer(val -> config.savingCompressionLevel = val)
.setDefaultValue(DEFAULT.savingCompressionLevel)
.build());
}
private static void addThreadsCategory(ConfigBuilder builder, ZenithConfig config) {
ConfigCategory category = builder.getOrCreateCategory(Text.translatable("zenith.config.threads"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
category.addEntry(entryBuilder.startIntSlider(Text.translatable("zenith.config.threads.ingest"), config.ingestThreads, 1, Runtime.getRuntime().availableProcessors())
.setTooltip(Text.translatable("zenith.config.general.ingest.tooltip"))
.setSaveConsumer(val -> config.ingestThreads = val)
.setDefaultValue(DEFAULT.ingestThreads)
.build());
category.addEntry(entryBuilder.startIntSlider(Text.translatable("zenith.config.threads.saving"), config.savingThreads, 1, Runtime.getRuntime().availableProcessors())
.setTooltip(Text.translatable("zenith.config.general.saving.tooltip"))
.setSaveConsumer(val -> config.savingThreads = val)
.setDefaultValue(DEFAULT.savingThreads)
.build());
category.addEntry(entryBuilder.startIntSlider(Text.translatable("zenith.config.threads.render"), config.renderThreads, 1, Runtime.getRuntime().availableProcessors())
.setTooltip(Text.translatable("zenith.config.general.render.tooltip"))
.setSaveConsumer(val -> config.renderThreads = val)
.setDefaultValue(DEFAULT.renderThreads)
.build());
}
private static void addStorageCategory(ConfigBuilder builder, ZenithConfig config) {
ConfigCategory category = builder.getOrCreateCategory(Text.translatable("zenith.config.storage"));
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
//Temporary until i figure out how to do more complex multi layer configuration for storage
category.addEntry(entryBuilder.startStrField(Text.translatable("zenith.config.storage.path"), config.storagePath)
.setTooltip(Text.translatable("zenith.config.storage.path.tooltip"))
.setSaveConsumer(val -> config.storagePath = val)
.setDefaultValue(DEFAULT.storagePath)
.build());
}
} }

View File

@@ -1,5 +1,6 @@
package me.cortex.zenith.client.core; package me.cortex.zenith.client.core;
import me.cortex.zenith.client.config.ZenithConfig;
import me.cortex.zenith.client.core.rendering.*; import me.cortex.zenith.client.core.rendering.*;
import me.cortex.zenith.client.core.rendering.building.RenderGenerationService; import me.cortex.zenith.client.core.rendering.building.RenderGenerationService;
import me.cortex.zenith.client.core.util.DebugUtil; import me.cortex.zenith.client.core.util.DebugUtil;
@@ -39,7 +40,7 @@ import java.util.*;
public class VoxelCore { public class VoxelCore {
private static final Set<Block> biomeTintableAllFaces = new HashSet<>(List.of(Blocks.OAK_LEAVES, Blocks.JUNGLE_LEAVES, Blocks.ACACIA_LEAVES, Blocks.DARK_OAK_LEAVES, Blocks.VINE, Blocks.MANGROVE_LEAVES, private static final Set<Block> biomeTintableAllFaces = new HashSet<>(List.of(Blocks.OAK_LEAVES, Blocks.JUNGLE_LEAVES, Blocks.ACACIA_LEAVES, Blocks.DARK_OAK_LEAVES, Blocks.VINE, Blocks.MANGROVE_LEAVES,
Blocks.TALL_GRASS, Blocks.LARGE_FERN, Blocks.TALL_GRASS, Blocks.LARGE_FERN,
//Blocks.SHORT_GRASS, Blocks.SHORT_GRASS,
Blocks.SPRUCE_LEAVES, Blocks.SPRUCE_LEAVES,
Blocks.BIRCH_LEAVES, Blocks.BIRCH_LEAVES,
@@ -66,31 +67,23 @@ public class VoxelCore {
SharedIndexBuffer.INSTANCE.id(); SharedIndexBuffer.INSTANCE.id();
this.renderer = new Gl46FarWorldRenderer(); this.renderer = new Gl46FarWorldRenderer();
System.out.println("Renderer initialized"); System.out.println("Renderer initialized");
this.world = new WorldEngine(new FragmentedStorageBackendAdaptor(), 4, 20, 5);//"storagefile.db"//"ethoslab.db" this.world = new WorldEngine(new FragmentedStorageBackendAdaptor(new File(ZenithConfig.CONFIG.storagePath)), ZenithConfig.CONFIG.ingestThreads, ZenithConfig.CONFIG.savingThreads, ZenithConfig.CONFIG.savingCompressionLevel, 5);//"storagefile.db"//"ethoslab.db"
System.out.println("World engine"); System.out.println("World engine");
this.renderTracker = new RenderTracker(this.world, this.renderer); this.renderTracker = new RenderTracker(this.world, this.renderer);
this.renderGen = new RenderGenerationService(this.world,7, this.renderTracker::processBuildResult); this.renderGen = new RenderGenerationService(this.world,ZenithConfig.CONFIG.renderThreads, this.renderTracker::processBuildResult);
this.world.setDirtyCallback(this.renderTracker::sectionUpdated); this.world.setDirtyCallback(this.renderTracker::sectionUpdated);
this.renderTracker.setRenderGen(this.renderGen); this.renderTracker.setRenderGen(this.renderGen);
System.out.println("Render tracker and generator initialized"); System.out.println("Render tracker and generator initialized");
//To get to chunk scale multiply the scale by 2, the scale is after how many chunks does the lods halve //To get to chunk scale multiply the scale by 2, the scale is after how many chunks does the lods halve
this.distanceTracker = new DistanceTracker(this.renderTracker, 5, 64);//16//64 this.distanceTracker = new DistanceTracker(this.renderTracker, 5, ZenithConfig.CONFIG.qualityScale);
System.out.println("Distance tracker initialized"); System.out.println("Distance tracker initialized");
this.postProcessing = null;//new PostProcessing(); this.postProcessing = null;//new PostProcessing();
this.world.getMapper().setCallbacks(this::stateUpdate, this::biomeUpdate); this.world.getMapper().setCallbacks(this::stateUpdate, this::biomeUpdate);
//Runtime.getRuntime().addShutdownHook(this.shutdownThread);
//WorldImporter importer = new WorldImporter(this.world, MinecraftClient.getInstance().world);
//importer.importWorldAsyncStart(new File("saves/New World/region"));
for (var state : this.world.getMapper().getStateEntries()) { for (var state : this.world.getMapper().getStateEntries()) {
this.stateUpdate(state); this.stateUpdate(state);
} }
@@ -100,7 +93,6 @@ public class VoxelCore {
} }
System.out.println("Entry updates applied"); System.out.println("Entry updates applied");
System.out.println("Voxel core initialized"); System.out.println("Voxel core initialized");
} }

View File

@@ -1,6 +1,7 @@
package me.cortex.zenith.client.mixin.minecraft; package me.cortex.zenith.client.mixin.minecraft;
import me.cortex.zenith.client.IGetVoxelCore; import me.cortex.zenith.client.IGetVoxelCore;
import me.cortex.zenith.client.config.ZenithConfig;
import me.cortex.zenith.client.core.VoxelCore; import me.cortex.zenith.client.core.VoxelCore;
import net.minecraft.client.render.*; import net.minecraft.client.render.*;
import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.util.math.MatrixStack;
@@ -28,13 +29,17 @@ public abstract class MixinWorldRenderer implements IGetVoxelCore {
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;setupTerrain(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/Frustum;ZZ)V", shift = At.Shift.AFTER)) @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;setupTerrain(Lnet/minecraft/client/render/Camera;Lnet/minecraft/client/render/Frustum;ZZ)V", shift = At.Shift.AFTER))
private void injectSetup(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f projectionMatrix, CallbackInfo ci) { private void injectSetup(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f projectionMatrix, CallbackInfo ci) {
this.core.renderSetup(this.frustum, camera); if (this.core != null) {
this.core.renderSetup(this.frustum, camera);
}
} }
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;renderLayer(Lnet/minecraft/client/render/RenderLayer;Lnet/minecraft/client/util/math/MatrixStack;DDDLorg/joml/Matrix4f;)V", ordinal = 2, shift = At.Shift.AFTER)) @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;renderLayer(Lnet/minecraft/client/render/RenderLayer;Lnet/minecraft/client/util/math/MatrixStack;DDDLorg/joml/Matrix4f;)V", ordinal = 2, shift = At.Shift.AFTER))
private void injectOpaqueRender(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f projectionMatrix, CallbackInfo ci) { private void injectOpaqueRender(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f projectionMatrix, CallbackInfo ci) {
var cam = camera.getPos(); if (this.core != null) {
this.core.renderOpaque(matrices, cam.x, cam.y, cam.z); var cam = camera.getPos();
this.core.renderOpaque(matrices, cam.x, cam.y, cam.z);
}
} }
public VoxelCore getVoxelCore() { public VoxelCore getVoxelCore() {
@@ -58,10 +63,25 @@ public abstract class MixinWorldRenderer implements IGetVoxelCore {
} }
return; return;
} }
if (this.core != null) { if (this.core != null) {
throw new IllegalStateException("Core is not null"); this.core.shutdown();
this.core = null;
}
if (ZenithConfig.CONFIG.enabled) {
this.core = new VoxelCore();
}
}
@Override
public void reloadVoxelCore() {
if (this.core != null) {
this.core.shutdown();
this.core = null;
}
if (this.world != null && ZenithConfig.CONFIG.enabled) {
this.core = new VoxelCore();
} }
this.core = new VoxelCore();
} }
@Inject(method = "close", at = @At("HEAD")) @Inject(method = "close", at = @At("HEAD"))
@@ -72,8 +92,6 @@ public abstract class MixinWorldRenderer implements IGetVoxelCore {
} }
} }
@Redirect(method = "render", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F"), require = 0) @Redirect(method = "render", at = @At(value = "INVOKE", target = "Ljava/lang/Math;max(FF)F"), require = 0)
private float redirectMax(float a, float b) { private float redirectMax(float a, float b) {
return a; return a;

View File

@@ -9,7 +9,7 @@ import java.nio.ByteBuffer;
import static org.lwjgl.util.zstd.Zstd.*; import static org.lwjgl.util.zstd.Zstd.*;
public class SaveLoadSystem { public class SaveLoadSystem {
public static ByteBuffer serialize(WorldSection section) { public static ByteBuffer serialize(WorldSection section, int compressionLevel) {
var data = section.copyData(); var data = section.copyData();
var compressed = new Short[data.length]; var compressed = new Short[data.length];
Long2ShortOpenHashMap LUT = new Long2ShortOpenHashMap(); Long2ShortOpenHashMap LUT = new Long2ShortOpenHashMap();
@@ -48,7 +48,7 @@ public class SaveLoadSystem {
raw.limit(raw.position()); raw.limit(raw.position());
raw.rewind(); raw.rewind();
ByteBuffer compressedData = MemoryUtil.memAlloc((int)ZSTD_COMPRESSBOUND(raw.remaining())); ByteBuffer compressedData = MemoryUtil.memAlloc((int)ZSTD_COMPRESSBOUND(raw.remaining()));
long compressedSize = ZSTD_compress(compressedData, raw, 7); long compressedSize = ZSTD_compress(compressedData, raw, compressionLevel);
compressedData.limit((int) compressedSize); compressedData.limit((int) compressedSize);
compressedData.rewind(); compressedData.rewind();
MemoryUtil.memFree(raw); MemoryUtil.memFree(raw);

View File

@@ -24,18 +24,18 @@ public class WorldEngine {
public void setDirtyCallback(Consumer<WorldSection> tracker) { public void setDirtyCallback(Consumer<WorldSection> tracker) {
this.dirtyCallback = dirtyCallback; this.dirtyCallback = tracker;
} }
public Mapper getMapper() {return this.mapper;} public Mapper getMapper() {return this.mapper;}
public WorldEngine(StorageBackend storageBackend, int ingestWorkers, int savingServiceWorkers, int maxMipLayers) { public WorldEngine(StorageBackend storageBackend, int ingestWorkers, int savingServiceWorkers, int compressionLevel, int maxMipLayers) {
this.maxMipLevels = maxMipLayers; this.maxMipLevels = maxMipLayers;
this.storage = storageBackend; this.storage = storageBackend;
this.mapper = new Mapper(this.storage); this.mapper = new Mapper(this.storage);
this.sectionTracker = new ActiveSectionTracker(maxMipLayers, this::unsafeLoadSection); this.sectionTracker = new ActiveSectionTracker(maxMipLayers, this::unsafeLoadSection);
this.savingService = new SectionSavingService(this, savingServiceWorkers); this.savingService = new SectionSavingService(this, savingServiceWorkers, compressionLevel);
this.ingestService = new VoxelIngestService(this, ingestWorkers); this.ingestService = new VoxelIngestService(this, ingestWorkers);
} }

View File

@@ -15,12 +15,13 @@ public class SectionSavingService {
private volatile boolean running = true; private volatile boolean running = true;
private final Thread[] workers; private final Thread[] workers;
private final int compressionLevel;
private final ConcurrentLinkedDeque<WorldSection> saveQueue = new ConcurrentLinkedDeque<>(); private final ConcurrentLinkedDeque<WorldSection> saveQueue = new ConcurrentLinkedDeque<>();
private final Semaphore saveCounter = new Semaphore(0); private final Semaphore saveCounter = new Semaphore(0);
private final WorldEngine world; private final WorldEngine world;
public SectionSavingService(WorldEngine worldEngine, int workers) { public SectionSavingService(WorldEngine worldEngine, int workers, int compressionLevel) {
this.workers = new Thread[workers]; this.workers = new Thread[workers];
for (int i = 0; i < workers; i++) { for (int i = 0; i < workers; i++) {
var worker = new Thread(this::saveWorker); var worker = new Thread(this::saveWorker);
@@ -29,7 +30,7 @@ public class SectionSavingService {
worker.start(); worker.start();
this.workers[i] = worker; this.workers[i] = worker;
} }
this.compressionLevel = compressionLevel;
this.world = worldEngine; this.world = worldEngine;
} }
@@ -41,7 +42,7 @@ public class SectionSavingService {
section.assertNotFree(); section.assertNotFree();
section.inSaveQueue.set(false); section.inSaveQueue.set(false);
var saveData = SaveLoadSystem.serialize(section); var saveData = SaveLoadSystem.serialize(section, this.compressionLevel);
this.world.storage.setSectionData(section.getKey(), saveData); this.world.storage.setSectionData(section.getKey(), saveData);
MemoryUtil.memFree(saveData); MemoryUtil.memFree(saveData);

View File

@@ -5,15 +5,22 @@ import me.cortex.zenith.common.world.storage.lmdb.LMDBStorageBackend;
import net.minecraft.util.math.random.RandomSeed; import net.minecraft.util.math.random.RandomSeed;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Files;
//Segments the section data into multiple dbs //Segments the section data into multiple dbs
public class FragmentedStorageBackendAdaptor extends StorageBackend { public class FragmentedStorageBackendAdaptor extends StorageBackend {
private final StorageBackend[] backends = new StorageBackend[32]; private final StorageBackend[] backends = new StorageBackend[32];
public FragmentedStorageBackendAdaptor() { public FragmentedStorageBackendAdaptor(File directory) {
try {
Files.createDirectories(directory.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < this.backends.length; i++) { for (int i = 0; i < this.backends.length; i++) {
this.backends[i] = new LMDBStorageBackend(new File("storage-db-"+i+".db")); this.backends[i] = new LMDBStorageBackend(directory.toPath().resolve("storage-db-"+i+".db").toFile());//
} }
} }