Continues work

This commit is contained in:
mcrcortex
2025-05-12 22:25:38 +10:00
parent d1957680e8
commit 0028e5ec8f
7 changed files with 123 additions and 35 deletions

View File

@@ -88,7 +88,7 @@ public class ModelBakerySubsystem {
do { do {
this.factory.addEntry(i); this.factory.addEntry(i);
j++; j++;
if (budget<(System.nanoTime() - start)+1000) if (25<j)//budget<(System.nanoTime() - start)+1000
break; break;
i = this.blockIdQueue.poll(); i = this.blockIdQueue.poll();
} while (i != null); } while (i != null);

View File

@@ -56,13 +56,15 @@ public class ChunkBoundRenderer {
} }
public void addSection(long pos) { public void addSection(long pos) {
if (!this.remQueue.remove(pos)) {
this.addQueue.add(pos); this.addQueue.add(pos);
this.remQueue.remove(pos); }
} }
public void removeSection(long pos) { public void removeSection(long pos) {
if (!this.addQueue.remove(pos)) {
this.remQueue.add(pos); this.remQueue.add(pos);
this.addQueue.remove(pos); }
} }
//Bind and render, changing as little gl state as possible so that the caller may configure how it wants to render //Bind and render, changing as little gl state as possible so that the caller may configure how it wants to render

View File

@@ -133,6 +133,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
DownloadStream.INSTANCE.tick(); DownloadStream.INSTANCE.tick();
this.nodeManager.tick(this.traversal.getNodeBuffer()); this.nodeManager.tick(this.traversal.getNodeBuffer());
//glFlush();
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here?? this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
@@ -184,6 +185,8 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
this.world.getMapper().setBiomeCallback(null); this.world.getMapper().setBiomeCallback(null);
this.world.getMapper().setStateCallback(null); this.world.getMapper().setStateCallback(null);
this.nodeManager.stop();
this.modelService.shutdown(); this.modelService.shutdown();
this.renderGen.shutdown(); this.renderGen.shutdown();
this.viewportSelector.free(); this.viewportSelector.free();
@@ -191,8 +194,6 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
this.traversal.free(); this.traversal.free();
this.nodeCleaner.free(); this.nodeCleaner.free();
this.nodeManager.stop();
this.geometryData.free(); this.geometryData.free();
} }

View File

