From 3a800ec9cae27b8a7b7dcb62b30d63b382f286fb Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:05:58 +1000 Subject: [PATCH] Added build task removal --- .../building/RenderGenerationService.java | 76 +++++++++++-------- .../voxy/common/thread/ServiceSlice.java | 16 +++- .../voxy/common/thread/ServiceThreadPool.java | 5 ++ .../common/world/ActiveSectionTracker.java | 14 +++- .../cortex/voxy/common/world/WorldEngine.java | 9 +++ 5 files changed, 85 insertions(+), 35 deletions(-) diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java index 894290fb..b7c88170 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderGenerationService.java @@ -1,6 +1,7 @@ package me.cortex.voxy.client.core.rendering.building; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.longs.Long2ObjectFunction; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import me.cortex.voxy.client.core.model.IdNotYetComputedException; import me.cortex.voxy.client.core.model.ModelBakerySubsystem; @@ -16,8 +17,13 @@ import java.util.function.Supplier; //TODO: Add a render cache public class RenderGenerationService { - public interface TaskChecker {boolean check(int lvl, int x, int y, int z);} - private record BuildTask(long position, Supplier sectionSupplier, boolean[] hasDoneModelRequest) {} + private static final class BuildTask { + final long position; + boolean hasDoneModelRequest; + private BuildTask(long position) { + this.position = position; + } + } private final Long2ObjectLinkedOpenHashMap taskQueue = new Long2ObjectLinkedOpenHashMap<>(); @@ -61,6 +67,10 @@ public class RenderGenerationService { } } + private WorldSection acquireSection(long pos) { + return this.world.acquireIfExists(pos); + } + //TODO: add a generated render data cache private void processJob(RenderDataFactory factory) { BuildTask task; @@ -72,7 +82,7 @@ public class RenderGenerationService { } } //long time = BuiltSection.getTime(); - var section = task.sectionSupplier.get(); + var section = this.acquireSection(task.position); if (section == null) { this.resultConsumer.accept(BuiltSection.empty(task.position)); return; @@ -85,7 +95,7 @@ public class RenderGenerationService { if (!this.modelBakery.factory.hasModelForBlockId(e.id)) { this.modelBakery.requestBlockBake(e.id); } - if (task.hasDoneModelRequest[0]) { + if (task.hasDoneModelRequest) { try { Thread.sleep(10); } catch (InterruptedException ex) { @@ -95,11 +105,18 @@ public class RenderGenerationService { //The reason for the extra id parameter is that we explicitly add/check against the exception id due to e.g. requesting accross a chunk boarder wont be captured in the request this.computeAndRequestRequiredModels(section, e.id); } - //We need to reinsert the build task into the queue - //System.err.println("Render task failed to complete due to un-computed client id"); - synchronized (this.taskQueue) { - var queuedTask = this.taskQueue.computeIfAbsent(section.key, (a)->task); - queuedTask.hasDoneModelRequest[0] = true;//Mark (or remark) the section as having chunks requested + + { + //We need to reinsert the build task into the queue + BuildTask queuedTask; + synchronized (this.taskQueue) { + queuedTask = this.taskQueue.putIfAbsent(section.key, task); + } + if (queuedTask == null) { + queuedTask = task; + } + + queuedTask.hasDoneModelRequest = true;//Mark (or remark) the section as having chunks requested if (queuedTask == task) {//use the == not .equal to see if we need to release a permit this.threads.execute();//Since we put in queue, release permit @@ -113,37 +130,34 @@ public class RenderGenerationService { } } - public void enqueueTask(int lvl, int x, int y, int z) { - this.enqueueTask(lvl, x, y, z, (l,x1,y1,z1)->true); - } - public void enqueueTask(long position) { - this.enqueueTask(position, (l,x1,y1,z1)->true); - } - - public void enqueueTask(int lvl, int x, int y, int z, TaskChecker checker) { - this.enqueueTask(WorldEngine.getWorldSectionId(lvl, x, y, z), checker); - } - - public void enqueueTask(long ikey, TaskChecker checker) { + public void enqueueTask(long pos) { synchronized (this.taskQueue) { - this.taskQueue.computeIfAbsent(ikey, key->{ + this.taskQueue.computeIfAbsent(pos, key->{ this.threads.execute(); - return new BuildTask(ikey, ()->{ - if (checker.check(WorldEngine.getLevel(ikey), WorldEngine.getX(ikey), WorldEngine.getY(ikey), WorldEngine.getZ(ikey))) { - return this.world.acquireIfExists(WorldEngine.getLevel(ikey), WorldEngine.getX(ikey), WorldEngine.getY(ikey), WorldEngine.getZ(ikey)); - } else { - return null; - } - }, new boolean[1]); + return new BuildTask(key); }); } } - public int getTaskCount() { - return this.threads.getJobCount(); + public void removeTask(long pos) { + BuildTask task; + synchronized (this.taskQueue) { + task = this.taskQueue.remove(pos); + } + if (task != null) { + if (!this.threads.steal()) { + throw new IllegalStateException("Failed to steal a task!!!"); + } + } } + /* + public void enqueueTask(int lvl, int x, int y, int z) { + this.enqueueTask(WorldEngine.getWorldSectionId(lvl, x, y, z)); + } + */ + public void shutdown() { this.threads.shutdown(); diff --git a/src/main/java/me/cortex/voxy/common/thread/ServiceSlice.java b/src/main/java/me/cortex/voxy/common/thread/ServiceSlice.java index 0bd0681c..736c0b4c 100644 --- a/src/main/java/me/cortex/voxy/common/thread/ServiceSlice.java +++ b/src/main/java/me/cortex/voxy/common/thread/ServiceSlice.java @@ -77,7 +77,9 @@ public class ServiceSlice extends TrackedObject { if (this.activeCount.decrementAndGet() < 0) { throw new IllegalStateException("Alive count negative!"); } - this.jobCount2.decrementAndGet(); + if (this.jobCount2.decrementAndGet() < 0) { + throw new IllegalStateException("Job count negative!"); + } } return true; } @@ -133,4 +135,16 @@ public class ServiceSlice extends TrackedObject { Thread.yield(); } } + + //Steal a job, if there is no job available return false + public boolean steal() { + if (!this.jobCount.tryAcquire()) { + return false; + } + if (this.jobCount2.decrementAndGet() < 0) { + throw new IllegalStateException("Job count negative!!!"); + } + this.threadPool.steal(this); + return true; + } } diff --git a/src/main/java/me/cortex/voxy/common/thread/ServiceThreadPool.java b/src/main/java/me/cortex/voxy/common/thread/ServiceThreadPool.java index f7d263b0..90ca02d4 100644 --- a/src/main/java/me/cortex/voxy/common/thread/ServiceThreadPool.java +++ b/src/main/java/me/cortex/voxy/common/thread/ServiceThreadPool.java @@ -102,6 +102,11 @@ public class ServiceThreadPool { this.jobCounter.release(1); } + void steal(ServiceSlice service) { + this.totalJobWeight.addAndGet(-service.weightPerJob); + this.jobCounter.acquireUninterruptibly(1); + } + private void worker(int threadId) { long seed = 1234342; int revolvingSelector = 0; 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 0a47e6ee..12ae98f8 100644 --- a/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java +++ b/src/main/java/me/cortex/voxy/common/world/ActiveSectionTracker.java @@ -30,7 +30,10 @@ public class ActiveSectionTracker { } public WorldSection acquire(int lvl, int x, int y, int z, boolean nullOnEmpty) { - long key = WorldEngine.getWorldSectionId(lvl, x, y, z); + return this.acquire(WorldEngine.getWorldSectionId(lvl, x, y, z), nullOnEmpty); + } + + public WorldSection acquire(long key, boolean nullOnEmpty) { var cache = this.loadedSectionCache[this.getCacheArrayIndex(key)]; VolatileHolder holder = null; boolean isLoader = false; @@ -50,7 +53,12 @@ public class ActiveSectionTracker { //If this thread was the one to create the reference then its the thread to load the section if (isLoader) { - var section = new WorldSection(lvl, x, y, z, this); + var section = new WorldSection(WorldEngine.getLevel(key), + WorldEngine.getX(key), + WorldEngine.getY(key), + WorldEngine.getZ(key), + this); + int status = -1;//this.dataCache.load(section); if (status == -1) {//Cache miss status = this.loader.load(section); @@ -85,7 +93,7 @@ public class ActiveSectionTracker { return section; } } - return this.acquire(lvl, x, y, z, nullOnEmpty); + return this.acquire(key, nullOnEmpty); } } 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 52ba5183..128d1f17 100644 --- a/src/main/java/me/cortex/voxy/common/world/WorldEngine.java +++ b/src/main/java/me/cortex/voxy/common/world/WorldEngine.java @@ -17,6 +17,7 @@ public class WorldEngine { public static final int UPDATE_TYPE_BLOCK_BIT = 1; public static final int UPDATE_TYPE_CHILD_EXISTENCE_BIT = 2; public static final int UPDATE_FLAGS = UPDATE_TYPE_BLOCK_BIT | UPDATE_TYPE_CHILD_EXISTENCE_BIT; + public interface ISectionChangeCallback {void accept(WorldSection section, int updateFlags);} @@ -77,6 +78,14 @@ public class WorldEngine { return this.sectionTracker.acquire(lvl, x, y, z, false); } + public WorldSection acquire(long pos) { + return this.sectionTracker.acquire(pos, false); + } + + public WorldSection acquireIfExists(long pos) { + return this.sectionTracker.acquire(pos, true); + } + //TODO: Fixme/optimize, cause as the lvl gets higher, the size of x,y,z gets smaller so i can dynamically compact the format // depending on the lvl, which should optimize colisions and whatnot public static long getWorldSectionId(int lvl, int x, int y, int z) {