mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-18 04:19:19 +02:00
502 lines
14 KiB
C++
502 lines
14 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "scene/eu7/eu7_pack_mesh_loader.h"
|
|
|
|
#include "model/AnimModel.h"
|
|
#include "model/MdlMngr.h"
|
|
#include "scene/eu7/eu7_pack_bench.h"
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/utilities.h"
|
|
|
|
#include <chrono>
|
|
#include <deque>
|
|
#include <mutex>
|
|
#include <queue>
|
|
#include <unordered_set>
|
|
|
|
namespace scene::eu7 {
|
|
namespace {
|
|
|
|
std::mutex g_pack_mesh_load_mutex;
|
|
|
|
struct PackMeshQueueEntry {
|
|
int priority { 0 };
|
|
std::uint64_t sequence { 0 };
|
|
std::string model_file;
|
|
};
|
|
|
|
struct PackMeshQueueCompare {
|
|
[[nodiscard]] bool
|
|
operator()( PackMeshQueueEntry const &lhs, PackMeshQueueEntry const &rhs ) const {
|
|
if( lhs.priority != rhs.priority ) {
|
|
return lhs.priority > rhs.priority;
|
|
}
|
|
return lhs.sequence > rhs.sequence;
|
|
}
|
|
};
|
|
|
|
struct PackMeshLoader {
|
|
std::mutex mutex;
|
|
std::priority_queue<
|
|
PackMeshQueueEntry,
|
|
std::vector<PackMeshQueueEntry>,
|
|
PackMeshQueueCompare> queue;
|
|
std::unordered_map<std::string, int> queued;
|
|
std::unordered_map<std::string, TModel3d *> ready;
|
|
std::uint64_t sequence { 0 };
|
|
bool running { false };
|
|
};
|
|
|
|
PackMeshLoader g_loader;
|
|
|
|
[[nodiscard]] std::string
|
|
pack_model_texture_dir( std::string model_file ) {
|
|
if( model_file.empty() || model_file == "notload" ) {
|
|
return {};
|
|
}
|
|
replace_slashes( model_file );
|
|
erase_extension( model_file );
|
|
auto const slash_pos { model_file.rfind( '/' ) };
|
|
if( slash_pos == std::string::npos ) {
|
|
return {};
|
|
}
|
|
return model_file.substr( 0, slash_pos + 1 );
|
|
}
|
|
|
|
class PackTexturePathScope {
|
|
public:
|
|
explicit PackTexturePathScope( std::string const &model_file ) {
|
|
m_saved_path = Global.asCurrentTexturePath;
|
|
auto const model_dir { pack_model_texture_dir( model_file ) };
|
|
if( false == model_dir.empty() && model_file.find( '/' ) != std::string::npos ) {
|
|
Global.asCurrentTexturePath = model_dir;
|
|
m_active = true;
|
|
}
|
|
}
|
|
|
|
~PackTexturePathScope() {
|
|
if( m_active ) {
|
|
Global.asCurrentTexturePath = std::move( m_saved_path );
|
|
}
|
|
}
|
|
|
|
PackTexturePathScope( PackTexturePathScope const & ) = delete;
|
|
PackTexturePathScope &operator=( PackTexturePathScope const & ) = delete;
|
|
|
|
private:
|
|
std::string m_saved_path;
|
|
bool m_active { false };
|
|
};
|
|
|
|
[[nodiscard]] bool
|
|
pack_mesh_path_valid( std::string const &model_file ) {
|
|
return false == model_file.empty() && model_file != "notload";
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
pack_mesh_pointer_usable( TModel3d *const mesh ) {
|
|
return mesh != nullptr;
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
pack_mesh_cached_usable(
|
|
std::unordered_map<std::string, TModel3d *> const &session_cache,
|
|
std::string const &model_file ) {
|
|
auto const found { session_cache.find( model_file ) };
|
|
return found != session_cache.end() && pack_mesh_pointer_usable( found->second );
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
pack_mesh_globally_usable( std::string model_file ) {
|
|
if( false == pack_mesh_path_valid( model_file ) ) {
|
|
return false;
|
|
}
|
|
replace_slashes( model_file );
|
|
if( false == TModelsManager::IsModelCached( model_file ) ) {
|
|
return false;
|
|
}
|
|
|
|
TModel3d *mesh { nullptr };
|
|
{
|
|
std::lock_guard<std::mutex> lock { g_pack_mesh_load_mutex };
|
|
mesh = TModelsManager::GetModel( model_file, false, false );
|
|
}
|
|
return pack_mesh_pointer_usable( mesh );
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
pop_pack_mesh_queue_entry( std::string &model_file ) {
|
|
model_file.clear();
|
|
while( false == g_loader.queue.empty() ) {
|
|
auto entry { g_loader.queue.top() };
|
|
g_loader.queue.pop();
|
|
auto const found { g_loader.queued.find( entry.model_file ) };
|
|
if(
|
|
found == g_loader.queued.end() ||
|
|
found->second != entry.priority ) {
|
|
continue;
|
|
}
|
|
model_file = std::move( entry.model_file );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
load_pack_mesh_on_main_thread( std::string const &model_file ) {
|
|
PackBenchTimer const load_timer { &Eu7PackBench::loader_thread_getmodel_ms };
|
|
TModel3d *mesh { nullptr };
|
|
{
|
|
std::lock_guard<std::mutex> lock { g_pack_mesh_load_mutex };
|
|
PackTexturePathScope const scope { model_file };
|
|
mesh = TModelsManager::GetModel( model_file, false, false );
|
|
if( pack_mesh_pointer_usable( mesh ) ) {
|
|
TAnimModel::warm_instanceable_cache( mesh );
|
|
}
|
|
}
|
|
pack_bench_inc( &Eu7PackBench::loader_thread_disk_loads );
|
|
|
|
std::lock_guard<std::mutex> lock { g_loader.mutex };
|
|
g_loader.ready.emplace( model_file, mesh );
|
|
g_loader.queued.erase( model_file );
|
|
}
|
|
|
|
[[nodiscard]] std::size_t
|
|
pump_pack_mesh_loader_main(
|
|
double const budget_ms,
|
|
std::size_t const max_loads ) {
|
|
if( false == g_loader.running ) {
|
|
return 0;
|
|
}
|
|
|
|
auto const started { std::chrono::steady_clock::now() };
|
|
std::size_t loaded { 0 };
|
|
|
|
while( loaded < max_loads ) {
|
|
if( budget_ms > 0.0 ) {
|
|
auto const elapsed_ms {
|
|
std::chrono::duration<double, std::milli>(
|
|
std::chrono::steady_clock::now() - started ).count() };
|
|
if( elapsed_ms >= budget_ms ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::string model_file;
|
|
{
|
|
std::lock_guard<std::mutex> lock { g_loader.mutex };
|
|
if( false == pop_pack_mesh_queue_entry( model_file ) ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
load_pack_mesh_on_main_thread( model_file );
|
|
++loaded;
|
|
}
|
|
|
|
return loaded;
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
try_consume_loader_ready(
|
|
std::string const &model_file,
|
|
std::unordered_map<std::string, TModel3d *> &session_cache ) {
|
|
TModel3d *mesh { nullptr };
|
|
{
|
|
std::lock_guard<std::mutex> lock { g_loader.mutex };
|
|
auto const found { g_loader.ready.find( model_file ) };
|
|
if( found == g_loader.ready.end() ) {
|
|
return false;
|
|
}
|
|
mesh = found->second;
|
|
g_loader.ready.erase( found );
|
|
}
|
|
session_cache.emplace( model_file, mesh );
|
|
pack_bench_inc( &Eu7PackBench::stream_mesh_async_ready );
|
|
return pack_mesh_pointer_usable( mesh );
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
sync_resolve_global_cached_mesh(
|
|
std::string const &model_file,
|
|
std::unordered_map<std::string, TModel3d *> &session_cache ) {
|
|
if( false == pack_mesh_globally_usable( model_file ) ) {
|
|
return false;
|
|
}
|
|
|
|
TModel3d *mesh { nullptr };
|
|
{
|
|
PackBenchTimer const load_timer { &Eu7PackBench::main_cold_getmodel_ms };
|
|
std::lock_guard<std::mutex> lock { g_pack_mesh_load_mutex };
|
|
PackTexturePathScope const scope { model_file };
|
|
mesh = TModelsManager::GetModel( model_file, false, false );
|
|
pack_bench_inc( &Eu7PackBench::main_cold_getmodel_calls );
|
|
if( pack_mesh_pointer_usable( mesh ) ) {
|
|
TAnimModel::warm_instanceable_cache( mesh );
|
|
}
|
|
}
|
|
pack_bench_inc( &Eu7PackBench::stream_mesh_global_hit );
|
|
session_cache.emplace( model_file, mesh );
|
|
return pack_mesh_pointer_usable( mesh );
|
|
}
|
|
|
|
void
|
|
enqueue_pack_mesh_load( std::string model_file, int const priority ) {
|
|
if( false == pack_mesh_path_valid( model_file ) ) {
|
|
return;
|
|
}
|
|
replace_slashes( model_file );
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock { g_loader.mutex };
|
|
if( g_loader.ready.contains( model_file ) ) {
|
|
return;
|
|
}
|
|
auto const found { g_loader.queued.find( model_file ) };
|
|
if( found != g_loader.queued.end() ) {
|
|
if( priority >= found->second ) {
|
|
return;
|
|
}
|
|
found->second = priority;
|
|
}
|
|
else {
|
|
g_loader.queued.emplace( model_file, priority );
|
|
}
|
|
g_loader.queue.push(
|
|
PackMeshQueueEntry { priority, g_loader.sequence++, std::move( model_file ) } );
|
|
}
|
|
pack_bench_inc( &Eu7PackBench::stream_mesh_async_queued );
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
wait_for_loader_ready(
|
|
std::string const &model_file,
|
|
double const block_budget_ms ) {
|
|
auto const deadline {
|
|
block_budget_ms > 0.0 ?
|
|
std::chrono::steady_clock::now() +
|
|
std::chrono::duration_cast<std::chrono::steady_clock::duration>(
|
|
std::chrono::duration<double, std::milli>( block_budget_ms ) ) :
|
|
std::chrono::steady_clock::time_point::max() };
|
|
|
|
while( true ) {
|
|
{
|
|
std::lock_guard<std::mutex> lock { g_loader.mutex };
|
|
if( g_loader.ready.contains( model_file ) ) {
|
|
return true;
|
|
}
|
|
if( g_loader.queued.contains( model_file ) == false ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if( block_budget_ms > 0.0 && std::chrono::steady_clock::now() >= deadline ) {
|
|
return false;
|
|
}
|
|
|
|
auto remaining_ms { block_budget_ms };
|
|
if( block_budget_ms > 0.0 ) {
|
|
remaining_ms = std::chrono::duration<double, std::milli>(
|
|
deadline - std::chrono::steady_clock::now() ).count();
|
|
if( remaining_ms <= 0.0 ) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
(void)pump_pack_mesh_loader_main( remaining_ms, 1 );
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void
|
|
start_pack_mesh_loader() {
|
|
stop_pack_mesh_loader();
|
|
g_loader.running = true;
|
|
}
|
|
|
|
void
|
|
stop_pack_mesh_loader() {
|
|
g_loader.running = false;
|
|
|
|
std::lock_guard<std::mutex> lock { g_loader.mutex };
|
|
g_loader.queue = decltype( g_loader.queue )();
|
|
g_loader.queued.clear();
|
|
g_loader.ready.clear();
|
|
}
|
|
|
|
void
|
|
reset_pack_mesh_loader() {
|
|
stop_pack_mesh_loader();
|
|
}
|
|
|
|
void
|
|
request_pack_mesh_load( std::string const &model_file, int const priority ) {
|
|
if( false == g_loader.running ) {
|
|
return;
|
|
}
|
|
if( false == pack_mesh_path_valid( model_file ) ) {
|
|
return;
|
|
}
|
|
if( pack_mesh_globally_usable( model_file ) ) {
|
|
return;
|
|
}
|
|
enqueue_pack_mesh_load( model_file, priority );
|
|
}
|
|
|
|
void
|
|
request_pack_mesh_load_paths(
|
|
std::vector<std::string> const &model_files,
|
|
std::size_t const max_enqueue,
|
|
int const priority ) {
|
|
if( false == g_loader.running ) {
|
|
return;
|
|
}
|
|
|
|
std::size_t enqueued { 0 };
|
|
for( auto const &path : model_files ) {
|
|
if( max_enqueue > 0 && enqueued >= max_enqueue ) {
|
|
break;
|
|
}
|
|
if( false == pack_mesh_path_valid( path ) ) {
|
|
continue;
|
|
}
|
|
if( pack_mesh_globally_usable( path ) ) {
|
|
continue;
|
|
}
|
|
enqueue_pack_mesh_load( path, priority );
|
|
++enqueued;
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] std::size_t
|
|
pump_pack_mesh_loader(
|
|
double const budget_ms,
|
|
std::size_t const max_loads ) {
|
|
return pump_pack_mesh_loader_main( budget_ms, max_loads );
|
|
}
|
|
|
|
[[nodiscard]] TModel3d *
|
|
session_cached_pack_mesh(
|
|
std::unordered_map<std::string, TModel3d *> const &session_cache,
|
|
std::string const &model_file ) {
|
|
auto const found { session_cache.find( model_file ) };
|
|
return found != session_cache.end() ? found->second : nullptr;
|
|
}
|
|
|
|
[[nodiscard]] TModel3d *
|
|
ensure_pack_mesh_in_session_cache(
|
|
std::string model_file,
|
|
std::unordered_map<std::string, TModel3d *> &session_cache,
|
|
PackMeshLoadWait const wait,
|
|
double const block_budget_ms ) {
|
|
if( false == pack_mesh_path_valid( model_file ) ) {
|
|
return nullptr;
|
|
}
|
|
replace_slashes( model_file );
|
|
if( pack_mesh_cached_usable( session_cache, model_file ) ) {
|
|
pack_bench_inc( &Eu7PackBench::stream_mesh_session_hit );
|
|
return session_cached_pack_mesh( session_cache, model_file );
|
|
}
|
|
if( session_cache.contains( model_file ) ) {
|
|
session_cache.erase( model_file );
|
|
}
|
|
|
|
if( try_consume_loader_ready( model_file, session_cache ) ) {
|
|
return session_cached_pack_mesh( session_cache, model_file );
|
|
}
|
|
if( sync_resolve_global_cached_mesh( model_file, session_cache ) ) {
|
|
return session_cached_pack_mesh( session_cache, model_file );
|
|
}
|
|
|
|
request_pack_mesh_load( model_file );
|
|
|
|
if( wait == PackMeshLoadWait::BlockUntilReady ) {
|
|
if( wait_for_loader_ready( model_file, block_budget_ms ) ) {
|
|
if( try_consume_loader_ready( model_file, session_cache ) ) {
|
|
return session_cached_pack_mesh( session_cache, model_file );
|
|
}
|
|
}
|
|
pack_bench_inc( &Eu7PackBench::stream_mesh_async_wait_timeout );
|
|
return nullptr;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
[[nodiscard]] std::size_t
|
|
pack_mesh_loader_queue_depth() {
|
|
std::lock_guard<std::mutex> lock { g_loader.mutex };
|
|
return g_loader.queued.size();
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
try_adopt_pack_mesh_for_slice(
|
|
std::string model_file,
|
|
std::unordered_map<std::string, TModel3d *> &session_cache ) {
|
|
if( model_file.empty() || model_file == "notload" ) {
|
|
return true;
|
|
}
|
|
replace_slashes( model_file );
|
|
if( pack_mesh_cached_usable( session_cache, model_file ) ) {
|
|
return true;
|
|
}
|
|
if( session_cache.contains( model_file ) ) {
|
|
session_cache.erase( model_file );
|
|
}
|
|
if( try_consume_loader_ready( model_file, session_cache ) ) {
|
|
return true;
|
|
}
|
|
return sync_resolve_global_cached_mesh( model_file, session_cache );
|
|
}
|
|
|
|
[[nodiscard]] std::size_t
|
|
drain_pack_mesh_loader_ready(
|
|
std::unordered_map<std::string, TModel3d *> &session_cache,
|
|
std::size_t const max_drain ) {
|
|
std::vector<std::pair<std::string, TModel3d *>> drained;
|
|
{
|
|
std::lock_guard<std::mutex> lock { g_loader.mutex };
|
|
drained.reserve( g_loader.ready.size() );
|
|
for( auto it { g_loader.ready.begin() }; it != g_loader.ready.end(); ) {
|
|
if( max_drain > 0 && drained.size() >= max_drain ) {
|
|
break;
|
|
}
|
|
drained.emplace_back( it->first, it->second );
|
|
it = g_loader.ready.erase( it );
|
|
}
|
|
}
|
|
|
|
std::size_t adopted { 0 };
|
|
for( auto & [path, mesh] : drained ) {
|
|
session_cache.emplace( std::move( path ), mesh );
|
|
pack_bench_inc( &Eu7PackBench::stream_mesh_async_ready );
|
|
pack_bench_inc( &Eu7PackBench::stream_mesh_async_drained );
|
|
if( pack_mesh_pointer_usable( mesh ) ) {
|
|
++adopted;
|
|
}
|
|
}
|
|
return adopted;
|
|
}
|
|
|
|
[[nodiscard]] std::size_t
|
|
pack_mesh_loader_ready_count() {
|
|
std::lock_guard<std::mutex> lock { g_loader.mutex };
|
|
return g_loader.ready.size();
|
|
}
|
|
|
|
[[nodiscard]] std::size_t
|
|
pack_mesh_loader_worker_count() {
|
|
return 0;
|
|
}
|
|
|
|
} // namespace scene::eu7
|