attempted to improve ingest perfomance by only saving on section unload

This commit is contained in:
mcrcortex
2025-06-21 12:25:46 +10:00
parent ed181c1dcd
commit 1a7cd37741
4 changed files with 57 additions and 7 deletions

View File

@@ -190,6 +190,23 @@ public class ActiveSectionTracker {
void tryUnload(WorldSection section) { void tryUnload(WorldSection section) {
if (this.engine != null) this.engine.lastActiveTime = System.currentTimeMillis(); if (this.engine != null) this.engine.lastActiveTime = System.currentTimeMillis();
if (section.isDirty&&this.engine!=null) {
if (section.tryAcquire()) {
if (section.setNotDirty()) {//If the section is dirty we must enqueue for saving
this.engine.saveSection(section);
}
section.release(false);//Special
} else {
VarHandle.loadLoadFence();
if (section.isDirty) {
throw new IllegalStateException("Section was dirty but is also unloaded, this is very bad");
}
}
}
if (section.getRefCount() != 0) {
return;
}
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;

View File

@@ -118,8 +118,8 @@ public class WorldEngine {
if (this.dirtyCallback != null) { if (this.dirtyCallback != null) {
this.dirtyCallback.accept(section, changeState); this.dirtyCallback.accept(section, changeState);
} }
if (this.saveCallback != null) { if (!section.inSaveQueue) {
this.saveCallback.save(this, section); section.markDirty();
} }
} }
@@ -181,4 +181,11 @@ public class WorldEngine {
//TODO: maybe dont need to tick the last active time? //TODO: maybe dont need to tick the last active time?
this.lastActiveTime = System.currentTimeMillis(); this.lastActiveTime = System.currentTimeMillis();
} }
public void saveSection(WorldSection section) {
section.setNotDirty();
if (this.saveCallback != null) {
this.saveCallback.save(this, section);
}
}
} }

View File

@@ -22,12 +22,16 @@ public final class WorldSection {
static final VarHandle ATOMIC_STATE_HANDLE; static final VarHandle ATOMIC_STATE_HANDLE;
private static final VarHandle NON_EMPTY_CHILD_HANDLE; private static final VarHandle NON_EMPTY_CHILD_HANDLE;
private static final VarHandle NON_EMPTY_BLOCK_HANDLE; private static final VarHandle NON_EMPTY_BLOCK_HANDLE;
private static final VarHandle IN_SAVE_QUEUE_HANDLE;
private static final VarHandle IS_DIRTY_HANDLE;
static { static {
try { try {
ATOMIC_STATE_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "atomicState", int.class); ATOMIC_STATE_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "atomicState", int.class);
NON_EMPTY_CHILD_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "nonEmptyChildren", byte.class); NON_EMPTY_CHILD_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "nonEmptyChildren", byte.class);
NON_EMPTY_BLOCK_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "nonEmptyBlockCount", int.class); NON_EMPTY_BLOCK_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "nonEmptyBlockCount", int.class);
IN_SAVE_QUEUE_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "inSaveQueue", boolean.class);
IS_DIRTY_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "isDirty", boolean.class);
} catch (NoSuchFieldException | IllegalAccessException e) { } catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -55,7 +59,8 @@ public final class WorldSection {
volatile byte nonEmptyChildren; volatile byte nonEmptyChildren;
final ActiveSectionTracker tracker; final ActiveSectionTracker tracker;
public final AtomicBoolean inSaveQueue = new AtomicBoolean(); volatile boolean inSaveQueue;
volatile boolean isDirty;
//When the first bit is set it means its loaded //When the first bit is set it means its loaded
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -131,6 +136,10 @@ public final class WorldSection {
//TODO: add the ability to hint to the tracker that yes the section is unloaded, try to cache it in a secondary cache since it will be reused/needed later //TODO: add the ability to hint to the tracker that yes the section is unloaded, try to cache it in a secondary cache since it will be reused/needed later
public int release() { public int release() {
return release(true);
}
int release(boolean unload) {
int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, -2)) - 2; int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, -2)) - 2;
if (state < 1) { if (state < 1) {
throw new IllegalStateException("Section got into an invalid state"); throw new IllegalStateException("Section got into an invalid state");
@@ -138,7 +147,7 @@ public final class WorldSection {
if ((state & 1) == 0) { if ((state & 1) == 0) {
throw new IllegalStateException("Tried releasing a freed section"); throw new IllegalStateException("Tried releasing a freed section");
} }
if ((state>>1)==0) { if ((state>>1)==0 && unload) {
if (this.tracker != null) { if (this.tracker != null) {
this.tracker.tryUnload(this); this.tracker.tryUnload(this);
} else { } else {
@@ -271,4 +280,20 @@ public final class WorldSection {
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);
} }
public boolean exchangeIsInSaveQueue(boolean state) {
return ((boolean) IN_SAVE_QUEUE_HANDLE.compareAndExchange(this, !state, state)) == !state;
}
public void markDirty() {
IS_DIRTY_HANDLE.getAndSet(this, true);
}
public boolean setNotDirty() {
return (boolean) IS_DIRTY_HANDLE.getAndSet(this, false);
}
public boolean isFreed() {
return (((int)ATOMIC_STATE_HANDLE.get(this))&1)==0;
}
} }

View File

@@ -28,8 +28,9 @@ public class SectionSavingService {
var section = task.section; var section = task.section;
section.assertNotFree(); section.assertNotFree();
try { try {
section.inSaveQueue.set(false); if (section.exchangeIsInSaveQueue(false)) {
task.engine.storage.saveSection(section); task.engine.storage.saveSection(section);
}
} catch (Exception e) { } catch (Exception e) {
Logger.error("Voxy saver had an exception while executing please check logs and report error", e); Logger.error("Voxy saver had an exception while executing please check logs and report error", e);
} }
@@ -47,7 +48,7 @@ public class SectionSavingService {
public void enqueueSave(WorldEngine in, WorldSection section) { public void enqueueSave(WorldEngine in, WorldSection section) {
//If its not enqueued for saving then enqueue it //If its not enqueued for saving then enqueue it
if (!section.inSaveQueue.getAndSet(true)) { if (section.exchangeIsInSaveQueue(true)) {
//Acquire the section for use //Acquire the section for use
section.acquire(); section.acquire();