@@ -266,6 +266,9 @@ public class RenderGenerationService {
public void enqueueTask(long pos) { public void enqueueTask(long pos) {
if (!this.threads.isAlive()) {
return;
}
boolean[] isOurs = new boolean[1]; boolean[] isOurs = new boolean[1];
long stamp = this.taskMapLock.writeLock(); long stamp = this.taskMapLock.writeLock();
BuildTask task = this.taskMap.computeIfAbsent(pos, p->{ BuildTask task = this.taskMap.computeIfAbsent(pos, p->{

View File

@@ -4,7 +4,6 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntConsumer; import it.unimi.dsi.fastutil.ints.IntConsumer;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongConsumer;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.ISectionWatcher; import me.cortex.voxy.client.core.rendering.ISectionWatcher;
@@ -13,6 +12,7 @@ import me.cortex.voxy.client.core.rendering.section.geometry.BasicAsyncGeometryM
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData; import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData; import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.client.core.rendering.util.UploadStream;
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.world.WorldSection; import me.cortex.voxy.common.world.WorldSection;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
@@ -30,16 +30,19 @@ import static me.cortex.voxy.client.core.rendering.section.geometry.BasicSection
// this is done off thread to reduce the amount of work done on the render thread, improving frame stability and reducing runtime overhead // this is done off thread to reduce the amount of work done on the render thread, improving frame stability and reducing runtime overhead
public class AsyncNodeManager { public class AsyncNodeManager {
private static final VarHandle RESULT_HANDLE; private static final VarHandle RESULT_HANDLE;
private static final VarHandle RESULT_CACHE_1_HANDLE;
private static final VarHandle RESULT_CACHE_2_HANDLE;
static { static {
try { try {
RESULT_HANDLE = MethodHandles.lookup().findVarHandle(AsyncNodeManager.class, "results", SyncResults.class); RESULT_HANDLE = MethodHandles.lookup().findVarHandle(AsyncNodeManager.class, "results", SyncResults.class);
RESULT_CACHE_1_HANDLE = MethodHandles.lookup().findVarHandle(AsyncNodeManager.class, "resultCache1", SyncResults.class);
RESULT_CACHE_2_HANDLE = MethodHandles.lookup().findVarHandle(AsyncNodeManager.class, "resultCache2", SyncResults.class);
} catch (NoSuchFieldException | IllegalAccessException e) { } catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
private final Thread thread; private final Thread thread;
private final StampedLock lock = new StampedLock();
public final int maxNodeCount; public final int maxNodeCount;
private volatile boolean running = true; private volatile boolean running = true;
@@ -50,10 +53,14 @@ public class AsyncNodeManager {
private final AtomicInteger workCounter = new AtomicInteger(); private final AtomicInteger workCounter = new AtomicInteger();
private volatile SyncResults results = null; private volatile SyncResults results = null;
private volatile SyncResults resultCache1 = new SyncResults();
private volatile SyncResults resultCache2 = new SyncResults();
//locals for during iteration //locals for during iteration
private final IntOpenHashSet tlnIdChange = new IntOpenHashSet();//"Encoded" add/remove id, first bit indicates if its add or remove, 1 is add private final IntOpenHashSet tlnIdChange = new IntOpenHashSet();//"Encoded" add/remove id, first bit indicates if its add or remove, 1 is add
private boolean needsWaitForSync = false;
public AsyncNodeManager(int maxNodeCount, ISectionWatcher watcher, IGeometryData geometryData) { public AsyncNodeManager(int maxNodeCount, ISectionWatcher watcher, IGeometryData geometryData) {
//Note the current implmentation of ISectionWatcher is threadsafe //Note the current implmentation of ISectionWatcher is threadsafe
@@ -64,10 +71,13 @@ public class AsyncNodeManager {
this.maxNodeCount = maxNodeCount; this.maxNodeCount = maxNodeCount;
this.thread = new Thread(()->{ this.thread = new Thread(()->{
try {
while (this.running) { while (this.running) {
this.run(); this.run();
} }
//TODO: cleanup here? maybe? } catch (Exception e) {
Logger.error("Critical error occurred in async processor, things will be broken", e);
}
}); });
this.thread.setName("Async Node Manager"); this.thread.setName("Async Node Manager");
@@ -91,15 +101,32 @@ public class AsyncNodeManager {
}); });
this.manager.setTLNCallbacks(id->{ this.manager.setTLNCallbacks(id->{
if (!this.tlnIdChange.remove(id)) { if (!this.tlnIdChange.remove(id)) {
this.tlnIdChange.add(id|(1<<31)); if (!this.tlnIdChange.add(id|(1<<31))) {
throw new IllegalStateException();
}
} }
}, id -> { }, id -> {
if (!this.tlnIdChange.remove(id|(1<<31))) { if (!this.tlnIdChange.remove(id|(1<<31))) {
this.tlnIdChange.add(id); if (!this.tlnIdChange.add(id)) {
throw new IllegalStateException();
}
} }
}); });
} }
private SyncResults getMakeResultObject() {
SyncResults resultSet = (SyncResults)RESULT_CACHE_1_HANDLE.getAndSet(this, null);
if (resultSet == null) {//Not in the first object
resultSet = (SyncResults)RESULT_CACHE_2_HANDLE.getAndSet(this, null);
}
if (resultSet == null) {
throw new IllegalStateException("There should always be an object in the result set cache pair");
}
//Reset everything to default
resultSet.reset();
return resultSet;
}
private void run() { private void run() {
if (this.workCounter.get() == 0) { if (this.workCounter.get() == 0) {
LockSupport.park(); LockSupport.park();
@@ -114,7 +141,7 @@ public class AsyncNodeManager {
//This is a funny thing, wait a bit, this allows for better batching, but this thread is independent of everything else so waiting a bit should be mostly ok //This is a funny thing, wait a bit, this allows for better batching, but this thread is independent of everything else so waiting a bit should be mostly ok
try { try {
Thread.sleep(20); Thread.sleep(10);
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -165,7 +192,7 @@ public class AsyncNodeManager {
job.release(); job.release();
} while (true); } while (true);
for (int limit = 0; limit < 100; limit++) {//Limit uploading for (int limit = 0; limit < 200; limit++) {//Limit uploading
var job = this.geometryUpdateQueue.poll(); var job = this.geometryUpdateQueue.poll();
if (job == null) if (job == null)
break; break;
@@ -214,9 +241,20 @@ public class AsyncNodeManager {
} while (true); } while (true);
if (this.workCounter.addAndGet(-workDone) < 0) { if (this.workCounter.addAndGet(-workDone) < 0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//Due to synchronization "issues", wait a millis (give up this time slice)
if (this.workCounter.get() < 0) {
throw new IllegalStateException("Work counter less than zero"); throw new IllegalStateException("Work counter less than zero");
} }
}
if (workDone == 0) {//Nothing happened, which is odd, but just return
return;
}
//===================== //=====================
//process output events and atomically sync to results //process output events and atomically sync to results
@@ -259,7 +297,7 @@ public class AsyncNodeManager {
//TODO: also note! this can be done for the processing of rendered out block models!! //TODO: also note! this can be done for the processing of rendered out block models!!
// (it might be able to also be put in this thread, maybe? but is proabably worth putting in own thread for latency reasons) // (it might be able to also be put in this thread, maybe? but is proabably worth putting in own thread for latency reasons)
if (this.needsWaitForSync) {
while (RESULT_HANDLE.get(this) != null && this.running) { while (RESULT_HANDLE.get(this) != null && this.running) {
try { try {
Thread.sleep(10); Thread.sleep(10);
@@ -267,12 +305,14 @@ public class AsyncNodeManager {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
}
var prev = (SyncResults) RESULT_HANDLE.getAndSet(this, null); var prev = (SyncResults) RESULT_HANDLE.getAndSet(this, null);
SyncResults results = null; SyncResults results = null;
if (prev == null) { if (prev == null) {
results = new SyncResults(); this.needsWaitForSync = false;
results = this.getMakeResultObject();
//Clear old data (if it exists), create a new result set //Clear old data (if it exists), create a new result set
results.tlnDelta.addAll(this.tlnIdChange); results.tlnDelta.addAll(this.tlnIdChange);
this.tlnIdChange.clear(); this.tlnIdChange.clear();
@@ -288,8 +328,9 @@ public class AsyncNodeManager {
var iter = this.tlnIdChange.intIterator(); var iter = this.tlnIdChange.intIterator();
while (iter.hasNext()) { while (iter.hasNext()) {
int val = iter.nextInt(); int val = iter.nextInt();
results.tlnDelta.remove(val ^ (1 << 31));//Remove opposite if (!results.tlnDelta.remove(val ^ (1 << 31))) {//Remove opposite
results.tlnDelta.add(val);//Add this results.tlnDelta.add(val);//Add this if not added
}
} }
this.tlnIdChange.clear(); this.tlnIdChange.clear();
} }
@@ -336,6 +377,7 @@ public class AsyncNodeManager {
this.geometryManager.writeMetadata(val, placeId*32L + results.geometryIdUpdateData.address); this.geometryManager.writeMetadata(val, placeId*32L + results.geometryIdUpdateData.address);
} }
ids.clear(); ids.clear();
this.needsWaitForSync |= results.geometryIdUpdateMap.size()>250;
} }
//Node updates //Node updates
@@ -353,6 +395,7 @@ public class AsyncNodeManager {
this.manager.writeNode(val, placeId*16L + results.nodeIdUpdateData.address); this.manager.writeNode(val, placeId*16L + results.nodeIdUpdateData.address);
} }
ids.clear(); ids.clear();
this.needsWaitForSync |= results.nodeIdUpdateMap.size()>=512;
} }
} }
@@ -430,8 +473,14 @@ public class AsyncNodeManager {
if (doCommit) { if (doCommit) {
UploadStream.INSTANCE.commit(); UploadStream.INSTANCE.commit();
} }
results.nodeIdUpdateData.free();
results.geometryIdUpdateData.free(); //Insert the result set into the cache
if (!RESULT_CACHE_1_HANDLE.compareAndSet(this, null, results)) {
//Failed to insert into result set 1, insert it into result set 2
if (!RESULT_CACHE_2_HANDLE.compareAndSet(this, null, results)) {
throw new IllegalStateException("Could not insert result into cache");
}
}
} }
@@ -461,28 +510,35 @@ public class AsyncNodeManager {
} }
} }
public void submitRequestBatch(MemoryBuffer batch) { public void submitRequestBatch(MemoryBuffer batch) {//Only called from render thread
this.requestBatchQueue.add(batch); this.requestBatchQueue.add(batch);
this.addWork(); this.addWork();
} }
public void submitChildChange(WorldSection section) { public void submitChildChange(WorldSection section) {
if (!this.running) {
return;
}
section.acquire();//We must acquire the section before putting in the queue section.acquire();//We must acquire the section before putting in the queue
this.childUpdateQueue.add(section); this.childUpdateQueue.add(section);
this.addWork(); this.addWork();
} }
public void submitGeometryResult(BuiltSection geometry) { public void submitGeometryResult(BuiltSection geometry) {
if (!this.running) {
geometry.free();
return;
}
this.geometryUpdateQueue.add(geometry); this.geometryUpdateQueue.add(geometry);
this.addWork(); this.addWork();
} }
public void submitRemoveBatch(MemoryBuffer batch) { public void submitRemoveBatch(MemoryBuffer batch) {//Only called from render thread
this.removeBatchQueue.add(batch); this.removeBatchQueue.add(batch);
this.addWork(); this.addWork();
} }
public void addTopLevel(long section) { public void addTopLevel(long section) {//Only called from render thread
if (!this.running) throw new IllegalStateException("Not running"); if (!this.running) throw new IllegalStateException("Not running");
long stamp = this.tlnLock.writeLock(); long stamp = this.tlnLock.writeLock();
int state = this.tlnAdd.add(section)?1:0; int state = this.tlnAdd.add(section)?1:0;
@@ -495,7 +551,7 @@ public class AsyncNodeManager {
this.tlnLock.unlockWrite(stamp); this.tlnLock.unlockWrite(stamp);
} }
public void removeTopLevel(long section) { public void removeTopLevel(long section) {//Only called from render thread
if (!this.running) throw new IllegalStateException("Not running"); if (!this.running) throw new IllegalStateException("Not running");
long stamp = this.tlnLock.writeLock(); long stamp = this.tlnLock.writeLock();
int state = this.tlnRem.add(section)?1:0; int state = this.tlnRem.add(section)?1:0;
@@ -548,7 +604,23 @@ public class AsyncNodeManager {
buffer.free(); buffer.free();
} }
//TODO: CLEANUP the sync data! if (RESULT_HANDLE.get(this) != null) {
var result = (SyncResults)RESULT_HANDLE.getAndSet(this, null);
result.geometryUploads.forEach((a,b)->b.free());
result.nodeIdUpdateData.free();
result.geometryIdUpdateData.free();
}
if (RESULT_CACHE_1_HANDLE.get(this) != null) {//Clear cache 1
var result = (SyncResults)RESULT_CACHE_1_HANDLE.getAndSet(this, null);
result.nodeIdUpdateData.free();
result.geometryIdUpdateData.free();
}
if (RESULT_CACHE_2_HANDLE.get(this) != null) {//Clear cache 2
var result = (SyncResults)RESULT_CACHE_2_HANDLE.getAndSet(this, null);
result.nodeIdUpdateData.free();
result.geometryIdUpdateData.free();
}
} }
//Results object, which is to be synced between the render thread and worker thread //Results object, which is to be synced between the render thread and worker thread
@@ -579,5 +651,13 @@ public class AsyncNodeManager {
this.geometryIdUpdateMap.defaultReturnValue(-1); this.geometryIdUpdateMap.defaultReturnValue(-1);
} }
public void reset() {
this.nodeIdUpdateMap.clear();
this.currentMaxNodeId = 0;
this.tlnDelta.clear();
this.geometrySectionCount = 0;
this.geometryUploads.clear();
this.geometryIdUpdateMap.clear();
}
} }
} }

View File

@@ -132,7 +132,9 @@ public class HierarchicalOcclusionTraverser {
//Use clear buffer, yes know is a bad idea, TODO: replace //Use clear buffer, yes know is a bad idea, TODO: replace
//Add the new top level node to the queue //Add the new top level node to the queue
glClearNamedBufferSubData(this.topNodeIds.id, GL_R32UI, aid*4L, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{id}); glClearNamedBufferSubData(this.topNodeIds.id, GL_R32UI, aid*4L, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{id});
this.topNode2idxMapping.put(id, aid); if (this.topNode2idxMapping.put(id, aid) != -1) {
throw new IllegalStateException();
}
this.idx2topNodeMapping[aid] = id; this.idx2topNodeMapping[aid] = id;
} }

View File

@@ -93,7 +93,7 @@ public class ServiceSlice extends TrackedObject {
//Tells the system that a single instance of this service needs executing //Tells the system that a single instance of this service needs executing
public void execute() { public void execute() {
if (!this.alive) { if (!this.alive) {
Logger.error("Tried to do work on a dead service: " + this.name); Logger.error("Tried to do work on a dead service: " + this.name, new Throwable());
return; return;
} }
this.jobCount.release(); this.jobCount.release();