diff --git a/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java b/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java index 04ea1100..071a6866 100644 --- a/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java +++ b/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable; import java.lang.invoke.VarHandle; import java.util.Arrays; import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.StampedLock; public class ActiveSectionTracker { @@ -18,7 +19,7 @@ public class ActiveSectionTracker { //Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane private final Long2ObjectOpenHashMap>[] loadedSectionCache; - private final ReentrantLock[] locks;//TODO: replace with StampedLocks + private final StampedLock[] locks; private final SectionLoader loader; private final int lruSize; @@ -37,11 +38,11 @@ public class ActiveSectionTracker { this.loader = loader; this.loadedSectionCache = new Long2ObjectOpenHashMap[1<(cacheSize); - this.locks = new ReentrantLock[1<(1024); - this.locks[i] = new ReentrantLock(); + this.locks[i] = new StampedLock(); } } @@ -55,26 +56,37 @@ public class ActiveSectionTracker { final var lock = this.locks[index]; VolatileHolder holder = null; boolean isLoader = false; - WorldSection section; + WorldSection section = null; - lock.lock(); { - VarHandle.fullFence(); + long stamp = lock.readLock(); holder = cache.get(key); - if (holder == null) { + if (holder != null) {//Return already loaded entry + section = holder.obj; + if (section != null) { + section.acquire(); + lock.unlockRead(stamp); + return section; + } + lock.unlockRead(stamp); + } else {//Try to create holder holder = new VolatileHolder<>(); - cache.put(key, holder); - isLoader = true; + long ws = lock.tryConvertToWriteLock(stamp); + if (ws == 0) {//Failed to convert, unlock read and get write + lock.unlockRead(stamp); + stamp = lock.writeLock(); + } else { + stamp = ws; + } + var eHolder = cache.putIfAbsent(key, holder);//We put if absent because on failure to convert to write, it leaves race condition + lock.unlockWrite(stamp); + if (eHolder == null) {//We are the loader + isLoader = true; + } else { + holder = eHolder; + } } - section = holder.obj; - if (section != null) { - section.acquire(); - lock.unlock(); - return section; - } - VarHandle.fullFence(); } - lock.unlock(); if (isLoader) { synchronized (this.lruSecondaryCache) { @@ -128,6 +140,8 @@ public class ActiveSectionTracker { } } //lock.unlock(); + + //We failed everything, try get it again return this.acquire(key, nullOnEmpty); } } @@ -137,9 +151,8 @@ public class ActiveSectionTracker { final var cache = this.loadedSectionCache[index]; WorldSection sec = null; final var lock = this.locks[index]; - lock.lock(); + long stamp = lock.writeLock(); { - VarHandle.fullFence(); if (section.trySetFreed()) { var cached = cache.remove(section.key); var obj = cached.obj; @@ -151,9 +164,8 @@ public class ActiveSectionTracker { } sec = section; } - VarHandle.fullFence(); } - lock.unlock(); + lock.unlockWrite(stamp); if (sec != null) { WorldSection a; diff --git a/src/main/java/me/cortex/voxy/common/world/WorldEngine.java b/src/main/java/me/cortex/voxy/common/world/WorldEngine.java index ad8f11ea..c2aeca2a 100644 --- a/src/main/java/me/cortex/voxy/common/world/WorldEngine.java +++ b/src/main/java/me/cortex/voxy/common/world/WorldEngine.java @@ -51,7 +51,7 @@ public class WorldEngine { this.storage = storage; this.mapper = new Mapper(this.storage); //5 cache size bits means that the section tracker has 32 separate maps that it uses - this.sectionTracker = new ActiveSectionTracker(10, storage::loadSection, 2048, this); + this.sectionTracker = new ActiveSectionTracker(6, storage::loadSection, 2048, this); } public WorldSection acquireIfExists(int lvl, int x, int y, int z) { diff --git a/src/main/java/me/cortex/voxy/common/world/WorldSection.java b/src/main/java/me/cortex/voxy/common/world/WorldSection.java index 4b541185..9d06325c 100644 --- a/src/main/java/me/cortex/voxy/common/world/WorldSection.java +++ b/src/main/java/me/cortex/voxy/common/world/WorldSection.java @@ -119,10 +119,8 @@ public final class WorldSection { public int acquire(int count) { int state =((int) ATOMIC_STATE_HANDLE.getAndAdd(this, count<<1)) + (count<<1); - if (VERIFY_WORLD_SECTION_EXECUTION) { - if ((state & 1) == 0) { - throw new IllegalStateException("Tried to acquire unloaded section"); - } + if ((state & 1) == 0) { + throw new IllegalStateException("Tried to acquire unloaded section"); } return state>>1; } @@ -134,13 +132,11 @@ 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 public int release() { int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, -2)) - 2; - if (VERIFY_WORLD_SECTION_EXECUTION) { - if (state < 1) { - throw new IllegalStateException("Section got into an invalid state"); - } - if ((state & 1) == 0) { - throw new IllegalStateException("Tried releasing a freed section"); - } + if (state < 1) { + throw new IllegalStateException("Section got into an invalid state"); + } + if ((state & 1) == 0) { + throw new IllegalStateException("Tried releasing a freed section"); } if ((state>>1)==0) { if (this.tracker != null) { @@ -159,10 +155,8 @@ public final class WorldSection { //Returns true on success, false on failure boolean trySetFreed() { int witness = (int) ATOMIC_STATE_HANDLE.compareAndExchange(this, 1, 0); - if (VERIFY_WORLD_SECTION_EXECUTION) { - if ((witness & 1) == 0 && witness != 0) { - throw new IllegalStateException("Section marked as free but has refs"); - } + if ((witness & 1) == 0 && witness != 0) { + throw new IllegalStateException("Section marked as free but has refs"); } return witness == 1; }