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 {
VoxelCore getVoxelCore();
void reloadVoxelCore();
}

View File

@@ -1,15 +1,67 @@
package me.cortex.zenith.client.config;
public class ZenithConfig {
int qualityScale;
int ingestThreads;
int savingThreads;
int renderThreads;
int savingCompressionLevel;
StorageConfig storageConfig;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.fabricmc.loader.api.FabricLoader;
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 class FragmentedStorageConfig 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.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 {
private static final ZenithConfig DEFAULT = new ZenithConfig();
@Override
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;
import me.cortex.zenith.client.config.ZenithConfig;
import me.cortex.zenith.client.core.rendering.*;
import me.cortex.zenith.client.core.rendering.building.RenderGenerationService;
import me.cortex.zenith.client.core.util.DebugUtil;
@@ -39,7 +40,7 @@ import java.util.*;
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,
Blocks.TALL_GRASS, Blocks.LARGE_FERN,
//Blocks.SHORT_GRASS,
Blocks.SHORT_GRASS,
Blocks.SPRUCE_LEAVES,
Blocks.BIRCH_LEAVES,
@@ -66,31 +67,23 @@ public class VoxelCore {
SharedIndexBuffer.INSTANCE.id();
this.renderer = new Gl46FarWorldRenderer();
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");
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.renderTracker.setRenderGen(this.renderGen);
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
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");
this.postProcessing = null;//new PostProcessing();
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()) {
this.stateUpdate(state);
}
@@ -100,7 +93,6 @@ public class VoxelCore {
}
System.out.println("Entry updates applied");
System.out.println("Voxel core initialized");
}

View File

@@ -1,6 +1,7 @@
package me.cortex.zenith.client.mixin.minecraft;
import me.cortex.zenith.client.IGetVoxelCore;
import me.cortex.zenith.client.config.ZenithConfig;
import me.cortex.zenith.client.core.VoxelCore;
import net.minecraft.client.render.*;
import net.minecraft.client.util.math.MatrixStack;
@@ -28,14 +29,18 @@ 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))
private void injectSetup(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f projectionMatrix, CallbackInfo ci) {
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))
private void injectOpaqueRender(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f projectionMatrix, CallbackInfo ci) {
if (this.core != null) {
var cam = camera.getPos();
this.core.renderOpaque(matrices, cam.x, cam.y, cam.z);
}
}
public VoxelCore getVoxelCore() {
return this.core;
@@ -58,11 +63,26 @@ public abstract class MixinWorldRenderer implements IGetVoxelCore {
}
return;
}
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();
}
}
@Inject(method = "close", at = @At("HEAD"))
private void injectClose(CallbackInfo ci) {
@@ -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)
private float redirectMax(float a, float b) {
return a;

View File

@@ -9,7 +9,7 @@ import java.nio.ByteBuffer;
import static org.lwjgl.util.zstd.Zstd.*;
public class SaveLoadSystem {
public static ByteBuffer serialize(WorldSection section) {
public static ByteBuffer serialize(WorldSection section, int compressionLevel) {
var data = section.copyData();
var compressed = new Short[data.length];
Long2ShortOpenHashMap LUT = new Long2ShortOpenHashMap();
@@ -48,7 +48,7 @@ public class SaveLoadSystem {
raw.limit(raw.position());
raw.rewind();
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.rewind();
MemoryUtil.memFree(raw);

View File

@@ -24,18 +24,18 @@ public class WorldEngine {
public void setDirtyCallback(Consumer<WorldSection> tracker) {
this.dirtyCallback = dirtyCallback;
this.dirtyCallback = tracker;
}
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.storage = storageBackend;
this.mapper = new Mapper(this.storage);
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);
}

View File

@@ -15,12 +15,13 @@ public class SectionSavingService {
private volatile boolean running = true;
private final Thread[] workers;
private final int compressionLevel;
private final ConcurrentLinkedDeque<WorldSection> saveQueue = new ConcurrentLinkedDeque<>();
private final Semaphore saveCounter = new Semaphore(0);
private final WorldEngine world;
public SectionSavingService(WorldEngine worldEngine, int workers) {
public SectionSavingService(WorldEngine worldEngine, int workers, int compressionLevel) {
this.workers = new Thread[workers];
for (int i = 0; i < workers; i++) {
var worker = new Thread(this::saveWorker);
@@ -29,7 +30,7 @@ public class SectionSavingService {
worker.start();
this.workers[i] = worker;
}
this.compressionLevel = compressionLevel;
this.world = worldEngine;
}
@@ -41,7 +42,7 @@ public class SectionSavingService {
section.assertNotFree();
section.inSaveQueue.set(false);
var saveData = SaveLoadSystem.serialize(section);
var saveData = SaveLoadSystem.serialize(section, this.compressionLevel);
this.world.storage.setSectionData(section.getKey(), 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 java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
//Segments the section data into multiple dbs
public class FragmentedStorageBackendAdaptor extends StorageBackend {
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++) {
this.backends[i] = new LMDBStorageBackend(new File("storage-db-"+i+".db"));
this.backends[i] = new LMDBStorageBackend(directory.toPath().resolve("storage-db-"+i+".db").toFile());//
}
}