This commit is contained in:
mcrcortex
2025-03-06 14:37:23 +10:00
parent ff82c2d761
commit c0a578cdd5
34 changed files with 471 additions and 329 deletions

View File

@@ -5,21 +5,15 @@ import me.cortex.voxy.client.saver.ContextSelectionSystem;
import me.cortex.voxy.client.terrain.WorldImportCommand;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientWorldEvents;
import net.minecraft.client.world.ClientWorld;
public class Voxy implements ClientModInitializer {
public class VoxyClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
/*
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
dispatcher.register(WorldImportCommand.register());
});
}
private static final ContextSelectionSystem SELECTOR = new ContextSelectionSystem();
public static VoxelCore createVoxelCore(ClientWorld world) {
var selection = SELECTOR.getBestSelectionOrCreate(world);
return new VoxelCore(selection);
});*/
}
}

View File

@@ -21,6 +21,7 @@ public class VoxyConfig {
public static VoxyConfig CONFIG = loadOrCreate();
public boolean enabled = true;
public boolean enableRendering = true;
public boolean ingestEnabled = true;
//public int renderDistance = 128;
public int serviceThreads = Math.max(Runtime.getRuntime().availableProcessors()/2, 1);

View File

@@ -2,7 +2,8 @@ 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.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.commonImpl.VoxyCommon;
import me.shedaniel.clothconfig2.api.ConfigBuilder;
import me.shedaniel.clothconfig2.api.ConfigCategory;
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
@@ -35,9 +36,13 @@ public class VoxyConfigScreenFactory implements ModMenuApi {
builder.setSavingRunnable(() -> {
//After saving the core should be reloaded/reset
var world = (IGetVoxelCore)MinecraftClient.getInstance().worldRenderer;
var world = MinecraftClient.getInstance().worldRenderer;
if (world != null && ON_SAVE_RELOAD) {
world.reloadVoxelCore();
//Reload voxy
((IGetVoxyRenderSystem)world).shutdownRenderer();
VoxyCommon.shutdownInstance();
VoxyCommon.createInstance();
((IGetVoxyRenderSystem)world).createRenderer();
}
ON_SAVE_RELOAD = false;
VoxyConfig.CONFIG.save();

View File

@@ -1,9 +0,0 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.VoxelCore;
public interface IGetVoxelCore {
VoxelCore getVoxelCore();
void reloadVoxelCore();
}

View File

@@ -0,0 +1,9 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.rendering.VoxyRenderSystem;
public interface IGetVoxyRenderSystem {
VoxyRenderSystem getVoxyRenderSystem();
void shutdownRenderer();
void createRenderer();
}

View File

@@ -66,29 +66,18 @@ import static org.lwjgl.opengl.GL30C.*;
//REMOVE setRenderGen like holy hell
public class VoxelCore {
private final WorldEngine world;
private final RenderService renderer;
private final PostProcessing postProcessing;
private final ServiceThreadPool serviceThreadPool;
public final ServiceThreadPool serviceThreadPool;
public final WorldImportWrapper importer;
public VoxelCore(ContextSelectionSystem.Selection worldSelection) {
var cfg = worldSelection.getConfig();
this.serviceThreadPool = new ServiceThreadPool(VoxyConfig.CONFIG.serviceThreads);
this.world = worldSelection.createEngine(this.serviceThreadPool);
this.world = null;//worldSelection.createEngine(this.serviceThreadPool);
Logger.info("Initializing voxy core");
this.importer = new WorldImportWrapper(this.serviceThreadPool, this.world);
//Trigger the shared index buffer loading
SharedIndexBuffer.INSTANCE.id();
Capabilities.init();//Ensure clinit is called
this.renderer = new RenderService(this.world, this.serviceThreadPool);
this.postProcessing = new PostProcessing();
Logger.info("Voxy core initialized");
//this.verifyTopNodeChildren(0,0,0);
@@ -99,130 +88,16 @@ public class VoxelCore {
//this.testFullMesh();
}
public void enqueueIngest(WorldChunk worldChunk) {
this.world.ingestService.enqueueIngest(worldChunk);
}
public void renderSetup(Frustum frustum, Camera camera) {
this.renderer.setup(camera);
PrintfDebugUtil.tick();
}
private static Matrix4f makeProjectionMatrix(float near, float far) {
//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();
var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true);
float fov = gameRenderer.getFov(gameRenderer.getCamera(), client.getRenderTickCounter().getTickDelta(true), true);
projection.setPerspective(fov * 0.01745329238474369f,
(float) client.getWindow().getFramebufferWidth() / (float)client.getWindow().getFramebufferHeight(),
near, far);
return projection;
}
//TODO: Make a reverse z buffer
private static Matrix4f computeProjectionMat() {
return new Matrix4f(RenderSystem.getProjectionMatrix()).mulLocal(
makeProjectionMatrix(0.05f, MinecraftClient.getInstance().gameRenderer.getFarPlaneDistance()).invert()
).mulLocal(makeProjectionMatrix(16, 16*3000));
}
//private static final ModelTextureBakery mtb = new ModelTextureBakery(16, 16);
//private static final RawDownloadStream downstream = new RawDownloadStream(1<<20);
public void renderOpaque(MatrixStack matrices, double cameraX, double cameraY, double cameraZ) {
/*
int allocation = downstream.download(2*4*6*16*16, ptr->{
});
mtb.renderFacesToStream(Blocks.WHITE_STAINED_GLASS.getDefaultState(), 123456, false, downstream.getBufferId(), allocation);
downstream.submit();
downstream.tick();
*/
//if (true) return;
if (IrisUtil.irisShadowActive()) {
return;
}
if (false) {
float CHANGE_PER_SECOND = 30;
//Auto fps targeting
if (MinecraftClient.getInstance().getCurrentFps() < 45) {
VoxyConfig.CONFIG.subDivisionSize = Math.min(VoxyConfig.CONFIG.subDivisionSize + CHANGE_PER_SECOND / Math.max(1f, MinecraftClient.getInstance().getCurrentFps()), 256);
}
if (55 < MinecraftClient.getInstance().getCurrentFps()) {
VoxyConfig.CONFIG.subDivisionSize = Math.max(VoxyConfig.CONFIG.subDivisionSize - CHANGE_PER_SECOND / Math.max(1f, MinecraftClient.getInstance().getCurrentFps()), 30);
}
}
//Do some very cheeky stuff for MiB
if (false) {
int sector = (((int)Math.floor(cameraX)>>4)+512)>>10;
cameraX -= sector<<14;//10+4
cameraY += (16+(256-32-sector*30))*16;
}
matrices.push();
matrices.translate(-cameraX, -cameraY, -cameraZ);
matrices.pop();
var projection = computeProjectionMat();//RenderSystem.getProjectionMatrix();
//var projection = RenderSystem.getProjectionMatrix();
var viewport = this.renderer.getViewport();
viewport
.setProjection(projection)
.setModelView(matrices.peek().getPositionMatrix())
.setCamera(cameraX, cameraY, cameraZ)
.setScreenSize(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
viewport.frameId++;
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
if (boundFB == 0) {
throw new IllegalStateException("Cannot use the default framebuffer as cannot source from it");
}
//TODO: use the raw depth buffer texture instead
//int boundDepthBuffer = glGetNamedFramebufferAttachmentParameteri(boundFB, GL_DEPTH_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
//TODO:FIXME!!! ??
this.postProcessing.setup(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight, boundFB);
this.renderer.renderFarAwayOpaque(viewport);
//Compute the SSAO of the rendered terrain, TODO: fix it breaking depth or breaking _something_ am not sure what
this.postProcessing.computeSSAO(projection, matrices);
//We can render the translucent directly after as it is the furthest translucent objects
this.renderer.renderFarAwayTranslucent(viewport);
this.postProcessing.renderPost(projection, RenderSystem.getProjectionMatrix(), boundFB);
}
public void addDebugInfo(List<String> debug) {
debug.add("");
debug.add("");
debug.add("Voxy Core: " + VoxyCommon.MOD_VERSION);
debug.add("MemoryBuffer, Count/Size (mb): " + MemoryBuffer.getCount() + "/" + (MemoryBuffer.getTotalSize()/1_000_000));
debug.add("GlBuffer, Count/Size (mb): " + GlBuffer.getCount() + "/" + (GlBuffer.getTotalSize()/1_000_000));
/*
debug.add("Ingest service tasks: " + this.world.ingestService.getTaskCount());
debug.add("Saving service tasks: " + this.world.savingService.getTaskCount());
debug.add("Render service tasks: " + this.renderGen.getTaskCount());
*/
debug.add("I/S tasks: " + this.world.ingestService.getTaskCount() + "/"+this.world.savingService.getTaskCount());
this.world.addDebugData(debug);
this.renderer.addDebugData(debug);
PrintfDebugUtil.addToOut(debug);
}
//Note: when doing translucent rendering, only need to sort when generating the geometry, or when crossing into the center zone
@@ -230,8 +105,6 @@ public class VoxelCore {
// since they are AABBS crossing the normal is impossible without one of the axis being equal
public void shutdown() {
Logger.info("Flushing download stream");
DownloadStream.INSTANCE.flushWaitClear();
//if (Thread.currentThread() != this.shutdownThread) {
// Runtime.getRuntime().removeShutdownHook(this.shutdownThread);
@@ -239,10 +112,6 @@ public class VoxelCore {
//this.world.getMapper().forceResaveStates();
this.importer.shutdown();
Logger.info("Shutting down rendering");
try {this.renderer.shutdown();} catch (Exception e) {Logger.error("Error shutting down renderer", e);}
Logger.info("Shutting down post processor");
if (this.postProcessing!=null){try {this.postProcessing.shutdown();} catch (Exception e) {Logger.error("Error shutting down post processor", e);}}
Logger.info("Shutting down world engine");
try {this.world.shutdown();} catch (Exception e) {Logger.error("Error shutting down world engine", e);}
Logger.info("Shutting down service thread pool");

View File

@@ -4,6 +4,7 @@ import me.cortex.voxy.client.taskbar.Taskbar;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.commonImpl.VoxyCommon;
import me.cortex.voxy.commonImpl.importers.WorldImporter;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.hud.ClientBossBar;
@@ -28,7 +29,6 @@ public class WorldImportWrapper {
}
public void shutdown() {
Logger.info("Shutting down importer");
if (this.importer != null) {
try {
@@ -58,7 +58,7 @@ public class WorldImportWrapper {
}
public boolean createWorldImporter(World mcWorld, IImporterFactory factory) {
if (this.importer == null) {
this.importer = new WorldImporter(this.world, mcWorld, this.pool);
this.importer = new WorldImporter(this.world, mcWorld, this.pool, VoxyCommon.getInstance().getSavingService());
}
if (this.importer.isBusy()) {
return false;

View File

@@ -6,9 +6,6 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import me.cortex.voxy.client.core.IGetVoxelCore;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger;
@@ -40,13 +37,8 @@ import java.util.stream.Stream;
import static me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
import static org.lwjgl.opengl.GL11C.GL_NEAREST_MIPMAP_LINEAR;
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MAX_LOD;
import static org.lwjgl.opengl.GL12C.GL_TEXTURE_MIN_LOD;
import static org.lwjgl.opengl.GL33.glDeleteSamplers;
import static org.lwjgl.opengl.GL33.glGenSamplers;
import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
import static org.lwjgl.opengl.GL45C.glTextureSubImage2D;
//Manages the storage and updating of model states, textures and colours

View File

@@ -1,6 +1,5 @@
package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.Voxy;
import me.cortex.voxy.client.core.gl.shader.IShaderProcessor;
import me.cortex.voxy.client.core.gl.shader.PrintfInjector;
import me.cortex.voxy.client.core.gl.shader.ShaderType;

View File

@@ -44,7 +44,10 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
private final MessageQueue<WorldSection> sectionUpdateQueue;
private final MessageQueue<BuiltSection> geometryUpdateQueue;
private final WorldEngine world;
public RenderService(WorldEngine world, ServiceThreadPool serviceThreadPool) {
this.world = world;
this.modelService = new ModelBakerySubsystem(world.getMapper());
//Max sections: ~500k
@@ -224,6 +227,11 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
}
public void shutdown() {
//Cleanup callbacks
this.world.setDirtyCallback(null);
this.world.getMapper().setBiomeCallback(null);
this.world.getMapper().setStateCallback(null);
this.modelService.shutdown();
this.renderGen.shutdown();
this.viewportSelector.free();

View File

@@ -0,0 +1,152 @@
package me.cortex.voxy.client.core.rendering;
import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.common.world.WorldEngine;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Frustum;
import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.util.List;
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
public class VoxyRenderSystem {
private final RenderService renderer;
private final PostProcessing postProcessing;
public VoxyRenderSystem(WorldEngine world, ServiceThreadPool threadPool) {
//Trigger the shared index buffer loading
SharedIndexBuffer.INSTANCE.id();
Capabilities.init();//Ensure clinit is called
this.renderer = new RenderService(world, threadPool);
this.postProcessing = new PostProcessing();
}
public void renderSetup(Frustum frustum, Camera camera) {
this.renderer.setup(camera);
PrintfDebugUtil.tick();
}
private static Matrix4f makeProjectionMatrix(float near, float far) {
//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();
var gameRenderer = client.gameRenderer;//tickCounter.getTickDelta(true);
float fov = gameRenderer.getFov(gameRenderer.getCamera(), client.getRenderTickCounter().getTickDelta(true), true);
projection.setPerspective(fov * 0.01745329238474369f,
(float) client.getWindow().getFramebufferWidth() / (float)client.getWindow().getFramebufferHeight(),
near, far);
return projection;
}
//TODO: Make a reverse z buffer
private static Matrix4f computeProjectionMat() {
return new Matrix4f(RenderSystem.getProjectionMatrix()).mulLocal(
makeProjectionMatrix(0.05f, MinecraftClient.getInstance().gameRenderer.getFarPlaneDistance()).invert()
).mulLocal(makeProjectionMatrix(16, 16*3000));
}
//private static final ModelTextureBakery mtb = new ModelTextureBakery(16, 16);
//private static final RawDownloadStream downstream = new RawDownloadStream(1<<20);
public void renderOpaque(MatrixStack matrices, double cameraX, double cameraY, double cameraZ) {
/*
int allocation = downstream.download(2*4*6*16*16, ptr->{
});
mtb.renderFacesToStream(Blocks.WHITE_STAINED_GLASS.getDefaultState(), 123456, false, downstream.getBufferId(), allocation);
downstream.submit();
downstream.tick();
*/
//if (true) return;
if (IrisUtil.irisShadowActive()) {
return;
}
if (false) {
float CHANGE_PER_SECOND = 30;
//Auto fps targeting
if (MinecraftClient.getInstance().getCurrentFps() < 45) {
VoxyConfig.CONFIG.subDivisionSize = Math.min(VoxyConfig.CONFIG.subDivisionSize + CHANGE_PER_SECOND / Math.max(1f, MinecraftClient.getInstance().getCurrentFps()), 256);
}
if (55 < MinecraftClient.getInstance().getCurrentFps()) {
VoxyConfig.CONFIG.subDivisionSize = Math.max(VoxyConfig.CONFIG.subDivisionSize - CHANGE_PER_SECOND / Math.max(1f, MinecraftClient.getInstance().getCurrentFps()), 30);
}
}
//Do some very cheeky stuff for MiB
if (false) {
int sector = (((int)Math.floor(cameraX)>>4)+512)>>10;
cameraX -= sector<<14;//10+4
cameraY += (16+(256-32-sector*30))*16;
}
matrices.push();
matrices.translate(-cameraX, -cameraY, -cameraZ);
matrices.pop();
var projection = computeProjectionMat();//RenderSystem.getProjectionMatrix();
//var projection = RenderSystem.getProjectionMatrix();
var viewport = this.renderer.getViewport();
viewport
.setProjection(projection)
.setModelView(matrices.peek().getPositionMatrix())
.setCamera(cameraX, cameraY, cameraZ)
.setScreenSize(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight);
viewport.frameId++;
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
if (boundFB == 0) {
throw new IllegalStateException("Cannot use the default framebuffer as cannot source from it");
}
//TODO: use the raw depth buffer texture instead
//int boundDepthBuffer = glGetNamedFramebufferAttachmentParameteri(boundFB, GL_DEPTH_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
//TODO:FIXME!!! ??
this.postProcessing.setup(MinecraftClient.getInstance().getFramebuffer().textureWidth, MinecraftClient.getInstance().getFramebuffer().textureHeight, boundFB);
this.renderer.renderFarAwayOpaque(viewport);
//Compute the SSAO of the rendered terrain, TODO: fix it breaking depth or breaking _something_ am not sure what
this.postProcessing.computeSSAO(projection, matrices);
//We can render the translucent directly after as it is the furthest translucent objects
this.renderer.renderFarAwayTranslucent(viewport);
this.postProcessing.renderPost(projection, RenderSystem.getProjectionMatrix(), boundFB);
}
public void addDebugInfo(List<String> debug) {
debug.add("GlBuffer, Count/Size (mb): " + GlBuffer.getCount() + "/" + (GlBuffer.getTotalSize()/1_000_000));
this.renderer.addDebugData(debug);
PrintfDebugUtil.addToOut(debug);
}
public void shutdown() {
Logger.info("Flushing download stream");
DownloadStream.INSTANCE.flushWaitClear();
Logger.info("Shutting down rendering");
try {this.renderer.shutdown();} catch (Exception e) {Logger.error("Error shutting down renderer", e);}
Logger.info("Shutting down post processor");
if (this.postProcessing!=null){try {this.postProcessing.shutdown();} catch (Exception e) {Logger.error("Error shutting down post processor", e);}}
}
}

View File

@@ -1,6 +1,7 @@
package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.core.IGetVoxelCore;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.hud.DebugHud;
import org.spongepowered.asm.mixin.Mixin;
@@ -15,9 +16,15 @@ public class MixinDebugHud {
@Inject(method = "getRightText", at = @At("TAIL"))
private void injectDebug(CallbackInfoReturnable<List<String>> cir) {
var ret = cir.getReturnValue();
var core = ((IGetVoxelCore) MinecraftClient.getInstance().worldRenderer).getVoxelCore();
if (core != null) {
core.addDebugInfo(ret);
var instance = VoxyCommon.getInstance();
if (instance != null) {
ret.add("");
ret.add("");
instance.addDebug(ret);
}
var renderer = ((IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer).getVoxyRenderSystem();
if (renderer != null) {
renderer.addDebugInfo(ret);
}
}
}

View File

@@ -1,7 +1,11 @@
package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.RunArgs;
import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
import net.minecraft.client.world.ClientWorld;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -13,4 +17,16 @@ public class MixinMinecraftClient {
private void injectRenderDoc(RunArgs args, CallbackInfo ci) {
//System.load("C:\\Program Files\\RenderDoc\\renderdoc.dll");
}
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;Z)V", at = @At("TAIL"))
private void voxy$injectWorldClose(CallbackInfo ci) {
VoxyCommon.shutdownInstance();
}
@Inject(method = "joinWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;setWorld(Lnet/minecraft/client/world/ClientWorld;)V", shift = At.Shift.BEFORE))
private void voxy$injectInitialization(ClientWorld world, DownloadingTerrainScreen.WorldEntryReason worldEntryReason, CallbackInfo ci) {
if (VoxyConfig.CONFIG.enabled) {
VoxyCommon.createInstance();
}
}
}

View File

@@ -1,12 +1,15 @@
package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.Voxy;
import me.cortex.voxy.client.core.IGetVoxelCore;
import me.cortex.voxy.client.VoxyClient;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.VoxelCore;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.client.core.rendering.VoxyRenderSystem;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.commonImpl.VoxyCommon;
import me.cortex.voxy.commonImpl.VoxyInstance;
import net.minecraft.client.render.*;
import net.minecraft.client.util.ObjectAllocator;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.client.world.ClientWorld;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
@@ -15,81 +18,71 @@ import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(WorldRenderer.class)
public abstract class MixinWorldRenderer implements IGetVoxelCore {
public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem {
@Shadow private Frustum frustum;
@Shadow private @Nullable ClientWorld world;
@Unique private VoxelCore core;
@Unique private VoxyRenderSystem renderer;
@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(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
if (this.core != null) {
this.core.renderSetup(this.frustum, camera);
if (this.renderer != null) {
this.renderer.renderSetup(this.frustum, camera);
}
}
@Unique
public void populateCore() {
if (this.core != null) {
throw new IllegalStateException("Trying to create new core while a core already exists");
}
this.core = Voxy.createVoxelCore(this.world);
}
public VoxelCore getVoxelCore() {
return this.core;
@Override
public VoxyRenderSystem getVoxyRenderSystem() {
return this.renderer;
}
@Inject(method = "reload()V", at = @At("TAIL"))
private void resetVoxelCore(CallbackInfo ci) {
if (this.world != null && this.core != null) {
this.core.shutdown();
this.core = null;
if (VoxyConfig.CONFIG.enabled) {
this.populateCore();
}
private void reloadVoxyRenderer(CallbackInfo ci) {
this.shutdownRenderer();
if (this.world != null) {
this.createRenderer();
}
}
@Inject(method = "setWorld", at = @At("TAIL"))
private void initVoxelCore(ClientWorld world, CallbackInfo ci) {
if (world == null) {
if (this.core != null) {
this.core.shutdown();
this.core = null;
}
return;
}
if (this.core != null) {
this.core.shutdown();
this.core = null;
}
if (VoxyConfig.CONFIG.enabled) {
this.populateCore();
}
}
@Override
public void reloadVoxelCore() {
if (this.core != null) {
this.core.shutdown();
this.core = null;
}
if (this.world != null && VoxyConfig.CONFIG.enabled) {
this.populateCore();
this.shutdownRenderer();
}
}
@Inject(method = "close", at = @At("HEAD"))
private void injectClose(CallbackInfo ci) {
if (this.core != null) {
this.core.shutdown();
this.core = null;
this.shutdownRenderer();
}
@Override
public void shutdownRenderer() {
if (this.renderer != null) {
this.renderer.shutdown();
this.renderer = null;
}
}
@Override
public void createRenderer() {
if (this.renderer != null) throw new IllegalStateException("Cannot have multiple renderers");
if ((!VoxyConfig.CONFIG.enableRendering)||(!VoxyConfig.CONFIG.enabled)) {
Logger.info("Not creating renderer due to disabled");
return;
}
var instance = VoxyCommon.getInstance();
if (instance == null) {
Logger.error("Not creating renderer due to null instance");
return;
}
WorldEngine world = instance.getOrMakeWorld(this.world);
if (world == null) {
Logger.error("Null world selected");
return;
}
this.renderer = new VoxyRenderSystem(world, instance.getThreadPool());
}
}

View File

@@ -1,7 +1,6 @@
package me.cortex.voxy.client.mixin.sodium;
import me.cortex.voxy.client.core.IGetVoxelCore;
import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import net.caffeinemc.mods.sodium.client.gl.device.CommandList;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
import net.caffeinemc.mods.sodium.client.render.chunk.DefaultChunkRenderer;
@@ -9,13 +8,10 @@ import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderListItera
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.FabricUtil;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -26,12 +22,12 @@ public class MixinDefaultChunkRenderer {
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer;end(Lnet/caffeinemc/mods/sodium/client/render/chunk/terrain/TerrainRenderPass;)V", shift = At.Shift.BEFORE))
private void injectRender(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform camera, CallbackInfo ci) {
if (renderPass == DefaultTerrainRenderPasses.CUTOUT) {
var core = ((IGetVoxelCore) MinecraftClient.getInstance().worldRenderer).getVoxelCore();
if (core != null) {
var renderer = ((IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer).getVoxyRenderSystem();
if (renderer != null) {
var stack = new MatrixStack();
stack.loadIdentity();
stack.multiplyPositionMatrix(new Matrix4f(matrices.modelView()));
core.renderOpaque(stack, camera.x, camera.y, camera.z);
renderer.renderOpaque(stack, camera.x, camera.y, camera.z);
}
}
}

View File

@@ -1,7 +1,7 @@
package me.cortex.voxy.client.mixin.sodium;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.IGetVoxelCore;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionManager;
import net.minecraft.client.world.ClientWorld;
import org.spongepowered.asm.mixin.Final;
@@ -13,14 +13,14 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = RenderSectionManager.class, remap = false)
public class MixinRenderSectionManager {
@Shadow @Final private ClientWorld level;
@Inject(method = "onChunkRemoved", at = @At("HEAD"))
private void injectIngest(int x, int z, CallbackInfo ci) {
var core = ((IGetVoxelCore)(this.level.worldRenderer)).getVoxelCore();
if (core != null && VoxyConfig.CONFIG.ingestEnabled) {
core.enqueueIngest(this.level.getChunk(x, z));
//TODO: Am not quite sure if this is right
var instance = VoxyCommon.getInstance();
if (instance != null && VoxyConfig.CONFIG.ingestEnabled) {
instance.getIngestService().enqueueIngest(this.level.getChunk(x, z));
}
}
}

View File

@@ -104,10 +104,6 @@ public class ContextSelectionSystem {
return this.config.sectionStorageConfig.build(ctx);
}
public WorldEngine createEngine(ServiceThreadPool serviceThreadPool) {
return new WorldEngine(this.createSectionStorageBackend(), serviceThreadPool, VoxyConfig.CONFIG.secondaryLruCacheSize);
}
//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

View File

@@ -5,8 +5,8 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import me.cortex.voxy.client.core.IGetVoxelCore;
import me.cortex.voxy.client.core.VoxelCore;
import me.cortex.voxy.commonImpl.VoxyCommon;
import me.cortex.voxy.commonImpl.VoxyInstance;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
@@ -20,8 +20,9 @@ import java.util.concurrent.CompletableFuture;
public class WorldImportCommand {
/*
public static LiteralArgumentBuilder<FabricClientCommandSource> register() {
return ClientCommandManager.literal("voxy").requires((ctx)-> ((IGetVoxelCore)MinecraftClient.getInstance().worldRenderer).getVoxelCore() != null)
return ClientCommandManager.literal("voxy").requires((ctx)-> VoxyCommon.getInstance() != null)
.then(ClientCommandManager.literal("import")
.then(ClientCommandManager.literal("world")
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
@@ -138,5 +139,5 @@ public class WorldImportCommand {
String finalInnerDir = innerDir;
return core.importer.createWorldImporter(instance.player.clientWorld,
(importer, up, done)->importer.importZippedRegionDirectoryAsyncStart(zip, finalInnerDir, up, done))?0:1;
}
}*/
}

View File

@@ -1,5 +1,8 @@
package me.cortex.voxy.common;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
import org.slf4j.LoggerFactory;
import java.util.stream.Collectors;
@@ -16,7 +19,11 @@ public class Logger {
}
}
var stackEntry = new Throwable().getStackTrace()[1];
LOGGER.error("["+stackEntry.getClassName()+"]: "+ Stream.of(args).map(Object::toString).collect(Collectors.joining(" ")), throwable);
String error = "["+stackEntry.getClassName()+"]: "+ Stream.of(args).map(Object::toString).collect(Collectors.joining(" "));
LOGGER.error(error, throwable);
if (!VoxyCommon.IS_DEDICATED_SERVER) {
MinecraftClient.getInstance().executeSync(()->{var player = MinecraftClient.getInstance().player; if (player != null) player.sendMessage(Text.literal(error), true);});
}
}
public static void warn(Object... args) {

View File

@@ -4,6 +4,7 @@ import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import me.cortex.voxy.common.Logger;
import net.fabricmc.loader.api.FabricLoader;
import java.io.BufferedReader;
@@ -132,20 +133,19 @@ public class Serialization {
nameMethod.setAccessible(true);
} catch (NoSuchMethodException e) {}
if (nameMethod == null) {
System.err.println("WARNING: Config class " + clzName + " doesnt contain a getConfigTypeName and thus wont be serializable");
Logger.error("WARNING: Config class " + clzName + " doesnt contain a getConfigTypeName and thus wont be serializable");
continue outer;
}
count++;
String name = (String) nameMethod.invoke(null);
serializers.computeIfAbsent(clz, GsonConfigSerialization::new)
.register(name, (Class) original);
System.out.println("Registered " + original.getSimpleName() + " as " + name + " for config type " + clz.getSimpleName());
Logger.info("Registered " + original.getSimpleName() + " as " + name + " for config type " + clz.getSimpleName());
break;
}
}
} catch (Exception e) {
System.err.println("Error while setting up config serialization");
e.printStackTrace();
Logger.error("Error while setting up config serialization", e);
}
}
@@ -155,7 +155,7 @@ public class Serialization {
}
GSON = builder.create();
System.out.println("Registered " + count + " config types");
Logger.info("Registered " + count + " config types");
}
private static List<String> collectAllClasses(String pack) {
@@ -173,7 +173,7 @@ public class Serialization {
}
}).collect(Collectors.toList());
} catch (Exception e) {
System.err.println("Failed to collect classes in package: " + pack);
Logger.error("Failed to collect classes in package: " + pack, e);
return List.of();
}
}
@@ -191,8 +191,7 @@ public class Serialization {
return Stream.of();
}
}).collect(Collectors.toList());
} catch (
IOException e) {
} catch (IOException e) {
throw new RuntimeException(e);
}
}

View File

@@ -93,7 +93,7 @@ public class ServiceSlice extends TrackedObject {
//Tells the system that a single instance of this service needs executing
public void execute() {
if (!this.alive) {
System.err.println("Tried to do work on a dead service");
Logger.error("Tried to do work on a dead service: " + this.name);
return;
}
this.jobCount.release();

View File

@@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import me.cortex.voxy.common.util.VolatileHolder;
import me.cortex.voxy.common.world.other.Mapper;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
@@ -18,10 +19,18 @@ public class ActiveSectionTracker {
private final SectionLoader loader;
private final int maxLRUSectionPerSlice;
private final Long2ObjectLinkedOpenHashMap<WorldSection>[] lruSecondaryCache;
private final Long2ObjectLinkedOpenHashMap<WorldSection>[] lruSecondaryCache;//TODO: THIS NEEDS TO BECOME A GLOBAL STATIC CACHE
@Nullable
public final WorldEngine engine;
public ActiveSectionTracker(int numSlicesBits, SectionLoader loader, int cacheSize) {
this(numSlicesBits, loader, cacheSize, null);
}
@SuppressWarnings("unchecked")
public ActiveSectionTracker(int numSlicesBits, SectionLoader loader, int cacheSize) {
public ActiveSectionTracker(int numSlicesBits, SectionLoader loader, int cacheSize, WorldEngine engine) {
this.engine = engine;
this.loader = loader;
this.loadedSectionCache = new Long2ObjectOpenHashMap[1<<numSlicesBits];
this.lruSecondaryCache = new Long2ObjectLinkedOpenHashMap[1<<numSlicesBits];

View File

@@ -2,18 +2,10 @@ package me.cortex.voxy.common.world;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.config.section.SectionStorage;
import me.cortex.voxy.common.util.ThreadLocalMemoryBuffer;
import me.cortex.voxy.common.voxelization.VoxelizedSection;
import me.cortex.voxy.common.world.other.Mapper;
import me.cortex.voxy.common.world.service.SectionSavingService;
import me.cortex.voxy.common.world.service.VoxelIngestService;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import java.util.Arrays;
import java.util.List;
//Use an LMDB backend to store the world, use a local inmemory cache for lod sections
// automatically manages and invalidates sections of the world as needed
public class WorldEngine {
public static final int MAX_LOD_LAYERS = 5;
@@ -22,36 +14,35 @@ public class WorldEngine {
public static final int UPDATE_FLAGS = UPDATE_TYPE_BLOCK_BIT | UPDATE_TYPE_CHILD_EXISTENCE_BIT;
public interface ISectionChangeCallback {void accept(WorldSection section, int updateFlags);}
public interface ISectionSaveCallback {void save(WorldEngine engine, WorldSection section);}
public final SectionStorage storage;
private final Mapper mapper;
private final ActiveSectionTracker sectionTracker;
public final VoxelIngestService ingestService;
public final SectionSavingService savingService;
private ISectionChangeCallback dirtyCallback;
private ISectionSaveCallback saveCallback;
private final int maxMipLevels;
public void setDirtyCallback(ISectionChangeCallback callback) {
this.dirtyCallback = callback;
}
public Mapper getMapper() {return this.mapper;}
public WorldEngine(SectionStorage storage, ServiceThreadPool serviceThreadPool, int cacheCount) {
this(storage, serviceThreadPool, MAX_LOD_LAYERS, cacheCount);
public void setSaveCallback(ISectionSaveCallback callback) {
this.saveCallback = callback;
}
private WorldEngine(SectionStorage storage, ServiceThreadPool serviceThreadPool, int maxMipLayers, int cacheCount) {
public Mapper getMapper() {return this.mapper;}
public WorldEngine(SectionStorage storage, int cacheCount) {
this(storage, MAX_LOD_LAYERS, cacheCount);
}
private WorldEngine(SectionStorage storage, int maxMipLayers, int cacheCount) {
this.maxMipLevels = maxMipLayers;
this.storage = storage;
this.mapper = new Mapper(this.storage);
//4 cache size bits means that the section tracker has 16 separate maps that it uses
this.sectionTracker = new ActiveSectionTracker(4, storage::loadSection, cacheCount);
this.savingService = new SectionSavingService(this, serviceThreadPool);
this.ingestService = new VoxelIngestService(this, serviceThreadPool);
this.sectionTracker = new ActiveSectionTracker(4, storage::loadSection, cacheCount, this);
}
public WorldSection acquireIfExists(int lvl, int x, int y, int z) {
@@ -104,7 +95,9 @@ public class WorldEngine {
if (this.dirtyCallback != null) {
this.dirtyCallback.accept(section, changeState);
}
this.savingService.enqueueSave(section);
if (this.saveCallback != null) {
this.saveCallback.save(this, section);
}
}
@@ -207,10 +200,9 @@ public class WorldEngine {
}
public void shutdown() {
try {this.storage.flush();} catch (Exception e) {e.printStackTrace();}
try {this.mapper.close();} catch (Exception e) {Logger.error(e);}
try {this.storage.flush();} catch (Exception e) {Logger.error(e);}
//Shutdown in this order to preserve as much data as possible
try {this.ingestService.shutdown();} catch (Exception e) {e.printStackTrace();}
try {this.savingService.shutdown();} catch (Exception e) {e.printStackTrace();}
try {this.storage.close();} catch (Exception e) {e.printStackTrace();}
try {this.storage.close();} catch (Exception e) {Logger.error(e);}
}
}

View File

@@ -259,4 +259,8 @@ public final class WorldSection {
public static WorldSection _createRawUntrackedUnsafeSection(int lvl, int x, int y, int z) {
return new WorldSection(lvl, x, y, z, null);
}
public ActiveSectionTracker _getSectionTracker() {
return this.tracker;
}
}

View File

@@ -282,6 +282,10 @@ public class Mapper {
this.storage.flush();
}
public void close() {
}
public static final class StateEntry {
public final int id;

View File

@@ -16,34 +16,40 @@ import java.util.concurrent.ConcurrentLinkedDeque;
// might have some issues with threading if the same section is saved from multiple threads?
public class SectionSavingService {
private final ServiceSlice threads;
private final ConcurrentLinkedDeque<WorldSection> saveQueue = new ConcurrentLinkedDeque<>();
private final WorldEngine world;
private record SaveEntry(WorldEngine engine, WorldSection section) {}
private final ConcurrentLinkedDeque<SaveEntry> saveQueue = new ConcurrentLinkedDeque<>();
public SectionSavingService(WorldEngine worldEngine, ServiceThreadPool threadPool) {
this.world = worldEngine;
public SectionSavingService(ServiceThreadPool threadPool) {
this.threads = threadPool.createServiceNoCleanup("Section saving service", 100, () -> this::processJob);
}
private void processJob() {
var section = this.saveQueue.pop();
var task = this.saveQueue.pop();
var section = task.section;
section.assertNotFree();
try {
section.inSaveQueue.set(false);
this.world.storage.saveSection(section);
task.engine.storage.saveSection(section);
} catch (Exception e) {
String err = "Voxy saver had an exception while executing please check logs and report error";
Logger.error(err, e);
MinecraftClient.getInstance().executeSync(()->MinecraftClient.getInstance().player.sendMessage(Text.literal(err), true));
Logger.error("Voxy saver had an exception while executing please check logs and report error", e);
}
section.release();
}
public void enqueueSave(WorldSection section) {
if (section._getSectionTracker() != null && section._getSectionTracker().engine != null) {
this.enqueueSave(section._getSectionTracker().engine, section);
} else {
Logger.error("Tried saving world section, but did not have world associated");
}
}
public void enqueueSave(WorldEngine in, WorldSection section) {
//If its not enqueued for saving then enqueue it
if (!section.inSaveQueue.getAndSet(true)) {
//Acquire the section for use
section.acquire();
this.saveQueue.add(section);
this.saveQueue.add(new SaveEntry(in, section));
this.threads.execute();
}
}

View File

@@ -1,12 +1,14 @@
package me.cortex.voxy.common.world.service;
import it.unimi.dsi.fastutil.Pair;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.voxelization.ILightingSupplier;
import me.cortex.voxy.common.voxelization.VoxelizedSection;
import me.cortex.voxy.common.voxelization.WorldConversionFactory;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.thread.ServiceSlice;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.commonImpl.IVoxyWorldGetter;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.world.LightType;
import net.minecraft.world.chunk.ChunkNibbleArray;
@@ -20,12 +22,10 @@ import java.util.concurrent.ConcurrentLinkedDeque;
public class VoxelIngestService {
private static final ThreadLocal<VoxelizedSection> SECTION_CACHE = ThreadLocal.withInitial(VoxelizedSection::createEmpty);
private final ServiceSlice threads;
private record IngestSection(int cx, int cy, int cz, ChunkSection section, ChunkNibbleArray blockLight, ChunkNibbleArray skyLight){}
private record IngestSection(int cx, int cy, int cz, WorldEngine world, ChunkSection section, ChunkNibbleArray blockLight, ChunkNibbleArray skyLight){}
private final ConcurrentLinkedDeque<IngestSection> ingestQueue = new ConcurrentLinkedDeque<>();
private final WorldEngine world;
public VoxelIngestService(WorldEngine world, ServiceThreadPool pool) {
this.world = world;
public VoxelIngestService(ServiceThreadPool pool) {
this.threads = pool.createServiceNoCleanup("Ingest service", 100, ()-> this::processJob);
}
@@ -35,7 +35,7 @@ public class VoxelIngestService {
var vs = SECTION_CACHE.get().setPosition(task.cx, task.cy, task.cz);
if (section.isEmpty() && task.blockLight==null && task.skyLight==null) {//If the chunk section has lighting data, propagate it
this.world.insertUpdate(vs.zero());
task.world.insertUpdate(vs.zero());
} else {
ILightingSupplier supplier = (x,y,z) -> (byte) 0;
var sla = task.skyLight;
@@ -65,13 +65,13 @@ public class VoxelIngestService {
}
VoxelizedSection csec = WorldConversionFactory.convert(
SECTION_CACHE.get(),
this.world.getMapper(),
task.world.getMapper(),
section.getBlockStateContainer(),
section.getBiomeContainer(),
supplier
);
WorldConversionFactory.mipSection(csec, this.world.getMapper());
this.world.insertUpdate(csec);
WorldConversionFactory.mipSection(csec, task.world.getMapper());
task.world.insertUpdate(csec);
}
}
@@ -80,6 +80,15 @@ public class VoxelIngestService {
}
public void enqueueIngest(WorldChunk chunk) {
var engine = ((IVoxyWorldGetter)chunk.getWorld()).getWorldEngine();
if (engine == null) {
Logger.error("Could not ingest chunk as does not have world engine");
return;
}
this.enqueueIngest(engine, chunk);
}
public void enqueueIngest(WorldEngine engine, WorldChunk chunk) {
var lightingProvider = chunk.getWorld().getLightingProvider();
var blp = lightingProvider.get(LightType.BLOCK);
var slp = lightingProvider.get(LightType.SKY);
@@ -107,7 +116,7 @@ public class VoxelIngestService {
continue;
}
this.ingestQueue.add(new IngestSection(chunk.getPos().x, i, chunk.getPos().z, section, bl, sl));
this.ingestQueue.add(new IngestSection(chunk.getPos().x, i, chunk.getPos().z, engine, section, bl, sl));
this.threads.execute();
}
}

View File

@@ -7,6 +7,8 @@ import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
public class VoxyCommon implements ModInitializer {
private static VoxyInstance INSTANCE;
public static final String MOD_VERSION;
public static final boolean IS_DEDICATED_SERVER;
@@ -27,11 +29,6 @@ public class VoxyCommon implements ModInitializer {
@Override
public void onInitialize() {
//this.serviceThreadPool = new ServiceThreadPool(VoxyConfig.CONFIG.serviceThreads);
//TODO: need to have a common config with server/client configs deriving from it
// maybe server/client extend it? or something? cause like client needs server config (at least partially sometimes)
// but server doesnt need client config
}
public static void breakpoint() {
@@ -44,4 +41,22 @@ public class VoxyCommon implements ModInitializer {
public static boolean isVerificationFlagOn(String name) {
return (!GlobalVerificationDisableOverride) && System.getProperty("voxy."+name, "true").equals("true");
}
public static VoxyInstance getInstance() {
return INSTANCE;
}
public static void shutdownInstance() {
if (INSTANCE != null) {
INSTANCE.shutdown();
INSTANCE = null;
}
}
public static void createInstance() {
if (INSTANCE != null) {
throw new IllegalStateException("Cannot create multiple instances");
}
INSTANCE = new VoxyInstance(12);
}
}

View File

@@ -0,0 +1,75 @@
package me.cortex.voxy.commonImpl;
import me.cortex.voxy.client.saver.ContextSelectionSystem;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.service.SectionSavingService;
import me.cortex.voxy.common.world.service.VoxelIngestService;
import net.minecraft.client.world.ClientWorld;
import java.util.List;
public class VoxyInstance {
private final ServiceThreadPool threadPool;
private final SectionSavingService savingService;
private final VoxelIngestService ingestService;
public VoxyInstance(int threadCount) {
this.threadPool = new ServiceThreadPool(threadCount);
this.savingService = new SectionSavingService(this.threadPool);
this.ingestService = new VoxelIngestService(this.threadPool);
}
public void addDebug(List<String> debug) {
debug.add("Voxy Core: " + VoxyCommon.MOD_VERSION);
debug.add("MemoryBuffer, Count/Size (mb): " + MemoryBuffer.getCount() + "/" + (MemoryBuffer.getTotalSize()/1_000_000));
debug.add("I/S: " + this.ingestService.getTaskCount() + "/" + this.savingService.getTaskCount());
}
public void shutdown() {
Logger.info("Shutdown voxy instance");
try {this.ingestService.shutdown();} catch (Exception e) {Logger.error(e);}
try {this.savingService.shutdown();} catch (Exception e) {Logger.error(e);}
try {this.threadPool.shutdown();} catch (Exception e) {Logger.error(e);}
}
public ServiceThreadPool getThreadPool() {
return this.threadPool;
}
public VoxelIngestService getIngestService() {
return this.ingestService;
}
public SectionSavingService getSavingService() {
return this.savingService;
}
public void flush() {
try {
while (this.ingestService.getTaskCount() != 0) {
Thread.sleep(10);
}
while (this.savingService.getTaskCount() != 0) {
Thread.sleep(10);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private static final ContextSelectionSystem SELECTOR = new ContextSelectionSystem();
public WorldEngine getOrMakeWorld(ClientWorld world) {
var vworld = ((IVoxyWorldGetter)world).getWorldEngine();
if (vworld == null) {
vworld = new WorldEngine(SELECTOR.getBestSelectionOrCreate(world).createSectionStorageBackend(), 1024);
vworld.setSaveCallback(this.savingService::enqueueSave);
((IVoxyWorldSetter)world).setWorldEngine(vworld);
}
return vworld;
}
}

View File

@@ -9,6 +9,7 @@ import me.cortex.voxy.common.voxelization.WorldConversionFactory;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.thread.ServiceSlice;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.common.world.service.SectionSavingService;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
@@ -37,6 +38,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -57,9 +59,13 @@ public class WorldImporter {
private final ServiceSlice threadPool;
private volatile boolean isRunning;
public WorldImporter(WorldEngine worldEngine, World mcWorld, ServiceThreadPool servicePool) {
public WorldImporter(WorldEngine worldEngine, World mcWorld, ServiceThreadPool servicePool, SectionSavingService savingService) {
this(worldEngine, mcWorld, servicePool, ()->savingService.getTaskCount() < 4000);
}
public WorldImporter(WorldEngine worldEngine, World mcWorld, ServiceThreadPool servicePool, BooleanSupplier runChecker) {
this.world = worldEngine;
this.threadPool = servicePool.createServiceNoCleanup("World importer", 1, ()->()->this.jobQueue.poll().run(), ()->this.world.savingService.getTaskCount() < 4000);
this.threadPool = servicePool.createServiceNoCleanup("World importer", 1, ()->()->this.jobQueue.poll().run(), runChecker);
var biomeRegistry = mcWorld.getRegistryManager().getOrThrow(RegistryKeys.BIOME);
var defaultBiome = biomeRegistry.getOrThrow(BiomeKeys.PLAINS);

View File

@@ -1,31 +1,18 @@
package me.cortex.voxy.client.mixin.chunky;
package me.cortex.voxy.commonImpl.mixin.chunky;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import me.cortex.voxy.client.Voxy;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.IGetVoxelCore;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.server.world.ChunkHolder;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.minecraft.server.world.OptionalChunk;
import net.minecraft.server.world.ServerChunkLoadingManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.WorldChunk;
import org.joml.Matrix4f;
import org.popcraft.chunky.mixin.ServerChunkCacheMixin;
import org.popcraft.chunky.platform.FabricWorld;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
@Mixin(FabricWorld.class)
public class MixinFabricWorld {
@@ -34,9 +21,9 @@ public class MixinFabricWorld {
var future = original.call(instance, i, j, chunkStatus, b);
return future.thenApplyAsync(res->{
res.ifPresent(chunk -> {
var core = ((IGetVoxelCore)(MinecraftClient.getInstance().worldRenderer)).getVoxelCore();
if (core != null && VoxyConfig.CONFIG.ingestEnabled) {
core.enqueueIngest((WorldChunk) chunk);
var voxyInstance = VoxyCommon.getInstance();
if (voxyInstance != null) {
voxyInstance.getIngestService().enqueueIngest((WorldChunk) chunk);
}
});
return res;

View File

@@ -3,12 +3,11 @@
"package": "me.cortex.voxy.client.mixin",
"compatibilityLevel": "JAVA_17",
"client": [
"chunky.MixinFabricWorld",
"joml.AccessFrustumIntersection",
"minecraft.MixinClientCommonNetworkHandler",
"minecraft.MixinThreadExecutor",
"minecraft.MixinDebugHud",
"minecraft.MixinMinecraftClient",
"minecraft.MixinThreadExecutor",
"minecraft.MixinWorldRenderer",
"sodium.MixinDefaultChunkRenderer",
"sodium.MixinRenderSectionManager"

View File

@@ -6,6 +6,7 @@
"defaultRequire": 1
},
"mixins": [
"chunky.MixinFabricWorld",
"minecraft.MixinWorld"
]
}

View File

@@ -18,7 +18,7 @@
"environment": "*",
"entrypoints": {
"client": [
"me.cortex.voxy.client.Voxy"
"me.cortex.voxy.client.VoxyClient"
],
"modmenu": [
"me.cortex.voxy.client.config.VoxyConfigScreenFactory"