World importer uses service system

This commit is contained in:
mcrcortex
2024-08-07 01:47:22 +10:00
parent 6eed71656d
commit 8797ee56a8
6 changed files with 121 additions and 58 deletions

View File

@@ -193,15 +193,18 @@ public class VoxelCore {
public boolean createWorldImporter(World mcWorld, File worldPath) { public boolean createWorldImporter(World mcWorld, File worldPath) {
if (this.importer != null) { if (this.importer != null) {
this.importer = new WorldImporter(this.world, mcWorld, this.serviceThreadPool);
}
if (this.importer.isBusy()) {
return false; return false;
} }
var importer = new WorldImporter(this.world, mcWorld);
var bossBar = new ClientBossBar(MathHelper.randomUuid(), Text.of("Voxy world importer"), 0.0f, BossBar.Color.GREEN, BossBar.Style.PROGRESS, false, false, false); var bossBar = new ClientBossBar(MathHelper.randomUuid(), 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); MinecraftClient.getInstance().inGameHud.getBossBarHud().bossBars.put(bossBar.getUuid(), bossBar);
importer.importWorldAsyncStart(worldPath, 4, (a,b)-> this.importer.importWorldAsyncStart(worldPath, (a,b)->
MinecraftClient.getInstance().executeSync(()-> { MinecraftClient.getInstance().executeSync(()-> {
bossBar.setPercent(((float) a)/((float) b)); bossBar.setPercent(((float) a)/((float) b));
bossBar.setName(Text.of("Voxy import: "+ a+"/"+b + " region files")); bossBar.setName(Text.of("Voxy import: "+ a+"/"+b + " chunks"));
}), }),
()-> { ()-> {
MinecraftClient.getInstance().executeSync(()-> { MinecraftClient.getInstance().executeSync(()-> {
@@ -210,9 +213,7 @@ public class VoxelCore {
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg)); MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.literal(msg));
System.err.println(msg); System.err.println(msg);
}); });
this.importer = null;
}); });
this.importer = importer;
return true; return true;
} }

View File

