More work on node manager

This commit is contained in:
mcrcortex
2024-08-13 09:18:56 +10:00
parent 1d1e244f03
commit d4714989b4
5 changed files with 166 additions and 52 deletions

View File

@@ -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) {

View File

@@ -68,11 +68,11 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, 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));
}
}
}

View File

@@ -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<LeafExpansionRequest> 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<NodeChildRequest> 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++) {
long childPos = makeChildPos(pos, i);
//Insert all the children into the tracking map with the node id
this.activeSectionMap.put(childPos, 0);
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_LEAF) != 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.updateFilterer.watch(childPos)) {
throw new IllegalStateException("Failed to watch childPos");
}
}
this.nodeData.setNodeRequest(node, requestId);
}
@@ -100,35 +132,66 @@ public class HierarchicalNodeManager {
// when mesh result, need to remove the old child allocation block and make a new block to fit the
// new count of children
int nodeId = this.activeSectionMap.get(update.position());
if (nodeId == -1) {
final long position = update.position();
final var geometryData = update.geometry();
int nodeId = this.activeSectionMap.get(position);
if (nodeId == NO_NODE) {
System.err.println("Received update 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 (update.geometry() != null) {
update.geometry().free();
if (geometryData != null) {
geometryData.free();
}
} else {
//Part of a request (top bit is set to 1)
if ((nodeId&(1<<31))!=0) {
nodeId &= ~(1<<31);
var request = this.leafRequests.get(nodeId);
if (nodeId == SENTINAL_TOP_NODE_INFLIGHT) {
//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
nodeId = this.nodeData.allocate();
this.activeSectionMap.put(position, nodeId|ID_TYPE_TOP);
int geometry = -1;
if (!geometryData.isEmpty()) {
geometry = this.geometryManager.uploadSection(geometryData);
} else {
geometryData.free();
}
this.fillNode(nodeId, position, geometry, update.childExistence());
} else {
//Not part of a request, just a node update, if node is currently a leaf node, it might have a
// leaf request associated with it, which might need an update if
int type = (nodeId & ID_TYPE_MSK);
nodeId &= ~ID_TYPE_MSK;
if (type == ID_TYPE_LEAF) {
this.leafDataUpdate(nodeId, update);
} else if (type == ID_TYPE_NONE || type == ID_TYPE_TOP) {
//Not part of a request, just a node update
} else {
throw new IllegalStateException("Should not reach here");
}
}
}
}
private void fillNode(int node, long position, int geometry, byte childExistence) {
this.nodeData.setNodePosition(node, position);
this.nodeData.setNodeGeometry(node, geometry);
this.nodeData.setNodeChildExistence(node, childExistence);
}
}
private void leafDataUpdate(int nodeId, SectionUpdate update) {
var request = this.requests.get(nodeId);
}
private int updateNodeGeometry(int node, BuiltSection geometry) {
int previousGeometry = -1;
int previousGeometry = this.nodeData.getNodeGeometry(node);
int newGeometry = -1;
if (this.nodeData.hasGeometry(node)) {
previousGeometry = this.nodeData.getNodeGeometry(node);
if (previousGeometry != -1) {
if (!geometry.isEmpty()) {
newGeometry = this.geometryManager.uploadReplaceSection(previousGeometry, geometry);
} else {
@@ -153,6 +216,17 @@ public class HierarchicalNodeManager {
}
}
private void createSingleNode() {
}
private static int getChildIdx(long pos) {
int x = WorldEngine.getX(pos);
int y = WorldEngine.getY(pos);
int z = WorldEngine.getZ(pos);
return (x&1)|((y&1)<<1)|((z&1)<<2);
}
private static long makeChildPos(long basePos, int addin) {
int lvl = WorldEngine.getLevel(basePos);
if (lvl == 0) {

View File

@@ -1,7 +1,6 @@
package me.cortex.voxy.client.core.rendering.hierachical2;
//Request of the leaf node to expand
class LeafExpansionRequest {
class NodeChildRequest {
//Child states contain micrometadata in the top bits
// such as isEmpty, and isEmptyButEventuallyHasNonEmptyChild
private final long nodePos;
@@ -11,13 +10,20 @@ class LeafExpansionRequest {
private byte results;
private byte mask;
LeafExpansionRequest(long nodePos) {
NodeChildRequest(long nodePos) {
this.nodePos = nodePos;
}
public int getChildMeshResult(int childIdx) {
if ((this.mask&(1<<childIdx))==0) {
throw new IllegalStateException("Tried getting mesh result of child not in mask");
}
return this.childStates[childIdx];
}
public int putChildResult(int childIdx, int mesh) {
if ((this.mask&(1<<childIdx))==0) {
throw new IllegalStateException("Tried putting child into leaf which doesnt match mask");
throw new IllegalStateException("Tried putting child into request which isnt in mask");
}
//Note the mesh can be -ve meaning empty mesh, but we should still mark that node as having a result
boolean isFirstInsert = (this.results&(1<<childIdx))==0;

View File

@@ -13,12 +13,17 @@ public final class NodeStore {
this.allocationSet = new HierarchicalBitSet(maxNodeCount);
}
private static int id2idx(int idx) {
return idx*LONGS_PER_NODE;
}
public int allocate() {
int id = this.allocationSet.allocateNext();
if (id < 0) {
throw new IllegalStateException("Failed to allocate node slot!");
}
this.ensureSized(id);
this.clear(id);
return id;
}
@@ -31,6 +36,9 @@ public final class NodeStore {
throw new IllegalStateException("Failed to allocate " + count + " consecutive nodes!!");
}
this.ensureSized(id + (count-1));
for (int i = 0; i < count; i++) {
this.clear(id + i);
}
return id;
}
@@ -45,34 +53,49 @@ public final class NodeStore {
}
}
private void free(int nodeId) {
if (!this.allocationSet.free(nodeId)) {
throw new IllegalStateException("Node " + nodeId + " was not allocated!");
}
}
private void clear(int nodeId) {
}
public void setNodePosition(int node, long position) {
this.localNodeData[id2idx(node)] = position;
}
public long nodePosition(int nodeId) {
return this.localNodeData[nodeId<<2];
return this.localNodeData[id2idx(nodeId)];
}
public boolean nodeExists(int nodeId) {
return false;
return this.allocationSet.isSet(nodeId);
}
public boolean hasGeometry(int node) {
return false;
}
public int getNodeGeometry(int node) {
return 0;
return -1;
}
public void setNodeGeometry(int node, int geometryId) {
}
public void setNodeRequest(int node, int requestId) {
}
public void markRequestInFlight(int nodeId) {
}
public boolean nodeRequestInFlight(int nodeId) {
public boolean isNodeRequestInFlight(int nodeId) {
return false;
}
@@ -82,9 +105,21 @@ public final class NodeStore {
public byte getNodeChildExistence(int nodeId) {return 0;}
public void setNodeChildExistence(int node, byte existence) {
}
public int getChildPtr(int nodeId) {
return -1;
}
public void setChildPtr(int nodeId, int ptr) {
}
//Writes out a nodes data to the ptr in the compacted/reduced format
public void writeNode(long ptr, int nodeId) {
}
}