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

fixed eventlauncher parsing

ringall mode in ghostmode
This commit is contained in:
maj00r
2026-06-23 23:00:54 +02:00
parent 06409a54ea
commit 06d44b50b0
4 changed files with 62 additions and 14 deletions

View File

@@ -59,8 +59,12 @@ namespace scene {
// v7: a visual model node's marker also carries the model's local position (3 f32), so the // v7: a visual model node's marker also carries the model's local position (3 f32), so the
// camera-ring visual load can distance-test and skip a node in O(1) without reading any // camera-ring visual load can distance-test and skip a node in O(1) without reading any
// of its tokens -- the difference between scanning 1M flora nodes per ring and not. // of its tokens -- the difference between scanning 1M flora nodes per ring and not.
// v8: fixes the eventlauncher node terminator (it ends with "end", not "endevent"); twins
// baked before this had an over-long eventlauncher span that, when the node was skipped
// in the visual pass, swallowed the following endorigin -> the origin stack accumulated
// and flung terrain/models across the map. bumping invalidates those bad twins.
// bumping the version invalidates older twins so they are recompiled rather than misread. // bumping the version invalidates older twins so they are recompiled rather than misread.
constexpr std::uint32_t SCENERYBINARY_MAGIC { MAKE_ID4( 'e', 'u', '7', 7 ) }; constexpr std::uint32_t SCENERYBINARY_MAGIC { MAKE_ID4( 'e', 'u', '7', 8 ) };
// which entries a reader serves in a given load pass; nodes outside the requested class // which entries a reader serves in a given load pass; nodes outside the requested class
// are skipped (directives/includes are always served, to keep transform/group state) // are skipped (directives/includes are always served, to keep transform/group state)

View File

@@ -53,7 +53,7 @@ inline double ring_max2( int const K ) { return RING_OUTER2[ ( K < RING_COUNT ?
// per-frame time budget (ms) the driver spends streaming visual nodes. larger = the // per-frame time budget (ms) the driver spends streaming visual nodes. larger = the
// surroundings fill in faster but the frame it runs on is longer; on a heavy scene (low fps) // surroundings fill in faster but the frame it runs on is longer; on a heavy scene (low fps)
// a too-small budget is a tiny duty cycle, so streaming a million flora instances drags. // a too-small budget is a tiny duty cycle, so streaming a million flora instances drags.
constexpr int VISUAL_BUDGET_MS { 16 }; constexpr int VISUAL_BUDGET_MS { 24 };
} // anonymous namespace } // anonymous namespace
std::shared_ptr<deserializer_state> std::shared_ptr<deserializer_state>
@@ -144,6 +144,35 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
// the nodes in the current ring are built, the rest skipped in O(1) // the nodes in the current ring are built, the rest skipped in O(1)
m_ringactive = state->visualphase; m_ringactive = state->visualphase;
if( true == m_ringactive ) { if( true == m_ringactive ) {
if( false == state->ringeye_valid ) {
// the visual streaming builds nodes nearest-this-point first, so it must be the
// player's spawn. the camera isn't positioned during load (especially in ghostview),
// so prefer the player vehicle's position; fall back to the camera, then the
// scenery's first camera directive. wait a few frames if nothing is available yet.
auto const iszero = []( glm::dvec3 const &V ) { return ( V.x == 0.0 ) && ( V.y == 0.0 ) && ( V.z == 0.0 ); };
glm::dvec3 eye = Global.pCamera.Pos;
char const *src = "camera";
if( true == iszero( eye ) ) {
auto *player = simulation::Vehicles.find( Global.local_start_vehicle );
if( player != nullptr ) { eye = player->GetPosition(); src = "player vehicle"; }
}
if( true == iszero( eye ) ) { eye = Global.FreeCameraInit[ 0 ]; src = "camera directive"; }
if( ( true == iszero( eye ) ) && ( state->ringeye_waits < 120 ) ) {
++state->ringeye_waits;
return true; // nothing positioned yet; try again next frame
}
state->ringeye = eye;
state->ringeye_valid = true;
// no spawn/camera to centre on (e.g. ghostview): nearest-first is meaningless, so
// build every visual node in a single budgeted pass instead of partitioning into
// distance rings (which would dump everything in the far ring and stream it last).
state->ringall = iszero( eye );
WriteLog( std::string( "Progressive visual load: " )
+ ( state->ringall ?
"no camera centre -- building all visual nodes in one pass" :
"ring centre at " + std::to_string( eye.x ) + " " + std::to_string( eye.y ) + " " + std::to_string( eye.z ) + " (from " + src + ")" ) );
}
m_ringall = state->ringall;
m_ringindex = state->ringindex; m_ringindex = state->ringindex;
m_ringeye = state->ringeye; m_ringeye = state->ringeye;
m_ringmin2 = ring_min2( state->ringindex ); m_ringmin2 = ring_min2( state->ringindex );
@@ -184,7 +213,7 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
// decoding any of its tokens. this is what keeps the per-ring replay cheap when a // decoding any of its tokens. this is what keeps the per-ring replay cheap when a
// scenery has a million flora instances. (shapes/older twins have no marker // scenery has a million flora instances. (shapes/older twins have no marker
// position; they fall through to deserialize_node, which ring-tests them itself.) // position; they fall through to deserialize_node, which ring-tests them itself.)
if( token == "node" ) { if( ( false == m_ringall ) && ( token == "node" ) ) {
double x, y, z; double x, y, z;
if( true == Input.currentNodePosition( x, y, z ) ) { if( true == Input.currentNodePosition( x, y, z ) ) {
auto const world { transform( glm::dvec3{ x, y, z }, Scratchpad ) }; auto const world { transform( glm::dvec3{ x, y, z }, Scratchpad ) };
@@ -234,6 +263,13 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
// single-pass placement exactly; without it an unbalanced origin left on the stack would // single-pass placement exactly; without it an unbalanced origin left on the stack would
// be applied again and shift every deferred visual node ("terrain dumped beside the tracks"). // be applied again and shift every deferred visual node ("terrain dumped beside the tracks").
auto const resettransform = [ &Scratchpad ]() { auto const resettransform = [ &Scratchpad ]() {
// a well-formed pass ends with a balanced (empty) transform stack; a leftover means
// an origin/scale was pushed but never popped -- e.g. a node whose binary marker span
// over-ran its terminator and skipped the following endorigin. warn rather than let it
// silently accumulate into the next pass.
if( false == Scratchpad.location.offset.empty() ) {
WriteLog( "Bad scenery: " + std::to_string( Scratchpad.location.offset.size() ) + " unbalanced origin(s) left on the stack at end of a load pass" );
}
Scratchpad.location.offset = {}; Scratchpad.location.offset = {};
Scratchpad.location.scale = {}; Scratchpad.location.scale = {};
Scratchpad.location.rotation = glm::vec3{}; }; Scratchpad.location.rotation = glm::vec3{}; };
@@ -241,22 +277,22 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
// first (infrastructure) pass finished: the scenario is now playable (tracks, events, // first (infrastructure) pass finished: the scenario is now playable (tracks, events,
// signals, the player train are all loaded). hand control back so the loader can switch // signals, the player train are all loaded). hand control back so the loader can switch
// to the driver; the visual nodes load progressively from the driver, replayed once per // to the driver; the visual nodes load progressively from the driver, replayed once per
// camera-distance ring (nearest first). the camera is sampled once here so the ring // camera-distance ring (nearest first). the ring centre is sampled later, on the first
// partition stays stable across passes. only possible when replaying a binary twin -- a // driver pass (the camera isn't positioned here yet). only possible when replaying a binary
// text/compile load did everything in one pass (restartReplay returns false). // twin -- a text/compile load did everything in one pass (restartReplay returns false).
if( ( false == state->visualphase ) if( ( false == state->visualphase )
&& ( true == Input.restartReplay( scene::scenery_load_pass::visual ) ) ) { && ( true == Input.restartReplay( scene::scenery_load_pass::visual ) ) ) {
state->visualphase = true; state->visualphase = true;
state->ringindex = 0; state->ringindex = 0;
state->ringeye = Global.pCamera.Pos;
resettransform(); resettransform();
WriteLog( "Progressive visual load: streaming deferred nodes nearest-camera first (" + std::to_string( RING_COUNT ) + " rings)" ); WriteLog( "Progressive visual load: streaming deferred nodes nearest-camera first (" + std::to_string( RING_COUNT ) + " rings)" );
return false; // infrastructure ready -> go to driver; visuals continue there return false; // infrastructure ready -> go to driver; visuals continue there
} }
// a ring pass finished: advance to the next (farther) ring and replay again, until the // a ring pass finished: advance to the next (farther) ring and replay again, until the
// outermost ring has been built // outermost ring has been built. skipped in build-all mode, which builds everything in one pass.
if( ( true == state->visualphase ) if( ( true == state->visualphase )
&& ( false == state->ringall )
&& ( state->ringindex < ring_lastindex() ) && ( state->ringindex < ring_lastindex() )
&& ( true == Input.restartReplay( scene::scenery_load_pass::visual ) ) ) { && ( true == Input.restartReplay( scene::scenery_load_pass::visual ) ) ) {
++state->ringindex; ++state->ringindex;
@@ -668,8 +704,9 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
|| ( nodedata.type == "triangle_fan" ) ) { || ( nodedata.type == "triangle_fan" ) ) {
// explicit shapes have no single position to ring-test by, so build them only in the // explicit shapes have no single position to ring-test by, so build them only in the
// nearest ring pass (ring 0) and skip them in O(1) on the farther passes // nearest ring pass (ring 0) and skip them in O(1) on the farther passes (build-all
if( ( true == m_ringactive ) && ( m_ringindex > 0 ) ) { // mode is a single pass, so they always build there)
if( ( true == m_ringactive ) && ( false == m_ringall ) && ( m_ringindex > 0 ) ) {
if( false == Input.skipReplayNode() ) { skip_until( Input, "endtri" ); } if( false == Input.skipReplayNode() ) { skip_until( Input, "endtri" ); }
return; return;
} }
@@ -697,8 +734,8 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
|| ( nodedata.type == "line_strip" ) || ( nodedata.type == "line_strip" )
|| ( nodedata.type == "line_loop" ) ) { || ( nodedata.type == "line_loop" ) ) {
// see the triangles branch: explicit shapes build in ring 0 only // see the triangles branch: explicit shapes build in ring 0 only (or always, build-all)
if( ( true == m_ringactive ) && ( m_ringindex > 0 ) ) { if( ( true == m_ringactive ) && ( false == m_ringall ) && ( m_ringindex > 0 ) ) {
if( false == Input.skipReplayNode() ) { skip_until( Input, "endline" ); } if( false == Input.skipReplayNode() ) { skip_until( Input, "endline" ); }
return; return;
} }
@@ -1055,7 +1092,7 @@ state_serializer::deserialize_model( cParser &Input, scene::scratch_data &Scratc
// marker; this is the fallback for nodes that reached here (in-ring, or no marker pos). // marker; this is the fallback for nodes that reached here (in-ring, or no marker pos).
// use the marker position when present so this ring decision matches the dispatch one // use the marker position when present so this ring decision matches the dispatch one
// exactly -- otherwise a node near a ring boundary could be dropped by both adjacent rings. // exactly -- otherwise a node near a ring boundary could be dropped by both adjacent rings.
if( true == m_ringactive ) { if( ( true == m_ringactive ) && ( false == m_ringall ) ) {
glm::dvec3 ringlocal { location }; glm::dvec3 ringlocal { location };
double mx, my, mz; double mx, my, mz;
if( true == Input.currentNodePosition( mx, my, mz ) ) { ringlocal = glm::dvec3{ mx, my, mz }; } if( true == Input.currentNodePosition( mx, my, mz ) ) { ringlocal = glm::dvec3{ mx, my, mz }; }

View File

@@ -33,6 +33,12 @@ struct deserializer_state {
// starts so the ring partition is stable across passes. // starts so the ring partition is stable across passes.
int ringindex { 0 }; int ringindex { 0 };
glm::dvec3 ringeye { 0.0 }; glm::dvec3 ringeye { 0.0 };
// ringeye must be sampled from the camera *after* control passes to the driver (the
// loader hasn't positioned the camera yet -- sampling there put every node in the far
// ring and built models last / seemingly never). sampled lazily on the first driver pass.
bool ringeye_valid { false };
int ringeye_waits { 0 }; // frames spent waiting for the camera to be positioned
bool ringall { false }; // no usable camera centre -> build all visual nodes in one pass
deserializer_state(std::string const &File, cParser::buffertype const Type, const std::string &Path, bool const Loadtraction) deserializer_state(std::string const &File, cParser::buffertype const Type, const std::string &Path, bool const Loadtraction)
: scenariofile(File), input(File, Type, Path, Loadtraction) { } : scenariofile(File), input(File, Type, Path, Loadtraction) { }
@@ -104,6 +110,7 @@ private:
// deserialize_continue() call so deserialize_model()/deserialize_node() can ring-test a // deserialize_continue() call so deserialize_model()/deserialize_node() can ring-test a
// node by distance. inactive (builds everything) outside the ring/visual phase. // node by distance. inactive (builds everything) outside the ring/visual phase.
bool m_ringactive { false }; bool m_ringactive { false };
bool m_ringall { false }; // no camera centre available -> build every node in one pass
int m_ringindex { 0 }; int m_ringindex { 0 };
glm::dvec3 m_ringeye { 0.0 }; glm::dvec3 m_ringeye { 0.0 };
double m_ringmin2 { 0.0 }; double m_ringmin2 { 0.0 };

View File

@@ -108,7 +108,7 @@ inline bool classifyNodeType(std::string const &Type, bool &Visual, std::string
{ "traction", false, "endtraction" }, { "traction", false, "endtraction" },
{ "tractionpowersource", false, "end" }, { "tractionpowersource", false, "end" },
{ "memcell", false, "endmemcell" }, { "memcell", false, "endmemcell" },
{ "eventlauncher", false, "endevent" }, { "eventlauncher", false, "end" },
{ "sound", false, "endsound" }, { "sound", false, "endsound" },
{ "dynamic", false, "enddynamic" }, { "dynamic", false, "enddynamic" },
}; };