Work on nodes
This commit is contained in:
@@ -2,12 +2,11 @@ package me.cortex.voxy.client.core.rendering.hierachical2;
|
||||
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||
import me.cortex.voxy.client.core.rendering.building.SectionPositionUpdateFilterer;
|
||||
import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager;
|
||||
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||
import me.cortex.voxy.common.world.WorldSection;
|
||||
import me.cortex.voxy.client.core.util.ExpandingObjectAllocationList;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
import me.jellysquid.mods.sodium.client.util.MathUtil;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
@@ -15,10 +14,10 @@ import org.lwjgl.system.MemoryUtil;
|
||||
public class HierarchicalNodeManager {
|
||||
public static final int NODE_MSK = ((1<<24)-1);
|
||||
public final int maxNodeCount;
|
||||
private final long[] localNodeData;
|
||||
private final AbstractSectionGeometryManager geometryManager;
|
||||
private final HierarchicalBitSet allocationSet;
|
||||
private final NodeStore nodeData;
|
||||
private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap();
|
||||
private final ExpandingObjectAllocationList<LeafExpansionRequest> leafRequests = new ExpandingObjectAllocationList<>(LeafExpansionRequest[]::new);
|
||||
private final AbstractSectionGeometryManager geometryManager;
|
||||
private final SectionPositionUpdateFilterer updateFilterer;
|
||||
public HierarchicalNodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, SectionPositionUpdateFilterer updateFilterer) {
|
||||
if (!MathUtil.isPowerOfTwo(maxNodeCount)) {
|
||||
@@ -27,36 +26,105 @@ public class HierarchicalNodeManager {
|
||||
if (maxNodeCount>(1<<24)) {
|
||||
throw new IllegalArgumentException("Max node count cannot exceed 2^24");
|
||||
}
|
||||
this.activeSectionMap.defaultReturnValue(-1);
|
||||
this.updateFilterer = updateFilterer;
|
||||
this.allocationSet = new HierarchicalBitSet(maxNodeCount);
|
||||
this.maxNodeCount = maxNodeCount;
|
||||
this.localNodeData = new long[maxNodeCount*4];
|
||||
this.nodeData = new NodeStore(maxNodeCount);
|
||||
this.geometryManager = geometryManager;
|
||||
|
||||
|
||||
|
||||
new Thread(()->{
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
for(int x = -1; x<=1;x++) {
|
||||
for (int z = -1; z <= 1; z++) {
|
||||
for (int y = -3; y <= 3; y++) {
|
||||
updateFilterer.watch(0,x,y,z);
|
||||
updateFilterer.watch(4,x,y,z);
|
||||
updateFilterer.unwatch(4,x,y,z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void processRequestQueue(int count, long ptr) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
int op = MemoryUtil.memGetInt(ptr+(i*4L));
|
||||
int node = op&NODE_MSK;
|
||||
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_MSK;
|
||||
if (!this.nodeData.nodeExists(node)) {
|
||||
throw new IllegalStateException("Tried processing a node that doesnt exist: " + node);
|
||||
}
|
||||
if (this.nodeData.nodeRequestInFlight(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);
|
||||
|
||||
long pos = this.nodeData.nodePosition(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)) {
|
||||
//TODO: the localNodeData should have a bitset of what children are definitely empty
|
||||
// use that to msk the request, HOWEVER there is a race condition e.g.
|
||||
// leaf node is requested and has only 1 child marked as non empty
|
||||
// however then an update occures and a different child now becomes non empty,
|
||||
// this will trigger a processBuildResult for parent
|
||||
// so need to ensure that when that happens, if the parent has an inflight leaf expansion request
|
||||
// for the leaf request to be updated to account for the new maybe child node
|
||||
// NOTE: a section can have empty geometry but some of its children might not, so need to mark and
|
||||
// submit a node at that level but with empty section, (specially marked) so that the traversal
|
||||
// can recurse into those children as needed
|
||||
|
||||
//Enqueue a leaf expansion request
|
||||
var request = new LeafExpansionRequest(pos);
|
||||
int requestId = this.leafRequests.put(request);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
long childPos = makeChildPos(pos, i);
|
||||
//Insert all the children into the tracking map with the node id
|
||||
this.activeSectionMap.put(childPos, 0);
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
public void processBuildResult(BuiltSection section) {
|
||||
if (!section.isEmpty()) {
|
||||
this.geometryManager.uploadSection(section);
|
||||
int nodeId = this.activeSectionMap.get(section.position);
|
||||
if (nodeId == -1) {
|
||||
//Not tracked or mapped to a node!!!
|
||||
} else {
|
||||
section.free();
|
||||
//Part of a request (top bit is set to 1)
|
||||
if ((nodeId&(1<<31))!=0) {
|
||||
|
||||
} else {
|
||||
//Not part of a request, just a node update,
|
||||
// however could result in a reallocation if it needs to mark a child position as being possibly visible
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long makeChildPos(long basePos, int addin) {
|
||||
int lvl = WorldEngine.getLevel(basePos);
|
||||
if (lvl == 0) {
|
||||
throw new IllegalArgumentException("Cannot create a child lower than lod level 0");
|
||||
}
|
||||
return WorldEngine.getWorldSectionId(lvl-1,
|
||||
(WorldEngine.getX(basePos)<<1)|(addin&1),
|
||||
(WorldEngine.getY(basePos)<<1)|((addin>>1)&1),
|
||||
(WorldEngine.getZ(basePos)<<1)|((addin>>2)&1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.cortex.voxy.client.core.rendering.hierachical2;
|
||||
|
||||
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||
|
||||
public final class NodeStore {
|
||||
private final HierarchicalBitSet allocationSet;
|
||||
private final long[] localNodeData;
|
||||
public NodeStore(int maxNodeCount) {
|
||||
this.localNodeData = new long[maxNodeCount*4];
|
||||
this.allocationSet = new HierarchicalBitSet(maxNodeCount);
|
||||
}
|
||||
|
||||
public long nodePosition(int nodeId) {
|
||||
return this.localNodeData[nodeId<<2];
|
||||
}
|
||||
|
||||
public boolean nodeExists(int nodeId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void markRequestInFlight(int nodeId) {
|
||||
|
||||
}
|
||||
|
||||
public boolean nodeRequestInFlight(int nodeId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isLeafNode(int nodeId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.MarkedObjectList;
|
||||
import me.cortex.voxy.client.core.util.MarkedCachedObjectList;
|
||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
@@ -302,7 +302,7 @@ public class NodeManager {
|
||||
|
||||
//The request queue should be like some array that can reuse objects to prevent gc nightmare + like a bitset to find an avalible free slot
|
||||
// hashmap might work bar the gc overhead
|
||||
private final MarkedObjectList<LeafRequest> leafRequests = new MarkedObjectList<>(LeafRequest[]::new, LeafRequest::new);
|
||||
private final MarkedCachedObjectList<LeafRequest> leafRequests = new MarkedCachedObjectList<>(LeafRequest[]::new, LeafRequest::new);
|
||||
|
||||
private static int pos2octnode(long pos) {
|
||||
return (WorldEngine.getX(pos)&1)|((WorldEngine.getY(pos)&1)<<1)|((WorldEngine.getZ(pos)&1)<<2);
|
||||
|
||||
@@ -106,7 +106,7 @@ public class MDICSectionRenderer extends AbstractSectionRenderer<MDICViewport, B
|
||||
glBindVertexArray(RenderService.STATIC_VAO);//Needs to be before binding
|
||||
this.bindRenderingBuffers();
|
||||
|
||||
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 4*3, Math.min((int)(this.geometryManager.getSectionCount()*4.4), 400_000), 0);
|
||||
glMultiDrawElementsIndirectCountARB(GL_TRIANGLES, GL_UNSIGNED_SHORT, 0, 4*3, Math.min((int)(this.geometryManager.getSectionCount()*4.4+128), 400_000), 0);
|
||||
|
||||
glEnable(GL_CULL_FACE);
|
||||
glBindVertexArray(0);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package me.cortex.voxy.client.core.util;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
|
||||
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||
|
||||
public class ExpandingObjectAllocationList<T> {
|
||||
private static final float GROWTH_FACTOR = 0.75f;
|
||||
|
||||
private final Int2ObjectFunction<T[]> arrayGenerator;
|
||||
private final HierarchicalBitSet bitSet = new HierarchicalBitSet();
|
||||
private T[] objects;//Should maybe make a getter function instead
|
||||
|
||||
public ExpandingObjectAllocationList(Int2ObjectFunction<T[]> arrayGenerator) {
|
||||
this.arrayGenerator = arrayGenerator;
|
||||
this.objects = this.arrayGenerator.apply(16);
|
||||
}
|
||||
|
||||
public int put(T obj) {
|
||||
//Gets an unused id for some entry in objects, if its null fill it
|
||||
int id = this.bitSet.allocateNext();
|
||||
if (this.objects.length <= id) {
|
||||
//Resize and copy over the objects array
|
||||
int newLen = this.objects.length + (int)Math.ceil(this.objects.length*GROWTH_FACTOR);
|
||||
T[] newArr = this.arrayGenerator.apply(newLen);
|
||||
System.arraycopy(this.objects, 0, newArr, 0, this.objects.length);
|
||||
this.objects = newArr;
|
||||
}
|
||||
this.objects[id] = obj;
|
||||
return id;
|
||||
}
|
||||
|
||||
public void release(int id) {
|
||||
if (!this.bitSet.free(id)) {
|
||||
throw new IllegalArgumentException("Index " + id + " was already released");
|
||||
}
|
||||
}
|
||||
|
||||
public T get(int index) {
|
||||
//Make the checking that index is allocated optional, as it might cause overhead due to multiple cacheline misses
|
||||
if (!this.bitSet.isSet(index)) {
|
||||
throw new IllegalArgumentException("Index " + index + " is not allocated");
|
||||
}
|
||||
return this.objects[index];
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package me.cortex.voxy.client.core.rendering.util;
|
||||
package me.cortex.voxy.client.core.util;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction;
|
||||
import me.cortex.voxy.common.util.HierarchicalBitSet;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class MarkedObjectList<T> {
|
||||
public class MarkedCachedObjectList<T> {
|
||||
private static final float GROWTH_FACTOR = 0.75f;
|
||||
|
||||
private final Int2ObjectFunction<T[]> arrayGenerator;
|
||||
@@ -13,7 +13,7 @@ public class MarkedObjectList<T> {
|
||||
private final HierarchicalBitSet bitSet = new HierarchicalBitSet();
|
||||
private T[] objects;//Should maybe make a getter function instead
|
||||
|
||||
public MarkedObjectList(Int2ObjectFunction<T[]> arrayGenerator, Supplier<T> nullSupplier) {
|
||||
public MarkedCachedObjectList(Int2ObjectFunction<T[]> arrayGenerator, Supplier<T> nullSupplier) {
|
||||
this.arrayGenerator = arrayGenerator;
|
||||
this.nullSupplier = nullSupplier;
|
||||
this.objects = this.arrayGenerator.apply(16);
|
||||
Reference in New Issue
Block a user