Add support for importing directly from zip files
This commit is contained in:
@@ -4,15 +4,12 @@ import com.mojang.blaze3d.systems.RenderSystem;
|
|||||||
import me.cortex.voxy.client.config.VoxyConfig;
|
import me.cortex.voxy.client.config.VoxyConfig;
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
import me.cortex.voxy.client.core.model.ColourDepthTextureData;
|
|
||||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||||
import me.cortex.voxy.client.core.model.ModelTextureBakery;
|
|
||||||
import me.cortex.voxy.client.core.rendering.*;
|
import me.cortex.voxy.client.core.rendering.*;
|
||||||
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory4;
|
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory4;
|
||||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||||
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
|
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
|
||||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||||
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
|
|
||||||
import me.cortex.voxy.client.core.util.IrisUtil;
|
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
||||||
import me.cortex.voxy.client.taskbar.Taskbar;
|
import me.cortex.voxy.client.taskbar.Taskbar;
|
||||||
@@ -24,7 +21,7 @@ import me.cortex.voxy.common.thread.ServiceThreadPool;
|
|||||||
import me.cortex.voxy.common.world.WorldSection;
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
import me.cortex.voxy.common.world.other.Mapper;
|
import me.cortex.voxy.common.world.other.Mapper;
|
||||||
import me.cortex.voxy.commonImpl.VoxyCommon;
|
import me.cortex.voxy.commonImpl.VoxyCommon;
|
||||||
import net.minecraft.block.Blocks;
|
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.gui.hud.ClientBossBar;
|
import net.minecraft.client.gui.hud.ClientBossBar;
|
||||||
import net.minecraft.client.render.Camera;
|
import net.minecraft.client.render.Camera;
|
||||||
@@ -37,11 +34,12 @@ import net.minecraft.world.World;
|
|||||||
import net.minecraft.world.chunk.WorldChunk;
|
import net.minecraft.world.chunk.WorldChunk;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.lwjgl.opengl.GL11;
|
import org.lwjgl.opengl.GL11;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL30C.*;
|
import static org.lwjgl.opengl.GL30C.*;
|
||||||
|
|
||||||
@@ -73,8 +71,7 @@ public class VoxelCore {
|
|||||||
private final PostProcessing postProcessing;
|
private final PostProcessing postProcessing;
|
||||||
private final ServiceThreadPool serviceThreadPool;
|
private final ServiceThreadPool serviceThreadPool;
|
||||||
|
|
||||||
private WorldImporter importer;
|
public final WorldImportWrapper importer;
|
||||||
private UUID importerBossBarUUID;
|
|
||||||
|
|
||||||
public VoxelCore(ContextSelectionSystem.Selection worldSelection) {
|
public VoxelCore(ContextSelectionSystem.Selection worldSelection) {
|
||||||
var cfg = worldSelection.getConfig();
|
var cfg = worldSelection.getConfig();
|
||||||
@@ -83,6 +80,8 @@ public class VoxelCore {
|
|||||||
this.world = worldSelection.createEngine(this.serviceThreadPool);
|
this.world = worldSelection.createEngine(this.serviceThreadPool);
|
||||||
Logger.info("Initializing voxy core");
|
Logger.info("Initializing voxy core");
|
||||||
|
|
||||||
|
this.importer = new WorldImportWrapper(this.serviceThreadPool, this.world);
|
||||||
|
|
||||||
//Trigger the shared index buffer loading
|
//Trigger the shared index buffer loading
|
||||||
SharedIndexBuffer.INSTANCE.id();
|
SharedIndexBuffer.INSTANCE.id();
|
||||||
Capabilities.init();//Ensure clinit is called
|
Capabilities.init();//Ensure clinit is called
|
||||||
@@ -240,10 +239,7 @@ public class VoxelCore {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
//this.world.getMapper().forceResaveStates();
|
//this.world.getMapper().forceResaveStates();
|
||||||
if (this.importer != null) {
|
this.importer.shutdown();
|
||||||
Logger.info("Shutting down importer");
|
|
||||||
try {this.importer.shutdown();this.importer = null;} catch (Exception e) {Logger.error("Error shutting down importer", e);}
|
|
||||||
}
|
|
||||||
Logger.info("Shutting down rendering");
|
Logger.info("Shutting down rendering");
|
||||||
try {this.renderer.shutdown();} catch (Exception e) {Logger.error("Error shutting down renderer", e);}
|
try {this.renderer.shutdown();} catch (Exception e) {Logger.error("Error shutting down renderer", e);}
|
||||||
Logger.info("Shutting down post processor");
|
Logger.info("Shutting down post processor");
|
||||||
@@ -253,47 +249,6 @@ public class VoxelCore {
|
|||||||
Logger.info("Shutting down service thread pool");
|
Logger.info("Shutting down service thread pool");
|
||||||
this.serviceThreadPool.shutdown();
|
this.serviceThreadPool.shutdown();
|
||||||
Logger.info("Voxel core shut down");
|
Logger.info("Voxel core shut down");
|
||||||
//Remove bossbar
|
|
||||||
if (this.importerBossBarUUID != null) {
|
|
||||||
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.remove(this.importerBossBarUUID);
|
|
||||||
Taskbar.INSTANCE.setIsNone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean createWorldImporter(World mcWorld, File worldPath) {
|
|
||||||
if (this.importer == null) {
|
|
||||||
this.importer = new WorldImporter(this.world, mcWorld, this.serviceThreadPool);
|
|
||||||
}
|
|
||||||
if (this.importer.isBusy()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Taskbar.INSTANCE.setProgress(0,10000);
|
|
||||||
Taskbar.INSTANCE.setIsProgression();
|
|
||||||
|
|
||||||
this.importerBossBarUUID = MathHelper.randomUuid();
|
|
||||||
var bossBar = new ClientBossBar(this.importerBossBarUUID, Text.of("Voxy world importer"), 0.0f, BossBar.Color.GREEN, BossBar.Style.PROGRESS, false, false, false);
|
|
||||||
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.put(bossBar.getUuid(), bossBar);
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
this.importer.importWorldAsyncStart(worldPath, (a,b)->
|
|
||||||
MinecraftClient.getInstance().executeSync(()-> {
|
|
||||||
Taskbar.INSTANCE.setProgress(a, b);
|
|
||||||
bossBar.setPercent(((float) a)/((float) b));
|
|
||||||
bossBar.setName(Text.of("Voxy import: "+ a+"/"+b + " chunks"));
|
|
||||||
}),
|
|
||||||
chunkCount -> {
|
|
||||||
MinecraftClient.getInstance().executeSync(()-> {
|
|
||||||
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.remove(this.importerBossBarUUID);
|
|
||||||
this.importerBossBarUUID = null;
|
|
||||||
long delta = System.currentTimeMillis() - start;
|
|
||||||
|
|
||||||
String msg = "Voxy world import finished in " + (delta/1000) + " seconds, averaging " + (chunkCount/(delta/1000)) + " chunks per second";
|
|
||||||
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg));
|
|
||||||
Logger.info(msg);
|
|
||||||
Taskbar.INSTANCE.setIsNone();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorldEngine getWorldEngine() {
|
public WorldEngine getWorldEngine() {
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package me.cortex.voxy.client.core;
|
||||||
|
|
||||||
|
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.importers.WorldImporter;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.gui.hud.ClientBossBar;
|
||||||
|
import net.minecraft.entity.boss.BossBar;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
import net.minecraft.util.math.MathHelper;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class WorldImportWrapper {
|
||||||
|
|
||||||
|
private WorldImporter importer;
|
||||||
|
private final ServiceThreadPool pool;
|
||||||
|
private final WorldEngine world;
|
||||||
|
private UUID importerBossBarUUID;
|
||||||
|
|
||||||
|
public WorldImportWrapper(ServiceThreadPool pool, WorldEngine world) {
|
||||||
|
this.pool = pool;
|
||||||
|
this.world = world;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
Logger.info("Shutting down importer");
|
||||||
|
try {this.importer.shutdown();this.importer = null;} catch (Exception e) {Logger.error("Error shutting down importer", e);}
|
||||||
|
|
||||||
|
//Remove bossbar
|
||||||
|
if (this.importerBossBarUUID != null) {
|
||||||
|
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.remove(this.importerBossBarUUID);
|
||||||
|
Taskbar.INSTANCE.setIsNone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopImporter() {
|
||||||
|
if (this.isImporterRunning()) {
|
||||||
|
this.importer.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IImporterFactory {
|
||||||
|
void create(WorldImporter importer, WorldImporter.UpdateCallback updateCallback, Consumer<Integer> onCompletion);
|
||||||
|
}
|
||||||
|
public boolean createWorldImporter(World mcWorld, IImporterFactory factory) {
|
||||||
|
if (this.importer == null) {
|
||||||
|
this.importer = new WorldImporter(this.world, mcWorld, this.pool);
|
||||||
|
}
|
||||||
|
if (this.importer.isBusy()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Taskbar.INSTANCE.setProgress(0,10000);
|
||||||
|
Taskbar.INSTANCE.setIsProgression();
|
||||||
|
|
||||||
|
this.importerBossBarUUID = MathHelper.randomUuid();
|
||||||
|
var bossBar = new ClientBossBar(this.importerBossBarUUID, Text.of("Voxy world importer"), 0.0f, BossBar.Color.GREEN, BossBar.Style.PROGRESS, false, false, false);
|
||||||
|
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.put(bossBar.getUuid(), bossBar);
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
factory.create(this.importer, (a, b)->
|
||||||
|
MinecraftClient.getInstance().executeSync(()-> {
|
||||||
|
Taskbar.INSTANCE.setProgress(a, b);
|
||||||
|
bossBar.setPercent(((float) a)/((float) b));
|
||||||
|
bossBar.setName(Text.of("Voxy import: "+ a+"/"+b + " chunks"));
|
||||||
|
}),
|
||||||
|
chunkCount -> {
|
||||||
|
MinecraftClient.getInstance().executeSync(()-> {
|
||||||
|
MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.remove(this.importerBossBarUUID);
|
||||||
|
this.importerBossBarUUID = null;
|
||||||
|
long delta = System.currentTimeMillis() - start;
|
||||||
|
|
||||||
|
String msg = "Voxy world import finished in " + (delta/1000) + " seconds, averaging " + (chunkCount/(delta/1000)) + " chunks per second";
|
||||||
|
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg));
|
||||||
|
Logger.info(msg);
|
||||||
|
Taskbar.INSTANCE.setIsNone();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isImporterRunning() {
|
||||||
|
return this.importer != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ 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.IGetVoxelCore;
|
import me.cortex.voxy.client.core.IGetVoxelCore;
|
||||||
|
import me.cortex.voxy.client.core.VoxelCore;
|
||||||
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
|
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
|
||||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||||
import net.minecraft.client.MinecraftClient;
|
import net.minecraft.client.MinecraftClient;
|
||||||
@@ -14,43 +15,62 @@ import net.minecraft.command.CommandSource;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
|
||||||
public class WorldImportCommand {
|
public class WorldImportCommand {
|
||||||
public static LiteralArgumentBuilder<FabricClientCommandSource> register() {
|
public static LiteralArgumentBuilder<FabricClientCommandSource> register() {
|
||||||
return ClientCommandManager.literal("voxy").then(
|
return ClientCommandManager.literal("voxy").requires((ctx)-> ((IGetVoxelCore)MinecraftClient.getInstance().worldRenderer).getVoxelCore() != null)
|
||||||
ClientCommandManager.literal("import")
|
.then(ClientCommandManager.literal("import")
|
||||||
.then(ClientCommandManager.literal("world")
|
.then(ClientCommandManager.literal("world")
|
||||||
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
|
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
|
||||||
.suggests(WorldImportCommand::importWorldSuggester)
|
.suggests(WorldImportCommand::importWorldSuggester)
|
||||||
.executes(WorldImportCommand::importWorld)))
|
.executes(WorldImportCommand::importWorld)))
|
||||||
.then(ClientCommandManager.literal("bobby")
|
.then(ClientCommandManager.literal("bobby")
|
||||||
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
|
.then(ClientCommandManager.argument("world_name", StringArgumentType.string())
|
||||||
|
.suggests(WorldImportCommand::importBobbySuggester)
|
||||||
.executes(WorldImportCommand::importBobby)))
|
.executes(WorldImportCommand::importBobby)))
|
||||||
.then(ClientCommandManager.literal("raw")
|
.then(ClientCommandManager.literal("raw")
|
||||||
.then(ClientCommandManager.argument("path", StringArgumentType.string())
|
.then(ClientCommandManager.argument("path", StringArgumentType.string())
|
||||||
.executes(WorldImportCommand::importRaw))));
|
.executes(WorldImportCommand::importRaw)))
|
||||||
|
.then(ClientCommandManager.literal("zip")
|
||||||
|
.then(ClientCommandManager.argument("zipPath", StringArgumentType.string())
|
||||||
|
.executes(WorldImportCommand::importZip)
|
||||||
|
.then(ClientCommandManager.argument("innerPath", StringArgumentType.string())
|
||||||
|
.executes(WorldImportCommand::importZip))))
|
||||||
|
.then(ClientCommandManager.literal("cancel")
|
||||||
|
.requires((ctx)->((IGetVoxelCore)MinecraftClient.getInstance().worldRenderer).getVoxelCore().importer.isImporterRunning())
|
||||||
|
.executes((ctx)->{((IGetVoxelCore)MinecraftClient.getInstance().worldRenderer).getVoxelCore().importer.stopImporter(); return 0;}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean fileBasedImporter(File directory) {
|
||||||
|
var instance = MinecraftClient.getInstance();
|
||||||
|
var core = ((IGetVoxelCore)instance.worldRenderer).getVoxelCore();
|
||||||
|
return core.importer.createWorldImporter(instance.player.clientWorld,
|
||||||
|
(importer, up, done)->importer.importRegionDirectoryAsyncStart(directory, up, done));
|
||||||
|
}
|
||||||
|
|
||||||
private static int importRaw(CommandContext<FabricClientCommandSource> ctx) {
|
private static int importRaw(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
var instance = MinecraftClient.getInstance();
|
return fileBasedImporter(new File(ctx.getArgument("path", String.class)))?0:1;
|
||||||
var file = new File(ctx.getArgument("path", String.class));
|
|
||||||
((IGetVoxelCore)instance.worldRenderer).getVoxelCore().createWorldImporter(MinecraftClient.getInstance().player.clientWorld, file);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int importBobby(CommandContext<FabricClientCommandSource> ctx) {
|
private static int importBobby(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
var instance = MinecraftClient.getInstance();
|
|
||||||
var file = new File(".bobby").toPath().resolve(ctx.getArgument("world_name", String.class)).toFile();
|
var file = new File(".bobby").toPath().resolve(ctx.getArgument("world_name", String.class)).toFile();
|
||||||
((IGetVoxelCore)instance.worldRenderer).getVoxelCore().createWorldImporter(MinecraftClient.getInstance().player.clientWorld, file);
|
return fileBasedImporter(file)?0:1;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CompletableFuture<Suggestions> importWorldSuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
|
private static CompletableFuture<Suggestions> importWorldSuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
|
||||||
|
return fileDirectorySuggester(MinecraftClient.getInstance().runDirectory.toPath().resolve("saves"), sb);
|
||||||
|
}
|
||||||
|
private static CompletableFuture<Suggestions> importBobbySuggester(CommandContext<FabricClientCommandSource> ctx, SuggestionsBuilder sb) {
|
||||||
|
return fileDirectorySuggester(new File(".bobby").toPath(), sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompletableFuture<Suggestions> fileDirectorySuggester(Path dir, SuggestionsBuilder sb) {
|
||||||
try {
|
try {
|
||||||
var worlds = Files.list(MinecraftClient.getInstance().runDirectory.toPath().resolve("saves")).toList();
|
var worlds = Files.list(dir).toList();
|
||||||
for (var world : worlds) {
|
for (var world : worlds) {
|
||||||
if (!world.toFile().isDirectory()) {
|
if (!world.toFile().isDirectory()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -71,10 +91,24 @@ public class WorldImportCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int importWorld(CommandContext<FabricClientCommandSource> ctx) {
|
private static int importWorld(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
var instance = MinecraftClient.getInstance();
|
|
||||||
var file = new File("saves").toPath().resolve(ctx.getArgument("world_name", String.class)).resolve("region").toFile();
|
var file = new File("saves").toPath().resolve(ctx.getArgument("world_name", String.class)).resolve("region").toFile();
|
||||||
((IGetVoxelCore)instance.worldRenderer).getVoxelCore().createWorldImporter(MinecraftClient.getInstance().player.clientWorld, file);
|
return fileBasedImporter(file)?0:1;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int importZip(CommandContext<FabricClientCommandSource> ctx) {
|
||||||
|
var zip = new File(ctx.getArgument("zipPath", String.class));
|
||||||
|
var innerDir = "region/";
|
||||||
|
try {
|
||||||
|
innerDir = ctx.getArgument("innerPath", String.class);
|
||||||
|
} catch (Exception e) {}
|
||||||
|
|
||||||
|
var instance = MinecraftClient.getInstance();
|
||||||
|
var core = ((IGetVoxelCore)instance.worldRenderer).getVoxelCore();
|
||||||
|
if (core == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
String finalInnerDir = innerDir;
|
||||||
|
return core.importer.createWorldImporter(instance.player.clientWorld,
|
||||||
|
(importer, up, done)->importer.importZippedRegionDirectoryAsyncStart(zip, finalInnerDir, up, done))?0:1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -252,6 +252,10 @@ public final class WorldSection {
|
|||||||
return prev != next;
|
return prev != next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void _unsafeSetNonEmptyChildren(byte nonEmptyChildren) {
|
||||||
|
NON_EMPTY_CHILD_HANDLE.set(this, nonEmptyChildren);
|
||||||
|
}
|
||||||
|
|
||||||
public static WorldSection _createRawUntrackedUnsafeSection(int lvl, int x, int y, int z) {
|
public static WorldSection _createRawUntrackedUnsafeSection(int lvl, int x, int y, int z) {
|
||||||
return new WorldSection(lvl, x, y, z, null);
|
return new WorldSection(lvl, x, y, z, null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package me.cortex.voxy.commonImpl.importers;
|
package me.cortex.voxy.commonImpl.importers;
|
||||||
|
|
||||||
import com.mojang.serialization.Codec;
|
import com.mojang.serialization.Codec;
|
||||||
import me.cortex.voxy.common.util.ByteBufferBackedInputStream;
|
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
import me.cortex.voxy.common.util.MemoryBuffer;
|
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||||
import me.cortex.voxy.common.util.UnsafeUtil;
|
import me.cortex.voxy.common.util.UnsafeUtil;
|
||||||
@@ -26,13 +25,15 @@ import net.minecraft.world.chunk.ChunkStatus;
|
|||||||
import net.minecraft.world.chunk.PalettedContainer;
|
import net.minecraft.world.chunk.PalettedContainer;
|
||||||
import net.minecraft.world.chunk.ReadableContainer;
|
import net.minecraft.world.chunk.ReadableContainer;
|
||||||
import net.minecraft.world.storage.ChunkCompressionFormat;
|
import net.minecraft.world.storage.ChunkCompressionFormat;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||||
|
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.channels.Channels;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@@ -127,37 +128,83 @@ public class WorldImporter {
|
|||||||
this.threadPool.shutdown();
|
this.threadPool.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private interface IImporterMethod <T> {
|
||||||
|
void importRegion(T file) throws Exception;
|
||||||
|
}
|
||||||
|
|
||||||
private volatile Thread worker;
|
private volatile Thread worker;
|
||||||
private UpdateCallback updateCallback;
|
private UpdateCallback updateCallback;
|
||||||
public void importWorldAsyncStart(File directory, UpdateCallback updateCallback, Consumer<Integer> onCompletion) {
|
public void importRegionDirectoryAsyncStart(File directory, UpdateCallback updateCallback, Consumer<Integer> onCompletion) {
|
||||||
|
var files = directory.listFiles((dir, name) -> {
|
||||||
|
var sections = name.split("\\.");
|
||||||
|
if (sections.length != 4 || (!sections[0].equals("r")) || (!sections[3].equals("mca"))) {
|
||||||
|
Logger.error("Unknown file: " + name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (files == null) {
|
||||||
|
onCompletion.accept(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Arrays.sort(files, File::compareTo);
|
||||||
|
this.importRegionsAsyncStart(files, this::importRegionFile, updateCallback, onCompletion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void importZippedRegionDirectoryAsyncStart(File zip, String innerDirectory, UpdateCallback updateCallback, Consumer<Integer> onCompletion) {
|
||||||
|
try {
|
||||||
|
innerDirectory = innerDirectory.replace("\\\\", "\\").replace("\\", "/");
|
||||||
|
var file = ZipFile.builder().setFile(zip).get();
|
||||||
|
ArrayList<ZipArchiveEntry> regions = new ArrayList<>();
|
||||||
|
for (var e = file.getEntries(); e.hasMoreElements();) {
|
||||||
|
var entry = e.nextElement();
|
||||||
|
if (entry.isDirectory()||!entry.getName().startsWith(innerDirectory)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var parts = entry.getName().split("/");
|
||||||
|
var name = parts[parts.length-1];
|
||||||
|
var sections = name.split("\\.");
|
||||||
|
if (sections.length != 4 || (!sections[0].equals("r")) || (!sections[3].equals("mca"))) {
|
||||||
|
Logger.error("Unknown file: " + name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
regions.add(entry);
|
||||||
|
}
|
||||||
|
this.importRegionsAsyncStart(regions.toArray(ZipArchiveEntry[]::new), (entry)->{
|
||||||
|
var buf = new MemoryBuffer(entry.getSize());
|
||||||
|
try (var channel = Channels.newChannel(file.getInputStream(entry))) {
|
||||||
|
if (channel.read(buf.asByteBuffer()) != buf.size) {
|
||||||
|
buf.free();
|
||||||
|
throw new IllegalStateException("Could not read full zip entry");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = entry.getName().split("/");
|
||||||
|
var name = parts[parts.length-1];
|
||||||
|
var sections = name.split("\\.");
|
||||||
|
this.importRegion(buf, Integer.parseInt(sections[1]), Integer.parseInt(sections[2]));
|
||||||
|
buf.free();
|
||||||
|
|
||||||
|
}, updateCallback, onCompletion);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void importRegionsAsyncStart(T[] regionFiles, IImporterMethod<T> importer, UpdateCallback updateCallback, Consumer<Integer> onCompletion) {
|
||||||
this.totalChunks.set(0);
|
this.totalChunks.set(0);
|
||||||
this.estimatedTotalChunks.set(0);
|
this.estimatedTotalChunks.set(0);
|
||||||
this.chunksProcessed.set(0);
|
this.chunksProcessed.set(0);
|
||||||
this.updateCallback = updateCallback;
|
this.updateCallback = updateCallback;
|
||||||
this.worker = new Thread(() -> {
|
this.worker = new Thread(() -> {
|
||||||
this.isRunning = true;
|
this.isRunning = true;
|
||||||
var files = directory.listFiles();
|
this.estimatedTotalChunks.addAndGet(regionFiles.length*1024);
|
||||||
if (files == null) {
|
for (var file : regionFiles) {
|
||||||
onCompletion.accept(0);
|
|
||||||
}
|
|
||||||
Arrays.sort(files, File::compareTo);
|
|
||||||
this.estimatedTotalChunks.addAndGet(files.length*1024);
|
|
||||||
for (var file : files) {
|
|
||||||
if (!file.isFile()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var name = file.getName();
|
|
||||||
var sections = name.split("\\.");
|
|
||||||
if (sections.length != 4 || (!sections[0].equals("r")) || (!sections[3].equals("mca"))) {
|
|
||||||
Logger.error("Unknown file: " + name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int rx = Integer.parseInt(sections[1]);
|
|
||||||
int rz = Integer.parseInt(sections[2]);
|
|
||||||
this.estimatedTotalChunks.addAndGet(-1024);
|
this.estimatedTotalChunks.addAndGet(-1024);
|
||||||
try {
|
try {
|
||||||
this.importRegionFile(file.toPath(), rx, rz);
|
importer.importRegion(file);
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
while ((this.totalChunks.get()-this.chunksProcessed.get() > 10_000) && this.isRunning) {
|
while ((this.totalChunks.get()-this.chunksProcessed.get() > 10_000) && this.isRunning) {
|
||||||
@@ -168,7 +215,9 @@ public class WorldImporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this.isRunning) {
|
if (!this.isRunning) {
|
||||||
|
this.threadPool.blockTillEmpty();
|
||||||
onCompletion.accept(this.totalChunks.get());
|
onCompletion.accept(this.totalChunks.get());
|
||||||
|
this.worker = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,31 +235,41 @@ public class WorldImporter {
|
|||||||
});
|
});
|
||||||
this.worker.setName("World importer");
|
this.worker.setName("World importer");
|
||||||
this.worker.start();
|
this.worker.start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBusy() {
|
public boolean isBusy() {
|
||||||
return this.worker != null;
|
return this.worker != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importRegionFile(Path file, int x, int z) throws IOException {
|
private void importRegionFile(File file) throws IOException {
|
||||||
try (var fileStream = FileChannel.open(file, StandardOpenOption.READ)) {
|
var name = file.getName();
|
||||||
|
var sections = name.split("\\.");
|
||||||
|
if (sections.length != 4 || (!sections[0].equals("r")) || (!sections[3].equals("mca"))) {
|
||||||
|
Logger.error("Unknown file: " + name);
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
int rx = Integer.parseInt(sections[1]);
|
||||||
|
int rz = Integer.parseInt(sections[2]);
|
||||||
|
|
||||||
|
try (var fileStream = FileChannel.open(file.toPath(), StandardOpenOption.READ)) {
|
||||||
var fileData = new MemoryBuffer(fileStream.size());
|
var fileData = new MemoryBuffer(fileStream.size());
|
||||||
if (fileStream.read(fileData.asByteBuffer(), 0) < 8192) {
|
if (fileStream.read(fileData.asByteBuffer(), 0) < 8192) {
|
||||||
fileData.free();
|
fileData.free();
|
||||||
Logger.warn("Header of region file invalid");
|
Logger.warn("Header of region file invalid");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.importRegionFile(fileData, x, z);
|
this.importRegion(fileData, rx, rz);
|
||||||
fileData.free();
|
fileData.free();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void importRegionFile(MemoryBuffer regionFile, int x, int z) throws IOException {
|
private void importRegion(MemoryBuffer regionFile, int x, int z) {
|
||||||
//if (true) return;
|
|
||||||
|
|
||||||
//Find and load all saved chunks
|
//Find and load all saved chunks
|
||||||
|
if (regionFile.size < 8192) {//File not big enough
|
||||||
|
Logger.warn("Header of region file invalid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (int idx = 0; idx < 1024; idx++) {
|
for (int idx = 0; idx < 1024; idx++) {
|
||||||
int sectorMeta = Integer.reverseBytes(MemoryUtil.memGetInt(regionFile.address+idx*4));//Assumes little endian
|
int sectorMeta = Integer.reverseBytes(MemoryUtil.memGetInt(regionFile.address+idx*4));//Assumes little endian
|
||||||
if (sectorMeta == 0) {
|
if (sectorMeta == 0) {
|
||||||
@@ -221,6 +280,10 @@ public class WorldImporter {
|
|||||||
int sectorCount = sectorMeta&((1<<8)-1);
|
int sectorCount = sectorMeta&((1<<8)-1);
|
||||||
|
|
||||||
//TODO: create memory copy for each section
|
//TODO: create memory copy for each section
|
||||||
|
if (regionFile.size < (sectorCount+sectorStart)*4096L) {
|
||||||
|
Logger.warn("Cannot access chunk sector as it goes out of bounds. start: " + sectorStart + " count: " + sectorCount + " fileSize: " + regionFile.size);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
var data = new MemoryBuffer(sectorCount*4096).cpyFrom(regionFile.address+sectorStart*4096L);
|
var data = new MemoryBuffer(sectorCount*4096).cpyFrom(regionFile.address+sectorStart*4096L);
|
||||||
|
|
||||||
boolean addedToQueue = false;
|
boolean addedToQueue = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user