mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-22 19:49:19 +02:00
Stream visual scenery in the driver (play starts after infrastructure pass)
Splits the progressive load across modes: the loader runs only the first (infrastructure) pass and then hands off to the driver, so play begins as soon as tracks/traction/events/signals/the player train are ready. The deferred visual nodes (3d models, terrain shapes/lines) then stream in from the driver, a small budget per frame, so the world fills in without a long up-front wait. - state_serializer: the infrastructure pass now returns to the caller (instead of chaining the visual pass), restarting the twin for the visual pass; the visual pass uses a small ~8 ms/frame budget and marks the load done at the end. A text/compile load (no twin) stays single-pass and finishes as before. - state_manager: retains the load state across the loader->driver hand-off and exposes loading_visuals()/continue_loading_visuals(). - driver_mode::update(): advances the deferred visual load each frame. Verified on td.scn (playable in ~2 s, visuals complete a few seconds later, no duplicate objects) and on the large tomaszewo scenery (flora/terrain/models deferred while infrastructure loads). Inserts run on the main thread before the render step, so progressive loading needs no locking. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -171,6 +171,10 @@ bool driver_mode::update()
|
|||||||
simulation::State.update_scripting_interface();
|
simulation::State.update_scripting_interface();
|
||||||
simulation::Environment.update();
|
simulation::Environment.update();
|
||||||
|
|
||||||
|
// progressive loading: stream in the deferred visual nodes a little each frame, so
|
||||||
|
// the world fills in after play has already started on the infrastructure pass
|
||||||
|
simulation::State.continue_loading_visuals();
|
||||||
|
|
||||||
if (deltatime != 0.0)
|
if (deltatime != 0.0)
|
||||||
{
|
{
|
||||||
// jak pauza, to nie ma po co tego przeliczać
|
// jak pauza, to nie ma po co tego przeliczać
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ bool is_ready { false };
|
|||||||
std::shared_ptr<deserializer_state>
|
std::shared_ptr<deserializer_state>
|
||||||
state_manager::deserialize_begin(std::string const &Scenariofile) {
|
state_manager::deserialize_begin(std::string const &Scenariofile) {
|
||||||
|
|
||||||
return m_serializer.deserialize_begin( Scenariofile );
|
m_loadstate = m_serializer.deserialize_begin( Scenariofile );
|
||||||
|
return m_loadstate;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@@ -65,6 +66,26 @@ state_manager::deserialize_continue(std::shared_ptr<deserializer_state> state) {
|
|||||||
return m_serializer.deserialize_continue(state);
|
return m_serializer.deserialize_continue(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// true while a progressive load still has visual nodes to stream in (driver phase)
|
||||||
|
bool
|
||||||
|
state_manager::loading_visuals() const {
|
||||||
|
|
||||||
|
return ( m_loadstate && m_loadstate->visualphase && ( false == m_loadstate->done ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// advances the deferred visual-node load by one budgeted step; clears the state when done.
|
||||||
|
// called every frame from the driver so the world's visuals fill in after play starts.
|
||||||
|
void
|
||||||
|
state_manager::continue_loading_visuals() {
|
||||||
|
|
||||||
|
if( false == loading_visuals() ) { return; }
|
||||||
|
m_serializer.deserialize_continue( m_loadstate );
|
||||||
|
if( m_loadstate->done ) {
|
||||||
|
WriteLog( "Progressive visual load complete" );
|
||||||
|
m_loadstate = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// stores class data in specified file, in legacy (text) format
|
// stores class data in specified file, in legacy (text) format
|
||||||
void
|
void
|
||||||
state_manager::export_as_text( std::string const &Scenariofile ) const {
|
state_manager::export_as_text( std::string const &Scenariofile ) const {
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ public:
|
|||||||
// continues deserialization for given context, amount limited by time, returns true if needs to be called again
|
// continues deserialization for given context, amount limited by time, returns true if needs to be called again
|
||||||
bool
|
bool
|
||||||
deserialize_continue(std::shared_ptr<deserializer_state> state);
|
deserialize_continue(std::shared_ptr<deserializer_state> state);
|
||||||
|
// progressive load: true while deferred visual nodes are still streaming in (driver phase)
|
||||||
|
bool
|
||||||
|
loading_visuals() const;
|
||||||
|
// advances the deferred visual-node load by one budgeted step (call once per frame)
|
||||||
|
void
|
||||||
|
continue_loading_visuals();
|
||||||
// stores class data in specified file, in legacy (text) format
|
// stores class data in specified file, in legacy (text) format
|
||||||
void
|
void
|
||||||
export_as_text( std::string const &Scenariofile ) const;
|
export_as_text( std::string const &Scenariofile ) const;
|
||||||
@@ -62,6 +68,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
// members
|
// members
|
||||||
state_serializer m_serializer;
|
state_serializer m_serializer;
|
||||||
|
std::shared_ptr<deserializer_state> m_loadstate; // retained across loader->driver for visual streaming
|
||||||
struct {
|
struct {
|
||||||
std::shared_ptr<TMemCell> weather, time, date;
|
std::shared_ptr<TMemCell> weather, time, date;
|
||||||
} m_scriptinginterface;
|
} m_scriptinginterface;
|
||||||
|
|||||||
@@ -148,7 +148,10 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto timenow = std::chrono::steady_clock::now();
|
auto timenow = std::chrono::steady_clock::now();
|
||||||
if( std::chrono::duration_cast<std::chrono::milliseconds>( timenow - timelast ).count() >= 200 ) {
|
// small per-frame budget while streaming visuals in the driver (avoid stutter),
|
||||||
|
// generous budget while the loading screen is up (infrastructure pass)
|
||||||
|
auto const budget = ( state->visualphase ? 8 : 200 );
|
||||||
|
if( std::chrono::duration_cast<std::chrono::milliseconds>( timenow - timelast ).count() >= budget ) {
|
||||||
Application.set_progress( Input.getProgress(), Input.getFullProgress() );
|
Application.set_progress( Input.getProgress(), Input.getFullProgress() );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -161,13 +164,15 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
|
|||||||
deserialize_firstinit( Input, Scratchpad );
|
deserialize_firstinit( Input, Scratchpad );
|
||||||
}
|
}
|
||||||
|
|
||||||
// first (infrastructure) pass finished: run a second pass over the same twin to load
|
// first (infrastructure) pass finished: the scenario is now playable (tracks, events,
|
||||||
// the visual nodes that were skipped. only possible when replaying a binary twin; a
|
// signals, the player train are all loaded). hand control back so the loader can switch
|
||||||
// text/compile load did everything in one pass (restartReplay returns false).
|
// to the driver; the visual nodes load progressively from the driver via a second pass
|
||||||
|
// over the same twin. only possible when replaying a binary 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;
|
||||||
return true; // continue with the visual pass
|
return false; // infrastructure ready -> go to driver; visuals continue there
|
||||||
}
|
}
|
||||||
|
|
||||||
scene::Groups.close();
|
scene::Groups.close();
|
||||||
@@ -182,6 +187,7 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
|
|||||||
// before we report the scenario as loaded
|
// before we report the scenario as loaded
|
||||||
scene::scenerybinary_wait_all();
|
scene::scenerybinary_wait_all();
|
||||||
|
|
||||||
|
state->done = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ struct deserializer_state {
|
|||||||
// progressive (two-pass) load over a binary twin: first pass loads infrastructure,
|
// progressive (two-pass) load over a binary twin: first pass loads infrastructure,
|
||||||
// second pass loads visual nodes. false while in the first (infrastructure) pass.
|
// second pass loads visual nodes. false while in the first (infrastructure) pass.
|
||||||
bool visualphase { false };
|
bool visualphase { false };
|
||||||
|
// set once the whole load (both passes / single text pass) has fully finished
|
||||||
|
bool done { false };
|
||||||
|
|
||||||
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) { }
|
||||||
|
|||||||
Reference in New Issue
Block a user