Refactored voxy frontend to use "WorldIdentifier" system
This commit is contained in:
@@ -1,27 +1,35 @@
|
|||||||
package me.cortex.voxy.client;
|
package me.cortex.voxy.client;
|
||||||
|
|
||||||
import me.cortex.voxy.client.config.VoxyConfig;
|
import me.cortex.voxy.client.config.VoxyConfig;
|
||||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.util.Pair;
|
import me.cortex.voxy.common.config.ConfigBuildCtx;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.config.Serialization;
|
||||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
import me.cortex.voxy.common.config.compressors.ZSTDCompressor;
|
||||||
|
import me.cortex.voxy.common.config.section.SectionSerializationStorage;
|
||||||
|
import me.cortex.voxy.common.config.section.SectionStorage;
|
||||||
|
import me.cortex.voxy.common.config.section.SectionStorageConfig;
|
||||||
|
import me.cortex.voxy.common.config.storage.other.CompressionStorageAdaptor;
|
||||||
|
import me.cortex.voxy.common.config.storage.rocksdb.RocksDBStorageBackend;
|
||||||
import me.cortex.voxy.commonImpl.ImportManager;
|
import me.cortex.voxy.commonImpl.ImportManager;
|
||||||
import me.cortex.voxy.commonImpl.VoxyInstance;
|
import me.cortex.voxy.commonImpl.VoxyInstance;
|
||||||
|
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.world.ClientWorld;
|
import net.minecraft.util.WorldSavePath;
|
||||||
import net.minecraft.world.World;
|
|
||||||
|
|
||||||
import java.util.Random;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
public class VoxyClientInstance extends VoxyInstance {
|
public class VoxyClientInstance extends VoxyInstance {
|
||||||
public static boolean isInGame = false;
|
public static boolean isInGame = false;
|
||||||
|
|
||||||
private static final ContextSelectionSystem SELECTOR = new ContextSelectionSystem();
|
private final SectionStorageConfig storageConfig;
|
||||||
|
private final Path basePath = getBasePath();
|
||||||
public VoxyClientInstance() {
|
public VoxyClientInstance() {
|
||||||
super(VoxyConfig.CONFIG.serviceThreads);
|
super(VoxyConfig.CONFIG.serviceThreads);
|
||||||
|
this.storageConfig = getCreateStorageConfig(this.basePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -29,37 +37,123 @@ public class VoxyClientInstance extends VoxyInstance {
|
|||||||
return new ClientImportManager();
|
return new ClientImportManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SectionStorage createStorage(WorldIdentifier identifier) {
|
||||||
|
var ctx = new ConfigBuildCtx();
|
||||||
|
ctx.setProperty(ConfigBuildCtx.BASE_SAVE_PATH, this.basePath.toString());
|
||||||
|
ctx.setProperty(ConfigBuildCtx.WORLD_IDENTIFIER, getWorldId(identifier));
|
||||||
|
ctx.pushPath(ConfigBuildCtx.DEFAULT_STORAGE_PATH);
|
||||||
|
return this.storageConfig.build(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(WorldIdentifier identifier) {
|
||||||
|
String data = identifier.biomeSeed + identifier.key.toString();
|
||||||
|
try {
|
||||||
|
return bytesToHex(MessageDigest.getInstance("SHA-256").digest(data.getBytes())).substring(0, 32);
|
||||||
|
} catch (
|
||||||
|
NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SectionStorageConfig getCreateStorageConfig(Path path) {
|
||||||
|
var json = path.resolve("config.json");
|
||||||
|
Config config = null;
|
||||||
|
if (Files.exists(json)) {
|
||||||
|
try {
|
||||||
|
config = Serialization.GSON.fromJson(Files.readString(json), Config.class);
|
||||||
|
if (config == null) {
|
||||||
|
throw new IllegalStateException("Config deserialization null, reverting to default");
|
||||||
|
}
|
||||||
|
if (config.sectionStorageConfig == null) {
|
||||||
|
throw new IllegalStateException("Config section storage null, reverting to default");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.error("Failed to load the storage configuration file, resetting it to default, this will probably break your save if you used a custom storage config", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
config = DEFAULT_STORAGE_CONFIG;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Files.writeString(json, Serialization.GSON.toJson(config));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to deserialize the default config, aborting!", e);
|
||||||
|
}
|
||||||
|
if (config == null) {
|
||||||
|
throw new IllegalStateException("Config is still null\n");
|
||||||
|
}
|
||||||
|
return config.sectionStorageConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Config {
|
||||||
|
public SectionStorageConfig sectionStorageConfig;
|
||||||
|
}
|
||||||
|
private static final Config DEFAULT_STORAGE_CONFIG;
|
||||||
|
static {
|
||||||
|
var config = new Config();
|
||||||
|
|
||||||
|
//Load the default config
|
||||||
|
var baseDB = new RocksDBStorageBackend.Config();
|
||||||
|
|
||||||
|
var compressor = new ZSTDCompressor.Config();
|
||||||
|
compressor.compressionLevel = 1;
|
||||||
|
|
||||||
|
var compression = new CompressionStorageAdaptor.Config();
|
||||||
|
compression.delegate = baseDB;
|
||||||
|
compression.compressor = compressor;
|
||||||
|
|
||||||
|
var serializer = new SectionSerializationStorage.Config();
|
||||||
|
serializer.storage = compression;
|
||||||
|
config.sectionStorageConfig = serializer;
|
||||||
|
|
||||||
|
DEFAULT_STORAGE_CONFIG = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path getBasePath() {
|
||||||
|
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().interactionManager;
|
||||||
|
if (netHandle == null) {
|
||||||
|
Logger.error("Network handle null");
|
||||||
|
basePath = basePath.resolve("UNKNOWN");
|
||||||
|
} else {
|
||||||
|
var info = netHandle.networkHandler.getServerInfo();
|
||||||
|
if (info == null) {
|
||||||
|
Logger.error("Server info null");
|
||||||
|
basePath = basePath.resolve("UNKNOWN");
|
||||||
|
} else {
|
||||||
|
if (info.isRealm()) {
|
||||||
|
basePath = basePath.resolve("realms");
|
||||||
|
} else {
|
||||||
|
basePath = basePath.resolve(info.address.replace(":", "_"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return basePath.toAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
private WorldEngine getOrCreateEngine(World world) {
|
|
||||||
/*
|
/*
|
||||||
ClientWorld cw = null;
|
|
||||||
if (world instanceof ClientWorld && MinecraftClient.getInstance().isIntegratedServerRunning()) {
|
|
||||||
cw = (ClientWorld) world;
|
|
||||||
var world2 = MinecraftClient.getInstance().getServer().getWorld(world.getRegistryKey());
|
|
||||||
if (world2 == null) {
|
|
||||||
Logger.error("could not get server world for client world with registry key: " + world.getRegistryKey());
|
|
||||||
} else {
|
|
||||||
world = world2;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
var vworld = ((IVoxyWorld)world).getWorldEngine();
|
|
||||||
if (vworld == null) {
|
|
||||||
vworld = this.createWorld(SELECTOR.getBestSelectionOrCreate(world).createSectionStorageBackend());
|
|
||||||
((IVoxyWorld)world).setWorldEngine(vworld);
|
|
||||||
//testDbPerformance2(vworld);
|
|
||||||
} else {
|
|
||||||
if (!this.activeWorlds.contains(vworld)) {
|
|
||||||
throw new IllegalStateException("World referenced does not exist in instance");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vworld;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WorldEngine getOrMakeRenderWorld(World world) {
|
|
||||||
return this.getOrCreateEngine(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void testDbPerformance(WorldEngine engine) {
|
private static void testDbPerformance(WorldEngine engine) {
|
||||||
Random r = new Random(123456);
|
Random r = new Random(123456);
|
||||||
r.nextLong();
|
r.nextLong();
|
||||||
@@ -149,4 +243,5 @@ public class VoxyClientInstance extends VoxyInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import com.mojang.brigadier.context.CommandContext;
|
|||||||
import com.mojang.brigadier.suggestion.Suggestions;
|
import com.mojang.brigadier.suggestion.Suggestions;
|
||||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
|
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||||
import me.cortex.voxy.commonImpl.importers.DHImporter;
|
import me.cortex.voxy.commonImpl.importers.DHImporter;
|
||||||
import me.cortex.voxy.commonImpl.importers.WorldImporter;
|
import me.cortex.voxy.commonImpl.importers.WorldImporter;
|
||||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
|
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
|
||||||
@@ -67,8 +68,6 @@ public class VoxyCommands {
|
|||||||
if (wr!=null) {
|
if (wr!=null) {
|
||||||
((IGetVoxyRenderSystem)wr).shutdownRenderer();
|
((IGetVoxyRenderSystem)wr).shutdownRenderer();
|
||||||
}
|
}
|
||||||
var w = ((IVoxyWorld)MinecraftClient.getInstance().world);
|
|
||||||
if (w != null) w.shutdownEngine();
|
|
||||||
|
|
||||||
VoxyCommon.shutdownInstance();
|
VoxyCommon.shutdownInstance();
|
||||||
VoxyCommon.createInstance();
|
VoxyCommon.createInstance();
|
||||||
@@ -98,9 +97,10 @@ public class VoxyCommands {
|
|||||||
}
|
}
|
||||||
|
|
||||||
File dbFile_ = dbFile;
|
File dbFile_ = dbFile;
|
||||||
var engine = instance.getOrMakeRenderWorld(MinecraftClient.getInstance().player.clientWorld);
|
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().player.clientWorld);
|
||||||
|
if (engine==null)return 1;
|
||||||
return instance.getImportManager().makeAndRunIfNone(engine, ()->
|
return instance.getImportManager().makeAndRunIfNone(engine, ()->
|
||||||
new DHImporter(dbFile_, engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.getSavingService()))?0:1;
|
new DHImporter(dbFile_, engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.savingServiceRateLimiter))?0:1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean fileBasedImporter(File directory) {
|
private static boolean fileBasedImporter(File directory) {
|
||||||
@@ -108,9 +108,11 @@ public class VoxyCommands {
|
|||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var engine = instance.getOrMakeRenderWorld(MinecraftClient.getInstance().player.clientWorld);
|
|
||||||
|
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().player.clientWorld);
|
||||||
|
if (engine==null) return false;
|
||||||
return instance.getImportManager().makeAndRunIfNone(engine, ()->{
|
return instance.getImportManager().makeAndRunIfNone(engine, ()->{
|
||||||
var importer = new WorldImporter(engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.getSavingService());
|
var importer = new WorldImporter(engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.savingServiceRateLimiter);
|
||||||
importer.importRegionDirectoryAsync(directory);
|
importer.importRegionDirectoryAsync(directory);
|
||||||
return importer;
|
return importer;
|
||||||
});
|
});
|
||||||
@@ -200,12 +202,15 @@ public class VoxyCommands {
|
|||||||
}
|
}
|
||||||
String finalInnerDir = innerDir;
|
String finalInnerDir = innerDir;
|
||||||
|
|
||||||
var engine = instance.getOrMakeRenderWorld(MinecraftClient.getInstance().player.clientWorld);
|
var engine = WorldIdentifier.ofEngine(MinecraftClient.getInstance().player.clientWorld);
|
||||||
return instance.getImportManager().makeAndRunIfNone(engine, ()->{
|
if (engine != null) {
|
||||||
var importer = new WorldImporter(engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.getSavingService());
|
return instance.getImportManager().makeAndRunIfNone(engine, () -> {
|
||||||
|
var importer = new WorldImporter(engine, MinecraftClient.getInstance().player.clientWorld, instance.getThreadPool(), instance.savingServiceRateLimiter);
|
||||||
importer.importZippedRegionDirectoryAsync(zip, finalInnerDir);
|
importer.importZippedRegionDirectoryAsync(zip, finalInnerDir);
|
||||||
return importer;
|
return importer;
|
||||||
})?0:1;
|
}) ? 0 : 1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int cancelImport(CommandContext<FabricClientCommandSource> fabricClientCommandSourceCommandContext) {
|
private static int cancelImport(CommandContext<FabricClientCommandSource> fabricClientCommandSourceCommandContext) {
|
||||||
@@ -213,7 +218,10 @@ public class VoxyCommands {
|
|||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
var world = instance.getOrMakeRenderWorld(MinecraftClient.getInstance().player.clientWorld);
|
var world = WorldIdentifier.ofEngineNullable(MinecraftClient.getInstance().player.clientWorld);
|
||||||
|
if (world != null) {
|
||||||
return instance.getImportManager().cancelImport(world)?0:1;
|
return instance.getImportManager().cancelImport(world)?0:1;
|
||||||
}
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
package me.cortex.voxy.client.config;
|
package me.cortex.voxy.client.config;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
|
|
||||||
import com.terraformersmc.modmenu.api.ModMenuApi;
|
|
||||||
import me.cortex.voxy.client.RenderStatistics;
|
import me.cortex.voxy.client.RenderStatistics;
|
||||||
import me.cortex.voxy.client.VoxyClientInstance;
|
import me.cortex.voxy.client.VoxyClientInstance;
|
||||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||||
import me.cortex.voxy.common.Logger;
|
|
||||||
import me.cortex.voxy.common.util.cpu.CpuLayout;
|
import me.cortex.voxy.common.util.cpu.CpuLayout;
|
||||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
import net.caffeinemc.mods.sodium.client.gui.SodiumOptionsGUI;
|
|
||||||
import net.caffeinemc.mods.sodium.client.gui.options.*;
|
import net.caffeinemc.mods.sodium.client.gui.options.*;
|
||||||
import net.caffeinemc.mods.sodium.client.gui.options.control.SliderControl;
|
import net.caffeinemc.mods.sodium.client.gui.options.control.SliderControl;
|
||||||
import net.caffeinemc.mods.sodium.client.gui.options.control.TickBoxControl;
|
import net.caffeinemc.mods.sodium.client.gui.options.control.TickBoxControl;
|
||||||
@@ -50,10 +45,6 @@ public abstract class VoxyConfigScreenPages {
|
|||||||
if (vrsh != null) {
|
if (vrsh != null) {
|
||||||
vrsh.shutdownRenderer();
|
vrsh.shutdownRenderer();
|
||||||
}
|
}
|
||||||
var world = (IVoxyWorld) MinecraftClient.getInstance().world;
|
|
||||||
if (world != null) {
|
|
||||||
world.shutdownEngine();
|
|
||||||
}
|
|
||||||
VoxyCommon.shutdownInstance();
|
VoxyCommon.shutdownInstance();
|
||||||
}
|
}
|
||||||
}, s -> s.enabled)
|
}, s -> s.enabled)
|
||||||
@@ -72,11 +63,6 @@ public abstract class VoxyConfigScreenPages {
|
|||||||
if (vrsh != null) {
|
if (vrsh != null) {
|
||||||
vrsh.shutdownRenderer();
|
vrsh.shutdownRenderer();
|
||||||
}
|
}
|
||||||
var world = (IVoxyWorld) MinecraftClient.getInstance().world;
|
|
||||||
if (world != null) {
|
|
||||||
world.shutdownEngine();
|
|
||||||
}
|
|
||||||
|
|
||||||
VoxyCommon.shutdownInstance();
|
VoxyCommon.shutdownInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,9 +42,13 @@ import java.util.List;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_ONE;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||||
|
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
|
||||||
import static org.lwjgl.opengl.GL11.GL_VIEWPORT;
|
import static org.lwjgl.opengl.GL11.GL_VIEWPORT;
|
||||||
import static org.lwjgl.opengl.GL11.glGetIntegerv;
|
import static org.lwjgl.opengl.GL11.glGetIntegerv;
|
||||||
import static org.lwjgl.opengl.GL11C.*;
|
import static org.lwjgl.opengl.GL11C.*;
|
||||||
|
import static org.lwjgl.opengl.GL14.glBlendFuncSeparate;
|
||||||
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
|
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
|
||||||
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
|
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
|
||||||
import static org.lwjgl.opengl.GL33.glBindSampler;
|
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||||
@@ -82,6 +86,9 @@ public class VoxyRenderSystem {
|
|||||||
this.renderDistanceTracker.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
|
this.renderDistanceTracker.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
|
||||||
|
|
||||||
this.chunkBoundRenderer = new ChunkBoundRenderer();
|
this.chunkBoundRenderer = new ChunkBoundRenderer();
|
||||||
|
|
||||||
|
//Keep the world loaded
|
||||||
|
this.worldIn.acquireRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRenderDistance(int renderDistance) {
|
public void setRenderDistance(int renderDistance) {
|
||||||
@@ -285,6 +292,9 @@ public class VoxyRenderSystem {
|
|||||||
try {this.renderer.shutdown();this.chunkBoundRenderer.free();} catch (Exception e) {Logger.error("Error shutting down renderer", e);}
|
try {this.renderer.shutdown();this.chunkBoundRenderer.free();} catch (Exception e) {Logger.error("Error shutting down renderer", e);}
|
||||||
Logger.info("Shutting down post processor");
|
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);}}
|
if (this.postProcessing!=null){try {this.postProcessing.shutdown();} catch (Exception e) {Logger.error("Error shutting down post processor", e);}}
|
||||||
|
|
||||||
|
//Release hold on the world
|
||||||
|
this.worldIn.releaseRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
|||||||
import me.cortex.voxy.client.core.VoxyRenderSystem;
|
import me.cortex.voxy.client.core.VoxyRenderSystem;
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
|
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||||
import net.minecraft.client.render.*;
|
import net.minecraft.client.render.*;
|
||||||
import net.minecraft.client.util.ObjectAllocator;
|
import net.minecraft.client.util.ObjectAllocator;
|
||||||
import net.minecraft.client.world.ClientWorld;
|
import net.minecraft.client.world.ClientWorld;
|
||||||
@@ -50,10 +50,6 @@ public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem {
|
|||||||
private void voxy$captureSetWorld(ClientWorld world, CallbackInfo ci) {
|
private void voxy$captureSetWorld(ClientWorld world, CallbackInfo ci) {
|
||||||
if (this.world != world) {
|
if (this.world != world) {
|
||||||
this.shutdownRenderer();
|
this.shutdownRenderer();
|
||||||
|
|
||||||
if (this.world != null) {
|
|
||||||
((IVoxyWorld)this.world).shutdownEngine();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +82,7 @@ public abstract class MixinWorldRenderer implements IGetVoxyRenderSystem {
|
|||||||
Logger.error("Not creating renderer due to null instance");
|
Logger.error("Not creating renderer due to null instance");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WorldEngine world = instance.getOrMakeRenderWorld(this.world);
|
WorldEngine world = WorldIdentifier.ofEngine(this.world);
|
||||||
if (world == null) {
|
if (world == null) {
|
||||||
Logger.error("Null world selected");
|
Logger.error("Null world selected");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import me.cortex.voxy.client.VoxyClientInstance;
|
|||||||
import me.cortex.voxy.client.config.VoxyConfig;
|
import me.cortex.voxy.client.config.VoxyConfig;
|
||||||
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
|
||||||
import me.cortex.voxy.client.core.VoxyRenderSystem;
|
import me.cortex.voxy.client.core.VoxyRenderSystem;
|
||||||
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
|
import me.cortex.voxy.common.world.service.VoxelIngestService;
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
|
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||||
import net.caffeinemc.mods.sodium.client.gl.device.CommandList;
|
import net.caffeinemc.mods.sodium.client.gl.device.CommandList;
|
||||||
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
|
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
|
||||||
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionFlags;
|
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionFlags;
|
||||||
@@ -61,16 +64,8 @@ public class MixinRenderSectionManager {
|
|||||||
@Inject(method = "onChunkRemoved", at = @At("HEAD"))
|
@Inject(method = "onChunkRemoved", at = @At("HEAD"))
|
||||||
private void injectIngest(int x, int z, CallbackInfo ci) {
|
private void injectIngest(int x, int z, CallbackInfo ci) {
|
||||||
//TODO: Am not quite sure if this is right
|
//TODO: Am not quite sure if this is right
|
||||||
var instance = VoxyCommon.getInstance();
|
if (VoxyConfig.CONFIG.ingestEnabled) {
|
||||||
if (instance != null && VoxyConfig.CONFIG.ingestEnabled) {
|
VoxelIngestService.tryAutoIngestChunk(this.level.getChunk(x, z));
|
||||||
var chunk = this.level.getChunk(x, z);
|
|
||||||
var world = chunk.getWorld();
|
|
||||||
if (world instanceof ClientWorld cw) {
|
|
||||||
var engine = ((VoxyClientInstance)instance).getOrMakeRenderWorld(cw);
|
|
||||||
if (engine != null) {
|
|
||||||
instance.getIngestService().enqueueIngest(engine, chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,13 +95,4 @@ public class MixinRenderSectionManager {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
@ModifyReturnValue(method = "getSearchDistance", at = @At("RETURN"))
|
|
||||||
private float voxy$increaseSearchDistanceFix(float searchDistance) {
|
|
||||||
if (((IGetVoxyRenderSystem)(this.level.worldRenderer)).getVoxyRenderSystem() == null) {
|
|
||||||
return searchDistance;
|
|
||||||
}
|
|
||||||
return searchDistance + 32;
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
package me.cortex.voxy.client.saver;
|
|
||||||
|
|
||||||
import me.cortex.voxy.client.config.VoxyConfig;
|
|
||||||
import me.cortex.voxy.common.Logger;
|
|
||||||
import me.cortex.voxy.common.config.section.SectionStorageConfig;
|
|
||||||
import me.cortex.voxy.common.config.section.SectionSerializationStorage;
|
|
||||||
import me.cortex.voxy.common.config.section.SectionStorage;
|
|
||||||
import me.cortex.voxy.common.config.compressors.ZSTDCompressor;
|
|
||||||
import me.cortex.voxy.common.config.ConfigBuildCtx;
|
|
||||||
import me.cortex.voxy.common.config.Serialization;
|
|
||||||
import me.cortex.voxy.common.config.storage.other.CompressionStorageAdaptor;
|
|
||||||
import me.cortex.voxy.common.config.storage.rocksdb.RocksDBStorageBackend;
|
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
|
||||||
import net.minecraft.client.MinecraftClient;
|
|
||||||
import net.minecraft.client.world.ClientWorld;
|
|
||||||
import net.minecraft.util.WorldSavePath;
|
|
||||||
import net.minecraft.world.World;
|
|
||||||
|
|
||||||
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 WorldConfig {
|
|
||||||
public int minYOverride = Integer.MAX_VALUE;
|
|
||||||
public int maxYOverride = Integer.MIN_VALUE;
|
|
||||||
public SectionStorageConfig sectionStorageConfig;
|
|
||||||
}
|
|
||||||
public static final WorldConfig DEFAULT_STORAGE_CONFIG;
|
|
||||||
static {
|
|
||||||
var config = new WorldConfig();
|
|
||||||
|
|
||||||
//Load the default config
|
|
||||||
var baseDB = new RocksDBStorageBackend.Config();
|
|
||||||
|
|
||||||
var compressor = new ZSTDCompressor.Config();
|
|
||||||
compressor.compressionLevel = 1;
|
|
||||||
|
|
||||||
var compression = new CompressionStorageAdaptor.Config();
|
|
||||||
compression.delegate = baseDB;
|
|
||||||
compression.compressor = compressor;
|
|
||||||
|
|
||||||
var serializer = new SectionSerializationStorage.Config();
|
|
||||||
serializer.storage = compression;
|
|
||||||
config.sectionStorageConfig = serializer;
|
|
||||||
|
|
||||||
DEFAULT_STORAGE_CONFIG = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Selection {
|
|
||||||
private final Path selectionFolder;
|
|
||||||
private final String worldId;
|
|
||||||
|
|
||||||
private WorldConfig config;
|
|
||||||
|
|
||||||
public Selection(Path selectionFolder, String worldId) {
|
|
||||||
this.selectionFolder = selectionFolder;
|
|
||||||
this.worldId = worldId;
|
|
||||||
loadStorageConfigOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadStorageConfigOrDefault() {
|
|
||||||
var json = this.selectionFolder.resolve("config.json");
|
|
||||||
|
|
||||||
if (Files.exists(json)) {
|
|
||||||
try {
|
|
||||||
this.config = Serialization.GSON.fromJson(Files.readString(json), WorldConfig.class);
|
|
||||||
if (this.config == null) {
|
|
||||||
throw new IllegalStateException("Config deserialization null, reverting to default");
|
|
||||||
}
|
|
||||||
if (this.config.sectionStorageConfig == null) {
|
|
||||||
throw new IllegalStateException("Config section storage null, reverting to default");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.error("Failed to load the storage configuration file, resetting it to default, this will probably break your save if you used a custom storage config", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.config = DEFAULT_STORAGE_CONFIG;
|
|
||||||
this.save();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to deserialize the default config, aborting!", e);
|
|
||||||
}
|
|
||||||
if (this.config == null) {
|
|
||||||
throw new IllegalStateException("Config is still null\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SectionStorage createSectionStorageBackend() {
|
|
||||||
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.config.sectionStorageConfig.build(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
//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("config.json");
|
|
||||||
var json = Serialization.GSON.toJson(this.config);
|
|
||||||
try {
|
|
||||||
Files.writeString(file, json);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public WorldConfig getConfig() {
|
|
||||||
return this.config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Gets dimension independent base world, if singleplayer, its the world name, if multiplayer, its the server ip
|
|
||||||
private static Path getBasePath() {
|
|
||||||
//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().interactionManager;
|
|
||||||
if (netHandle == null) {
|
|
||||||
Logger.error("Network handle null");
|
|
||||||
basePath = basePath.resolve("UNKNOWN");
|
|
||||||
} else {
|
|
||||||
var info = netHandle.networkHandler.getServerInfo();
|
|
||||||
if (info == null) {
|
|
||||||
Logger.error("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(World 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(World world) {
|
|
||||||
var path = getBasePath();
|
|
||||||
try {
|
|
||||||
Files.createDirectories(path);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return new Selection(path, getWorldId(world));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -152,7 +152,8 @@ public class Serialization {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var builder = new GsonBuilder();
|
var builder = new GsonBuilder()
|
||||||
|
.setPrettyPrinting();
|
||||||
for (var entry : serializers.entrySet()) {
|
for (var entry : serializers.entrySet()) {
|
||||||
builder.registerTypeAdapterFactory(entry.getValue());
|
builder.registerTypeAdapterFactory(entry.getValue());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.lang.invoke.VarHandle;
|
import java.lang.invoke.VarHandle;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.concurrent.locks.StampedLock;
|
import java.util.concurrent.locks.StampedLock;
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ public class ActiveSectionTracker {
|
|||||||
|
|
||||||
//Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane
|
//Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane
|
||||||
|
|
||||||
|
private final AtomicInteger loadedSections = new AtomicInteger();
|
||||||
private final Long2ObjectOpenHashMap<VolatileHolder<WorldSection>>[] loadedSectionCache;
|
private final Long2ObjectOpenHashMap<VolatileHolder<WorldSection>>[] loadedSectionCache;
|
||||||
private final StampedLock[] locks;
|
private final StampedLock[] locks;
|
||||||
private final SectionLoader loader;
|
private final SectionLoader loader;
|
||||||
@@ -53,6 +55,7 @@ public class ActiveSectionTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public WorldSection acquire(long key, boolean nullOnEmpty) {
|
public WorldSection acquire(long key, boolean nullOnEmpty) {
|
||||||
|
if (this.engine != null) this.engine.lastActiveTime = System.currentTimeMillis();
|
||||||
int index = this.getCacheArrayIndex(key);
|
int index = this.getCacheArrayIndex(key);
|
||||||
var cache = this.loadedSectionCache[index];
|
var cache = this.loadedSectionCache[index];
|
||||||
final var lock = this.locks[index];
|
final var lock = this.locks[index];
|
||||||
@@ -91,6 +94,7 @@ public class ActiveSectionTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isLoader) {
|
if (isLoader) {
|
||||||
|
this.loadedSections.incrementAndGet();
|
||||||
long stamp = this.lruLock.writeLock();
|
long stamp = this.lruLock.writeLock();
|
||||||
section = this.lruSecondaryCache.remove(key);
|
section = this.lruSecondaryCache.remove(key);
|
||||||
this.lruLock.unlockWrite(stamp);
|
this.lruLock.unlockWrite(stamp);
|
||||||
@@ -155,6 +159,7 @@ public class ActiveSectionTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void tryUnload(WorldSection section) {
|
void tryUnload(WorldSection section) {
|
||||||
|
if (this.engine != null) this.engine.lastActiveTime = System.currentTimeMillis();
|
||||||
int index = this.getCacheArrayIndex(section.key);
|
int index = this.getCacheArrayIndex(section.key);
|
||||||
final var cache = this.loadedSectionCache[index];
|
final var cache = this.loadedSectionCache[index];
|
||||||
WorldSection sec = null;
|
WorldSection sec = null;
|
||||||
@@ -194,6 +199,10 @@ public class ActiveSectionTracker {
|
|||||||
if (aa != null) {
|
if (aa != null) {
|
||||||
aa._releaseArray();
|
aa._releaseArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sec != null) {
|
||||||
|
this.loadedSections.decrementAndGet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getCacheArrayIndex(long pos) {
|
private int getCacheArrayIndex(long pos) {
|
||||||
@@ -207,11 +216,7 @@ public class ActiveSectionTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getLoadedCacheCount() {
|
public int getLoadedCacheCount() {
|
||||||
int res = 0;
|
return this.loadedSections.get();
|
||||||
for (var cache : this.loadedSectionCache) {
|
|
||||||
res += cache.size();
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSecondaryCacheSize() {
|
public int getSecondaryCacheSize() {
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ package me.cortex.voxy.common.world;
|
|||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.config.section.SectionStorage;
|
import me.cortex.voxy.common.config.section.SectionStorage;
|
||||||
import me.cortex.voxy.common.util.TrackedObject;
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
import me.cortex.voxy.common.voxelization.VoxelizedSection;
|
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
import me.cortex.voxy.commonImpl.VoxyInstance;
|
import me.cortex.voxy.commonImpl.VoxyInstance;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.invoke.VarHandle;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class WorldEngine {
|
public class WorldEngine {
|
||||||
public static final int MAX_LOD_LAYER = 4;
|
public static final int MAX_LOD_LAYER = 4;
|
||||||
|
|
||||||
@@ -40,6 +42,8 @@ public class WorldEngine {
|
|||||||
public boolean isLive() {return this.isLive;}
|
public boolean isLive() {return this.isLive;}
|
||||||
|
|
||||||
public final @Nullable VoxyInstance instanceIn;
|
public final @Nullable VoxyInstance instanceIn;
|
||||||
|
private final AtomicInteger refCount = new AtomicInteger();
|
||||||
|
volatile long lastActiveTime = System.currentTimeMillis();//Time in millis the world was last "active" i.e. had a total ref count or active section count of != 0
|
||||||
|
|
||||||
public WorldEngine(SectionStorage storage) {
|
public WorldEngine(SectionStorage storage) {
|
||||||
this(storage, null);
|
this(storage, null);
|
||||||
@@ -127,18 +131,51 @@ public class WorldEngine {
|
|||||||
return this.sectionTracker.getLoadedCacheCount();
|
return this.sectionTracker.getLoadedCacheCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void free() {
|
public void free() {
|
||||||
|
if (!this.isLive) throw new IllegalStateException();
|
||||||
|
this.isLive = false;
|
||||||
|
VarHandle.fullFence();
|
||||||
//Cannot free while there are loaded sections
|
//Cannot free while there are loaded sections
|
||||||
if (this.sectionTracker.getLoadedCacheCount() != 0) {
|
if (this.sectionTracker.getLoadedCacheCount() != 0) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.thisTracker.free();
|
this.thisTracker.free();
|
||||||
this.isLive = false;
|
|
||||||
try {this.mapper.close();} catch (Exception e) {Logger.error(e);}
|
try {this.mapper.close();} catch (Exception e) {Logger.error(e);}
|
||||||
try {this.storage.flush();} 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
|
//Shutdown in this order to preserve as much data as possible
|
||||||
try {this.storage.close();} catch (Exception e) {Logger.error(e);}
|
try {this.storage.close();} catch (Exception e) {Logger.error(e);}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final long TIMEOUT_MILLIS = 10_000;//10 second timeout (is to long? or to short??)
|
||||||
|
public boolean isWorldUsed() {
|
||||||
|
if (!this.isLive) throw new IllegalStateException();
|
||||||
|
return this.refCount.get() != 0 || this.sectionTracker.getLoadedCacheCount() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWorldIdle() {
|
||||||
|
if (this.isWorldUsed()) {
|
||||||
|
this.lastActiveTime = System.currentTimeMillis();//Force an update if is not active
|
||||||
|
VarHandle.fullFence();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return TIMEOUT_MILLIS<(System.currentTimeMillis()-this.lastActiveTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void markActive() {
|
||||||
|
this.lastActiveTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void acquireRef() {
|
||||||
|
this.refCount.incrementAndGet();
|
||||||
|
this.lastActiveTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseRef() {
|
||||||
|
if (this.refCount.decrementAndGet()<0) {
|
||||||
|
throw new IllegalStateException("ref count less than 0");
|
||||||
|
}
|
||||||
|
//TODO: maybe dont need to tick the last active time?
|
||||||
|
this.lastActiveTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ import me.cortex.voxy.common.world.WorldEngine;
|
|||||||
import me.cortex.voxy.common.thread.ServiceSlice;
|
import me.cortex.voxy.common.thread.ServiceSlice;
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||||
import me.cortex.voxy.common.world.WorldUpdater;
|
import me.cortex.voxy.common.world.WorldUpdater;
|
||||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
|
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||||
import net.minecraft.util.math.ChunkSectionPos;
|
import net.minecraft.util.math.ChunkSectionPos;
|
||||||
import net.minecraft.world.LightType;
|
import net.minecraft.world.LightType;
|
||||||
|
import net.minecraft.world.chunk.Chunk;
|
||||||
import net.minecraft.world.chunk.ChunkNibbleArray;
|
import net.minecraft.world.chunk.ChunkNibbleArray;
|
||||||
import net.minecraft.world.chunk.ChunkSection;
|
import net.minecraft.world.chunk.ChunkSection;
|
||||||
import net.minecraft.world.chunk.WorldChunk;
|
import net.minecraft.world.chunk.WorldChunk;
|
||||||
@@ -83,17 +85,6 @@ public class VoxelIngestService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enqueueIngest(WorldChunk chunk, boolean ignoreOnNullWorld) {
|
|
||||||
var engine = ((IVoxyWorld)chunk.getWorld()).getWorldEngine();
|
|
||||||
if (engine == null) {
|
|
||||||
if (!ignoreOnNullWorld) {
|
|
||||||
Logger.error("Could not ingest chunk as does not have world engine");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.enqueueIngest(engine, chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enqueueIngest(WorldEngine engine, WorldChunk chunk) {
|
public void enqueueIngest(WorldEngine engine, WorldChunk chunk) {
|
||||||
if (!engine.isLive()) {
|
if (!engine.isLive()) {
|
||||||
throw new IllegalStateException("Tried inserting chunk into WorldEngine that was not alive");
|
throw new IllegalStateException("Tried inserting chunk into WorldEngine that was not alive");
|
||||||
@@ -137,4 +128,20 @@ public class VoxelIngestService {
|
|||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
this.threads.shutdown();
|
this.threads.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Utility method to ingest a chunk into the given WorldIdentifier or world
|
||||||
|
public static boolean tryIngestChunk(WorldIdentifier worldId, WorldChunk chunk) {
|
||||||
|
if (worldId == null) return false;
|
||||||
|
var instance = VoxyCommon.getInstance();
|
||||||
|
if (instance == null) return false;
|
||||||
|
var engine = instance.getOrCreate(worldId);
|
||||||
|
if (engine == null) return false;
|
||||||
|
instance.getIngestService().enqueueIngest(engine, chunk);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Try to automatically ingest the chunk into the correct world
|
||||||
|
public static boolean tryAutoIngestChunk(WorldChunk chunk) {
|
||||||
|
return tryIngestChunk(WorldIdentifier.of(chunk.getWorld()), chunk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package me.cortex.voxy.commonImpl;
|
|
||||||
|
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
|
||||||
|
|
||||||
public interface IVoxyWorld {
|
|
||||||
WorldEngine getWorldEngine();
|
|
||||||
void setWorldEngine(WorldEngine engine);
|
|
||||||
void shutdownEngine();
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package me.cortex.voxy.commonImpl;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
public interface IWorldGetIdentifier {
|
||||||
|
WorldIdentifier voxy$getIdentifier();
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@ public class VoxyCommon implements ModInitializer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IInstanceFactory {VoxyInstance create();}
|
public interface IInstanceFactory {VoxyInstance create();}
|
||||||
|
|||||||
@@ -8,17 +8,23 @@ import me.cortex.voxy.common.world.WorldEngine;
|
|||||||
import me.cortex.voxy.common.world.service.SectionSavingService;
|
import me.cortex.voxy.common.world.service.SectionSavingService;
|
||||||
import me.cortex.voxy.common.world.service.VoxelIngestService;
|
import me.cortex.voxy.common.world.service.VoxelIngestService;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Set;
|
import java.util.concurrent.locks.StampedLock;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
//TODO: add thread access verification (I.E. only accessible on a single thread)
|
//TODO: add thread access verification (I.E. only accessible on a single thread)
|
||||||
public class VoxyInstance {
|
public abstract class VoxyInstance {
|
||||||
|
private volatile boolean isRunning = true;
|
||||||
|
private final Thread worldCleaner;
|
||||||
|
public final BooleanSupplier savingServiceRateLimiter;//Can run if this returns true
|
||||||
protected final ServiceThreadPool threadPool;
|
protected final ServiceThreadPool threadPool;
|
||||||
protected final SectionSavingService savingService;
|
protected final SectionSavingService savingService;
|
||||||
protected final VoxelIngestService ingestService;
|
protected final VoxelIngestService ingestService;
|
||||||
protected final Set<WorldEngine> activeWorlds = new HashSet<>();
|
|
||||||
|
private final StampedLock activeWorldLock = new StampedLock();
|
||||||
|
private final HashMap<WorldIdentifier, WorldEngine> activeWorlds = new HashMap<>();
|
||||||
|
|
||||||
protected final ImportManager importManager;
|
protected final ImportManager importManager;
|
||||||
|
|
||||||
@@ -28,35 +34,188 @@ public class VoxyInstance {
|
|||||||
this.savingService = new SectionSavingService(this.threadPool);
|
this.savingService = new SectionSavingService(this.threadPool);
|
||||||
this.ingestService = new VoxelIngestService(this.threadPool);
|
this.ingestService = new VoxelIngestService(this.threadPool);
|
||||||
this.importManager = this.createImportManager();
|
this.importManager = this.createImportManager();
|
||||||
|
this.savingServiceRateLimiter = ()->this.savingService.getTaskCount()<1200;
|
||||||
|
this.worldCleaner = new Thread(()->{
|
||||||
|
try {
|
||||||
|
while (this.isRunning) {
|
||||||
|
//noinspection BusyWait
|
||||||
|
Thread.sleep(1000);
|
||||||
|
this.cleanIdle();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//We are exiting, so just exit
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.error("Exception in world cleaner",e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.worldCleaner.setPriority(Thread.MIN_PRIORITY);
|
||||||
|
this.worldCleaner.setName("Active world cleaner");
|
||||||
|
this.worldCleaner.setDaemon(true);
|
||||||
|
this.worldCleaner.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ImportManager createImportManager() {
|
protected ImportManager createImportManager() {
|
||||||
return new ImportManager();
|
return new ImportManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServiceThreadPool getThreadPool() {
|
||||||
|
return this.threadPool;
|
||||||
|
}
|
||||||
|
public VoxelIngestService getIngestService() {
|
||||||
|
return this.ingestService;
|
||||||
|
}
|
||||||
|
public ImportManager getImportManager() {
|
||||||
|
return this.importManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: reference count the world object
|
||||||
|
// have automatic world cleanup after ~1 minute of inactivity and the reference count equaling zero possibly
|
||||||
|
// note, the reference count should be separate from the number of active chunks to prevent many issues
|
||||||
|
// a world is no longer active once it has no reference counts and no active chunks associated with it
|
||||||
|
public WorldEngine getNullable(WorldIdentifier identifier) {
|
||||||
|
var cache = identifier.cachedEngineObject;
|
||||||
|
WorldEngine world;
|
||||||
|
if (cache == null) {
|
||||||
|
world = null;
|
||||||
|
} else {
|
||||||
|
world = cache.get();
|
||||||
|
if (world == null) {
|
||||||
|
identifier.cachedEngineObject = null;
|
||||||
|
} else {
|
||||||
|
if (world.isLive()) {
|
||||||
|
if (world.instanceIn != this) {
|
||||||
|
throw new IllegalStateException("World cannot be in identifier cache, alive and not part of this instance");
|
||||||
|
}
|
||||||
|
//Successful cache hit
|
||||||
|
} else {
|
||||||
|
identifier.cachedEngineObject = null;
|
||||||
|
world = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (world == null) {//If the cached world is null, try get from the active worlds
|
||||||
|
long stamp = this.activeWorldLock.readLock();
|
||||||
|
world = this.activeWorlds.get(identifier);
|
||||||
|
this.activeWorldLock.unlockRead(stamp);
|
||||||
|
if (world != null) {//Setup cache
|
||||||
|
identifier.cachedEngineObject = new WeakReference<>(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (world != null) {
|
||||||
|
//Mark the world as active
|
||||||
|
world.markActive();
|
||||||
|
}
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldEngine getOrCreate(WorldIdentifier identifier) {
|
||||||
|
var world = this.getNullable(identifier);
|
||||||
|
if (world != null) {
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
long stamp = this.activeWorldLock.writeLock();
|
||||||
|
world = this.activeWorlds.get(identifier);
|
||||||
|
if (world == null) {
|
||||||
|
//Create world here
|
||||||
|
world = this.createWorld(identifier);
|
||||||
|
}
|
||||||
|
this.activeWorldLock.unlockWrite(stamp);
|
||||||
|
identifier.cachedEngineObject = new WeakReference<>(world);
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract SectionStorage createStorage(WorldIdentifier identifier);
|
||||||
|
|
||||||
|
private WorldEngine createWorld(WorldIdentifier identifier) {
|
||||||
|
if (this.activeWorlds.containsKey(identifier)) {
|
||||||
|
throw new IllegalStateException("Existing world with identifier");
|
||||||
|
}
|
||||||
|
Logger.info("Creating new world engine");
|
||||||
|
var world = new WorldEngine(this.createStorage(identifier), this);
|
||||||
|
world.setSaveCallback(this.savingService::enqueueSave);
|
||||||
|
this.activeWorlds.put(identifier, world);
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cleanIdle() {
|
||||||
|
List<WorldIdentifier> idleWorlds = null;
|
||||||
|
{
|
||||||
|
long stamp = this.activeWorldLock.readLock();
|
||||||
|
for (var pair : this.activeWorlds.entrySet()) {
|
||||||
|
if (pair.getValue().isWorldIdle()) {
|
||||||
|
if (idleWorlds == null) idleWorlds = new ArrayList<>();
|
||||||
|
idleWorlds.add(pair.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.activeWorldLock.unlockRead(stamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idleWorlds != null) {
|
||||||
|
//Shutdown and clear all idle worlds
|
||||||
|
long stamp = this.activeWorldLock.writeLock();
|
||||||
|
for (var id : idleWorlds) {
|
||||||
|
var world = this.activeWorlds.remove(id);
|
||||||
|
if (world == null) continue;//Race condition between unlock read and acquire write
|
||||||
|
if (!world.isWorldIdle()) {this.activeWorlds.put(id, world); continue;}//No longer idle
|
||||||
|
//If is here close and free the world
|
||||||
|
world.free();
|
||||||
|
}
|
||||||
|
this.activeWorldLock.unlockWrite(stamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void addDebug(List<String> debug) {
|
public void addDebug(List<String> debug) {
|
||||||
debug.add("Voxy Core: " + VoxyCommon.MOD_VERSION);
|
debug.add("Voxy Core: " + VoxyCommon.MOD_VERSION);
|
||||||
debug.add("MemoryBuffer, Count/Size (mb): " + MemoryBuffer.getCount() + "/" + (MemoryBuffer.getTotalSize()/1_000_000));
|
debug.add("MemoryBuffer, Count/Size (mb): " + MemoryBuffer.getCount() + "/" + (MemoryBuffer.getTotalSize()/1_000_000));
|
||||||
debug.add("I/S/AWSC: " + this.ingestService.getTaskCount() + "/" + this.savingService.getTaskCount() + "/[" + this.activeWorlds.stream().map(a->""+a.getActiveSectionCount()).collect(Collectors.joining(", ")) + "]");//Active world section count
|
debug.add("I/S/AWSC: " + this.ingestService.getTaskCount() + "/" + this.savingService.getTaskCount() + "/[" + this.activeWorlds.values().stream().map(a->""+a.getActiveSectionCount()).collect(Collectors.joining(", ")) + "]");//Active world section count
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
Logger.info("Shutdown voxy instance");
|
Logger.info("Shutting down voxy instance");
|
||||||
|
this.isRunning = false;
|
||||||
|
try {
|
||||||
|
this.worldCleaner.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
this.cleanIdle();
|
||||||
|
|
||||||
if (!this.activeWorlds.isEmpty()) {
|
if (!this.activeWorlds.isEmpty()) {
|
||||||
for (var world : this.activeWorlds) {
|
long stamp = this.activeWorldLock.readLock();
|
||||||
|
for (var world : this.activeWorlds.values()) {
|
||||||
this.importManager.cancelImport(world);
|
this.importManager.cancelImport(world);
|
||||||
}
|
}
|
||||||
|
this.activeWorldLock.unlockRead(stamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {this.ingestService.shutdown();} catch (Exception e) {Logger.error(e);}
|
try {this.ingestService.shutdown();} catch (Exception e) {Logger.error(e);}
|
||||||
try {this.savingService.shutdown();} catch (Exception e) {Logger.error(e);}
|
try {this.savingService.shutdown();} catch (Exception e) {Logger.error(e);}
|
||||||
|
|
||||||
|
|
||||||
|
long stamp = this.activeWorldLock.writeLock();
|
||||||
|
|
||||||
if (!this.activeWorlds.isEmpty()) {
|
if (!this.activeWorlds.isEmpty()) {
|
||||||
Logger.error("Not all worlds shutdown, force closing " + this.activeWorlds.size() + " worlds");
|
boolean printedNotice = false;
|
||||||
for (var world : new HashSet<>(this.activeWorlds)) {//Create a clone
|
for (var world : this.activeWorlds.values()) {
|
||||||
this.stopWorld(world);
|
if (world.isWorldUsed()) {
|
||||||
|
if (!printedNotice) {
|
||||||
|
printedNotice = true;
|
||||||
|
Logger.error("Not all worlds shutdown, force closing worlds");
|
||||||
}
|
}
|
||||||
|
while (world.isWorldUsed()) {
|
||||||
|
try {
|
||||||
|
//noinspection BusyWait
|
||||||
|
Thread.sleep(10);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Free the world
|
||||||
|
world.free();
|
||||||
|
}
|
||||||
|
this.activeWorlds.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {this.threadPool.shutdown();} catch (Exception e) {Logger.error(e);}
|
try {this.threadPool.shutdown();} catch (Exception e) {Logger.error(e);}
|
||||||
@@ -65,82 +224,6 @@ public class VoxyInstance {
|
|||||||
throw new IllegalStateException("Not all worlds shutdown");
|
throw new IllegalStateException("Not all worlds shutdown");
|
||||||
}
|
}
|
||||||
Logger.info("Instance shutdown");
|
Logger.info("Instance shutdown");
|
||||||
}
|
this.activeWorldLock.unlockWrite(stamp);
|
||||||
|
|
||||||
public ServiceThreadPool getThreadPool() {
|
|
||||||
return this.threadPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VoxelIngestService getIngestService() {
|
|
||||||
return this.ingestService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SectionSavingService getSavingService() {
|
|
||||||
return this.savingService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImportManager getImportManager() {
|
|
||||||
return this.importManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected WorldEngine createWorld(SectionStorage storage) {
|
|
||||||
var world = new WorldEngine(storage, this);
|
|
||||||
world.setSaveCallback(this.savingService::enqueueSave);
|
|
||||||
this.activeWorlds.add(world);
|
|
||||||
return world;
|
|
||||||
}
|
|
||||||
|
|
||||||
//There are 4 possible "states" for world selection/management
|
|
||||||
// 1) dedicated server
|
|
||||||
// 2) client singleplayer
|
|
||||||
// 3) client singleplayer as lan host (so also a server)
|
|
||||||
// 4) client multiplayer (remote server)
|
|
||||||
|
|
||||||
//The thing with singleplayer is that it is more efficent to make it bound to clientworld (think)
|
|
||||||
// so if make into singleplayer as host, would need to reload the system into that mode
|
|
||||||
// so that the world renderer uses the WorldEngine of the server
|
|
||||||
|
|
||||||
public void stopWorld(WorldEngine world) {
|
|
||||||
if (!this.activeWorlds.contains(world)) {
|
|
||||||
if (world.isLive()) {
|
|
||||||
throw new IllegalStateException("World cannot be live and not in world set");
|
|
||||||
}
|
|
||||||
throw new IllegalStateException("Cannot close world which is not part of instance");
|
|
||||||
}
|
|
||||||
if (!world.isLive()) {
|
|
||||||
throw new IllegalStateException("World cannot be in world set and not alive");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.importManager.cancelImport(world);
|
|
||||||
|
|
||||||
if (world.getActiveSectionCount() != 0) {
|
|
||||||
Logger.warn("Waiting for world to finish use: " + world );
|
|
||||||
while (world.getActiveSectionCount() != 0) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(100);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: maybe replace the flush with an atomic "in queue" counter that is per world
|
|
||||||
this.flush();
|
|
||||||
|
|
||||||
world.free();
|
|
||||||
this.activeWorlds.remove(world);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
104
src/main/java/me/cortex/voxy/commonImpl/WorldIdentifier.java
Normal file
104
src/main/java/me/cortex/voxy/commonImpl/WorldIdentifier.java
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package me.cortex.voxy.commonImpl;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
|
import net.minecraft.client.world.ClientWorld;
|
||||||
|
import net.minecraft.registry.Registries;
|
||||||
|
import net.minecraft.registry.RegistryKey;
|
||||||
|
import net.minecraft.registry.RegistryKeys;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
import net.minecraft.world.dimension.DimensionType;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class WorldIdentifier {
|
||||||
|
private static final RegistryKey<DimensionType> NULL_DIM_KEY = RegistryKey.of(RegistryKeys.DIMENSION_TYPE, Identifier.of("voxy:null_dimension_id"));
|
||||||
|
|
||||||
|
public final RegistryKey<World> key;
|
||||||
|
public final long biomeSeed;
|
||||||
|
public final RegistryKey<DimensionType> dimension;//Maybe?
|
||||||
|
private final transient long hashCode;
|
||||||
|
@Nullable transient WeakReference<WorldEngine> cachedEngineObject;
|
||||||
|
|
||||||
|
public WorldIdentifier(RegistryKey<World> key, long biomeSeed, @Nullable RegistryKey<DimensionType> dimension) {
|
||||||
|
dimension = dimension==null?NULL_DIM_KEY:dimension;
|
||||||
|
this.key = key;
|
||||||
|
this.biomeSeed = biomeSeed;
|
||||||
|
this.dimension = dimension;
|
||||||
|
this.hashCode = mixStafford13(key.hashCode()^biomeSeed)^mixStafford13(dimension.hashCode()^biomeSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return (int) this.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof WorldIdentifier other) {
|
||||||
|
return other.hashCode == this.hashCode &&
|
||||||
|
other.biomeSeed == this.biomeSeed &&
|
||||||
|
other.key == this.key &&//other.key.equals(this.key) &&
|
||||||
|
other.dimension == this.dimension//other.dimension.equals(this.dimension)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Quick access utility method to get or create a world object in the current instance
|
||||||
|
public WorldEngine getOrCreateEngine() {
|
||||||
|
var instance = VoxyCommon.getInstance();
|
||||||
|
if (instance == null) {
|
||||||
|
this.cachedEngineObject = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var engine = instance.getOrCreate(this);
|
||||||
|
if (engine==null) {
|
||||||
|
throw new IllegalStateException("Engine null on creation");
|
||||||
|
}
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldEngine getNullable() {
|
||||||
|
var instance = VoxyCommon.getInstance();
|
||||||
|
if (instance == null) {
|
||||||
|
this.cachedEngineObject = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return instance.getNullable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WorldIdentifier of(World world) {
|
||||||
|
//Gets or makes an identifier for world
|
||||||
|
if (world == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ((IWorldGetIdentifier)world).voxy$getIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Common utility function to get or create a world engine
|
||||||
|
public static WorldEngine ofEngine(World world) {
|
||||||
|
var id = of(world);
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return id.getOrCreateEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WorldEngine ofEngineNullable(World world) {
|
||||||
|
var id = of(world);
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return id.getNullable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long mixStafford13(long seed) {
|
||||||
|
seed += 918759875987111L;
|
||||||
|
seed = (seed ^ seed >>> 30) * -4658895280553007687L;
|
||||||
|
seed = (seed ^ seed >>> 27) * -7723592293110705685L;
|
||||||
|
return seed ^ seed >>> 31;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ import java.sql.SQLException;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
public class DHImporter implements IDataImporter {
|
public class DHImporter implements IDataImporter {
|
||||||
private final Connection db;
|
private final Connection db;
|
||||||
@@ -68,7 +69,7 @@ public class DHImporter implements IDataImporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DHImporter(File file, WorldEngine worldEngine, World mcWorld, ServiceThreadPool servicePool, SectionSavingService savingService) {
|
public DHImporter(File file, WorldEngine worldEngine, World mcWorld, ServiceThreadPool servicePool, BooleanSupplier rateLimiter) {
|
||||||
this.engine = worldEngine;
|
this.engine = worldEngine;
|
||||||
this.world = mcWorld;
|
this.world = mcWorld;
|
||||||
this.biomeRegistry = mcWorld.getRegistryManager().getOrThrow(RegistryKeys.BIOME);
|
this.biomeRegistry = mcWorld.getRegistryManager().getOrThrow(RegistryKeys.BIOME);
|
||||||
@@ -101,13 +102,14 @@ public class DHImporter implements IDataImporter {
|
|||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}, ()->savingService.getTaskCount() < 500);
|
}, rateLimiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runImport(IUpdateCallback updateCallback, ICompletionCallback completionCallback) {
|
public void runImport(IUpdateCallback updateCallback, ICompletionCallback completionCallback) {
|
||||||
if (this.isRunning()) {
|
if (this.isRunning()) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
this.engine.acquireRef();
|
||||||
this.updateCallback = updateCallback;
|
this.updateCallback = updateCallback;
|
||||||
this.runner = new Thread(()-> {
|
this.runner = new Thread(()-> {
|
||||||
Queue<Task> taskQ = new PriorityQueue<>(Comparator.comparingLong(Task::distanceFromZero));
|
Queue<Task> taskQ = new PriorityQueue<>(Comparator.comparingLong(Task::distanceFromZero));
|
||||||
@@ -356,6 +358,7 @@ public class DHImporter implements IDataImporter {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
this.threadPool.shutdown();
|
this.threadPool.shutdown();
|
||||||
|
this.engine.releaseRef();
|
||||||
try {
|
try {
|
||||||
this.db.close();
|
this.db.close();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
|
|||||||
@@ -55,9 +55,6 @@ public class WorldImporter implements IDataImporter {
|
|||||||
private final ServiceSlice threadPool;
|
private final ServiceSlice threadPool;
|
||||||
|
|
||||||
private volatile boolean isRunning;
|
private volatile boolean isRunning;
|
||||||
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) {
|
public WorldImporter(WorldEngine worldEngine, World mcWorld, ServiceThreadPool servicePool, BooleanSupplier runChecker) {
|
||||||
this.world = worldEngine;
|
this.world = worldEngine;
|
||||||
@@ -128,6 +125,7 @@ public class WorldImporter implements IDataImporter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isRunning = true;
|
this.isRunning = true;
|
||||||
|
this.world.acquireRef();
|
||||||
this.updateCallback = updateCallback;
|
this.updateCallback = updateCallback;
|
||||||
this.completionCallback = completionCallback;
|
this.completionCallback = completionCallback;
|
||||||
this.worker.start();
|
this.worker.start();
|
||||||
@@ -148,6 +146,7 @@ public class WorldImporter implements IDataImporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this.threadPool.isFreed()) {
|
if (!this.threadPool.isFreed()) {
|
||||||
|
this.world.releaseRef();
|
||||||
this.threadPool.shutdown();
|
this.threadPool.shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,6 +259,7 @@ public class WorldImporter implements IDataImporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.worker = null;
|
this.worker = null;
|
||||||
|
this.world.releaseRef();
|
||||||
this.threadPool.shutdown();
|
this.threadPool.shutdown();
|
||||||
this.completionCallback.onCompletion(this.totalChunks.get());
|
this.completionCallback.onCompletion(this.totalChunks.get());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package me.cortex.voxy.commonImpl.mixin.chunky;
|
|||||||
|
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||||
|
import me.cortex.voxy.common.world.service.VoxelIngestService;
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
|
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||||
import net.minecraft.server.world.OptionalChunk;
|
import net.minecraft.server.world.OptionalChunk;
|
||||||
import net.minecraft.world.chunk.Chunk;
|
import net.minecraft.world.chunk.Chunk;
|
||||||
import net.minecraft.world.chunk.ChunkStatus;
|
import net.minecraft.world.chunk.ChunkStatus;
|
||||||
@@ -19,18 +21,13 @@ public class MixinFabricWorld {
|
|||||||
@WrapOperation(method = "getChunkAtAsync", at = @At(value = "INVOKE", target = "Lorg/popcraft/chunky/mixin/ServerChunkCacheMixin;invokeGetChunkFutureMainThread(IILnet/minecraft/world/chunk/ChunkStatus;Z)Ljava/util/concurrent/CompletableFuture;"))
|
@WrapOperation(method = "getChunkAtAsync", at = @At(value = "INVOKE", target = "Lorg/popcraft/chunky/mixin/ServerChunkCacheMixin;invokeGetChunkFutureMainThread(IILnet/minecraft/world/chunk/ChunkStatus;Z)Ljava/util/concurrent/CompletableFuture;"))
|
||||||
private CompletableFuture<OptionalChunk<Chunk>> captureGeneratedChunk(ServerChunkCacheMixin instance, int i, int j, ChunkStatus chunkStatus, boolean b, Operation<CompletableFuture<OptionalChunk<Chunk>>> original) {
|
private CompletableFuture<OptionalChunk<Chunk>> captureGeneratedChunk(ServerChunkCacheMixin instance, int i, int j, ChunkStatus chunkStatus, boolean b, Operation<CompletableFuture<OptionalChunk<Chunk>>> original) {
|
||||||
var future = original.call(instance, i, j, chunkStatus, b);
|
var future = original.call(instance, i, j, chunkStatus, b);
|
||||||
if (true) {//TODO: ADD SERVER CONFIG THING
|
if (false) {//TODO: ADD SERVER CONFIG THING
|
||||||
return future;
|
return future;
|
||||||
} else {
|
} else {
|
||||||
return future.thenApplyAsync(res -> {
|
return future.thenApply(res -> {
|
||||||
res.ifPresent(chunk -> {
|
res.ifPresent(chunk -> {
|
||||||
var voxyInstance = VoxyCommon.getInstance();
|
if (chunk instanceof WorldChunk worldChunk) {
|
||||||
if (voxyInstance != null) {
|
VoxelIngestService.tryAutoIngestChunk(worldChunk);
|
||||||
try {
|
|
||||||
voxyInstance.getIngestService().enqueueIngest((WorldChunk) chunk, true);
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -1,36 +1,40 @@
|
|||||||
package me.cortex.voxy.commonImpl.mixin.minecraft;
|
package me.cortex.voxy.commonImpl.mixin.minecraft;
|
||||||
|
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.commonImpl.IWorldGetIdentifier;
|
||||||
import me.cortex.voxy.commonImpl.IVoxyWorld;
|
import me.cortex.voxy.commonImpl.WorldIdentifier;
|
||||||
|
import net.minecraft.registry.DynamicRegistryManager;
|
||||||
|
import net.minecraft.registry.RegistryKey;
|
||||||
|
import net.minecraft.registry.entry.RegistryEntry;
|
||||||
|
import net.minecraft.world.MutableWorldProperties;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
import net.minecraft.world.block.NeighborUpdater;
|
import net.minecraft.world.dimension.DimensionType;
|
||||||
import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
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;
|
||||||
|
|
||||||
@Mixin(World.class)
|
@Mixin(World.class)
|
||||||
public class MixinWorld implements IVoxyWorld {
|
public class MixinWorld implements IWorldGetIdentifier {
|
||||||
@Unique private WorldEngine voxyWorld;
|
@Unique
|
||||||
|
private WorldIdentifier identifier;
|
||||||
|
|
||||||
@Override
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
public WorldEngine getWorldEngine() {
|
private void voxy$injectIdentifier(MutableWorldProperties properties,
|
||||||
return this.voxyWorld;
|
RegistryKey<World> key,
|
||||||
|
DynamicRegistryManager registryManager,
|
||||||
|
RegistryEntry<DimensionType> dimensionEntry,
|
||||||
|
boolean isClient,
|
||||||
|
boolean debugWorld,
|
||||||
|
long seed,
|
||||||
|
int maxChainedNeighborUpdates,
|
||||||
|
CallbackInfo ci) {
|
||||||
|
this.identifier = new WorldIdentifier(key, seed, dimensionEntry.getKey().orElse(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setWorldEngine(WorldEngine engine) {
|
public WorldIdentifier voxy$getIdentifier() {
|
||||||
if (engine != null && this.voxyWorld != null) {
|
return this.identifier;
|
||||||
throw new IllegalStateException("WorldEngine not null");
|
|
||||||
}
|
|
||||||
this.voxyWorld = engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdownEngine() {
|
|
||||||
if (this.voxyWorld != null && this.voxyWorld.instanceIn != null) {
|
|
||||||
this.voxyWorld.instanceIn.stopWorld(this.voxyWorld);
|
|
||||||
this.setWorldEngine(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user