start
This commit is contained in:
@@ -0,0 +1,268 @@
|
|||||||
|
package me.cortex.voxy.client.core.rendering.hierachical;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.core.rendering.ISectionWatcher;
|
||||||
|
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||||
|
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||||
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.VarHandle;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
import java.util.concurrent.locks.StampedLock;
|
||||||
|
|
||||||
|
//An "async host" for a NodeManager, has specific synchonius entry and exit points
|
||||||
|
// 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 {
|
||||||
|
private static final VarHandle RESULT_HANDLE;
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
RESULT_HANDLE = MethodHandles.lookup().findVarHandle(AsyncNodeManager.class, "results", SyncResults.class);
|
||||||
|
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Thread thread;
|
||||||
|
private final StampedLock lock = new StampedLock();
|
||||||
|
private volatile boolean running = true;
|
||||||
|
|
||||||
|
private final NodeManager manager;
|
||||||
|
|
||||||
|
private final AtomicInteger workCounter = new AtomicInteger();
|
||||||
|
|
||||||
|
private volatile SyncResults results = null;
|
||||||
|
|
||||||
|
public AsyncNodeManager(int maxNodeCount, ISectionWatcher watcher) {//Note the current implmentation of ISectionWatcher is threadsafe
|
||||||
|
this.thread = new Thread(()->{
|
||||||
|
while (this.running) {
|
||||||
|
this.run();
|
||||||
|
}
|
||||||
|
//TODO: cleanup here? maybe?
|
||||||
|
});
|
||||||
|
this.thread.setName("Async Node Manager");
|
||||||
|
//TODO: modify BasicSectionGeometryManager to support async updates
|
||||||
|
this.manager = new NodeManager(maxNodeCount, null, watcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run() {
|
||||||
|
if (this.workCounter.get() == 0) {
|
||||||
|
LockSupport.park();
|
||||||
|
if (this.workCounter.get() == 0) {//No work
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: limit the number of jobs based on if the amount of updates to be submitted to the render thread gets to large
|
||||||
|
|
||||||
|
int workDone = 0;
|
||||||
|
do {
|
||||||
|
var job = this.childUpdateQueue.poll();
|
||||||
|
if (job == null)
|
||||||
|
break;
|
||||||
|
workDone++;
|
||||||
|
this.manager.processChildChange(job.key, job.getNonEmptyChildren());
|
||||||
|
job.release();
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
do {
|
||||||
|
var job = this.geometryUpdateQueue.poll();
|
||||||
|
if (job == null)
|
||||||
|
break;
|
||||||
|
workDone++;
|
||||||
|
this.manager.processGeometryResult(job);
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
do {
|
||||||
|
var job = this.requestBatchQueue.poll();
|
||||||
|
if (job == null)
|
||||||
|
break;
|
||||||
|
workDone++;
|
||||||
|
long ptr = job.address;
|
||||||
|
int count = MemoryUtil.memGetInt(ptr); ptr+=4;
|
||||||
|
if (job.size < count * 8L + 4) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
long pos = ((long)MemoryUtil.memGetInt(ptr))<<32; ptr += 4;
|
||||||
|
pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr)); ptr += 4;
|
||||||
|
this.manager.processRequest(pos);
|
||||||
|
}
|
||||||
|
job.free();
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
do {
|
||||||
|
var job = this.removeBatchQueue.poll();
|
||||||
|
if (job == null)
|
||||||
|
break;
|
||||||
|
workDone++;
|
||||||
|
long ptr = job.address;
|
||||||
|
int count = MemoryUtil.memGetInt(ptr); ptr+=4;
|
||||||
|
if (job.size < count * 8L + 4) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
long pos = ((long)MemoryUtil.memGetInt(ptr))<<32; ptr += 4;
|
||||||
|
pos |= Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr)); ptr += 4;
|
||||||
|
this.manager.removeNodeGeometry(pos);
|
||||||
|
}
|
||||||
|
job.free();
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
|
||||||
|
if (this.workCounter.addAndGet(-workDone)<0) {
|
||||||
|
throw new IllegalStateException("Work counter less than zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
//=====================
|
||||||
|
//process output events and atomically sync to results
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Events into manager
|
||||||
|
//manager.insertTopLevelNode();
|
||||||
|
//manager.removeTopLevelNode();
|
||||||
|
|
||||||
|
//manager.removeNodeGeometry();
|
||||||
|
|
||||||
|
//manager.processRequest();
|
||||||
|
//manager.processChildChange();
|
||||||
|
//manager.processGeometryResult();
|
||||||
|
|
||||||
|
|
||||||
|
//Outputs from manager
|
||||||
|
//manager.setClear();
|
||||||
|
//manager.setTLNCallbacks();
|
||||||
|
|
||||||
|
//manager.writeChanges()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Run in a loop, process all the input events, collect the output events merge with previous and publish
|
||||||
|
// note: inner event processing is a loop, is.. should be synced to attomic/volatile variable that is being watched
|
||||||
|
// when frametime comes around, want to exit out as quick as possible, or make the event publishing
|
||||||
|
// "effectivly immediately", that is, atomicly swap out the render side event updates
|
||||||
|
|
||||||
|
//like
|
||||||
|
// var current = <new events>
|
||||||
|
// var old = getAndSet(this.events, null);
|
||||||
|
// if (old != null) {current = merge(old, current);}
|
||||||
|
// getAndSet(this.events, current);
|
||||||
|
// if (old == null) {cleanAllEventsUpToThisPoint();}//(i.e. clear any buffers or maps containing data revolving around uncommited render thread data events)
|
||||||
|
|
||||||
|
// this creates a lock free event update loop, allowing the render thread to never stall on waiting
|
||||||
|
|
||||||
|
//TODO: NOTE: THIS MUST BE A SINGLE OBJECT THAT IS EXCHANGED
|
||||||
|
// for it to be effectivly synchonized all outgoing events/effects _MUST_ happen at the same time
|
||||||
|
// for this to be lock free an entire object containing ALL the events that must be synced must be exchanged
|
||||||
|
|
||||||
|
|
||||||
|
//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)
|
||||||
|
|
||||||
|
|
||||||
|
var prev = RESULT_HANDLE.getAndSet(this, null);
|
||||||
|
//TODO: merge results
|
||||||
|
SyncResults results = null;
|
||||||
|
if (!RESULT_HANDLE.compareAndSet(this, null, results)) {
|
||||||
|
throw new IllegalArgumentException("Should always have null");
|
||||||
|
}
|
||||||
|
if (prev == null) {
|
||||||
|
//Clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================================
|
||||||
|
//Incoming events
|
||||||
|
//TODO: add atomic counters for each event type probably
|
||||||
|
private final ConcurrentLinkedDeque<MemoryBuffer> requestBatchQueue = new ConcurrentLinkedDeque<>();
|
||||||
|
private final ConcurrentLinkedDeque<WorldSection> childUpdateQueue = new ConcurrentLinkedDeque<>();
|
||||||
|
private final ConcurrentLinkedDeque<BuiltSection> geometryUpdateQueue = new ConcurrentLinkedDeque<>();
|
||||||
|
|
||||||
|
private final ConcurrentLinkedDeque<MemoryBuffer> removeBatchQueue = new ConcurrentLinkedDeque<>();
|
||||||
|
|
||||||
|
private void addWork() {
|
||||||
|
if (!this.running) throw new IllegalStateException("Not running");
|
||||||
|
this.workCounter.incrementAndGet();
|
||||||
|
LockSupport.unpark(this.thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submitRequestBatch(MemoryBuffer batch) {
|
||||||
|
this.requestBatchQueue.add(batch);
|
||||||
|
this.addWork();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submitChildChange(WorldSection section) {
|
||||||
|
section.acquire();//We must acquire the section before putting in the queue
|
||||||
|
this.childUpdateQueue.add(section);
|
||||||
|
this.addWork();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submitGeometryResult(BuiltSection geometry) {
|
||||||
|
this.geometryUpdateQueue.add(geometry);
|
||||||
|
this.addWork();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submitRemoveBatch(MemoryBuffer batch) {
|
||||||
|
this.removeBatchQueue.add(batch);
|
||||||
|
this.addWork();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTopLevel(long section) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeTopLevel(long section) {
|
||||||
|
|
||||||
|
}
|
||||||
|
//==================================================================================================================
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
this.thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (!this.running) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
this.running = false;
|
||||||
|
LockSupport.unpark(this.thread);
|
||||||
|
try {
|
||||||
|
while (this.thread.isAlive()) {
|
||||||
|
LockSupport.unpark(this.thread);
|
||||||
|
this.thread.join(1000);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO CLEAN
|
||||||
|
}
|
||||||
|
|
||||||
|
//Primary synchronization
|
||||||
|
public void tick() {
|
||||||
|
var results = RESULT_HANDLE.getAndSet(this, null);//Acquire the results
|
||||||
|
if (results == null) {//There are no new results to process, return
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Results object, which is to be synced between the render thread and worker thread
|
||||||
|
private static final class SyncResults {
|
||||||
|
//Contains
|
||||||
|
// geometry uploads and id invalidations and the data
|
||||||
|
// node ids to invalidate/update and its data
|
||||||
|
// top level node ids to add/remove
|
||||||
|
// cleaner move operations
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user