From 351fac905255d80320cfec0a6ecd786e2de9aa48 Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:49:28 +1000 Subject: [PATCH] Work on nodes --- .../hierachical2/HierarchicalNodeManager.java | 106 ++++++++++++++---- .../rendering/hierachical2/NodeStore.java | 34 ++++++ .../rendering/hierarchical/NodeManager.java | 4 +- .../section/MDICSectionRenderer.java | 2 +- .../util/ExpandingObjectAllocationList.java | 45 ++++++++ .../MarkedCachedObjectList.java} | 6 +- 6 files changed, 172 insertions(+), 25 deletions(-) create mode 100644 src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeStore.java create mode 100644 src/main/java/me/cortex/voxy/client/core/util/ExpandingObjectAllocationList.java rename src/main/java/me/cortex/voxy/client/core/{rendering/util/MarkedObjectList.java => util/MarkedCachedObjectList.java} (90%) diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java index 2c55fb2a..c53f63d8 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/HierarchicalNodeManager.java @@ -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 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; - 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); + 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(4,x,y,z); + updateFilterer.unwatch(4,x,y,z); + } } } + }).start(); + } + + + + public void processRequestQueue(int count, long ptr) { + for (int requestIndex = 0; requestIndex < count; requestIndex++) { + int op = MemoryUtil.memGetInt(ptr + (requestIndex * 4L)); + this.processRequest(op); } } - 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; + 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)); + } } diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeStore.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeStore.java new file mode 100644 index 00000000..f4de4c59 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical2/NodeStore.java @@ -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; + } + +} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager.java index 420104e3..860bef82 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager.java @@ -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 leafRequests = new MarkedObjectList<>(LeafRequest[]::new, LeafRequest::new); + private final MarkedCachedObjectList 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); diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/section/MDICSectionRenderer.java b/src/main/java/me/cortex/voxy/client/core/rendering/section/MDICSectionRenderer.java index 6fc5864b..6bbfc15c 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/section/MDICSectionRenderer.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/section/MDICSectionRenderer.java @@ -106,7 +106,7 @@ public class MDICSectionRenderer extends AbstractSectionRenderer { + private static final float GROWTH_FACTOR = 0.75f; + + private final Int2ObjectFunction arrayGenerator; + private final HierarchicalBitSet bitSet = new HierarchicalBitSet(); + private T[] objects;//Should maybe make a getter function instead + + public ExpandingObjectAllocationList(Int2ObjectFunction 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]; + } +} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/util/MarkedObjectList.java b/src/main/java/me/cortex/voxy/client/core/util/MarkedCachedObjectList.java similarity index 90% rename from src/main/java/me/cortex/voxy/client/core/rendering/util/MarkedObjectList.java rename to src/main/java/me/cortex/voxy/client/core/util/MarkedCachedObjectList.java index e822500d..ce4a336b 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/util/MarkedObjectList.java +++ b/src/main/java/me/cortex/voxy/client/core/util/MarkedCachedObjectList.java @@ -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 { +public class MarkedCachedObjectList { private static final float GROWTH_FACTOR = 0.75f; private final Int2ObjectFunction arrayGenerator; @@ -13,7 +13,7 @@ public class MarkedObjectList { private final HierarchicalBitSet bitSet = new HierarchicalBitSet(); private T[] objects;//Should maybe make a getter function instead - public MarkedObjectList(Int2ObjectFunction arrayGenerator, Supplier nullSupplier) { + public MarkedCachedObjectList(Int2ObjectFunction arrayGenerator, Supplier nullSupplier) { this.arrayGenerator = arrayGenerator; this.nullSupplier = nullSupplier; this.objects = this.arrayGenerator.apply(16);