Rework to SectionRouter
This commit is contained in:
@@ -2,9 +2,9 @@ package me.cortex.voxy.client.core.rendering;
|
|||||||
|
|
||||||
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
|
||||||
import me.cortex.voxy.client.core.model.ModelStore;
|
import me.cortex.voxy.client.core.model.ModelStore;
|
||||||
|
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||||
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
|
||||||
import me.cortex.voxy.client.core.rendering.building.SectionUpdateRouter;
|
import me.cortex.voxy.client.core.rendering.building.SectionUpdateRouter;
|
||||||
import me.cortex.voxy.client.core.rendering.building.SectionUpdate;
|
|
||||||
import me.cortex.voxy.client.core.rendering.hierachical2.HierarchicalNodeManager;
|
import me.cortex.voxy.client.core.rendering.hierachical2.HierarchicalNodeManager;
|
||||||
import me.cortex.voxy.client.core.rendering.hierachical2.HierarchicalOcclusionTraverser;
|
import me.cortex.voxy.client.core.rendering.hierachical2.HierarchicalOcclusionTraverser;
|
||||||
import me.cortex.voxy.client.core.rendering.section.AbstractSectionRenderer;
|
import me.cortex.voxy.client.core.rendering.section.AbstractSectionRenderer;
|
||||||
@@ -12,13 +12,14 @@ import me.cortex.voxy.client.core.rendering.section.IUsesMeshlets;
|
|||||||
import me.cortex.voxy.client.core.rendering.section.MDICSectionRenderer;
|
import me.cortex.voxy.client.core.rendering.section.MDICSectionRenderer;
|
||||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
|
import me.cortex.voxy.common.util.MessageQueue;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||||
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
import net.minecraft.client.render.Camera;
|
import net.minecraft.client.render.Camera;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL42.*;
|
import static org.lwjgl.opengl.GL42.*;
|
||||||
|
|
||||||
@@ -37,7 +38,8 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
private final ModelBakerySubsystem modelService;
|
private final ModelBakerySubsystem modelService;
|
||||||
private final RenderGenerationService renderGen;
|
private final RenderGenerationService renderGen;
|
||||||
|
|
||||||
private final ConcurrentLinkedDeque<SectionUpdate> sectionUpdateQueue = new ConcurrentLinkedDeque<>();
|
private final MessageQueue<WorldSection> sectionUpdateQueue;
|
||||||
|
private final MessageQueue<BuiltSection> geometryUpdateQueue;
|
||||||
|
|
||||||
public RenderService(WorldEngine world, ServiceThreadPool serviceThreadPool) {
|
public RenderService(WorldEngine world, ServiceThreadPool serviceThreadPool) {
|
||||||
this.modelService = new ModelBakerySubsystem(world.getMapper());
|
this.modelService = new ModelBakerySubsystem(world.getMapper());
|
||||||
@@ -47,23 +49,25 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
this.sectionRenderer = (T) createSectionRenderer(this.modelService.getStore(),1<<19, (1L<<30)-1024);
|
this.sectionRenderer = (T) createSectionRenderer(this.modelService.getStore(),1<<19, (1L<<30)-1024);
|
||||||
|
|
||||||
//Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard
|
//Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard
|
||||||
var positionFilterForwarder = new SectionUpdateRouter();
|
var router = new SectionUpdateRouter();
|
||||||
|
|
||||||
this.nodeManager = new HierarchicalNodeManager(1<<21, this.sectionRenderer.getGeometryManager(), positionFilterForwarder);
|
this.nodeManager = new HierarchicalNodeManager(1<<21, this.sectionRenderer.getGeometryManager(), router);
|
||||||
|
|
||||||
|
this.sectionUpdateQueue = new MessageQueue<>(section -> {
|
||||||
|
byte childExistence = section.getNonEmptyChildren();
|
||||||
|
section.release();//TODO: move this to another thread (probably a service job to free, this is because freeing can cause a DB save which should not happen on the render thread)
|
||||||
|
this.nodeManager.processChildChange(section.key, childExistence);
|
||||||
|
});
|
||||||
|
this.geometryUpdateQueue = new MessageQueue<>(this.nodeManager::processGeometryResult);
|
||||||
|
|
||||||
this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport);
|
this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport);
|
||||||
this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool, this.sectionUpdateQueue::add, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets);
|
this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool, this.geometryUpdateQueue::push, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets);
|
||||||
|
|
||||||
positionFilterForwarder.setCallbacks(this.renderGen::enqueueTask, section -> {
|
router.setCallbacks(this.renderGen::enqueueTask, this.sectionUpdateQueue::push);
|
||||||
long time = SectionUpdate.getTime();
|
|
||||||
byte childExistence = section.getNonEmptyChildren();
|
|
||||||
|
|
||||||
this.sectionUpdateQueue.add(new SectionUpdate(section.key, time, null, childExistence));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, 512);
|
this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, 512);
|
||||||
|
|
||||||
world.setDirtyCallback(positionFilterForwarder::maybeForward);
|
world.setDirtyCallback(router::forward);
|
||||||
|
|
||||||
Arrays.stream(world.getMapper().getBiomeEntries()).forEach(this.modelService::addBiome);
|
Arrays.stream(world.getMapper().getBiomeEntries()).forEach(this.modelService::addBiome);
|
||||||
world.getMapper().setBiomeCallback(this.modelService::addBiome);
|
world.getMapper().setBiomeCallback(this.modelService::addBiome);
|
||||||
@@ -107,10 +111,9 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
//TODO: Need to find a proper way to fix this (if there even is one)
|
//TODO: Need to find a proper way to fix this (if there even is one)
|
||||||
if (true /* firstInvocationThisFrame */) {
|
if (true /* firstInvocationThisFrame */) {
|
||||||
DownloadStream.INSTANCE.tick();
|
DownloadStream.INSTANCE.tick();
|
||||||
//Process the build results here (this is done atomically/on the render thread)
|
|
||||||
while (!this.sectionUpdateQueue.isEmpty()) {
|
this.sectionUpdateQueue.consume();
|
||||||
this.nodeManager.processResult(this.sectionUpdateQueue.poll());
|
this.geometryUpdateQueue.consume();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
UploadStream.INSTANCE.tick();
|
UploadStream.INSTANCE.tick();
|
||||||
|
|
||||||
@@ -139,8 +142,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
this.sectionRenderer.free();
|
this.sectionRenderer.free();
|
||||||
this.traversal.free();
|
this.traversal.free();
|
||||||
//Release all the unprocessed built geometry
|
//Release all the unprocessed built geometry
|
||||||
this.sectionUpdateQueue.forEach(update -> {if(update.geometry()!=null)update.geometry().free();});
|
this.geometryUpdateQueue.clear(BuiltSection::free);
|
||||||
this.sectionUpdateQueue.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Viewport<?> getViewport() {
|
public Viewport<?> getViewport() {
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ public class RenderGenerationService {
|
|||||||
|
|
||||||
private final WorldEngine world;
|
private final WorldEngine world;
|
||||||
private final ModelBakerySubsystem modelBakery;
|
private final ModelBakerySubsystem modelBakery;
|
||||||
private final Consumer<SectionUpdate> resultConsumer;
|
private final Consumer<BuiltSection> resultConsumer;
|
||||||
private final boolean emitMeshlets;
|
private final boolean emitMeshlets;
|
||||||
|
|
||||||
private final ServiceSlice threads;
|
private final ServiceSlice threads;
|
||||||
|
|
||||||
|
|
||||||
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, Consumer<SectionUpdate> consumer, boolean emitMeshlets) {
|
public RenderGenerationService(WorldEngine world, ModelBakerySubsystem modelBakery, ServiceThreadPool serviceThreadPool, Consumer<BuiltSection> consumer, boolean emitMeshlets) {
|
||||||
this.emitMeshlets = emitMeshlets;
|
this.emitMeshlets = emitMeshlets;
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.modelBakery = modelBakery;
|
this.modelBakery = modelBakery;
|
||||||
@@ -67,10 +67,10 @@ public class RenderGenerationService {
|
|||||||
synchronized (this.taskQueue) {
|
synchronized (this.taskQueue) {
|
||||||
task = this.taskQueue.removeFirst();
|
task = this.taskQueue.removeFirst();
|
||||||
}
|
}
|
||||||
long time = SectionUpdate.getTime();
|
//long time = BuiltSection.getTime();
|
||||||
var section = task.sectionSupplier.get();
|
var section = task.sectionSupplier.get();
|
||||||
if (section == null) {
|
if (section == null) {
|
||||||
this.resultConsumer.accept(new SectionUpdate(task.position, time, BuiltSection.empty(task.position), (byte) 0));
|
this.resultConsumer.accept(BuiltSection.empty(task.position));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
section.assertNotFree();
|
section.assertNotFree();
|
||||||
@@ -103,11 +103,9 @@ public class RenderGenerationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte childMask = section.getNonEmptyChildren();
|
|
||||||
section.release();
|
section.release();
|
||||||
if (mesh != null) {
|
if (mesh != null) {//If the mesh is null it means it didnt finish, so dont submit
|
||||||
//Time is the time at the start of the update
|
this.resultConsumer.accept(mesh);
|
||||||
this.resultConsumer.accept(new SectionUpdate(section.key, time, mesh, childMask));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.building;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
public record SectionUpdate(long position, long buildTime, @Nullable BuiltSection geometry, byte childExistence) {
|
|
||||||
public static long getTime() {
|
|
||||||
return System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +1,90 @@
|
|||||||
package me.cortex.voxy.client.core.rendering.building;
|
package me.cortex.voxy.client.core.rendering.building;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
import me.cortex.voxy.common.world.WorldSection;
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
|
|
||||||
import java.util.function.LongConsumer;
|
import java.util.function.LongConsumer;
|
||||||
|
|
||||||
|
import static me.cortex.voxy.common.world.WorldEngine.UPDATE_TYPE_BLOCK_BIT;
|
||||||
|
|
||||||
public class SectionUpdateRouter {
|
public class SectionUpdateRouter {
|
||||||
private static final int SLICES = 1<<2;
|
private static final int SLICES = 1<<3;
|
||||||
public interface IChildUpdate {void accept(WorldSection section);}
|
public interface IChildUpdate {void accept(WorldSection section);}
|
||||||
|
|
||||||
private final LongOpenHashSet[] slices = new LongOpenHashSet[SLICES];
|
private final Long2ByteOpenHashMap[] slices = new Long2ByteOpenHashMap[SLICES];
|
||||||
{
|
{
|
||||||
for (int i = 0; i < this.slices.length; i++) {
|
for (int i = 0; i < this.slices.length; i++) {
|
||||||
this.slices[i] = new LongOpenHashSet();
|
this.slices[i] = new Long2ByteOpenHashMap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private LongConsumer renderForwardTo;
|
private LongConsumer renderMeshGen;
|
||||||
private IChildUpdate childUpdateCallback;
|
private IChildUpdate childUpdateCallback;
|
||||||
|
|
||||||
public void setCallbacks(LongConsumer forwardTo, IChildUpdate childUpdateCallback) {
|
public void setCallbacks(LongConsumer renderMeshGen, IChildUpdate childUpdateCallback) {
|
||||||
if (this.renderForwardTo != null) {
|
if (this.renderMeshGen != null) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
this.renderForwardTo = forwardTo;
|
this.renderMeshGen = renderMeshGen;
|
||||||
this.childUpdateCallback = childUpdateCallback;
|
this.childUpdateCallback = childUpdateCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean watch(int lvl, int x, int y, int z) {
|
public boolean watch(int lvl, int x, int y, int z, int types) {
|
||||||
return this.watch(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
return this.watch(WorldEngine.getWorldSectionId(lvl, x, y, z), types);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean watch(long position) {
|
public boolean watch(long position, int types) {
|
||||||
var set = this.slices[getSliceIndex(position)];
|
var set = this.slices[getSliceIndex(position)];
|
||||||
boolean added;
|
byte delta = 0;
|
||||||
synchronized (set) {
|
synchronized (set) {
|
||||||
added = set.add(position);
|
byte current = 0;
|
||||||
|
if (set.containsKey(position)) {
|
||||||
|
current = set.get(position);
|
||||||
}
|
}
|
||||||
if (added) {
|
delta = (byte) ((current&types)^types);
|
||||||
|
current |= (byte) types;
|
||||||
|
set.put(position, current);
|
||||||
|
}
|
||||||
|
if ((delta&UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||||
//If we added it, immediately invoke for an update
|
//If we added it, immediately invoke for an update
|
||||||
this.renderForwardTo.accept(position);
|
this.renderMeshGen.accept(position);
|
||||||
}
|
}
|
||||||
return added;
|
return delta!=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean unwatch(int lvl, int x, int y, int z) {
|
public boolean unwatch(int lvl, int x, int y, int z, int types) {
|
||||||
return this.unwatch(WorldEngine.getWorldSectionId(lvl, x, y, z));
|
return this.unwatch(WorldEngine.getWorldSectionId(lvl, x, y, z), types);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean unwatch(long position) {
|
public boolean unwatch(long position, int types) {
|
||||||
var set = this.slices[getSliceIndex(position)];
|
var set = this.slices[getSliceIndex(position)];
|
||||||
synchronized (set) {
|
synchronized (set) {
|
||||||
return set.remove(position);
|
byte current = set.get(position);
|
||||||
|
byte delta = (byte) (current&types);
|
||||||
|
current &= (byte) ~types;
|
||||||
|
if (current == 0) {
|
||||||
|
set.remove(position);
|
||||||
|
}
|
||||||
|
return delta!=0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void maybeForward(WorldSection section, int type) {
|
public void forward(WorldSection section, int type) {
|
||||||
final long position = section.key;
|
final long position = section.key;
|
||||||
var set = this.slices[getSliceIndex(position)];
|
var set = this.slices[getSliceIndex(position)];
|
||||||
boolean contains;
|
byte types = 0;
|
||||||
synchronized (set) {
|
synchronized (set) {
|
||||||
contains = set.contains(position);
|
types = set.getOrDefault(position, (byte)0);
|
||||||
}
|
}
|
||||||
if (contains) {
|
if (types!=0) {
|
||||||
if (type == 3) {//If its both, propagate to the render service
|
if ((type&WorldEngine.UPDATE_TYPE_CHILD_EXISTENCE_BIT)!=0) {
|
||||||
this.renderForwardTo.accept(position);
|
|
||||||
} else {
|
|
||||||
if (type == 2) {//If its only a existance update
|
|
||||||
this.childUpdateCallback.accept(section);
|
this.childUpdateCallback.accept(section);
|
||||||
} else {//If its only a geometry update
|
|
||||||
this.renderForwardTo.accept(position);
|
|
||||||
}
|
}
|
||||||
|
if ((type& UPDATE_TYPE_BLOCK_BIT)!=0) {
|
||||||
|
this.renderMeshGen.accept(section.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
|||||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||||
import me.cortex.voxy.client.core.rendering.building.SectionUpdateRouter;
|
import me.cortex.voxy.client.core.rendering.building.SectionUpdateRouter;
|
||||||
import me.cortex.voxy.client.core.rendering.building.SectionUpdate;
|
|
||||||
import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager;
|
import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager;
|
||||||
import me.cortex.voxy.client.core.util.ExpandingObjectAllocationList;
|
import me.cortex.voxy.client.core.util.ExpandingObjectAllocationList;
|
||||||
import me.cortex.voxy.common.world.WorldEngine;
|
import me.cortex.voxy.common.world.WorldEngine;
|
||||||
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
import me.jellysquid.mods.sodium.client.util.MathUtil;
|
import me.jellysquid.mods.sodium.client.util.MathUtil;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ public class HierarchicalNodeManager {
|
|||||||
throw new IllegalArgumentException("Position already in node set: " + WorldEngine.pprintPos(position));
|
throw new IllegalArgumentException("Position already in node set: " + WorldEngine.pprintPos(position));
|
||||||
}
|
}
|
||||||
this.activeSectionMap.put(position, SENTINAL_TOP_NODE_INFLIGHT);
|
this.activeSectionMap.put(position, SENTINAL_TOP_NODE_INFLIGHT);
|
||||||
this.updateRouter.watch(position);
|
this.updateRouter.watch(position, WorldEngine.UPDATE_FLAGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeTopLevelNode(long position) {
|
public void removeTopLevelNode(long position) {
|
||||||
@@ -65,64 +65,6 @@ public class HierarchicalNodeManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processRequestQueue(int count, long ptr) {
|
|
||||||
for (int requestIndex = 0; requestIndex < count; requestIndex++) {
|
|
||||||
int op = MemoryUtil.memGetInt(ptr + (requestIndex * 4L));
|
|
||||||
this.processRequest(op);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processRequest(int op) {
|
|
||||||
int node = op& NODE_ID_MSK;
|
|
||||||
if (!this.nodeData.nodeExists(node)) {
|
|
||||||
throw new IllegalStateException("Tried processing a node that doesnt exist: " + node);
|
|
||||||
}
|
|
||||||
if (this.nodeData.isNodeRequestInFlight(node)) {
|
|
||||||
throw new IllegalStateException("Tried processing a node that already has a request in flight: " + node + " pos: " + WorldEngine.pprintPos(this.nodeData.nodePosition(node)));
|
|
||||||
}
|
|
||||||
this.nodeData.markRequestInFlight(node);
|
|
||||||
|
|
||||||
|
|
||||||
//2 branches, either its a leaf node -> emit a leaf request
|
|
||||||
// or the nodes geometry must be empty (i.e. culled from the graph/tree) so add to tracker and watch
|
|
||||||
if (this.nodeData.isLeafNode(node)) {
|
|
||||||
this.makeLeafRequest(node, this.nodeData.getNodeChildExistence(node));
|
|
||||||
} else {
|
|
||||||
//Verify that the node section is not in the section store. if it is then it is a state desynchonization
|
|
||||||
// Note that a section can be "empty" but some of its children might not be
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void makeLeafRequest(int node, byte childExistence) {
|
|
||||||
long pos = this.nodeData.nodePosition(node);
|
|
||||||
|
|
||||||
//Enqueue a leaf expansion request
|
|
||||||
var request = new NodeChildRequest(pos);
|
|
||||||
int requestId = this.requests.put(request);
|
|
||||||
|
|
||||||
//Only request against the childExistence mask, since the guarantee is that if childExistence bit is not set then that child is guaranteed to be empty
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
if ((childExistence&(1<<i))==0) {
|
|
||||||
//Dont watch or enqueue the child node cause it doesnt exist
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
long childPos = makeChildPos(pos, i);
|
|
||||||
request.addChildRequirement(i);
|
|
||||||
//Insert all the children into the tracking map with the node id
|
|
||||||
if (this.activeSectionMap.put(childPos, requestId|ID_TYPE_REQUEST) != NO_NODE) {
|
|
||||||
throw new IllegalStateException("Leaf request creation failed to insert child into map as a mapping already existed for the node!");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Watch and request the child node at the given position
|
|
||||||
if (!this.updateRouter.watch(childPos)) {
|
|
||||||
throw new IllegalStateException("Failed to watch childPos");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.nodeData.setNodeRequest(node, requestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void removeSectionInternal(long position) {
|
private void removeSectionInternal(long position) {
|
||||||
int node = this.activeSectionMap.remove(position);
|
int node = this.activeSectionMap.remove(position);
|
||||||
if (node == NO_NODE) {
|
if (node == NO_NODE) {
|
||||||
@@ -177,56 +119,102 @@ public class HierarchicalNodeManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void processRequestQueue(int count, long ptr) {
|
||||||
|
for (int requestIndex = 0; requestIndex < count; requestIndex++) {
|
||||||
|
int op = MemoryUtil.memGetInt(ptr + (requestIndex * 4L));
|
||||||
|
this.processRequest(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRequest(int op) {
|
||||||
|
int node = op& NODE_ID_MSK;
|
||||||
|
if (!this.nodeData.nodeExists(node)) {
|
||||||
|
throw new IllegalStateException("Tried processing a node that doesnt exist: " + node);
|
||||||
|
}
|
||||||
|
if (this.nodeData.isNodeRequestInFlight(node)) {
|
||||||
|
throw new IllegalStateException("Tried processing a node that already has a request in flight: " + node + " pos: " + WorldEngine.pprintPos(this.nodeData.nodePosition(node)));
|
||||||
|
}
|
||||||
|
this.nodeData.markRequestInFlight(node);
|
||||||
|
|
||||||
|
|
||||||
//TODO: need to add a flag that says should do geometry uploads or something I.E. need to watch geometry
|
//2 branches, either its a leaf node -> emit a leaf request
|
||||||
// and existance updates seperatly, since cull geometry
|
// or the nodes geometry must be empty (i.e. culled from the graph/tree) so add to tracker and watch
|
||||||
public void processResult(SectionUpdate update) {
|
if (this.nodeData.isLeafNode(node)) {
|
||||||
//Need to handle cases
|
this.makeLeafRequest(node, this.nodeData.getNodeChildExistence(node));
|
||||||
// geometry update, leaf node, leaf request node, internal node
|
} else {
|
||||||
//Child emptiness update!!! this is the hard bit
|
//Verify that the node section is not in the section store. if it is then it is a state desynchonization
|
||||||
// if it is an internal node
|
// Note that a section can be "empty" but some of its children might not be
|
||||||
// if emptiness adds node, need to then send a mesh request and wait
|
}
|
||||||
// when mesh result, need to remove the old child allocation block and make a new block to fit the
|
}
|
||||||
// new count of children
|
|
||||||
|
|
||||||
//If the sections child existance bits fully empty, then the section should be removed
|
private void makeLeafRequest(int node, byte childExistence) {
|
||||||
|
long pos = this.nodeData.nodePosition(node);
|
||||||
|
|
||||||
|
//Enqueue a leaf expansion request
|
||||||
|
var request = new NodeChildRequest(pos);
|
||||||
|
int requestId = this.requests.put(request);
|
||||||
|
|
||||||
|
//Only request against the childExistence mask, since the guarantee is that if childExistence bit is not set then that child is guaranteed to be empty
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if ((childExistence&(1<<i))==0) {
|
||||||
|
//Dont watch or enqueue the child node cause it doesnt exist
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
long childPos = makeChildPos(pos, i);
|
||||||
|
request.addChildRequirement(i);
|
||||||
|
//Insert all the children into the tracking map with the node id
|
||||||
|
if (this.activeSectionMap.put(childPos, requestId|ID_TYPE_REQUEST) != NO_NODE) {
|
||||||
|
throw new IllegalStateException("Leaf request creation failed to insert child into map as a mapping already existed for the node!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Watch and request the child node at the given position
|
||||||
|
if (!this.updateRouter.watch(childPos, WorldEngine.UPDATE_FLAGS)) {
|
||||||
|
throw new IllegalStateException("Failed to watch childPos");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nodeData.setNodeRequest(node, requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
final long position = update.position();
|
|
||||||
final var geometryData = update.geometry();
|
public void processChildChange(long position, byte childExistence) {
|
||||||
int nodeId = this.activeSectionMap.get(position);
|
int nodeId = this.activeSectionMap.get(position);
|
||||||
if (nodeId == NO_NODE) {
|
if (nodeId == NO_NODE) {
|
||||||
System.err.println("Received update for section " + WorldEngine.pprintPos(position) + " however section position not in active in map! discarding");
|
System.err.println("Received child change for section " + WorldEngine.pprintPos(position) + " however section position not in active in map! discarding");
|
||||||
//Not tracked or mapped to a node!, discard it, it was probably in progress when it was removed from the map
|
|
||||||
if (geometryData != null) {
|
|
||||||
geometryData.free();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processGeometryResult(BuiltSection section) {
|
||||||
|
final long position = section.position;
|
||||||
|
int nodeId = this.activeSectionMap.get(position);
|
||||||
|
if (nodeId == NO_NODE) {
|
||||||
|
System.err.println("Received geometry for section " + WorldEngine.pprintPos(position) + " however section position not in active in map! discarding");
|
||||||
|
//Not tracked or mapped to a node!, discard it, it was probably in progress when it was removed from the map
|
||||||
|
section.free();
|
||||||
|
} else {
|
||||||
|
//TODO! need to not do this as it may have child data assocaited, should allocate when initally adding the TLN
|
||||||
if (nodeId == SENTINAL_TOP_NODE_INFLIGHT) {
|
if (nodeId == SENTINAL_TOP_NODE_INFLIGHT) {
|
||||||
//Special state for top level nodes that are in flight
|
//Special state for top level nodes that are in flight
|
||||||
if (geometryData == null) {
|
|
||||||
//FIXME: this is a bug, as the child existence could change and have an update sent, resulting in a desync
|
|
||||||
System.err.println("Top level inflight node " + WorldEngine.pprintPos(position) + " got a child msk update but was still in flight! discarding update");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Allocate a new node id
|
//Allocate a new node id
|
||||||
nodeId = this.nodeData.allocate();
|
nodeId = this.nodeData.allocate();
|
||||||
this.activeSectionMap.put(position, nodeId|ID_TYPE_TOP);
|
this.activeSectionMap.put(position, nodeId|ID_TYPE_TOP);
|
||||||
int geometry = -1;
|
int geometry = -1;
|
||||||
if (!geometryData.isEmpty()) {
|
if (!section.isEmpty()) {
|
||||||
geometry = this.geometryManager.uploadSection(geometryData);
|
geometry = this.geometryManager.uploadSection(section);
|
||||||
} else {
|
} else {
|
||||||
geometryData.free();
|
section.free();
|
||||||
}
|
}
|
||||||
this.fillNode(nodeId, position, geometry, update.childExistence());
|
this.fillNode(nodeId, position, geometry, (byte) 0);//INCORRECT
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
int type = (nodeId & ID_TYPE_MSK);
|
int type = (nodeId & ID_TYPE_MSK);
|
||||||
nodeId &= ~ID_TYPE_MSK;
|
nodeId &= ~ID_TYPE_MSK;
|
||||||
if (type == ID_TYPE_REQUEST) {
|
if (type == ID_TYPE_REQUEST) {
|
||||||
this.requestDataUpdate(nodeId, update);
|
this.requestDataUpdate(nodeId);
|
||||||
} else if (type == ID_TYPE_NONE || type == ID_TYPE_TOP) {
|
} else if (type == ID_TYPE_NONE || type == ID_TYPE_TOP) {
|
||||||
//Not part of a request, just a node update,
|
//Not part of a request, just a node update,
|
||||||
|
|
||||||
@@ -245,7 +233,7 @@ public class HierarchicalNodeManager {
|
|||||||
this.nodeData.setNodeChildExistence(node, childExistence);
|
this.nodeData.setNodeChildExistence(node, childExistence);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestDataUpdate(int nodeId, SectionUpdate update) {
|
private void requestDataUpdate(int nodeId) {
|
||||||
var request = this.requests.get(nodeId);
|
var request = this.requests.get(nodeId);
|
||||||
//Update for section part of a request, the request may be a leaf request update or an inner node update
|
//Update for section part of a request, the request may be a leaf request update or an inner node update
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package me.cortex.voxy.common.thread;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
|
import me.cortex.voxy.common.world.WorldSection;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.text.Text;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class QueuedServiceSlice <T> extends ServiceSlice {
|
||||||
|
private final ConcurrentLinkedDeque<T> queue = new ConcurrentLinkedDeque<>();
|
||||||
|
|
||||||
|
QueuedServiceSlice(ServiceThreadPool threadPool, Supplier<Consumer<T>> workerGenerator, String name, int weightPerJob, BooleanSupplier condition) {
|
||||||
|
super(threadPool, null, name, weightPerJob, condition);
|
||||||
|
//Fuck off java with the this bullshit before super constructor, fucking bullshit
|
||||||
|
super.setWorkerGenerator(() -> {
|
||||||
|
var work = workerGenerator.get();
|
||||||
|
return () -> work.accept(this.queue.pop());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
throw new IllegalStateException("Cannot call .execute() on a QueuedServiceSlice");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enqueue(T obj) {
|
||||||
|
this.queue.add(obj);
|
||||||
|
super.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ public class ServiceSlice extends TrackedObject {
|
|||||||
final int weightPerJob;
|
final int weightPerJob;
|
||||||
volatile boolean alive = true;
|
volatile boolean alive = true;
|
||||||
private final ServiceThreadPool threadPool;
|
private final ServiceThreadPool threadPool;
|
||||||
private final Supplier<Runnable> workerGenerator;
|
private Supplier<Runnable> workerGenerator;
|
||||||
final Semaphore jobCount = new Semaphore(0);
|
final Semaphore jobCount = new Semaphore(0);
|
||||||
private final Runnable[] runningCtxs;
|
private final Runnable[] runningCtxs;
|
||||||
private final AtomicInteger activeCount = new AtomicInteger();
|
private final AtomicInteger activeCount = new AtomicInteger();
|
||||||
@@ -25,9 +25,13 @@ public class ServiceSlice extends TrackedObject {
|
|||||||
this.threadPool = threadPool;
|
this.threadPool = threadPool;
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
this.runningCtxs = new Runnable[threadPool.getThreadCount()];
|
this.runningCtxs = new Runnable[threadPool.getThreadCount()];
|
||||||
this.workerGenerator = workerGenerator;
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.weightPerJob = weightPerJob;
|
this.weightPerJob = weightPerJob;
|
||||||
|
this.setWorkerGenerator(workerGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setWorkerGenerator(Supplier<Runnable> workerGenerator) {
|
||||||
|
this.workerGenerator = workerGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean doRun(int threadIndex) {
|
boolean doRun(int threadIndex) {
|
||||||
|
|||||||
@@ -37,13 +37,17 @@ public class ServiceThreadPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized ServiceSlice createService(String name, int weight, Supplier<Runnable> workGenerator, BooleanSupplier executionCondition) {
|
public synchronized ServiceSlice createService(String name, int weight, Supplier<Runnable> workGenerator, BooleanSupplier executionCondition) {
|
||||||
|
var service = new ServiceSlice(this, workGenerator, name, weight, executionCondition);
|
||||||
|
this.insertService(service);
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertService(ServiceSlice service) {
|
||||||
var current = this.serviceSlices;
|
var current = this.serviceSlices;
|
||||||
var newList = new ServiceSlice[current.length + 1];
|
var newList = new ServiceSlice[current.length + 1];
|
||||||
System.arraycopy(current, 0, newList, 0, current.length);
|
System.arraycopy(current, 0, newList, 0, current.length);
|
||||||
var service = new ServiceSlice(this, workGenerator, name, weight, executionCondition);
|
|
||||||
newList[current.length] = service;
|
newList[current.length] = service;
|
||||||
this.serviceSlices = newList;
|
this.serviceSlices = newList;
|
||||||
return service;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void removeService(ServiceSlice service) {
|
synchronized void removeService(ServiceSlice service) {
|
||||||
|
|||||||
39
src/main/java/me/cortex/voxy/common/util/MessageQueue.java
Normal file
39
src/main/java/me/cortex/voxy/common/util/MessageQueue.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package me.cortex.voxy.common.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class MessageQueue <T> {
|
||||||
|
private final Consumer<T> consumer;
|
||||||
|
private final ConcurrentLinkedDeque<T> queue = new ConcurrentLinkedDeque<>();
|
||||||
|
|
||||||
|
public MessageQueue(Consumer<T> consumer) {
|
||||||
|
this.consumer = consumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void push(T obj) {
|
||||||
|
this.queue.add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int consume() {
|
||||||
|
return this.consume(Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int consume(int max) {
|
||||||
|
int i = 0;
|
||||||
|
while (i < max) {
|
||||||
|
var entry = this.queue.poll();
|
||||||
|
if (entry == null) return i;
|
||||||
|
i++;
|
||||||
|
this.consumer.accept(entry);
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void clear(Consumer<T> cleaner) {
|
||||||
|
while (!this.queue.isEmpty()) {
|
||||||
|
cleaner.accept(this.queue.pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user