@@ -6,6 +6,8 @@ import me.cortex.voxy.common.voxelization.VoxelizedSection;
import me.cortex.voxy.common.voxelization.WorldConversionFactory; import me.cortex.voxy.common.voxelization.WorldConversionFactory;
import me.cortex.voxy.common.world.WorldEngine; import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.other.Mipper; import me.cortex.voxy.common.world.other.Mipper;
import me.cortex.voxy.common.world.thread.ServiceSlice;
import me.cortex.voxy.common.world.thread.ServiceThreadPool;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
@@ -28,6 +30,8 @@ import java.nio.ByteOrder;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -36,6 +40,7 @@ import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
public class WorldImporter { public class WorldImporter {
public interface UpdateCallback { public interface UpdateCallback {
void update(int finished, int outof); void update(int finished, int outof);
} }
@@ -43,12 +48,16 @@ public class WorldImporter {
private final WorldEngine world; private final WorldEngine world;
private final ReadableContainer<RegistryEntry<Biome>> defaultBiomeProvider; private final ReadableContainer<RegistryEntry<Biome>> defaultBiomeProvider;
private final Codec<ReadableContainer<RegistryEntry<Biome>>> biomeCodec; private final Codec<ReadableContainer<RegistryEntry<Biome>>> biomeCodec;
private final AtomicInteger totalRegions = new AtomicInteger(); private final AtomicInteger totalChunks = new AtomicInteger();
private final AtomicInteger regionsProcessed = new AtomicInteger(); private final AtomicInteger chunksProcessed = new AtomicInteger();
private final ConcurrentLinkedDeque<Runnable> jobQueue = new ConcurrentLinkedDeque<>();
private final ServiceSlice threadPool;
private volatile boolean isRunning; private volatile boolean isRunning;
public WorldImporter(WorldEngine worldEngine, World mcWorld) { public WorldImporter(WorldEngine worldEngine, World mcWorld, ServiceThreadPool servicePool) {
this.world = worldEngine; this.world = worldEngine;
this.threadPool = servicePool.createService("World importer", 1, ()-> ()->jobQueue.poll().run(), ()->this.world.savingService.getTaskCount() < 4000);
var biomeRegistry = mcWorld.getRegistryManager().get(RegistryKeys.BIOME); var biomeRegistry = mcWorld.getRegistryManager().get(RegistryKeys.BIOME);
var defaultBiome = biomeRegistry.entryOf(BiomeKeys.PLAINS); var defaultBiome = biomeRegistry.entryOf(BiomeKeys.PLAINS);
@@ -103,13 +112,17 @@ public class WorldImporter {
public void shutdown() { public void shutdown() {
this.isRunning = false; this.isRunning = false;
try {this.worker.join();} catch (InterruptedException e) {throw new RuntimeException(e);} try {this.worker.join();} catch (InterruptedException e) {throw new RuntimeException(e);}
this.threadPool.shutdown();
} }
private Thread worker; private volatile Thread worker;
public void importWorldAsyncStart(File directory, int threads, UpdateCallback updateCallback, Runnable onCompletion) { private UpdateCallback updateCallback;
public void importWorldAsyncStart(File directory, UpdateCallback updateCallback, Runnable onCompletion) {
this.totalChunks.set(0);
this.chunksProcessed.set(0);
this.updateCallback = updateCallback;
this.worker = new Thread(() -> { this.worker = new Thread(() -> {
this.isRunning = true; this.isRunning = true;
var workers = new ForkJoinPool(threads);
var files = directory.listFiles(); var files = directory.listFiles();
for (var file : files) { for (var file : files) {
if (!file.isFile()) { if (!file.isFile()) {
@@ -123,29 +136,32 @@ public class WorldImporter {
} }
int rx = Integer.parseInt(sections[1]); int rx = Integer.parseInt(sections[1]);
int rz = Integer.parseInt(sections[2]); int rz = Integer.parseInt(sections[2]);
this.totalRegions.addAndGet(1); try {
workers.submit(() -> { this.importRegionFile(file.toPath(), rx, rz);
try { } catch (IOException e) {
if (!this.isRunning) { throw new RuntimeException(e);
return; }
} while ((this.totalChunks.get()-this.chunksProcessed.get() > 10_000) && this.isRunning) {
this.importRegionFile(file.toPath(), rx, rz); Thread.onSpinWait();
int regionsProcessedCount = this.regionsProcessed.addAndGet(1); }
updateCallback.update(regionsProcessedCount, this.totalRegions.get()); if (!this.isRunning) {
} catch ( return;
Exception e) { }
e.printStackTrace(); }
} this.threadPool.blockTillEmpty();
}); while (this.chunksProcessed.get() != this.totalChunks.get() && this.isRunning) {
Thread.onSpinWait();
} }
workers.shutdown();
try {
workers.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {}
onCompletion.run(); onCompletion.run();
this.worker = null;
}); });
this.worker.setName("World importer"); this.worker.setName("World importer");
this.worker.start(); this.worker.start();
}
public boolean isBusy() {
return this.worker != null;
} }
private void importRegionFile(Path file, int x, int z) throws IOException { private void importRegionFile(Path file, int x, int z) throws IOException {
@@ -171,6 +187,7 @@ public class WorldImporter {
var data = MemoryUtil.memAlloc(sectorCount*4096).order(ByteOrder.BIG_ENDIAN); var data = MemoryUtil.memAlloc(sectorCount*4096).order(ByteOrder.BIG_ENDIAN);
fileStream.read(data, sectorStart*4096L); fileStream.read(data, sectorStart*4096L);
data.flip(); data.flip();
boolean addedToQueue = false;
{ {
int m = data.getInt(); int m = data.getInt();
byte b = data.get(); byte b = data.get();
@@ -188,19 +205,34 @@ public class WorldImporter {
} else if (n < 0) { } else if (n < 0) {
System.err.println("Declared size of chunk is negative"); System.err.println("Declared size of chunk is negative");
} else { } else {
try (var decompressedData = this.decompress(b, new ByteBufferBackedInputStream(data))) { addedToQueue = true;
if (decompressedData == null) { this.jobQueue.add(()-> {
System.err.println("Error decompressing chunk data"); if (!this.isRunning) {
} else { return;
var nbt = NbtIo.readCompound(decompressedData);
this.importChunkNBT(nbt);
} }
} try {
try (var decompressedData = this.decompress(b, new ByteBufferBackedInputStream(data))) {
if (decompressedData == null) {
System.err.println("Error decompressing chunk data");
} else {
var nbt = NbtIo.readCompound(decompressedData);
this.importChunkNBT(nbt);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
MemoryUtil.memFree(data);
}
});
this.totalChunks.incrementAndGet();
this.threadPool.execute();
} }
} }
} }
if (!addedToQueue) {
MemoryUtil.memFree(data); MemoryUtil.memFree(data);
}
} }
MemoryUtil.memFree(sectorsSavesBB); MemoryUtil.memFree(sectorsSavesBB);
@@ -230,6 +262,8 @@ public class WorldImporter {
System.err.println("Exception importing world chunk:"); System.err.println("Exception importing world chunk:");
e.printStackTrace(); e.printStackTrace();
} }
this.updateCallback.update(this.chunksProcessed.incrementAndGet(), this.totalChunks.get());
} }
private static int getIndex(int x, int y, int z) { private static int getIndex(int x, int y, int z) {
@@ -291,13 +325,6 @@ public class WorldImporter {
WorldConversionFactory.mipSection(csec, this.world.getMapper()); WorldConversionFactory.mipSection(csec, this.world.getMapper());
this.world.insertUpdate(csec); this.world.insertUpdate(csec);
while (this.world.savingService.getTaskCount() > 4000) {
try {
Thread.sleep(250);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
} }
} }

View File

@@ -25,7 +25,7 @@ public class UnsafeUtil {
//Copy the entire length of src to the dst memory where dst is a byte array (source length from dst) //Copy the entire length of src to the dst memory where dst is a byte array (source length from dst)
public static void memcpy(long src, byte[] dst) { public static void memcpy(long src, byte[] dst) {
UNSAFE.copyMemory(0, src, dst, BYTE_ARRAY_BASE_OFFSET, dst.length); UNSAFE.copyMemory(null, src, dst, BYTE_ARRAY_BASE_OFFSET, dst.length);
} }
//Copy the entire length of src to the dst memory where src is a byte array (source length from src) //Copy the entire length of src to the dst memory where src is a byte array (source length from src)

View File

@@ -1,5 +1,6 @@
package me.cortex.voxy.common.world; package me.cortex.voxy.common.world;
import it.unimi.dsi.fastutil.longs.Long2ShortFunction;
import it.unimi.dsi.fastutil.longs.Long2ShortOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ShortOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import me.cortex.voxy.common.util.MemoryBuffer; import me.cortex.voxy.common.util.MemoryBuffer;
@@ -33,28 +34,32 @@ public class SaveLoadSystem {
var data = section.copyData(); var data = section.copyData();
var compressed = new short[data.length]; var compressed = new short[data.length];
Long2ShortOpenHashMap LUT = new Long2ShortOpenHashMap(data.length); Long2ShortOpenHashMap LUT = new Long2ShortOpenHashMap(data.length);
LongArrayList LUTVAL = new LongArrayList(); LUT.defaultReturnValue((short) -1);
long[] lutValues = new long[32*16*16];//If there are more than this many states in a section... im concerned
short lutIndex = 0;
long pHash = 99; long pHash = 99;
for (int i = 0; i < data.length; i++) { for (int i = 0; i < data.length; i++) {
long block = data[i]; long block = data[i];
short mapping = LUT.computeIfAbsent(block, id->{ short mapping = LUT.putIfAbsent(block, lutIndex);
LUTVAL.add(id); if (mapping == -1) {
return (short)(LUTVAL.size()-1); mapping = lutIndex++;
}); lutValues[mapping] = block;
}
compressed[lin2z(i)] = mapping; compressed[lin2z(i)] = mapping;
pHash *= 127817112311121L; pHash *= 127817112311121L;
pHash ^= pHash>>31; pHash ^= pHash>>31;
pHash += 9918322711L; pHash += 9918322711L;
pHash ^= block; pHash ^= block;
} }
long[] lut = LUTVAL.toLongArray();
MemoryBuffer raw = new MemoryBuffer(compressed.length*2L+lut.length*8L+512); MemoryBuffer raw = new MemoryBuffer(compressed.length*2L+lutIndex*8L+512);
long ptr = raw.address; long ptr = raw.address;
long hash = section.key^(lut.length*1293481298141L); long hash = section.key^(lutIndex*1293481298141L);
MemoryUtil.memPutLong(ptr, section.key); ptr += 8; MemoryUtil.memPutLong(ptr, section.key); ptr += 8;
MemoryUtil.memPutInt(ptr, lut.length); ptr += 8; MemoryUtil.memPutInt(ptr, lutIndex); ptr += 4;
for (long id : lut) { for (int i = 0; i < lutIndex; i++) {
long id = lutValues[i];
MemoryUtil.memPutLong(ptr, id); ptr += 8; MemoryUtil.memPutLong(ptr, id); ptr += 8;
hash *= 1230987149811L; hash *= 1230987149811L;
hash += 12831; hash += 12831;

View File

@@ -7,6 +7,7 @@ import net.minecraft.text.Text;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier; import java.util.function.Supplier;
public class ServiceSlice extends TrackedObject { public class ServiceSlice extends TrackedObject {
@@ -18,9 +19,12 @@ public class ServiceSlice extends TrackedObject {
final Semaphore jobCount = new Semaphore(0); final Semaphore jobCount = new Semaphore(0);
private final Runnable[] runningCtxs; private final Runnable[] runningCtxs;
private final AtomicInteger activeCount = new AtomicInteger(); private final AtomicInteger activeCount = new AtomicInteger();
private final AtomicInteger jobCount2 = new AtomicInteger();
private final BooleanSupplier condition;
ServiceSlice(ServiceThreadPool threadPool, Supplier<Runnable> workerGenerator, String name, int weightPerJob) { ServiceSlice(ServiceThreadPool threadPool, Supplier<Runnable> workerGenerator, String name, int weightPerJob, BooleanSupplier condition) {
this.threadPool = threadPool; this.threadPool = threadPool;
this.condition = condition;
this.runningCtxs = new Runnable[threadPool.getThreadCount()]; this.runningCtxs = new Runnable[threadPool.getThreadCount()];
this.workerGenerator = workerGenerator; this.workerGenerator = workerGenerator;
this.name = name; this.name = name;
@@ -28,6 +32,11 @@ public class ServiceSlice extends TrackedObject {
} }
boolean doRun(int threadIndex) { boolean doRun(int threadIndex) {
//If executable
if (!this.condition.getAsBoolean()) {
return false;
}
//Run this thread once if possible //Run this thread once if possible
if (!this.jobCount.tryAcquire()) { if (!this.jobCount.tryAcquire()) {
return false; return false;
@@ -65,6 +74,7 @@ public class ServiceSlice extends TrackedObject {
if (this.activeCount.decrementAndGet() < 0) { if (this.activeCount.decrementAndGet() < 0) {
throw new IllegalStateException("Alive count negative!"); throw new IllegalStateException("Alive count negative!");
} }
this.jobCount2.decrementAndGet();
} }
return true; return true;
} }
@@ -75,6 +85,7 @@ public class ServiceSlice extends TrackedObject {
throw new IllegalStateException("Tried to do work on a dead service"); throw new IllegalStateException("Tried to do work on a dead service");
} }
this.jobCount.release(); this.jobCount.release();
this.jobCount2.incrementAndGet();
this.threadPool.execute(this); this.threadPool.execute(this);
} }
@@ -104,4 +115,18 @@ public class ServiceSlice extends TrackedObject {
public boolean hasJobs() { public boolean hasJobs() {
return this.jobCount.availablePermits() != 0; return this.jobCount.availablePermits() != 0;
} }
public void blockTillEmpty() {
while (this.activeCount.get() != 0 && this.alive) {
while (this.jobCount2.get() != 0 && this.alive) {
Thread.onSpinWait();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Thread.yield();
}
}
} }

View File

@@ -3,6 +3,7 @@ package me.cortex.voxy.common.world.thread;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -30,10 +31,14 @@ public class ServiceThreadPool {
} }
public synchronized ServiceSlice createService(String name, int weight, Supplier<Runnable> workGenerator) { public synchronized ServiceSlice createService(String name, int weight, Supplier<Runnable> workGenerator) {
return this.createService(name, weight, workGenerator, ()->true);
}
public synchronized ServiceSlice createService(String name, int weight, Supplier<Runnable> workGenerator, BooleanSupplier executionCondition) {
var current = this.serviceSlices; var current = this.serviceSlices;
var newList = new ServiceSlice[current.length + 1]; var newList = new ServiceSlice[current.length + 1];
System.arraycopy(current, 0, newList, 0, current.length); System.arraycopy(current, 0, newList, 0, current.length);
var service = new ServiceSlice(this, workGenerator, name, weight); var service = new ServiceSlice(this, workGenerator, name, weight, executionCondition);
newList[current.length] = service; newList[current.length] = service;
this.serviceSlices = newList; this.serviceSlices = newList;
return service; return service;