diff --git a/simulation/simulationstateserializer.cpp b/simulation/simulationstateserializer.cpp index 166cf1a6..78864177 100644 --- a/simulation/simulationstateserializer.cpp +++ b/simulation/simulationstateserializer.cpp @@ -49,6 +49,10 @@ state_serializer::deserialize_begin( std::string const &Scenariofile ) { std::make_shared( Scenariofile, cParser::buffer_FILE, Global.asCurrentSceneryPath, Global.bLoadTraction ); state->scenariofile = Scenariofile; state->scratchpad.name = Scenariofile; + // first pass loads infrastructure (tracks/traction/events/memcells/sounds + directives); + // visual nodes are skipped by the reader and loaded in a second pass. for a text/compile + // load (no twin) this is a no-op and everything loads in a single pass. + state->input.setReplayPass( scene::scenery_load_pass::infrastructure ); scene::Groups.create(); if( false == state->input.ok() ) @@ -104,11 +108,37 @@ state_serializer::deserialize_continue(std::shared_ptr state cParser &Input = state->input; scene::scratch_data &Scratchpad = state->scratchpad; + // stateful directives that build objects/lists; on the visual (second) pass they are + // skipped wholesale so their side effects (trainsets, events, cameras, ...) don't + // duplicate. transform/group directives (origin/rotate/scale/group) and idempotent + // setters are re-run, so deferred visual nodes get the correct placement. + static std::unordered_map const visualskip { + { "trainset", "endtrainset" }, + { "event", "endevent" }, + { "camera", "endcamera" }, + { "light", "endlight" }, + { "description", "enddescription" }, + { "test", "endtest" }, + { "sky", "endsky" }, + { "time", "endtime" }, + { "terrain", "endterrain" }, + }; + // deserialize content from the provided input auto timelast { std::chrono::steady_clock::now() }; std::string token { Input.getToken() }; while( false == token.empty() ) { + if( state->visualphase ) { + auto const skip = visualskip.find( token ); + if( skip != visualskip.end() ) { + // consume the stateful directive without running its handler + skip_until( Input, skip->second ); + token = Input.getToken(); + continue; + } + } + auto lookup = state->functionmap.find( token ); if( lookup != state->functionmap.end() ) { lookup->second(); @@ -131,6 +161,15 @@ state_serializer::deserialize_continue(std::shared_ptr state deserialize_firstinit( Input, Scratchpad ); } + // first (infrastructure) pass finished: run a second pass over the same twin to load + // the visual nodes that were skipped. only possible when replaying a binary twin; a + // text/compile load did everything in one pass (restartReplay returns false). + if( ( false == state->visualphase ) + && ( true == Input.restartReplay( scene::scenery_load_pass::visual ) ) ) { + state->visualphase = true; + return true; // continue with the visual pass + } + scene::Groups.close(); scene::Groups.update_map(); diff --git a/simulation/simulationstateserializer.h b/simulation/simulationstateserializer.h index dea1e20e..346aee54 100644 --- a/simulation/simulationstateserializer.h +++ b/simulation/simulationstateserializer.h @@ -22,6 +22,9 @@ struct deserializer_state { std::unordered_map< std::string, deserializefunctionbind> functionmap; + // progressive (two-pass) load over a binary twin: first pass loads infrastructure, + // second pass loads visual nodes. false while in the first (infrastructure) pass. + bool visualphase { false }; deserializer_state(std::string const &File, cParser::buffertype const Type, const std::string &Path, bool const Loadtraction) : scenariofile(File), input(File, Type, Path, Loadtraction) { } diff --git a/utilities/parser.cpp b/utilities/parser.cpp index 741de367..d9443158 100644 --- a/utilities/parser.cpp +++ b/utilities/parser.cpp @@ -641,6 +641,9 @@ void cParser::startIncludeDirect(std::string includefile, std::vectorallowRandomIncludes = allowRandomIncludes; mIncludeParser->autoclear(m_autoclear); + // the child inherits the current load pass so the whole include tree is filtered + // consistently (e.g. visuals-only on the second pass) + mIncludeParser->setReplayPass(m_replaypass); // a binary-twin replay child reports mSize 0 but is still valid if (mIncludeParser->mSize <= 0 && (false == mIncludeParser->m_replay)) { @@ -648,6 +651,29 @@ void cParser::startIncludeDirect(std::string includefile, std::vectorset_pass(Pass); + } +} + +bool cParser::restartReplay(scene::scenery_load_pass Pass) +{ + if ((false == m_replay) || (false == static_cast(m_reader))) + { + return false; + } + m_reader->open(m_twinbuf); + m_reader->set_pass(Pass); + m_replaypass = Pass; + m_replayexhausted = false; + mIncludeParser = nullptr; + return true; +} + bool cParser::handleIncludeIfPresent(std::string& token, bool ToLower, const char* Break) { // token-mode include: token == "include". NOTE: we only process the directive here // and report it; readToken loops to fetch the next token. fetching it here (the old diff --git a/utilities/parser.h b/utilities/parser.h index b6be4c70..4488adb3 100644 --- a/utilities/parser.h +++ b/utilities/parser.h @@ -22,6 +22,7 @@ http://mozilla.org/MPL/2.0/. // before cParser is defined. namespace scene { enum class scenery_file_kind : std::uint8_t; + enum class scenery_load_pass : std::uint8_t; class scenery_binary_writer; class scenery_binary_reader; } @@ -46,6 +47,13 @@ class cParser //: public std::stringstream // touching scene state, and return the (relative) filenames it includes so a parallel // baker can compile each of those twins on its own thread. requires BakeOnly ctor. std::vector bakeFile(); + // select which class of nodes the replay serves (propagated to include children). + // only meaningful in replay (twin) mode; ignored for text parsing. + void setReplayPass( scene::scenery_load_pass Pass ); + // rewind the replay to the start of the twin with a (possibly new) pass, dropping any + // open include child. used to run a second pass (visual) over an already-loaded twin. + // returns false if this parser isn't replaying a twin (no second pass possible). + bool restartReplay( scene::scenery_load_pass Pass ); // methods: template cParser & @@ -180,6 +188,7 @@ class cParser //: public std::stringstream // replay: this file is served from its binary twin instead of text bool m_replay { false }; bool m_replayexhausted { false }; // all twin entries consumed (for inline eof()) + scene::scenery_load_pass m_replaypass {}; // value-init == scenery_load_pass::all std::string m_twinbuf; // whole twin file held in memory; the reader views into it std::unique_ptr m_reader; // streams entries from m_twinbuf // compile: this file is being captured into a binary twin alongside the text read