16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-17 23:39:18 +02:00
Files
maszyna/scene/eu7/v2/eu7v2_emit_runtime.cpp
maj00r beacc00932 Add headless parallel eu7v2 scenario bake with streaming and PLCE placements
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>
2026-06-17 21:15:42 +02:00

889 lines
33 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 "scene/eu7/v2/eu7v2_emit_runtime.h"
#include "scene/eu7/v2/eu7v2_format.h"
#include "scene/eu7/v2/eu7v2_scene.h"
#include "scene/eu7/v2/eu7v2_records.h"
#include <eu07/scene/bake/pack_model_spool.hpp>
#include <eu07/scene/include_resolve.hpp>
#include <eu07/scene/runtime/scene.hpp>
#include <chrono>
#include <fstream>
#include <limits>
#include <span>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
namespace eu7v2 {
namespace {
namespace rt = eu07::scene::runtime;
namespace bake = eu07::scene::bake;
namespace codec = eu07::scene::binary::codec;
[[nodiscard]] bool is_model_inc_placement( bake::ModuleInclude const &inc ) {
if( !eu07::scene::detail::isIncFile( inc.sourcePath ) ) {
return false;
}
if( inc.parameters.size() < 5 ) {
return false;
}
try {
(void)std::stod( inc.parameters[ 1 ] );
(void)std::stod( inc.parameters[ 2 ] );
(void)std::stod( inc.parameters[ 3 ] );
(void)std::stod( inc.parameters[ 4 ] );
return true;
} catch( ... ) {
return false;
}
}
[[nodiscard]] bool try_parse_inc_placement(
std::span<std::string const> const params,
double &x,
double &y,
double &z,
float &rot_y ) {
if( params.size() < 5 ) {
return false;
}
try {
x = std::stod( params[ 1 ] );
y = std::stod( params[ 2 ] );
z = std::stod( params[ 3 ] );
rot_y = static_cast<float>( std::stod( params[ 4 ] ) );
return true;
} catch( ... ) {
return false;
}
}
[[nodiscard]] file_kind file_kind_for_text_path( std::filesystem::path const &text_path ) {
auto const ext { text_path.extension().string() };
if( ext == ".inc" ) {
return file_kind::module;
}
return file_kind::sim;
}
// Mirror of scene_baker but sourced from the parser's Runtime* records.
class runtime_baker {
public:
runtime_baker(
bake::RuntimeModule const &module,
bool const is_root,
std::vector<codec::ModelSectionBatch> const *pack_batches,
bake::PackModelSpoolFile const *pack_spool,
bake::ShapeSpoolFile const *shape_spool,
std::filesystem::path const &text_path )
: m_module( module )
, m_is_root( is_root )
, m_pack_batches( pack_batches )
, m_pack_spool( pack_spool )
, m_shape_spool( shape_spool )
, m_file_kind( file_kind_for_text_path( text_path ) ) {}
[[nodiscard]] std::vector<std::uint8_t> run() {
convert_models();
convert_shapes();
convert_lines();
convert_tracks();
convert_traction();
convert_power_sources();
convert_memcells();
convert_launchers();
convert_events();
convert_sounds();
convert_dynamics();
convert_trainsets();
convert_includes();
convert_meta();
return serialize();
}
[[nodiscard]] std::size_t model_total() const { return m_instances.size(); }
[[nodiscard]] std::size_t placement_total() const { return m_placements.size(); }
[[nodiscard]] std::size_t structural_include_total() const { return m_includes.size(); }
private:
[[nodiscard]] std::uint32_t str( std::string const &s ) { return m_strings.intern( s ); }
[[nodiscard]] std::uint32_t opt_str( std::string const &s ) {
return s.empty() ? kNoString : m_strings.intern( s );
}
[[nodiscard]] node_record node( rt::BasicNode const &n ) {
node_record out;
out.name = opt_str( n.name );
out.type = opt_str( n.nodeType );
out.area_center = { n.area.center.x, n.area.center.y, n.area.center.z };
out.area_radius = n.area.radius;
out.range_sq_min = n.rangeSquaredMin;
out.range_sq_max = n.rangeSquaredMax;
out.visible = n.visible;
return out;
}
[[nodiscard]] lighting_block lighting( rt::LightingData const &l ) {
lighting_block out;
out.diffuse[ 0 ] = l.diffuse.x; out.diffuse[ 1 ] = l.diffuse.y;
out.diffuse[ 2 ] = l.diffuse.z; out.diffuse[ 3 ] = l.diffuse.w;
out.ambient[ 0 ] = l.ambient.x; out.ambient[ 1 ] = l.ambient.y;
out.ambient[ 2 ] = l.ambient.z; out.ambient[ 3 ] = l.ambient.w;
out.specular[ 0 ] = l.specular.x; out.specular[ 1 ] = l.specular.y;
out.specular[ 2 ] = l.specular.z; out.specular[ 3 ] = l.specular.w;
return out;
}
[[nodiscard]] std::uint32_t intern_prototype( rt::RuntimeModelInstance const &model ) {
std::string key;
key.reserve( model.modelFile.size() + model.textureFile.size() + 32 );
key.append( model.modelFile );
key.push_back( '\x1f' );
key.append( model.textureFile );
key.push_back( '\x1f' );
key.push_back( model.transition ? '1' : '0' );
key.push_back( model.isTerrain ? '1' : '0' );
for( auto const s : model.lightStates ) {
key.append( std::to_string( s ) );
key.push_back( ',' );
}
key.push_back( '\x1f' );
for( auto const c : model.lightColors ) {
key.append( std::to_string( c ) );
key.push_back( ',' );
}
auto const it { m_prototype_lookup.find( key ) };
if( it != m_prototype_lookup.end() ) {
return it->second;
}
model_prototype proto;
proto.model_file = str( model.modelFile );
proto.texture_file = opt_str( model.textureFile );
proto.flags = 0;
if( model.transition ) {
proto.flags |= proto_flag::transition;
}
if( model.isTerrain ) {
proto.flags |= proto_flag::is_terrain;
}
proto.range_min = -1.f;
proto.range_max = -1.f;
proto.light_states = model.lightStates;
proto.light_colors = model.lightColors;
auto const id { static_cast<std::uint32_t>( m_prototypes.size() ) };
m_prototypes.push_back( std::move( proto ) );
m_prototype_lookup.emplace( std::move( key ), id );
return id;
}
void emit_model( rt::RuntimeModelInstance const &model ) {
auto const proto { intern_prototype( model ) };
model_instance inst;
inst.proto = proto;
inst.x = model.location.x;
inst.y = model.location.y;
inst.z = model.location.z;
inst.ax = static_cast<float>( model.angles.x );
inst.ay = static_cast<float>( model.angles.y );
inst.az = static_cast<float>( model.angles.z );
inst.sx = static_cast<float>( model.scale.x );
inst.sy = static_cast<float>( model.scale.y );
inst.sz = static_cast<float>( model.scale.z );
inst.cell_id = 0xffu;
inst.texture_override = kNoString;
inst.has_node = true;
inst.node = node( model.node );
m_instances.push_back( std::move( inst ) );
}
void convert_models() {
if( m_is_root && m_pack_spool != nullptr ) {
m_pack_spool->for_each_model( [&]( rt::RuntimeModelInstance const &model ) {
emit_model( model );
} );
}
if( m_is_root && m_pack_batches != nullptr ) {
// Root: models live in the flattened PACK batches, not scene.models.
for( auto const &batch : *m_pack_batches ) {
for( auto const &model : batch.models ) {
emit_model( model );
}
}
}
for( auto const &model : m_module.scene.models ) {
emit_model( model );
}
}
[[nodiscard]] shape_record make_shape_record( rt::RuntimeShapeNode const &shape ) {
shape_record r;
r.node = node( shape.node );
r.translucent = shape.translucent;
r.material = opt_str( shape.materialPath );
r.lighting = lighting( shape.lighting );
r.ox = shape.origin.x;
r.oy = shape.origin.y;
r.oz = shape.origin.z;
r.vertices.reserve( shape.vertices.size() );
for( auto const &v : shape.vertices ) {
mesh_vertex mv;
mv.px = static_cast<float>( v.position.x - shape.origin.x );
mv.py = static_cast<float>( v.position.y - shape.origin.y );
mv.pz = static_cast<float>( v.position.z - shape.origin.z );
mv.nx = static_cast<float>( v.normal.x );
mv.ny = static_cast<float>( v.normal.y );
mv.nz = static_cast<float>( v.normal.z );
mv.u = static_cast<float>( v.u );
mv.v = static_cast<float>( v.v );
r.vertices.push_back( mv );
}
return r;
}
void convert_shapes() {
if( m_shape_spool != nullptr ) {
m_shape_spool->for_each_shape( [&]( rt::RuntimeShapeNode const &shape ) {
(void)opt_str( shape.materialPath );
(void)node( shape.node );
} );
return;
}
for( auto const &shape : m_module.scene.shapes ) {
m_shapes.push_back( make_shape_record( shape ) );
}
}
void convert_lines() {
for( auto const &line : m_module.scene.lines ) {
lines_record r;
r.node = node( line.node );
r.lighting = lighting( line.lighting );
r.line_width = line.lineWidth;
r.ox = line.origin.x;
r.oy = line.origin.y;
r.oz = line.origin.z;
r.vertices.reserve( line.vertices.size() );
for( auto const &v : line.vertices ) {
r.vertices.push_back( { v.position.x, v.position.y, v.position.z } );
}
m_lines.push_back( std::move( r ) );
}
}
void convert_tracks() {
for( auto const &t : m_module.scene.tracks ) {
track_record r;
r.node = node( t.node );
r.track_type = static_cast<std::uint8_t>( t.trackType );
r.category = static_cast<std::uint8_t>( t.category );
r.length = t.length;
r.track_width = t.trackWidth;
r.friction = t.friction;
r.sound_distance = t.soundDistance;
r.quality_flag = t.qualityFlag;
r.damage_flag = t.damageFlag;
r.environment = static_cast<std::int8_t>( t.environment );
if( t.visibility.has_value() ) {
r.has_visibility = true;
r.visibility.material1 = str( t.visibility->material1 );
r.visibility.tex_length = t.visibility->texLength;
r.visibility.material2 = str( t.visibility->material2 );
r.visibility.tex_height1 = t.visibility->texHeight1;
r.visibility.tex_width = t.visibility->texWidth;
r.visibility.tex_slope = t.visibility->texSlope;
}
r.paths.reserve( t.paths.size() );
for( auto const &p : t.paths ) {
track_path tp;
tp.p_start = { p.pStart.x, p.pStart.y, p.pStart.z };
tp.roll_start = p.rollStart;
tp.cp_out = { p.cpOut.x, p.cpOut.y, p.cpOut.z };
tp.cp_in = { p.cpIn.x, p.cpIn.y, p.cpIn.z };
tp.p_end = { p.pEnd.x, p.pEnd.y, p.pEnd.z };
tp.roll_end = p.rollEnd;
tp.radius = p.radius;
r.paths.push_back( tp );
}
r.tail_keywords.reserve( t.tailKeywords.size() );
for( auto const &kv : t.tailKeywords ) {
r.tail_keywords.emplace_back( str( kv.first ), str( kv.second ) );
}
m_tracks.push_back( std::move( r ) );
}
}
void convert_traction() {
for( auto const &t : m_module.scene.traction ) {
traction_record r;
r.node = node( t.node );
r.power_supply_name = opt_str( t.powerSupplyName );
r.nominal_voltage = t.nominalVoltage;
r.max_current = t.maxCurrent;
r.resistivity = t.resistivityOhmPerM;
r.material = static_cast<std::uint8_t>( t.material );
r.wire_thickness = t.wireThickness;
r.damage_flag = t.damageFlag;
r.wire_p1 = { t.wireP1.x, t.wireP1.y, t.wireP1.z };
r.wire_p2 = { t.wireP2.x, t.wireP2.y, t.wireP2.z };
r.wire_p3 = { t.wireP3.x, t.wireP3.y, t.wireP3.z };
r.wire_p4 = { t.wireP4.x, t.wireP4.y, t.wireP4.z };
r.min_height = t.minHeight;
r.segment_length = t.segmentLength;
r.wire_count = t.wireCount;
r.wire_offset = t.wireOffset;
if( t.parallelName.has_value() ) {
r.has_parallel = true;
r.parallel_name = str( *t.parallelName );
}
m_traction.push_back( std::move( r ) );
}
}
void convert_power_sources() {
for( auto const &p : m_module.scene.powerSources ) {
power_source_record r;
r.node = node( p.node );
r.position = { p.position.x, p.position.y, p.position.z };
r.nominal_voltage = p.nominalVoltage;
r.voltage_frequency = p.voltageFrequency;
r.internal_resistance = p.internalResistance;
r.max_output_current = p.maxOutputCurrent;
r.fast_fuse_timeout = p.fastFuseTimeout;
r.fast_fuse_repetition = p.fastFuseRepetition;
r.slow_fuse_timeout = p.slowFuseTimeout;
r.modifier = static_cast<std::uint8_t>( p.modifier );
m_power.push_back( std::move( r ) );
}
}
void convert_memcells() {
for( auto const &m : m_module.scene.memcells ) {
memcell_record r;
r.node = node( m.node );
r.text = opt_str( m.text );
r.value1 = m.value1;
r.value2 = m.value2;
if( m.trackName.has_value() ) {
r.has_track = true;
r.track_name = str( *m.trackName );
}
m_memcells.push_back( std::move( r ) );
}
}
void convert_launchers() {
for( auto const &l : m_module.scene.eventLaunchers ) {
launcher_record r;
r.node = node( l.node );
r.location = { l.location.x, l.location.y, l.location.z };
r.radius_squared = l.radiusSquared;
r.activation_key = l.activationKey;
r.delta_time = l.deltaTime;
r.event1_name = opt_str( l.event1Name );
r.event2_name = opt_str( l.event2Name );
if( l.condition.has_value() ) {
r.has_condition = true;
r.condition.memcell_name = str( l.condition->memcellName );
r.condition.compare_text = str( l.condition->compareText );
r.condition.compare_value1 = l.condition->compareValue1;
r.condition.compare_value2 = l.condition->compareValue2;
r.condition.check_mask = l.condition->checkMask;
}
r.train_triggered = l.trainTriggered;
r.launch_hour = l.launchHour;
r.launch_minute = l.launchMinute;
m_launchers.push_back( std::move( r ) );
}
}
void convert_events() {
for( auto const &e : m_module.scene.events ) {
event_record r;
r.name = opt_str( e.name );
r.type = static_cast<std::uint8_t>( e.type );
r.delay = e.delay;
r.delay_random = e.delayRandom;
r.delay_departure = e.delayDeparture;
r.ignored = e.ignored;
r.passive = e.passive;
r.targets.reserve( e.targets.size() );
for( auto const &t : e.targets ) {
r.targets.push_back( str( t ) );
}
r.payload.reserve( e.payload.size() );
for( auto const &kv : e.payload ) {
r.payload.emplace_back( str( kv.first ), str( kv.second ) );
}
m_events.push_back( std::move( r ) );
}
}
void convert_sounds() {
for( auto const &s : m_module.scene.sounds ) {
sound_record r;
r.node = node( s.node );
r.location = { s.location.x, s.location.y, s.location.z };
r.wav_file = opt_str( s.wavFile );
m_sounds.push_back( std::move( r ) );
}
}
void convert_dynamics() {
for( auto const &d : m_module.scene.dynamics ) {
dynamic_record r;
r.node = node( d.node );
r.data_folder = opt_str( d.dataFolder );
r.skin_file = opt_str( d.skinFile );
r.mmd_file = opt_str( d.mmdFile );
r.track_name = opt_str( d.trackName );
r.offset = d.offset;
r.driver_type = opt_str( d.driverType );
r.coupling = d.coupling;
r.coupling_raw = opt_str( d.couplingRaw );
r.coupling_params = opt_str( d.couplingParams );
r.velocity = d.velocity;
r.load_count = d.loadCount;
r.load_type = opt_str( d.loadType );
if( d.destination.has_value() ) {
r.has_destination = true;
r.destination = str( *d.destination );
}
if( d.trainsetIndex.has_value() ) {
r.has_trainset = true;
r.trainset_index = static_cast<std::uint32_t>( *d.trainsetIndex );
}
m_dynamics.push_back( std::move( r ) );
}
}
void convert_trainsets() {
for( auto const &t : m_module.scene.trainsets ) {
trainset_record r;
r.name = opt_str( t.name );
r.track = opt_str( t.track );
r.offset = t.offset;
r.velocity = t.velocity;
r.assignment.reserve( t.assignment.size() );
for( auto const &kv : t.assignment ) {
r.assignment.emplace_back( str( kv.first ), str( kv.second ) );
}
r.vehicle_indices.reserve( t.vehicleIndices.size() );
for( auto const idx : t.vehicleIndices ) {
r.vehicle_indices.push_back( static_cast<std::uint32_t>( idx ) );
}
r.couplings.reserve( t.couplings.size() );
for( auto const c : t.couplings ) {
r.couplings.push_back( c );
}
r.driver_index =
t.driverIndex == static_cast<std::size_t>( -1 )
? 0xffffffffu
: static_cast<std::uint32_t>( t.driverIndex );
m_trainsets.push_back( std::move( r ) );
}
}
[[nodiscard]] transform_record transform( rt::TransformContext const &t ) {
transform_record out;
out.origin_stack.reserve( t.originStack.size() );
for( auto const &v : t.originStack ) {
out.origin_stack.push_back( { v.x, v.y, v.z } );
}
out.scale_stack.reserve( t.scaleStack.size() );
for( auto const &v : t.scaleStack ) {
out.scale_stack.push_back( { v.x, v.y, v.z } );
}
out.rotation = { t.rotation.x, t.rotation.y, t.rotation.z };
out.group_depth = static_cast<std::uint32_t>( t.groupStackDepth );
return out;
}
void convert_includes() {
for( auto const &inc : m_module.includes ) {
if( is_model_inc_placement( inc ) ) {
double x { 0.0 }, y { 0.0 }, z { 0.0 };
float rot_y { 0.f };
if( !try_parse_inc_placement( inc.parameters, x, y, z, rot_y ) ) {
continue;
}
module_placement_record p;
p.module_path = str(
binary_path_from_text( std::filesystem::path { inc.sourcePath } )
.generic_string() );
p.texture_override =
( inc.parameters.empty() || inc.parameters[ 0 ] == "none" )
? kNoString
: str( inc.parameters[ 0 ] );
p.x = x;
p.y = y;
p.z = z;
p.rotation_y = rot_y;
m_placements.push_back( std::move( p ) );
continue;
}
include_record r;
r.source_line = inc.sourceLine;
r.source_path = opt_str( inc.sourcePath );
r.binary_path = inc.sourcePath.empty()
? kNoString
: str( binary_path_from_text(
std::filesystem::path { inc.sourcePath } )
.generic_string() );
r.parameters.reserve( inc.parameters.size() );
for( auto const &param : inc.parameters ) {
r.parameters.push_back( str( param ) );
}
r.site_transform = transform( inc.siteTransform );
m_includes.push_back( std::move( r ) );
}
}
void convert_meta() {
m_meta.first_init_count = m_module.scene.firstInitCount;
m_meta.has_terrain_chunk = false;
// Root PACK batches are flattened into plain INST records, so the file
// carries no PACK streaming chunk; never advertise one.
m_meta.has_pack_chunk = false;
m_meta.placement_origin_x = m_module.includePlacement.origin_x_param;
m_meta.placement_origin_y = m_module.includePlacement.origin_y_param;
m_meta.placement_origin_z = m_module.includePlacement.origin_z_param;
m_meta.placement_rotation_y = m_module.includePlacement.rotation_y_param;
}
[[nodiscard]] std::vector<std::uint8_t> serialize() {
container_writer writer( m_file_kind );
byte_writer strs;
m_strings.serialize( strs );
writer.add_chunk( chunk::strs, strs );
byte_writer meta;
write_meta( meta, m_meta );
writer.add_chunk( chunk::meta, meta );
auto emit { [&]( std::uint32_t id, auto const &write_fn, auto const &items ) {
if( items.empty() ) {
return;
}
byte_writer payload;
write_fn( payload, items );
writer.add_chunk( id, payload );
} };
emit( chunk::incl, &write_includes, m_includes );
emit( chunk::plce, &write_module_placements, m_placements );
emit( chunk::prot, &write_prototypes, m_prototypes );
emit( chunk::inst, &write_instances, m_instances );
if( m_shape_spool != nullptr && m_shape_spool->shape_count() != 0 ) {
byte_writer payload;
payload.put_u32( static_cast<std::uint32_t>( m_shape_spool->shape_count() ) );
m_shape_spool->for_each_shape( [&]( rt::RuntimeShapeNode const &shape ) {
write_shape_record( payload, make_shape_record( shape ) );
} );
writer.add_chunk( chunk::shpe, payload );
} else {
emit( chunk::shpe, &write_shapes, m_shapes );
}
emit( chunk::line, &write_lines, m_lines );
emit( chunk::trak, &write_tracks, m_tracks );
emit( chunk::trac, &write_traction, m_traction );
emit( chunk::pwrs, &write_power_sources, m_power );
emit( chunk::memc, &write_memcells, m_memcells );
emit( chunk::laun, &write_launchers, m_launchers );
emit( chunk::evnt, &write_events, m_events );
emit( chunk::sond, &write_sounds, m_sounds );
emit( chunk::dynm, &write_dynamics, m_dynamics );
emit( chunk::trst, &write_trainsets, m_trainsets );
return writer.data();
}
bake::RuntimeModule const &m_module;
bool m_is_root;
std::vector<codec::ModelSectionBatch> const *m_pack_batches;
bake::PackModelSpoolFile const *m_pack_spool;
bake::ShapeSpoolFile const *m_shape_spool;
file_kind m_file_kind { file_kind::sim };
std::vector<module_placement_record> m_placements;
string_table m_strings;
std::vector<model_prototype> m_prototypes;
std::unordered_map<std::string, std::uint32_t> m_prototype_lookup;
std::vector<model_instance> m_instances;
std::vector<shape_record> m_shapes;
std::vector<lines_record> m_lines;
std::vector<track_record> m_tracks;
std::vector<traction_record> m_traction;
std::vector<power_source_record> m_power;
std::vector<memcell_record> m_memcells;
std::vector<launcher_record> m_launchers;
std::vector<event_record> m_events;
std::vector<sound_record> m_sounds;
std::vector<dynamic_record> m_dynamics;
std::vector<trainset_record> m_trainsets;
std::vector<include_record> m_includes;
module_meta m_meta;
};
// Decoded record counts of an emitted eu7v2 image (chunk-by-chunk).
struct decoded_counts {
std::size_t includes { 0 };
std::size_t placements { 0 };
std::size_t instances { 0 };
std::size_t shapes { 0 };
std::size_t lines { 0 };
std::size_t tracks { 0 };
std::size_t traction { 0 };
std::size_t power { 0 };
std::size_t memcells { 0 };
std::size_t launchers { 0 };
std::size_t events { 0 };
std::size_t sounds { 0 };
std::size_t dynamics { 0 };
std::size_t trainsets { 0 };
bool ok { true };
};
[[nodiscard]] decoded_counts decode_counts( std::vector<std::uint8_t> const &bytes ) {
decoded_counts c;
try {
container_reader reader( bytes.data(), bytes.size() );
chunk_view chunk;
while( reader.next( chunk ) ) {
auto r { chunk.reader() };
switch( chunk.id ) {
case chunk::incl: c.includes = read_includes( r ).size(); break;
case chunk::plce: c.placements = read_module_placements( r ).size(); break;
case chunk::inst: c.instances = read_instances( r ).size(); break;
case chunk::shpe: c.shapes = read_shapes( r ).size(); break;
case chunk::line: c.lines = read_lines( r ).size(); break;
case chunk::trak: c.tracks = read_tracks( r ).size(); break;
case chunk::trac: c.traction = read_traction( r ).size(); break;
case chunk::pwrs: c.power = read_power_sources( r ).size(); break;
case chunk::memc: c.memcells = read_memcells( r ).size(); break;
case chunk::laun: c.launchers = read_launchers( r ).size(); break;
case chunk::evnt: c.events = read_events( r ).size(); break;
case chunk::sond: c.sounds = read_sounds( r ).size(); break;
case chunk::dynm: c.dynamics = read_dynamics( r ).size(); break;
case chunk::trst: c.trainsets = read_trainsets( r ).size(); break;
default: break;
}
}
}
catch( parse_error const & ) {
c.ok = false;
}
return c;
}
[[nodiscard]] std::filesystem::path eu7v2_path_for( std::filesystem::path const &text_path ) {
return binary_path_from_text( text_path );
}
} // namespace
std::vector<std::uint8_t>
emit_runtime_module_bytes(
bake::RuntimeModule const &module,
bool const is_root,
std::vector<codec::ModelSectionBatch> const *pack_batches,
bake::PackModelSpoolFile const *pack_spool,
bake::ShapeSpoolFile const *shape_spool,
std::filesystem::path const &text_path ) {
runtime_baker baker( module, is_root, pack_batches, pack_spool, shape_spool, text_path );
return baker.run();
}
emit_outcome
emit_runtime_module(
bake::RuntimeModule const &module,
std::filesystem::path const &text_path,
bool const is_root,
std::vector<codec::ModelSectionBatch> const *pack_batches,
bool const verify,
bake::PackModelSpoolFile const *pack_spool,
bake::ShapeSpoolFile const *shape_spool ) {
emit_outcome outcome;
auto const ms_since { []( std::chrono::steady_clock::time_point const t0 ) {
return std::chrono::duration<double, std::milli>(
std::chrono::steady_clock::now() - t0 )
.count();
} };
auto const build_begin { std::chrono::steady_clock::now() };
runtime_baker baker( module, is_root, pack_batches, pack_spool, shape_spool, text_path );
std::vector<std::uint8_t> const bytes { baker.run() };
outcome.model_total = baker.model_total();
outcome.byte_size = bytes.size();
outcome.build_ms = ms_since( build_begin );
auto const out_path { eu7v2_path_for( text_path ) };
outcome.written_path = out_path.generic_string();
{
auto const write_begin { std::chrono::steady_clock::now() };
std::ofstream output { out_path, std::ios::binary | std::ios::trunc };
if( !output ) {
outcome.ok = false;
outcome.message = "nie mozna zapisac " + outcome.written_path;
return outcome;
}
output.write(
reinterpret_cast<char const *>( bytes.data() ),
static_cast<std::streamsize>( bytes.size() ) );
output.flush();
outcome.write_ms = ms_since( write_begin );
}
if( !verify ) {
return outcome;
}
auto const verify_begin { std::chrono::steady_clock::now() };
outcome.verified = true;
decoded_counts const dec { decode_counts( bytes ) };
std::size_t src_models { module.scene.models.size() };
if( is_root && pack_spool != nullptr ) {
src_models += pack_spool->model_count();
}
if( is_root && pack_batches != nullptr ) {
for( auto const &batch : *pack_batches ) {
src_models += batch.models.size();
}
}
std::size_t src_placements { 0 };
std::size_t src_structural { 0 };
for( auto const &inc : module.includes ) {
if( is_model_inc_placement( inc ) ) {
++src_placements;
} else {
++src_structural;
}
}
std::ostringstream report;
bool pass { dec.ok };
auto cmp { [&]( char const *label, std::size_t const src, std::size_t const got ) {
bool const ok { src == got };
pass = pass && ok;
report << " " << ( ok ? "ok " : "FAIL" ) << ' ' << label << " src=" << src
<< " eu7v2=" << got << '\n';
} };
cmp( "includes", src_structural, dec.includes );
cmp( "placements", src_placements, dec.placements );
cmp( "models", src_models, dec.instances );
cmp( "shapes", module.scene.shapes.size() + ( shape_spool != nullptr ? shape_spool->shape_count() : 0 ),
dec.shapes );
cmp( "lines", module.scene.lines.size(), dec.lines );
cmp( "tracks", module.scene.tracks.size(), dec.tracks );
cmp( "traction", module.scene.traction.size(), dec.traction );
cmp( "power", module.scene.powerSources.size(), dec.power );
cmp( "memcells", module.scene.memcells.size(), dec.memcells );
cmp( "launchers", module.scene.eventLaunchers.size(), dec.launchers );
cmp( "events", module.scene.events.size(), dec.events );
cmp( "sounds", module.scene.sounds.size(), dec.sounds );
cmp( "dynamics", module.scene.dynamics.size(), dec.dynamics );
cmp( "trainsets", module.scene.trainsets.size(), dec.trainsets );
outcome.verify_ok = pass;
outcome.message = report.str();
outcome.verify_ms = ms_since( verify_begin );
return outcome;
}
bool
verify_written_module(
std::filesystem::path const &path,
module_verify_spec const &spec,
bool const is_root,
std::size_t const pack_models,
std::string *message_out ) {
std::ifstream input { path, std::ios::binary };
if( !input ) {
if( message_out != nullptr ) {
*message_out = "nie mozna odczytac " + path.generic_string();
}
return false;
}
input.seekg( 0, std::ios::end );
const std::streamoff file_size { input.tellg() };
input.seekg( 0, std::ios::beg );
if( file_size <= 0 ) {
if( message_out != nullptr ) {
*message_out = "pusty plik " + path.generic_string();
}
return false;
}
std::vector<std::uint8_t> bytes( static_cast<std::size_t>( file_size ) );
input.read(
reinterpret_cast<char *>( bytes.data() ),
static_cast<std::streamsize>( bytes.size() ) );
decoded_counts const dec { decode_counts( bytes ) };
if( !dec.ok ) {
if( message_out != nullptr ) {
*message_out = "decode blad " + path.generic_string();
}
return false;
}
std::size_t src_models { spec.models };
if( is_root ) {
src_models += pack_models;
}
std::ostringstream report;
bool pass { dec.ok };
auto cmp { [&]( char const *label, std::size_t const src, std::size_t const got ) {
bool const ok { src == got };
pass = pass && ok;
report << " " << ( ok ? "ok " : "FAIL" ) << ' ' << label << " src=" << src
<< " eu7v2=" << got << '\n';
} };
cmp( "includes", spec.includes, dec.includes );
cmp( "placements", spec.placements, dec.placements );
cmp( "models", src_models, dec.instances );
cmp( "shapes", spec.shapes, dec.shapes );
cmp( "lines", spec.lines, dec.lines );
cmp( "tracks", spec.tracks, dec.tracks );
cmp( "traction", spec.traction, dec.traction );
cmp( "power", spec.power, dec.power );
cmp( "memcells", spec.memcells, dec.memcells );
cmp( "launchers", spec.launchers, dec.launchers );
cmp( "events", spec.events, dec.events );
cmp( "sounds", spec.sounds, dec.sounds );
cmp( "dynamics", spec.dynamics, dec.dynamics );
cmp( "trainsets", spec.trainsets, dec.trainsets );
if( message_out != nullptr ) {
*message_out = report.str();
}
return pass;
}
} // namespace eu7v2