diff --git a/src/main/java/me/cortex/voxy/client/config/VoxyConfig.java b/src/main/java/me/cortex/voxy/client/config/VoxyConfig.java index 1aabf368..13acb772 100644 --- a/src/main/java/me/cortex/voxy/client/config/VoxyConfig.java +++ b/src/main/java/me/cortex/voxy/client/config/VoxyConfig.java @@ -50,7 +50,6 @@ public class VoxyConfig { return config; } public void save() { - //Unsafe, todo: fixme! needs to be atomic! try { Files.writeString(getConfigPath(), GSON.toJson(this)); } catch (IOException e) { diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java b/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java index 64278bed..746a9f95 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java @@ -68,11 +68,11 @@ public class RenderService, J extends Vi Arrays.stream(world.getMapper().getBiomeEntries()).forEach(this.modelService::addBiome); world.getMapper().setBiomeCallback(this.modelService::addBiome); - - for (int x = -10; x <= 10; x++) { - for (int y = -3; y <= 3; y++) { - for (int z = -10; z <= 10; z++) { - positionFilterForwarder.watch(0, x, y ,z); + final int H_WIDTH = 1; + for (int x = -H_WIDTH; x <= H_WIDTH; x++) { + for (int y = -1; y <= 0; y++) { + for (int z = -H_WIDTH; z <= H_WIDTH; z++) { + this.nodeManager.insertTopLevelNode(WorldEngine.getWorldSectionId(4, x, y, z)); } } } 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 b850f5ca..3d360880 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 @@ -15,13 +15,26 @@ import org.lwjgl.system.MemoryUtil; //Contains no logic to interface with the gpu, nor does it contain any gpu buffers public class HierarchicalNodeManager { public static final int NODE_MSK = ((1<<24)-1); + private static final int NO_NODE = -1; + private static final int SENTINAL_TOP_NODE_INFLIGHT = -2; + + private static final int ID_TYPE_MSK = (3<<30); + private static final int ID_TYPE_NONE = 0; + private static final int ID_TYPE_LEAF = (2<<30); + private static final int ID_TYPE_TOP = (1<<30); + public final int maxNodeCount; - private final NodeStore nodeData; - private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap(); private final IntOpenHashSet nodeUpdates = new IntOpenHashSet(); - private final ExpandingObjectAllocationList leafRequests = new ExpandingObjectAllocationList<>(LeafExpansionRequest[]::new); + private final NodeStore nodeData; + + //Map from position->id, the top 2 bits contains specifies the type of id + private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap(); + + private final ExpandingObjectAllocationList requests = new ExpandingObjectAllocationList<>(NodeChildRequest[]::new); + private final AbstractSectionGeometryManager geometryManager; private final SectionPositionUpdateFilterer updateFilterer; + public HierarchicalNodeManager(int maxNodeCount, AbstractSectionGeometryManager geometryManager, SectionPositionUpdateFilterer updateFilterer) { if (!MathUtil.isPowerOfTwo(maxNodeCount)) { throw new IllegalArgumentException("Max node count must be a power of 2"); @@ -29,14 +42,28 @@ public class HierarchicalNodeManager { if (maxNodeCount>(1<<24)) { throw new IllegalArgumentException("Max node count cannot exceed 2^24"); } - this.activeSectionMap.defaultReturnValue(-1); + this.activeSectionMap.defaultReturnValue(NO_NODE); this.updateFilterer = updateFilterer; this.maxNodeCount = maxNodeCount; this.nodeData = new NodeStore(maxNodeCount); this.geometryManager = geometryManager; } + public void insertTopLevelNode(long position) { + if (this.activeSectionMap.containsKey(position)) { + throw new IllegalArgumentException("Position already in node set: " + WorldEngine.pprintPos(position)); + } + this.activeSectionMap.put(position, SENTINAL_TOP_NODE_INFLIGHT); + this.updateFilterer.watch(position); + } + public void removeTopLevelNode(long position) { + if (!this.activeSectionMap.containsKey(position)) { + throw new IllegalArgumentException("Position not in node set: " + WorldEngine.pprintPos(position)); + } + + + } public void processRequestQueue(int count, long ptr) { for (int requestIndex = 0; requestIndex < count; requestIndex++) { @@ -50,7 +77,7 @@ public class HierarchicalNodeManager { if (!this.nodeData.nodeExists(node)) { throw new IllegalStateException("Tried processing a node that doesnt exist: " + node); } - if (this.nodeData.nodeRequestInFlight(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); @@ -59,7 +86,7 @@ public class HierarchicalNodeManager { //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.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 @@ -68,26 +95,31 @@ public class HierarchicalNodeManager { private void makeLeafRequest(int node, byte childExistence) { long pos = this.nodeData.nodePosition(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); + 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<