mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 04:19:19 +02:00
Tune EU7 PACK streaming for fast camera flight and reduce frame hitches
This commit is contained in:
@@ -404,8 +404,6 @@ bool driver_mode::update()
|
||||
|
||||
Timer::subsystem.sim_total.stop();
|
||||
|
||||
simulation::State.drain_deferred_eu7_trainsets( 12.0 );
|
||||
|
||||
if( scene::eu7::section_stream_active() ) {
|
||||
if( scene::eu7::section_stream_needs_bootstrap() ) {
|
||||
scene::eu7::kick_section_stream_bootstrap();
|
||||
@@ -424,6 +422,12 @@ bool driver_mode::update()
|
||||
}
|
||||
}
|
||||
|
||||
simulation::State.drain_deferred_eu7_trainsets(
|
||||
scene::eu7::section_stream_active()
|
||||
&& scene::eu7::section_stream_frame_budget_pressure() ?
|
||||
4.0 :
|
||||
10.0 );
|
||||
|
||||
simulation::Region->update_sounds();
|
||||
audio::renderer.update(Global.iPause ? 0.0 : deltarealtime);
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ warm_one_pack_texture( std::string texture_file, std::unordered_set<std::string>
|
||||
if( false == seen.insert( texture_file ).second ) {
|
||||
return false;
|
||||
}
|
||||
if( false == g_session_warmed_textures.insert( texture_file ).second ) {
|
||||
if( g_session_warmed_textures.contains( texture_file ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ warm_one_pack_texture( std::string texture_file, std::unordered_set<std::string>
|
||||
log_pack_texture_fail( texture_file );
|
||||
return false;
|
||||
}
|
||||
|
||||
g_session_warmed_textures.insert( texture_file );
|
||||
GfxRenderer->Fetch_Material( resolved );
|
||||
|
||||
for( int skinindex { 1 }; skinindex <= 4; ++skinindex ) {
|
||||
|
||||
@@ -58,20 +58,30 @@ constexpr std::size_t kLoaderSliceColdMeshes { 32 };
|
||||
constexpr double kGameplayColdBudgetMs { 3.0 };
|
||||
constexpr double kCatchupColdBudgetMs { 5.0 };
|
||||
|
||||
constexpr int kInitialBootstrapRadius { 4 };
|
||||
constexpr int kStreamRadius { 11 };
|
||||
constexpr int kMovementLookahead { 7 };
|
||||
constexpr std::size_t kMaxPackStreamWorkers { 4 };
|
||||
constexpr std::size_t kMaxInFlightSections { 8 };
|
||||
constexpr std::size_t kMaxReadySections { 3 };
|
||||
constexpr int kInitialBootstrapRadius { 5 };
|
||||
constexpr int kStreamRadius { 14 };
|
||||
constexpr int kMovementLookahead { 10 };
|
||||
constexpr std::size_t kMaxPackStreamWorkers { 8 };
|
||||
constexpr std::size_t kMaxInFlightSections { 12 };
|
||||
constexpr std::size_t kMaxReadySections { 6 };
|
||||
constexpr double kStreamStatusLogIntervalSec { 5.0 };
|
||||
constexpr double kReenqueueDistanceM { 500.0 };
|
||||
constexpr double kCatchupReenqueueDistanceM { 40.0 };
|
||||
constexpr std::size_t kHeavySectionModelThreshold { 800 };
|
||||
constexpr double kUrgentApplyBudgetMs { 32.0 };
|
||||
constexpr std::size_t kUrgentSliceInstances { 512 };
|
||||
constexpr std::size_t kUrgentSliceColdMeshes { 20 };
|
||||
constexpr double kUrgentColdBudgetMs { 24.0 };
|
||||
constexpr std::size_t kUrgentMaxChunksPerDrain { 8 };
|
||||
constexpr double kReadyTexturePrefetchBudgetMs { 14.0 };
|
||||
constexpr std::size_t kReadyTexturePrefetchSlice { 256 };
|
||||
constexpr std::size_t kCatchupMaxInFlightSections { 28 };
|
||||
constexpr std::size_t kCatchupMaxReadySections { 14 };
|
||||
constexpr int kUrgentSectionDistanceKm { 1 };
|
||||
constexpr int kPreemptPendingDistanceKm { 2 };
|
||||
constexpr double kTeleportReenqueueDistanceM { 2000.0 };
|
||||
constexpr float kStationaryCatchupRingThreshold { 0.70f };
|
||||
constexpr double kStationaryCatchupSpeedMps { 5.0 };
|
||||
constexpr std::size_t kCatchupMaxInFlightSections { 20 };
|
||||
constexpr std::size_t kCatchupMaxReadySections { 8 };
|
||||
constexpr std::size_t kBootstrapDrainMs { 32 };
|
||||
constexpr std::size_t kBootstrapTimeoutMs { 120000 };
|
||||
constexpr std::chrono::milliseconds kPresentableHoldMs { 200 };
|
||||
@@ -98,6 +108,7 @@ struct PackSectionReady {
|
||||
std::unique_ptr<std::vector<Eu7Model>> models;
|
||||
bool failed { false };
|
||||
std::size_t apply_offset { 0 };
|
||||
std::size_t texture_warm_offset { 0 };
|
||||
};
|
||||
|
||||
struct SectionStreamState {
|
||||
@@ -169,6 +180,12 @@ enqueue_failed_section( PackSectionJob const &job );
|
||||
void
|
||||
release_pending_buffer();
|
||||
|
||||
[[nodiscard]] bool
|
||||
section_has_pack_models( int const row, int const column );
|
||||
|
||||
[[nodiscard]] std::size_t
|
||||
pending_section_total();
|
||||
|
||||
[[nodiscard]] double
|
||||
camera_stream_speed_mps();
|
||||
|
||||
@@ -306,8 +323,40 @@ gameplay_cold_budget_ms() {
|
||||
return g_stream_catchup ? kCatchupColdBudgetMs : kGameplayColdBudgetMs;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
pending_section_near_camera( int const max_distance_km = kUrgentSectionDistanceKm ) {
|
||||
if( false == g_stream.pending_apply.has_value() ) {
|
||||
return false;
|
||||
}
|
||||
auto const &batch { *g_stream.pending_apply };
|
||||
return section_manhattan_sections(
|
||||
batch.row,
|
||||
batch.column,
|
||||
g_stream.center_row,
|
||||
g_stream.center_column ) <= max_distance_km;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
pending_apply_is_urgent() {
|
||||
if( false == pending_section_near_camera() ) {
|
||||
return false;
|
||||
}
|
||||
return g_stream_catchup || camera_stream_speed_mps() > 80.0;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t
|
||||
adaptive_slice_instances( std::size_t const section_total ) {
|
||||
adaptive_slice_instances( std::size_t const section_total, bool const urgent = false ) {
|
||||
if( urgent ) {
|
||||
auto limit { kUrgentSliceInstances };
|
||||
if( section_total > 4000 ) {
|
||||
limit = std::min( limit, std::size_t { 384 } );
|
||||
}
|
||||
else if( section_total > 2000 ) {
|
||||
limit = std::min( limit, std::size_t { 448 } );
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
auto limit { gameplay_slice_instances() };
|
||||
if( g_stream_catchup ) {
|
||||
auto const speed { camera_stream_speed_mps() };
|
||||
@@ -344,7 +393,10 @@ adaptive_slice_instances( std::size_t const section_total ) {
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t
|
||||
adaptive_cold_meshes() {
|
||||
adaptive_cold_meshes( bool const urgent = false ) {
|
||||
if( urgent ) {
|
||||
return kUrgentSliceColdMeshes;
|
||||
}
|
||||
auto limit { gameplay_slice_cold_meshes() };
|
||||
if( pack_bench_stream().last_chunk_ms >= 12.0 ) {
|
||||
limit = 1;
|
||||
@@ -492,7 +544,82 @@ maybe_skip_stuck_apply_offset() {
|
||||
|
||||
void
|
||||
maybe_preempt_distant_pending() {
|
||||
// Wyłączone — najpierw stabilność: dokończ bieżącą sekcję zamiast preemptować.
|
||||
if(
|
||||
false == g_loading_screen_dismissed ||
|
||||
g_stream.bootstrap_active ||
|
||||
g_stream.bootstrap_pending ||
|
||||
false == g_stream.pending_apply.has_value() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &batch { *g_stream.pending_apply };
|
||||
if( batch.models == nullptr || batch.models->empty() ) {
|
||||
return;
|
||||
}
|
||||
if( g_stream.pending_apply_offset >= batch.models->size() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto const pending_dist {
|
||||
section_manhattan_sections(
|
||||
batch.row,
|
||||
batch.column,
|
||||
g_stream.center_row,
|
||||
g_stream.center_column ) };
|
||||
if( pending_dist <= kUrgentSectionDistanceKm ) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool nearer_ready { false };
|
||||
{
|
||||
std::lock_guard<std::mutex> lock { g_stream.ready.mutex };
|
||||
for( auto const &ready : g_stream.ready.data ) {
|
||||
if( ready.failed || ready.models == nullptr || ready.models->empty() ) {
|
||||
continue;
|
||||
}
|
||||
auto const ready_dist {
|
||||
section_manhattan_sections(
|
||||
ready.row,
|
||||
ready.column,
|
||||
g_stream.center_row,
|
||||
g_stream.center_column ) };
|
||||
if(
|
||||
ready_dist < pending_dist &&
|
||||
ready_dist <= kUrgentSectionDistanceKm ) {
|
||||
nearer_ready = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( false == nearer_ready ) {
|
||||
if(
|
||||
pending_dist <= kPreemptPendingDistanceKm ||
|
||||
camera_stream_speed_mps() <= 80.0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto const [cam_row, cam_col] {
|
||||
section_row_column( resolve_section_stream_position( Global.pCamera.Pos ) ) };
|
||||
if( section_has_pack_models( cam_row, cam_col ) ) {
|
||||
nearer_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
if( false == nearer_ready ) {
|
||||
return;
|
||||
}
|
||||
|
||||
batch.apply_offset = g_stream.pending_apply_offset;
|
||||
auto suspended { std::move( batch ) };
|
||||
g_stream.pending_apply.reset();
|
||||
g_stream.pending_apply_offset = 0;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock { g_stream.ready.mutex };
|
||||
g_stream.ready.data.push_front( std::move( suspended ) );
|
||||
}
|
||||
pack_bench_inc( &Eu7PackBench::stream_reenqueue );
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
@@ -502,9 +629,6 @@ apply_pending_chunk(
|
||||
std::size_t const max_cold_meshes,
|
||||
double const cold_budget_ms,
|
||||
std::size_t const max_chunks ) {
|
||||
(void)max_instances;
|
||||
(void)max_cold_meshes;
|
||||
(void)cold_budget_ms;
|
||||
if( g_stream.serializer == nullptr ) {
|
||||
return false;
|
||||
}
|
||||
@@ -550,9 +674,30 @@ apply_pending_chunk(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sekcja 1 km (komorka TCP) — apply w calosci, bez dzielenia na plasterki.
|
||||
auto const remaining { total - offset };
|
||||
auto const is_urgent { pending_apply_is_urgent() };
|
||||
auto const is_heavy {
|
||||
total > kHeavySectionModelThreshold || remaining > kHeavySectionModelThreshold };
|
||||
|
||||
std::size_t chunk_count { remaining };
|
||||
std::size_t cold_mesh_limit { remaining };
|
||||
double effective_cold_budget_ms { 0.0 };
|
||||
|
||||
if( is_heavy ) {
|
||||
chunk_count = std::min( remaining, adaptive_slice_instances( total, is_urgent ) );
|
||||
cold_mesh_limit = adaptive_cold_meshes( is_urgent );
|
||||
effective_cold_budget_ms =
|
||||
is_urgent ? kUrgentColdBudgetMs : gameplay_cold_budget_ms();
|
||||
if( max_instances > 0 ) {
|
||||
chunk_count = std::min( chunk_count, max_instances );
|
||||
}
|
||||
if( max_cold_meshes > 0 ) {
|
||||
cold_mesh_limit = std::min( cold_mesh_limit, max_cold_meshes );
|
||||
}
|
||||
if( cold_budget_ms > 0.0 ) {
|
||||
effective_cold_budget_ms = cold_budget_ms;
|
||||
}
|
||||
}
|
||||
|
||||
auto const chunk_started { std::chrono::steady_clock::now() };
|
||||
double cold_ms { 0.0 };
|
||||
@@ -565,9 +710,9 @@ apply_pending_chunk(
|
||||
auto const phase_started { std::chrono::steady_clock::now() };
|
||||
cold_loads = preload_slice_cold_meshes(
|
||||
models_ptr + offset,
|
||||
remaining,
|
||||
remaining,
|
||||
0.0,
|
||||
chunk_count,
|
||||
cold_mesh_limit,
|
||||
effective_cold_budget_ms,
|
||||
chunk_count );
|
||||
cold_ms = std::chrono::duration<double, std::milli>(
|
||||
std::chrono::steady_clock::now() - phase_started ).count();
|
||||
@@ -687,7 +832,9 @@ maybe_log_stream_status( glm::dvec3 const &world_position ) {
|
||||
pending_offset,
|
||||
pending_total,
|
||||
gameplay_apply_budget_ms(),
|
||||
adaptive_slice_instances( pending_total ) );
|
||||
g_stream.pending_apply.has_value() && g_stream.pending_apply->models != nullptr ?
|
||||
g_stream.pending_apply->models->size() :
|
||||
std::size_t { 0 } );
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
@@ -776,6 +923,10 @@ worker_loop( std::stop_token const stop_token ) {
|
||||
result.failed = result.models == nullptr;
|
||||
|
||||
if( result.failed ) {
|
||||
ErrorLog(
|
||||
"EU7 PACK: pusty odczyt sekcji " + std::to_string( job.row ) + "," +
|
||||
std::to_string( job.column ) + " (PIDX model_count=" +
|
||||
std::to_string( entry->model_count ) + ")" );
|
||||
enqueue_failed_section( job );
|
||||
continue;
|
||||
}
|
||||
@@ -783,6 +934,8 @@ worker_loop( std::stop_token const stop_token ) {
|
||||
pack_bench_inc(
|
||||
&Eu7PackBench::worker_models_decoded, result.models->size() );
|
||||
|
||||
preload_pack_models( *result.models );
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock { g_stream.ready.mutex };
|
||||
g_stream.ready.data.push_back( std::move( result ) );
|
||||
@@ -954,6 +1107,13 @@ enqueue_section_if_needed(
|
||||
if( g_stream.loaded_sections.contains( section_idx ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pusta komorka siatki (brak wpisu PACK) — nie kolejkuj, oznacz jako zaladowana.
|
||||
if( false == section_has_pack_models( row, column ) ) {
|
||||
g_stream.loaded_sections.insert( section_idx );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( g_stream.in_flight_sections.contains( section_idx ) ) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock { g_stream.jobs.mutex };
|
||||
@@ -1128,13 +1288,13 @@ enqueue_movement_lookahead(
|
||||
lookahead_steps = std::min( max_steps, 6 );
|
||||
}
|
||||
}
|
||||
if( max_steps > kMovementLookahead ) {
|
||||
{
|
||||
auto const cam_speed { camera_stream_speed_mps() };
|
||||
if( cam_speed > 200.0 ) {
|
||||
lookahead_steps = max_steps;
|
||||
}
|
||||
else if( cam_speed > 80.0 ) {
|
||||
lookahead_steps = std::max( lookahead_steps, std::min( max_steps, 8 ) );
|
||||
lookahead_steps = std::max( lookahead_steps, std::min( max_steps, 10 ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1276,9 +1436,86 @@ try_dequeue_ready_batch() {
|
||||
g_stream.pending_apply = std::move( batch );
|
||||
g_stream.pending_apply_offset = g_stream.pending_apply->apply_offset;
|
||||
g_stream.pending_apply->apply_offset = 0;
|
||||
|
||||
if(
|
||||
g_stream.pending_apply->models != nullptr &&
|
||||
g_stream.pending_apply->texture_warm_offset < g_stream.pending_apply->models->size() ) {
|
||||
(void)warm_pack_textures_main(
|
||||
g_stream.pending_apply->models->data() + g_stream.pending_apply->texture_warm_offset,
|
||||
g_stream.pending_apply->models->size() - g_stream.pending_apply->texture_warm_offset );
|
||||
g_stream.pending_apply->texture_warm_offset = g_stream.pending_apply->models->size();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
prefetch_ready_queue_textures( double const budget_ms ) {
|
||||
if( budget_ms <= 0.0 || GfxRenderer == nullptr ) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto const started { std::chrono::steady_clock::now() };
|
||||
std::vector<std::deque<PackSectionReady>::iterator> candidates;
|
||||
candidates.reserve( ready_queue_size() );
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock { g_stream.ready.mutex };
|
||||
for( auto it { g_stream.ready.data.begin() }; it != g_stream.ready.data.end(); ++it ) {
|
||||
if( it->failed || it->models == nullptr || it->models->empty() ) {
|
||||
continue;
|
||||
}
|
||||
if( it->texture_warm_offset >= it->models->size() ) {
|
||||
continue;
|
||||
}
|
||||
candidates.push_back( it );
|
||||
}
|
||||
}
|
||||
|
||||
if( candidates.empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(
|
||||
candidates.begin(),
|
||||
candidates.end(),
|
||||
[]( std::deque<PackSectionReady>::iterator const &lhs,
|
||||
std::deque<PackSectionReady>::iterator const &rhs ) {
|
||||
auto const lhs_dist {
|
||||
section_manhattan_sections(
|
||||
lhs->row,
|
||||
lhs->column,
|
||||
g_stream.center_row,
|
||||
g_stream.center_column ) };
|
||||
auto const rhs_dist {
|
||||
section_manhattan_sections(
|
||||
rhs->row,
|
||||
rhs->column,
|
||||
g_stream.center_row,
|
||||
g_stream.center_column ) };
|
||||
return lhs_dist < rhs_dist;
|
||||
} );
|
||||
|
||||
for( auto const it : candidates ) {
|
||||
auto &batch { *it };
|
||||
while( batch.texture_warm_offset < batch.models->size() ) {
|
||||
auto const remaining { batch.models->size() - batch.texture_warm_offset };
|
||||
auto const slice { std::min( remaining, kReadyTexturePrefetchSlice ) };
|
||||
(void)warm_pack_textures_main(
|
||||
batch.models->data() + batch.texture_warm_offset,
|
||||
slice );
|
||||
batch.texture_warm_offset += slice;
|
||||
|
||||
auto const elapsed_ms {
|
||||
std::chrono::duration<double, std::milli>(
|
||||
std::chrono::steady_clock::now() - started ).count() };
|
||||
if( elapsed_ms >= budget_ms ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
gameplay_stream_mode() {
|
||||
return g_loading_screen_dismissed
|
||||
@@ -1387,16 +1624,30 @@ drain_until_budget( double const budget_ms ) {
|
||||
|
||||
void
|
||||
sync_stream_limits( glm::dvec3 const &world_position ) {
|
||||
(void)world_position;
|
||||
g_stream_catchup = false;
|
||||
if( false == g_loading_screen_dismissed ) {
|
||||
g_stream_catchup = false;
|
||||
g_stream_max_in_flight = 6;
|
||||
g_stream_max_ready = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
g_stream_max_in_flight = kMaxInFlightSections;
|
||||
g_stream_max_ready = kMaxReadySections;
|
||||
auto const stream_position { resolve_section_stream_position( world_position ) };
|
||||
auto const cam_speed { camera_stream_speed_mps() };
|
||||
auto const outer_ring {
|
||||
section_stream_ring_progress( stream_position, g_stream.radius ) };
|
||||
|
||||
g_stream_catchup = (
|
||||
cam_speed > 80.0 ||
|
||||
outer_ring < 0.98f );
|
||||
|
||||
if( g_stream_catchup ) {
|
||||
g_stream_max_in_flight = kCatchupMaxInFlightSections;
|
||||
g_stream_max_ready = kCatchupMaxReadySections;
|
||||
}
|
||||
else {
|
||||
g_stream_max_in_flight = kMaxInFlightSections;
|
||||
g_stream_max_ready = kMaxReadySections;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -1578,8 +1829,19 @@ update_section_stream( glm::dvec3 const &world_position ) {
|
||||
section_stream_ready_around( stream_position, kSectionStreamGameplayRadiusKm ) };
|
||||
auto const ring_radius {
|
||||
inner_ring_ready ? g_stream.radius : kSectionStreamGameplayRadiusKm };
|
||||
auto const reenqueue_distance { kReenqueueDistanceM };
|
||||
auto max_lookahead { inner_ring_ready ? 5 : 0 };
|
||||
|
||||
auto const cam_speed { camera_stream_speed_mps() };
|
||||
auto const reenqueue_distance {
|
||||
( g_stream_catchup || cam_speed > 80.0 ) ?
|
||||
std::clamp( cam_speed * 0.25, kCatchupReenqueueDistanceM, kReenqueueDistanceM ) :
|
||||
kReenqueueDistanceM };
|
||||
auto max_lookahead { 0 };
|
||||
if( inner_ring_ready ) {
|
||||
max_lookahead = ( g_stream_catchup || cam_speed > 200.0 ) ?
|
||||
kMovementLookahead :
|
||||
( cam_speed > 80.0 ? 10 : 7 );
|
||||
}
|
||||
|
||||
auto const travel_forward { guess_travel_forward() };
|
||||
|
||||
auto const current_section_unloaded {
|
||||
@@ -1598,14 +1860,15 @@ update_section_stream( glm::dvec3 const &world_position ) {
|
||||
if( should_reenqueue || inner_ring_incomplete ) {
|
||||
pack_bench_inc( &Eu7PackBench::stream_reenqueue );
|
||||
}
|
||||
if( max_lookahead > 0 ) {
|
||||
enqueue_movement_lookahead(
|
||||
center_row,
|
||||
center_column,
|
||||
ring_radius,
|
||||
travel_forward,
|
||||
max_lookahead );
|
||||
}
|
||||
}
|
||||
|
||||
if( inner_ring_ready && max_lookahead > 0 && cam_speed > 50.0 ) {
|
||||
enqueue_movement_lookahead(
|
||||
center_row,
|
||||
center_column,
|
||||
ring_radius,
|
||||
travel_forward,
|
||||
max_lookahead );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1640,7 +1903,23 @@ drain_section_stream(
|
||||
kSectionStreamGameplayRadiusKm ) };
|
||||
|
||||
if( gameplay_stream_mode() && inner_ring_ready ) {
|
||||
drain_until_budget( kDrainBudgetMs );
|
||||
prefetch_ready_queue_textures(
|
||||
g_stream_catchup || camera_stream_speed_mps() > 80.0 ?
|
||||
kReadyTexturePrefetchBudgetMs :
|
||||
kReadyTexturePrefetchBudgetMs * 0.5 );
|
||||
|
||||
if( g_stream_catchup ) {
|
||||
auto const urgent { pending_apply_is_urgent() };
|
||||
drain_apply_budget(
|
||||
urgent ? kUrgentApplyBudgetMs : gameplay_apply_budget_ms(),
|
||||
adaptive_slice_instances( pending_section_total(), urgent ),
|
||||
adaptive_cold_meshes( urgent ),
|
||||
urgent ? kUrgentColdBudgetMs : gameplay_cold_budget_ms(),
|
||||
urgent ? kUrgentMaxChunksPerDrain : 1 );
|
||||
}
|
||||
else {
|
||||
drain_until_budget( kDrainBudgetMs );
|
||||
}
|
||||
maybe_log_stream_status( stream_position );
|
||||
}
|
||||
else if( gameplay_stream_mode() ) {
|
||||
@@ -2105,4 +2384,9 @@ reset_section_stream() {
|
||||
reset_pack_bench();
|
||||
}
|
||||
|
||||
bool
|
||||
section_stream_frame_budget_pressure() {
|
||||
return g_stream_catchup || pending_apply_is_urgent();
|
||||
}
|
||||
|
||||
} // namespace scene::eu7
|
||||
|
||||
@@ -106,4 +106,8 @@ section_stream_ring_progress(
|
||||
void
|
||||
reset_section_stream();
|
||||
|
||||
// PACK catchup / pilny apply — ogranicz rownolegle ladowanie skladow.
|
||||
[[nodiscard]] bool
|
||||
section_stream_frame_budget_pressure();
|
||||
|
||||
} // namespace scene::eu7
|
||||
|
||||
@@ -49,6 +49,9 @@ namespace {
|
||||
constexpr double kDeferTrainsetHorizDistM { 4000.0 };
|
||||
constexpr double kDeferTrainsetHorizDistSq {
|
||||
kDeferTrainsetHorizDistM * kDeferTrainsetHorizDistM };
|
||||
constexpr double kTrainsetDrainMaxDistM { 12000.0 };
|
||||
constexpr double kTrainsetDrainMaxDistSq {
|
||||
kTrainsetDrainMaxDistM * kTrainsetDrainMaxDistM };
|
||||
|
||||
struct DeferredEu7Trainset {
|
||||
scene::eu7::Eu7Trainset trainset;
|
||||
@@ -111,24 +114,39 @@ eu7_trainset_is_player(
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] double
|
||||
eu7_trainset_horiz_dist_sq( scene::eu7::Eu7Trainset const &trainset ) {
|
||||
auto *path { simulation::Paths.find( trainset.track ) };
|
||||
if( path == nullptr ) {
|
||||
return 0.0;
|
||||
[[nodiscard]] glm::dvec3
|
||||
eu7_observer_position() {
|
||||
if( FreeFlyModeFlag || Global.pCamera.m_owner == nullptr ) {
|
||||
return Global.pCamera.Pos;
|
||||
}
|
||||
|
||||
auto const spawn { eu7_trainset_spawn_origin() };
|
||||
if( spawn.x == 0.0 && spawn.y == 0.0 && spawn.z == 0.0 ) {
|
||||
return eu7_trainset_spawn_origin();
|
||||
}
|
||||
|
||||
[[nodiscard]] double
|
||||
eu7_trainset_horiz_dist_sq_to(
|
||||
scene::eu7::Eu7Trainset const &trainset,
|
||||
glm::dvec3 const &observer ) {
|
||||
auto *path { simulation::Paths.find( trainset.track ) };
|
||||
if( path == nullptr ) {
|
||||
return std::numeric_limits<double>::max();
|
||||
}
|
||||
|
||||
if( observer.x == 0.0 && observer.y == 0.0 && observer.z == 0.0 ) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
auto const track_pos { glm::dvec3 { path->location() } };
|
||||
auto const dx { track_pos.x - spawn.x };
|
||||
auto const dz { track_pos.z - spawn.z };
|
||||
auto const dx { track_pos.x - observer.x };
|
||||
auto const dz { track_pos.z - observer.z };
|
||||
return dx * dx + dz * dz;
|
||||
}
|
||||
|
||||
[[nodiscard]] double
|
||||
eu7_trainset_horiz_dist_sq( scene::eu7::Eu7Trainset const &trainset ) {
|
||||
return eu7_trainset_horiz_dist_sq_to( trainset, eu7_observer_position() );
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
eu7_should_load_trainset_now(
|
||||
scene::eu7::Eu7Trainset const &trainset,
|
||||
@@ -136,7 +154,7 @@ eu7_should_load_trainset_now(
|
||||
if( eu7_trainset_is_player( trainset, scene ) ) {
|
||||
return true;
|
||||
}
|
||||
if( eu7_trainset_horiz_dist_sq( trainset ) <= kDeferTrainsetHorizDistSq ) {
|
||||
if( eu7_trainset_horiz_dist_sq_to( trainset, eu7_observer_position() ) <= kDeferTrainsetHorizDistSq ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1412,43 +1430,52 @@ state_serializer::apply_eu7_scene(
|
||||
|
||||
void
|
||||
state_serializer::drain_deferred_eu7_trainsets( double const max_ms ) {
|
||||
if( g_deferred_eu7_trainsets.empty() ) {
|
||||
if( g_deferred_eu7_trainsets.empty() || max_ms <= 0.0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto const deadline {
|
||||
std::chrono::steady_clock::now() +
|
||||
std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
||||
std::chrono::duration<double, std::milli>( max_ms ) ) };
|
||||
auto const observer { eu7_observer_position() };
|
||||
auto best_it { g_deferred_eu7_trainsets.end() };
|
||||
auto best_dist_sq { std::numeric_limits<double>::max() };
|
||||
for( auto it { g_deferred_eu7_trainsets.begin() }; it != g_deferred_eu7_trainsets.end(); ++it ) {
|
||||
auto const dist_sq { eu7_trainset_horiz_dist_sq_to( it->trainset, observer ) };
|
||||
if( dist_sq < best_dist_sq ) {
|
||||
best_dist_sq = dist_sq;
|
||||
best_it = it;
|
||||
}
|
||||
}
|
||||
|
||||
if(
|
||||
best_it == g_deferred_eu7_trainsets.end() ||
|
||||
best_dist_sq > kTrainsetDrainMaxDistSq ) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto job { std::move( *best_it ) };
|
||||
g_deferred_eu7_trainsets.erase( best_it );
|
||||
|
||||
cParser dummy_parser( "", cParser::buffer_TEXT, "", false );
|
||||
scene::scratch_data scratchpad;
|
||||
|
||||
while(
|
||||
false == g_deferred_eu7_trainsets.empty() &&
|
||||
std::chrono::steady_clock::now() < deadline ) {
|
||||
auto job { std::move( g_deferred_eu7_trainsets.front() ) };
|
||||
g_deferred_eu7_trainsets.pop_front();
|
||||
scene::eu7::ScopedTimer const timer { scene::eu7::load_stats().trainset_ms };
|
||||
++scene::eu7::load_stats().trainsets;
|
||||
|
||||
scene::eu7::ScopedTimer const timer { scene::eu7::load_stats().trainset_ms };
|
||||
++scene::eu7::load_stats().trainsets;
|
||||
if( true == scratchpad.trainset.is_open ) {
|
||||
deserialize_endtrainset( dummy_parser, scratchpad );
|
||||
scratchpad.trainset.is_open = false;
|
||||
}
|
||||
|
||||
if( true == scratchpad.trainset.is_open ) {
|
||||
deserialize_endtrainset( dummy_parser, scratchpad );
|
||||
scratchpad.trainset.is_open = false;
|
||||
}
|
||||
scratchpad.trainset = scene::scratch_data::trainset_data();
|
||||
scratchpad.trainset.is_open = true;
|
||||
scratchpad.trainset.name = job.trainset.name;
|
||||
scratchpad.trainset.track = job.trainset.track;
|
||||
scratchpad.trainset.offset = job.trainset.offset;
|
||||
scratchpad.trainset.velocity = job.trainset.velocity;
|
||||
scratchpad.trainset.assignment = job.trainset.assignment;
|
||||
|
||||
scratchpad.trainset = scene::scratch_data::trainset_data();
|
||||
scratchpad.trainset.is_open = true;
|
||||
scratchpad.trainset.name = job.trainset.name;
|
||||
scratchpad.trainset.track = job.trainset.track;
|
||||
scratchpad.trainset.offset = job.trainset.offset;
|
||||
scratchpad.trainset.velocity = job.trainset.velocity;
|
||||
scratchpad.trainset.assignment = job.trainset.assignment;
|
||||
|
||||
std::size_t vehicle_slot { 0 };
|
||||
for( auto const &vehicle_source : job.vehicles ) {
|
||||
auto vehicle { vehicle_source };
|
||||
std::size_t vehicle_slot { 0 };
|
||||
for( auto const &vehicle_source : job.vehicles ) {
|
||||
auto vehicle { vehicle_source };
|
||||
if( vehicle_slot < job.trainset.couplings.size() ) {
|
||||
vehicle.coupling_raw = std::to_string( job.trainset.couplings[ vehicle_slot ] );
|
||||
}
|
||||
@@ -1554,11 +1581,11 @@ state_serializer::drain_deferred_eu7_trainsets( double const max_ms ) {
|
||||
}
|
||||
}
|
||||
|
||||
++vehicle_slot;
|
||||
}
|
||||
|
||||
deserialize_endtrainset( dummy_parser, scratchpad );
|
||||
++vehicle_slot;
|
||||
}
|
||||
|
||||
deserialize_endtrainset( dummy_parser, scratchpad );
|
||||
(void)max_ms;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -82,7 +82,7 @@ struct global_settings {
|
||||
int iConvertIndexRange{ 1000 }; // range of duplicate vertex scan
|
||||
bool file_binary_terrain{ true }; // enable binary terrain (de)serialization
|
||||
bool file_binary_terrain_state{true};
|
||||
int eu7_pack_stream_workers_percent{ 40 }; // EU7 PACK async loader thread pool size (% of CPU threads)
|
||||
int eu7_pack_stream_workers_percent{ 75 }; // EU7 PACK async loader thread pool size (% of CPU threads)
|
||||
bool eu7_auto_bake{ true }; // przy ladowaniu mapy: bake SCM→EU7 gdy brak lub nieaktualny .eu7
|
||||
int eu7_bake_threads{ 0 }; // watki bake (0 = auto = hardware_concurrency)
|
||||
// logs
|
||||
|
||||
Reference in New Issue
Block a user