16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-18 01:59:19 +02:00

Store model position in the v7 node marker for O(1) ring skipping

The camera-ring visual load replays the twin once per distance ring, and each pass
had to read every node's header + X Y Z tokens (~8 tokens through the cParser pipeline)
just to decide whether the node is in the current ring -- expensive when a scenery has
a million flora instances.

Bump the twin format to v7: a visual model node's marker now carries its local position
(3 f32), captured at bake time from the node's first three numbers. The reader hands the
position out (scenery_binary_reader::node_position), and the deserializer ring-tests a
model and skips it in O(1) over the marker span, straight from the dispatch loop, without
deserialize_node decoding any of its tokens. deserialize_model uses the same marker
position for its own ring test so the two decisions agree exactly (a node near a ring
boundary can't be dropped by both adjacent rings). Shapes/older twins have no marker
position and fall back to the token-based test. The per-frame visual budget is factored
into VISUAL_BUDGET_MS.

Verified: td.scn rebakes to v7, no duplicates, no unexpected tokens, completes ~1s.
tomaszewo (1M+ flora) stays memory-bounded with no duplicates; full streaming is still
paced by walking every node per ring (the remaining cost is the replay pipeline itself,
which a spatial section index would address).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
maj00r
2026-06-23 18:09:03 +02:00
parent d7a3e1310a
commit 06409a54ea
5 changed files with 105 additions and 14 deletions

View File

@@ -319,7 +319,7 @@ void cParser::bakeFinishNode()
// flush a node still open at end-of-file or one whose type was unrecognized
if (m_bakenode_active && m_writer)
{
m_writer->end_node(m_bakenode_visual);
m_writer->end_node(m_bakenode_visual, m_bakenode_haspos, m_bakenode_pos[0], m_bakenode_pos[1], m_bakenode_pos[2]);
}
m_bakenode_active = false;
}
@@ -774,6 +774,7 @@ void cParser::readToken(std::string &out, bool ToLower, const char *Break)
m_bakenode_active = true;
m_bakenode_count = 0;
m_bakenode_visual = false;
m_bakenode_haspos = false;
m_bakenode_end.clear();
}
else if (m_bakenode_active && m_bakenode_end.empty() && (lowered == "node"))
@@ -785,6 +786,7 @@ void cParser::readToken(std::string &out, bool ToLower, const char *Break)
m_bakenode_active = true;
m_bakenode_count = 0;
m_bakenode_visual = false;
m_bakenode_haspos = false;
m_bakenode_end.clear();
}
@@ -807,11 +809,18 @@ void cParser::readToken(std::string &out, bool ToLower, const char *Break)
{
// 5th node entry is the type token (node, range_max, range_min, name, type)
classifyNodeType(lowered, m_bakenode_visual, m_bakenode_end);
// a model node's entries 6,7,8 are its local X Y Z -- record them in the
// marker so the camera-ring load can skip the node without reading its body
m_bakenode_haspos = (lowered == "model");
}
else if (m_bakenode_haspos && (m_bakenode_count >= 6) && (m_bakenode_count <= 8))
{
m_bakenode_pos[m_bakenode_count - 6] = value;
}
else if ((false == m_bakenode_end.empty()) && (lowered == m_bakenode_end))
{
// terminator captured: close the node
m_writer->end_node(m_bakenode_visual);
m_writer->end_node(m_bakenode_visual, m_bakenode_haspos, m_bakenode_pos[0], m_bakenode_pos[1], m_bakenode_pos[2]);
m_bakenode_active = false;
}
}
@@ -903,6 +912,13 @@ bool cParser::skipReplayNode()
return false;
}
bool cParser::currentNodePosition(double &X, double &Y, double &Z)
{
// delegate to the deepest active include child (it serves the current node), like skip
if (mIncludeParser) { return mIncludeParser->currentNodePosition(X, Y, Z); }
return (m_replay && m_reader && m_reader->node_position(X, Y, Z));
}
std::vector<std::string> cParser::readParameters(cParser &Input)
{