mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-17 23:39:18 +02:00
Enable --eu7v2-bake from the main binary: parallel module pool, bounded-RAM spool flush, streaming terrain triangles, flat include/model parsing, and eu7v2 emit/load with optional verify. Large placement .scm files emit lean PLCE records and bake referenced .inc modules separately for reuse. - CLI: --eu7v2-bake, --eu7v2-verify, --eu7v2-mem-limit-gb, --eu7v2-threads, --eu7v2-max-parse; wire max_threads through to the bake parser - eu7v2 v2 records: PLCE placements, runtime emitter/loader, batch verify - Parallel bake pool with session cache; drop heavy-serial parse gate in spool mode; parse concurrency matches thread count - Streaming terrain: batched parallel parse+bake, scan/bake pipeline, shape spool with persistent buffered I/O and flush-before-read - Parallel flat-file streaming for models/includes; pack/model spool for low-memory incremental flush - Optional 50 GB private-bytes guard during headless bake Braniewo_szeroki: 160 modules, verify PASS, ~34s bake (nmt100 ~17s vs ~190s serial baseline). Co-authored-by: Cursor <cursoragent@cursor.com>
716 lines
22 KiB
C++
716 lines
22 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_loader.h"
|
|
|
|
#include "scene/eu7/eu7_apply.h"
|
|
#include "scene/eu7/eu7_emit.h"
|
|
#include "scene/eu7/eu7_load_stats.h"
|
|
#include "scene/eu7/eu7_pack_bench.h"
|
|
#include "scene/eu7/eu7_reader.h"
|
|
#include "scene/eu7/eu7_section_stream.h"
|
|
#include "scene/eu7/eu7_parameters.h"
|
|
#include "scene/eu7/eu7_transform.h"
|
|
#include "scene/eu7/v2/eu7v2_bake.h"
|
|
#include "scene/eu7/v2/eu7v2_format.h"
|
|
#include "scene/eu7/v2/eu7v2_load.h"
|
|
#include "scene/scene.h"
|
|
#include "scene/scenenode.h"
|
|
#include "rendering/renderer.h"
|
|
#include "simulation/simulation.h"
|
|
#include "simulation/simulationstateserializer.h"
|
|
#include "scene/sn_utils.h"
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/Logs.h"
|
|
#include "utilities/utilities.h"
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
namespace scene::eu7 {
|
|
|
|
namespace {
|
|
|
|
std::unordered_map<std::string, Eu7Module> g_module_file_cache;
|
|
bool g_packed_root_active { false };
|
|
|
|
// --- eu7v2 experiment: lossy lean-format transcode/load ---------------------
|
|
// Plik towarzyszacy obok legacy .eu7: <eu7>.v2
|
|
[[nodiscard]] std::string
|
|
eu7v2_sidecar_path( std::string const &eu7_path ) {
|
|
return eu7_path + ".v2";
|
|
}
|
|
|
|
// .v2 jest swiezy gdy istnieje i nie jest starszy niz zrodlowy .eu7.
|
|
[[nodiscard]] bool
|
|
eu7v2_sidecar_fresh( std::string const &eu7_path, std::string const &v2_path ) {
|
|
std::error_code ec;
|
|
if( false == std::filesystem::exists( v2_path, ec ) ) {
|
|
return false;
|
|
}
|
|
auto const v2_time { std::filesystem::last_write_time( v2_path, ec ) };
|
|
if( ec ) {
|
|
return false;
|
|
}
|
|
auto const src_time { std::filesystem::last_write_time( eu7_path, ec ) };
|
|
if( ec ) {
|
|
return false;
|
|
}
|
|
return v2_time >= src_time;
|
|
}
|
|
|
|
// Odczyt modulu z chudego .v2. Stratny: odtwarza wylacznie scene (PROT/INST/MESH
|
|
// + rekordy), bez includes/pack/prototypow/flag. Zwraca false przy bledzie.
|
|
[[nodiscard]] bool
|
|
read_eu7v2_module( std::string const &v2_path, Eu7Module &out ) {
|
|
std::ifstream input { v2_path, std::ios::binary };
|
|
if( !input ) {
|
|
return false;
|
|
}
|
|
std::vector<std::uint8_t> bytes(
|
|
( std::istreambuf_iterator<char>( input ) ),
|
|
std::istreambuf_iterator<char>() );
|
|
if( bytes.empty() ) {
|
|
return false;
|
|
}
|
|
Eu7Scene scene;
|
|
if( false == eu7v2::load_scene( bytes.data(), bytes.size(), scene ) ) {
|
|
return false;
|
|
}
|
|
out = Eu7Module{};
|
|
out.scene = std::move( scene );
|
|
return true;
|
|
}
|
|
|
|
// True gdy plik zaczyna sie magiem eu7v2 ('E','U','7','C').
|
|
[[nodiscard]] bool
|
|
file_is_eu7v2( std::string const &path ) {
|
|
std::ifstream input { path, std::ios::binary };
|
|
if( !input ) {
|
|
return false;
|
|
}
|
|
char magic[ 4 ] { 0, 0, 0, 0 };
|
|
input.read( magic, 4 );
|
|
if( input.gcount() != 4 ) {
|
|
return false;
|
|
}
|
|
return magic[ 0 ] == 'E' && magic[ 1 ] == 'U' && magic[ 2 ] == '7' && magic[ 3 ] == 'C';
|
|
}
|
|
|
|
// Pelny modul z formatu .eu7v2 (lossless): scena + includes + placement + flagi.
|
|
[[nodiscard]] bool
|
|
read_eu7v2_module_full( std::string const &path, Eu7Module &out ) {
|
|
std::ifstream input { path, std::ios::binary };
|
|
if( !input ) {
|
|
return false;
|
|
}
|
|
std::vector<std::uint8_t> bytes(
|
|
( std::istreambuf_iterator<char>( input ) ),
|
|
std::istreambuf_iterator<char>() );
|
|
if( bytes.empty() ) {
|
|
return false;
|
|
}
|
|
out = Eu7Module{};
|
|
return eu7v2::load_module( bytes.data(), bytes.size(), out );
|
|
}
|
|
|
|
// Zapis chudego .v2 obok legacy .eu7 (best-effort, ciche niepowodzenie).
|
|
void
|
|
write_eu7v2_module( std::string const &v2_path, Eu7Module const &module ) {
|
|
auto const bytes { eu7v2::bake_scene( module.scene ) };
|
|
std::ofstream output { v2_path, std::ios::binary | std::ios::trunc };
|
|
if( !output ) {
|
|
return;
|
|
}
|
|
output.write(
|
|
reinterpret_cast<char const *>( bytes.data() ),
|
|
static_cast<std::streamsize>( bytes.size() ) );
|
|
}
|
|
|
|
// Odczyt modulu z transkodem eu7v2 gdy Global.eu7v2_runtime wlaczone:
|
|
// - jest swiezy .v2 -> czytaj z niego (szybka, stratna sciezka),
|
|
// - brak/nieswiezy -> czytaj legacy i (dla nie-PACK) zapisz .v2 na nastepny raz.
|
|
[[nodiscard]] Eu7Module
|
|
read_module_maybe_v2( std::string const &resolved ) {
|
|
if( false == Global.eu7v2_runtime ) {
|
|
return read_module( resolved );
|
|
}
|
|
// Primary path: the module file itself is a native .eu7v2 container
|
|
// (text -> eu7v2 bake, no legacy .eu7). Load it losslessly.
|
|
if( file_is_eu7v2( resolved ) ) {
|
|
Eu7Module v2_module;
|
|
if( read_eu7v2_module_full( resolved, v2_module ) ) {
|
|
++load_stats().module_v2_loaded;
|
|
return v2_module;
|
|
}
|
|
throw std::runtime_error( "EU7v2: uszkodzony modul \"" + resolved + "\"" );
|
|
}
|
|
// Legacy .eu7 with optional ".v2" sidecar transcode (older experiment).
|
|
auto const v2_path { eu7v2_sidecar_path( resolved ) };
|
|
if( eu7v2_sidecar_fresh( resolved, v2_path ) ) {
|
|
Eu7Module v2_module;
|
|
if( read_eu7v2_module( v2_path, v2_module ) ) {
|
|
++load_stats().module_v2_loaded;
|
|
return v2_module;
|
|
}
|
|
WriteLog( "EU7v2: nieudany odczyt \"" + v2_path + "\", fallback legacy .eu7" );
|
|
}
|
|
auto legacy { read_module( resolved ) };
|
|
if( false == legacy.has_pack_chunk ) {
|
|
write_eu7v2_module( v2_path, legacy );
|
|
++load_stats().module_v2_baked;
|
|
}
|
|
return legacy;
|
|
}
|
|
|
|
void
|
|
apply_material( shape_node::shapenode_data &data, std::string material_name ) {
|
|
replace_slashes( material_name );
|
|
data.material = GfxRenderer->Fetch_Material( material_name );
|
|
|
|
auto const texturehandle {
|
|
data.material != null_handle ?
|
|
GfxRenderer->Material( data.material )->GetTexture( 0 ) :
|
|
null_handle };
|
|
auto const &texture {
|
|
texturehandle ?
|
|
GfxRenderer->Texture( texturehandle ) :
|
|
*ITexture::null_texture() };
|
|
if( texturehandle != null_handle ) {
|
|
data.translucent = (
|
|
contains( texture.get_name(), '@' ) && texture.get_has_alpha() );
|
|
}
|
|
else {
|
|
data.translucent = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
finalize_shape_bounds( shape_node &shape, shape_node::shapenode_data &data ) {
|
|
if( data.vertices.empty() ) {
|
|
return;
|
|
}
|
|
data.area.center = glm::dvec3( 0.0 );
|
|
for( auto const &vertex : data.vertices ) {
|
|
data.area.center += vertex.position;
|
|
}
|
|
data.area.center /=
|
|
static_cast<double>( data.vertices.size() );
|
|
shape.invalidate_radius();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
shape_node
|
|
build_shape_node( Eu7Shape const &source ) {
|
|
shape_node shape;
|
|
auto &data { shape.m_data };
|
|
data.rangesquared_min = source.node.range_squared_min;
|
|
data.rangesquared_max = source.node.range_squared_max;
|
|
data.visible = source.node.visible;
|
|
data.origin = source.origin;
|
|
data.lighting.diffuse = glm::vec4(
|
|
source.lighting.diffuse.x, source.lighting.diffuse.y,
|
|
source.lighting.diffuse.z, source.lighting.diffuse.w );
|
|
data.lighting.ambient = glm::vec4(
|
|
source.lighting.ambient.x, source.lighting.ambient.y,
|
|
source.lighting.ambient.z, source.lighting.ambient.w );
|
|
data.lighting.specular = glm::vec4(
|
|
source.lighting.specular.x, source.lighting.specular.y,
|
|
source.lighting.specular.z, source.lighting.specular.w );
|
|
if( source.translucent ) {
|
|
data.translucent = true;
|
|
}
|
|
apply_material( data, source.material_path );
|
|
data.vertices.resize( source.vertices.size() );
|
|
for( std::size_t i { 0 }; i < source.vertices.size(); ++i ) {
|
|
auto const &src { source.vertices[ i ] };
|
|
world_vertex &dst { data.vertices[ i ] };
|
|
dst.position = src.position;
|
|
dst.normal = src.normal;
|
|
dst.texture = glm::vec2( static_cast<float>( src.u ), static_cast<float>( src.v ) );
|
|
}
|
|
finalize_shape_bounds( shape, data );
|
|
return shape;
|
|
}
|
|
|
|
lines_node
|
|
build_lines_node( Eu7Lines const &source ) {
|
|
lines_node node;
|
|
auto &data { node.m_data };
|
|
data.rangesquared_min = source.node.range_squared_min;
|
|
data.rangesquared_max = source.node.range_squared_max;
|
|
data.visible = source.node.visible;
|
|
data.line_width = source.line_width;
|
|
data.lighting.diffuse = glm::vec4(
|
|
source.lighting.diffuse.x, source.lighting.diffuse.y,
|
|
source.lighting.diffuse.z, source.lighting.diffuse.w );
|
|
data.lighting.ambient = glm::vec4(
|
|
source.lighting.ambient.x, source.lighting.ambient.y,
|
|
source.lighting.ambient.z, source.lighting.ambient.w );
|
|
data.lighting.specular = glm::vec4(
|
|
source.lighting.specular.x, source.lighting.specular.y,
|
|
source.lighting.specular.z, source.lighting.specular.w );
|
|
data.origin = source.origin;
|
|
data.vertices.resize( source.vertices.size() );
|
|
for( std::size_t i { 0 }; i < source.vertices.size(); ++i ) {
|
|
data.vertices[ i ].position = source.vertices[ i ].position;
|
|
}
|
|
node.m_name = source.node.name;
|
|
return node;
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr std::uint32_t kMagic { MAKE_ID4( 'E', 'U', '7', 'B' ) };
|
|
constexpr std::uint32_t kVersionV4 { 4 };
|
|
constexpr std::uint32_t kVersionV5 { 5 };
|
|
constexpr std::uint32_t kVersionV6 { 6 };
|
|
constexpr std::uint32_t kVersionV7 { 7 };
|
|
constexpr std::uint32_t kVersionV8 { 8 };
|
|
constexpr std::uint32_t kChunkTerr { MAKE_ID4( 'T', 'E', 'R', 'R' ) };
|
|
|
|
std::unordered_set<std::string> g_load_session;
|
|
|
|
[[nodiscard]] std::string
|
|
scenery_relative_file( std::string const &resolved ) {
|
|
if( resolved.starts_with( Global.asCurrentSceneryPath ) ) {
|
|
return resolved.substr( Global.asCurrentSceneryPath.size() );
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
[[nodiscard]] std::string
|
|
include_binary_path( Eu7Include const &include ) {
|
|
if( !include.binary_path.empty() ) {
|
|
return resolve_scenery_path( include.binary_path );
|
|
}
|
|
if( !include.source_path.empty() ) {
|
|
return binary_path( include.source_path );
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void
|
|
evict_non_pack_modules_from_cache( std::string const &pack_root_resolved ) {
|
|
for( auto it { g_module_file_cache.begin() }; it != g_module_file_cache.end(); ) {
|
|
if( it->first != pack_root_resolved ) {
|
|
it = g_module_file_cache.erase( it );
|
|
}
|
|
else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
load_module_recursive(
|
|
std::string const &path,
|
|
simulation::state_serializer &serializer,
|
|
std::unordered_set<std::string> &loaded,
|
|
Eu7TransformContext const &include_prefix = {},
|
|
std::vector<std::string> const &include_parameters = {} ) {
|
|
++load_stats().module_visits;
|
|
|
|
auto const resolved { resolve_scenery_path( path ) };
|
|
auto const load_key { module_load_key( resolved, include_parameters ) };
|
|
if( loaded.contains( load_key ) ) {
|
|
++load_stats().module_deduped;
|
|
return true;
|
|
}
|
|
if( false == probe_file( resolved ) ) {
|
|
return false;
|
|
}
|
|
|
|
Eu7Module const *template_module { nullptr };
|
|
Eu7Module owned_module;
|
|
try {
|
|
auto const cached { g_module_file_cache.find( resolved ) };
|
|
if( cached != g_module_file_cache.end() ) {
|
|
++load_stats().module_cache_hit;
|
|
template_module = &cached->second;
|
|
}
|
|
else {
|
|
ScopedTimer const read_timer { load_stats().read_ms };
|
|
owned_module = read_module_maybe_v2( resolved );
|
|
++load_stats().module_read;
|
|
auto const emplaced { g_module_file_cache.emplace( resolved, std::move( owned_module ) ) };
|
|
template_module = &emplaced.first->second;
|
|
}
|
|
}
|
|
catch( std::exception const &ex ) {
|
|
ErrorLog( std::string{ "EU7: blad odczytu \"" + resolved + "\": " } + ex.what() );
|
|
return false;
|
|
}
|
|
|
|
if( g_packed_root_active && is_model_only_module( *template_module ) ) {
|
|
loaded.insert( load_key );
|
|
++load_stats().pack_skipped_includes;
|
|
return true;
|
|
}
|
|
|
|
loaded.insert( load_key );
|
|
++load_stats().module_applied;
|
|
|
|
if(
|
|
false == g_packed_root_active &&
|
|
include_parameters.empty() &&
|
|
transform_is_empty( include_prefix ) &&
|
|
template_module->has_pack_chunk ) {
|
|
g_packed_root_active = true;
|
|
}
|
|
|
|
std::vector<Eu7Include> fallback_includes;
|
|
for( auto const &include : template_module->includes ) {
|
|
auto const child { include_binary_path( include ) };
|
|
auto const ref {
|
|
include.source_path.empty() ? include.binary_path : include.source_path };
|
|
bool child_loaded { false };
|
|
if( !child.empty() && should_use_binary_module( ref ) ) {
|
|
child_loaded = load_module_recursive(
|
|
child,
|
|
serializer,
|
|
loaded,
|
|
include.site_transform,
|
|
include.parameters );
|
|
}
|
|
if( child_loaded ) {
|
|
continue;
|
|
}
|
|
if( !child.empty() ) {
|
|
WriteLog(
|
|
"EU7 include niedostepny, fallback SCM: " + include_text_path( include ) );
|
|
}
|
|
fallback_includes.push_back( include );
|
|
}
|
|
|
|
scene::scratch_data scratch;
|
|
auto const current_relative { scenery_relative_file( resolved ) };
|
|
if( false == fallback_includes.empty() ) {
|
|
ScopedTimer const scm_timer { load_stats().scm_fallback_ms };
|
|
load_stats().scm_fallback += fallback_includes.size();
|
|
for( auto const &include : fallback_includes ) {
|
|
serializer.deserialize_include_file(
|
|
include_text_path( include ),
|
|
current_relative,
|
|
include.parameters,
|
|
scratch );
|
|
}
|
|
}
|
|
|
|
if(
|
|
fallback_includes.empty() &&
|
|
is_model_only_module( *template_module ) ) {
|
|
++load_stats().model_fast_path;
|
|
|
|
std::vector<Eu7Model> models { template_module->scene.models };
|
|
{
|
|
ScopedTimer const place_timer { load_stats().place_fast_ms };
|
|
if( false == include_parameters.empty() ) {
|
|
apply_include_parameters_to_models( models, include_parameters );
|
|
}
|
|
if( false == transform_is_empty( include_prefix ) ) {
|
|
compose_models_with_prefix( models, include_prefix );
|
|
}
|
|
apply_include_placement_to_models(
|
|
models, template_module->include_placement, include_parameters );
|
|
}
|
|
serializer.apply_eu7_models( models, scratch );
|
|
}
|
|
else {
|
|
++load_stats().module_full_path;
|
|
|
|
Eu7Module module { *template_module };
|
|
{
|
|
ScopedTimer const place_timer { load_stats().place_full_ms };
|
|
if( false == include_parameters.empty() ) {
|
|
apply_include_parameters_to_scene( module.scene, include_parameters );
|
|
}
|
|
|
|
if( false == transform_is_empty( include_prefix ) ) {
|
|
compose_scene_with_include_prefix( module.scene, include_prefix );
|
|
}
|
|
|
|
apply_include_placement_to_scene(
|
|
module.scene, module.include_placement, include_parameters );
|
|
}
|
|
|
|
apply_module( module, serializer, scratch );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] bool
|
|
file_has_terr_chunk( std::string const &path ) {
|
|
std::ifstream input { path, std::ios::binary };
|
|
if( !input ) {
|
|
return false;
|
|
}
|
|
|
|
if( sn_utils::ld_uint32( input ) != kMagic ) {
|
|
return false;
|
|
}
|
|
const std::uint32_t version { sn_utils::ld_uint32( input ) };
|
|
if(
|
|
version != kVersionV4 && version != kVersionV5 && version != kVersionV6 &&
|
|
version != kVersionV7 && version != kVersionV8 ) {
|
|
return false;
|
|
}
|
|
|
|
while( input.peek() != EOF ) {
|
|
const std::uint32_t chunk_type { sn_utils::ld_uint32( input ) };
|
|
const std::uint32_t chunk_size { sn_utils::ld_uint32( input ) };
|
|
if( chunk_size < 8 ) {
|
|
return false;
|
|
}
|
|
if( chunk_type == kChunkTerr ) {
|
|
return true;
|
|
}
|
|
input.seekg( static_cast<std::streamoff>( chunk_size - 8 ), std::ios::cur );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::string
|
|
resolve_scenery_path( std::string const &reference ) {
|
|
auto path { reference };
|
|
while( false == path.empty() && path[ 0 ] == '$' ) {
|
|
path.erase( 0, 1 );
|
|
}
|
|
replace_slashes( path );
|
|
if( path.starts_with( Global.asCurrentSceneryPath ) ) {
|
|
return path;
|
|
}
|
|
return Global.asCurrentSceneryPath + path;
|
|
}
|
|
|
|
std::string
|
|
binary_path( std::string const &reference ) {
|
|
auto path { resolve_scenery_path( reference ) };
|
|
if( Global.eu7v2_runtime ) {
|
|
return eu7v2::binary_path_from_text( path ).generic_string();
|
|
}
|
|
char const *const ext { ".eu7" };
|
|
if( path.ends_with( ".scm" ) || path.ends_with( ".sbt" ) || path.ends_with( ".inc" ) ||
|
|
path.ends_with( ".scn" ) ) {
|
|
path.replace( path.size() - 4, 4, ext );
|
|
}
|
|
else if( false == path.ends_with( ".eu7" ) ) {
|
|
erase_extension( path );
|
|
path += ext;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
std::string
|
|
resolve_parser_include_path(
|
|
std::string const &parser_path,
|
|
std::string const ¤t_file,
|
|
std::string const &include_reference ) {
|
|
auto reference { include_reference };
|
|
while( false == reference.empty() && reference[ 0 ] == '$' ) {
|
|
reference.erase( 0, 1 );
|
|
}
|
|
replace_slashes( reference );
|
|
if( reference.starts_with( Global.asCurrentSceneryPath ) ) {
|
|
return reference;
|
|
}
|
|
if( reference.find( '/' ) != std::string::npos ) {
|
|
return parser_path + reference;
|
|
}
|
|
if( false == current_file.empty() ) {
|
|
auto const slash { current_file.find_last_of( '/' ) };
|
|
if( slash != std::string::npos ) {
|
|
return parser_path + current_file.substr( 0, slash + 1 ) + reference;
|
|
}
|
|
}
|
|
return parser_path + reference;
|
|
}
|
|
|
|
std::string
|
|
include_eu7_path(
|
|
std::string const &parser_path,
|
|
std::string const ¤t_file,
|
|
std::string const &include_reference ) {
|
|
return binary_path( resolve_parser_include_path( parser_path, current_file, include_reference ) );
|
|
}
|
|
|
|
std::string
|
|
terrain_binary_path( std::string const &terrain_reference ) {
|
|
return binary_path( terrain_reference );
|
|
}
|
|
|
|
bool
|
|
probe_file( std::string const &path ) {
|
|
if( false == FileExists( path ) ) {
|
|
return false;
|
|
}
|
|
if( Global.eu7v2_runtime && file_is_eu7v2( path ) ) {
|
|
return true;
|
|
}
|
|
return is_valid_eu7b_file( path );
|
|
}
|
|
|
|
bool
|
|
probe_baked_scenario( std::string const &scenario_file ) {
|
|
return probe_file( resolve_scenery_path( binary_path( scenario_file ) ) );
|
|
}
|
|
|
|
bool
|
|
is_text_module_extension( std::string const &path ) {
|
|
return path.ends_with( ".scn" ) || path.ends_with( ".scm" ) ||
|
|
path.ends_with( ".inc" ) || path.ends_with( ".sbt" );
|
|
}
|
|
|
|
std::string
|
|
text_source_path( std::string const &reference ) {
|
|
auto const resolved { resolve_scenery_path( reference ) };
|
|
if( is_text_module_extension( resolved ) ) {
|
|
return resolved;
|
|
}
|
|
if( resolved.ends_with( ".eu7" ) ) {
|
|
auto stem { resolved };
|
|
stem.erase( stem.size() - 4 );
|
|
for( auto const *ext : { ".scm", ".scn", ".inc", ".sbt" } ) {
|
|
auto const candidate { stem + ext };
|
|
if( FileExists( candidate ) ) {
|
|
return candidate;
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
bool
|
|
should_use_binary_module( std::string const &reference ) {
|
|
return probe_file( binary_path( reference ) );
|
|
}
|
|
|
|
bool
|
|
probe_terrain_file( std::string const &path ) {
|
|
return FileExists( path ) && file_has_terr_chunk( path );
|
|
}
|
|
|
|
bool
|
|
is_scenario_terrain( std::string const &scenario_file ) {
|
|
auto stem { scenario_file };
|
|
while( false == stem.empty() && stem[ 0 ] == '$' ) {
|
|
stem.erase( 0, 1 );
|
|
}
|
|
erase_extension( stem );
|
|
return probe_terrain_file( resolve_scenery_path( stem + ".eu7" ) );
|
|
}
|
|
|
|
bool
|
|
try_load_scenario_terrain( basic_region ®ion, std::string const &scenario_file ) {
|
|
auto stem { scenario_file };
|
|
while( false == stem.empty() && stem[ 0 ] == '$' ) {
|
|
stem.erase( 0, 1 );
|
|
}
|
|
erase_extension( stem );
|
|
return load_terrain( region, resolve_scenery_path( stem + ".eu7" ) );
|
|
}
|
|
|
|
void
|
|
insert_terrain_shapes( basic_region ®ion, Eu7Module const &module ) {
|
|
scene::scratch_data scratch;
|
|
std::size_t shape_count { 0 };
|
|
std::size_t vertex_count { 0 };
|
|
for( auto const &source : module.scene.terrain_shapes ) {
|
|
shape_node shape { build_shape_node( source ) };
|
|
vertex_count += source.vertices.size();
|
|
region.insert( shape, scratch, false );
|
|
++shape_count;
|
|
}
|
|
if( shape_count > 0 ) {
|
|
WriteLog(
|
|
"EU7 terrain shapes: count=" + std::to_string( shape_count ) +
|
|
" vertices=" + std::to_string( vertex_count ) );
|
|
}
|
|
}
|
|
|
|
bool
|
|
load_terrain( basic_region ®ion, std::string const &path ) {
|
|
if( false == probe_terrain_file( path ) ) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
auto const module { read_module( path ) };
|
|
if( module.scene.terrain_shapes.empty() ) {
|
|
ErrorLog( "EU7: brak chunka TERR w \"" + path + "\"" );
|
|
return false;
|
|
}
|
|
insert_terrain_shapes( region, module );
|
|
WriteLog( "EU7 terrain loaded: " + path );
|
|
return true;
|
|
}
|
|
catch( std::exception const &ex ) {
|
|
ErrorLog( std::string{ "EU7: blad terenu \"" + path + "\": " } + ex.what() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void
|
|
begin_load_session() {
|
|
g_load_session.clear();
|
|
g_packed_root_active = false;
|
|
reset_section_stream();
|
|
g_module_file_cache.clear();
|
|
reset_load_stats();
|
|
reset_pack_bench();
|
|
}
|
|
|
|
bool
|
|
is_module_loaded( std::string const &path ) {
|
|
return g_load_session.contains( resolve_scenery_path( path ) );
|
|
}
|
|
|
|
bool
|
|
pack_scenery_active() {
|
|
return g_packed_root_active;
|
|
}
|
|
|
|
bool
|
|
load_module( std::string const &path, simulation::state_serializer &serializer ) {
|
|
if( simulation::Region == nullptr ) {
|
|
ErrorLog( "EU7: Region nie jest zainicjalizowany" );
|
|
return false;
|
|
}
|
|
auto const resolved { resolve_scenery_path( path ) };
|
|
auto const ok { load_module_recursive( path, serializer, g_load_session ) };
|
|
if( ok ) {
|
|
log_load_stats();
|
|
if( auto const cached { g_module_file_cache.find( resolved ) };
|
|
cached != g_module_file_cache.end() && cached->second.has_pack_chunk ) {
|
|
init_section_stream( cached->second, resolved, serializer );
|
|
prime_section_stream( cached->second );
|
|
evict_non_pack_modules_from_cache( resolved );
|
|
}
|
|
else {
|
|
evict_non_pack_modules_from_cache( {} );
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
} // namespace scene::eu7
|