From 06fc3915f48393f5f7b77186dc9aac551cc4ee82 Mon Sep 17 00:00:00 2001 From: jerrrrycho Date: Fri, 2 Oct 2026 21:19:16 +0200 Subject: [PATCH 1/3] fix(renderer): guard replacable skin lookup and light colour access (#200) --- rendering/openglrenderer.cpp | 66 +++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index dcbd1df2..bc6c9d14 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -31,6 +31,23 @@ int constexpr EU07_PICKBUFFERSIZE { 1024 }; // size of (square) textures bound w int constexpr EU07_ENVIRONMENTBUFFERSIZE { 256 }; // size of (square) environmental cube map texture int constexpr EU07_REFLECTIONFIDELITYOFFSET { 250 }; // artificial increase of range for reflection pass detail reduction +namespace { + +// returns material assigned to the submodel, resolving replacable skins. null_handle if no skin set is bound +material_handle +submodel_material( TSubModel const *Submodel ) { + + if( Submodel->m_material >= 0 ) { + return Submodel->m_material; + } + return ( + TSubModel::ReplacableSkinId != nullptr ? + TSubModel::ReplacableSkinId[ -Submodel->m_material ] : + null_handle ); +} + +} // namespace + float const EU07_OPACITYDEFAULT { 0.5f }; bool @@ -1790,8 +1807,7 @@ opengl_renderer::Material( material_handle const Material ) const { opengl_material const & opengl_renderer::Material( TSubModel const * Submodel ) const { - auto const material { Submodel->m_material >= 0 ? Submodel->m_material : Submodel->ReplacableSkinId[ -Submodel->m_material ] }; - return m_materials.material( material ); + return m_materials.material( submodel_material( Submodel ) ); } // shader methods @@ -2768,10 +2784,7 @@ opengl_renderer::Render( TSubModel *Submodel ) { } #endif // material configuration: - auto const material{ ( - Submodel->m_material < 0 ? - Submodel->ReplacableSkinId[ -Submodel->m_material ] : // zmienialne skóry - Submodel->m_material ) }; // również 0 + auto const material{ submodel_material( Submodel ) }; // zmienialne skóry, również 0 // textures... Bind_Material( material ); // ...colors and opacity... @@ -2879,7 +2892,7 @@ opengl_renderer::Render( TSubModel *Submodel ) { // material configuration: // textures... if( Submodel->m_material < 0 ) { // zmienialne skóry - Bind_Material( TSubModel::ReplacableSkinId[ -Submodel->m_material ], Submodel ); + Bind_Material( submodel_material( Submodel ), Submodel ); } else { // również 0 @@ -2897,7 +2910,7 @@ opengl_renderer::Render( TSubModel *Submodel ) { ::glColor3fv( glm::value_ptr( pick_color( m_pickcontrolsitems.size() ) ) ); // textures... if( Submodel->m_material < 0 ) { // zmienialne skóry - Bind_Material( TSubModel::ReplacableSkinId[ -Submodel->m_material ], Submodel ); + Bind_Material( submodel_material( Submodel ), Submodel ); } else { // również 0 @@ -2971,10 +2984,10 @@ opengl_renderer::Render( TSubModel *Submodel ) { auto const unitstate = m_unitstate; switch_units( m_unitstate.diffuse, false, false ); - auto const *lightcolor { + glm::vec3 const lightcolor { Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override - glm::value_ptr( Submodel->f4Diffuse ) : - glm::value_ptr( Submodel->DiffuseOverride ) }; + glm::vec3( Submodel->f4Diffuse ) : + Submodel->DiffuseOverride }; // main draw call if( Global.Overcast > 1.f ) { @@ -2987,9 +3000,9 @@ opengl_renderer::Render( TSubModel *Submodel ) { ::glPointSize( pointsize * fogfactor ); ::glColor4f( - lightcolor[ 0 ], - lightcolor[ 1 ], - lightcolor[ 2 ], + lightcolor.r, + lightcolor.g, + lightcolor.b, Submodel->fVisible * std::min( 1.f, lightlevel ) * 0.5f ); ::glDepthMask( GL_FALSE ); m_geometry.draw( Submodel->m_geometry.handle ); @@ -2997,9 +3010,9 @@ opengl_renderer::Render( TSubModel *Submodel ) { } ::glPointSize( pointsize ); ::glColor4f( - lightcolor[ 0 ], - lightcolor[ 1 ], - lightcolor[ 2 ], + lightcolor.r, + lightcolor.g, + lightcolor.b, Submodel->fVisible * std::min( 1.f, lightlevel ) ); m_geometry.draw( Submodel->m_geometry.handle ); @@ -3845,10 +3858,7 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { #endif // material configuration: // textures... - auto const material { ( - Submodel->m_material < 0 ? - Submodel->ReplacableSkinId[ -Submodel->m_material ] : // zmienialne skóry - Submodel->m_material ) }; // również 0 + auto const material { submodel_material( Submodel ) }; // zmienialne skóry, również 0 // textures... Bind_Material( material ); // ...colors and opacity... @@ -3923,7 +3933,7 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { // material configuration: // textures... if( Submodel->m_material < 0 ) { // zmienialne skóry - Bind_Material( TSubModel::ReplacableSkinId[ -Submodel->m_material ], Submodel ); + Bind_Material( submodel_material( Submodel ), Submodel ); } else { // również 0 @@ -3986,14 +3996,14 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { auto const unitstate = m_unitstate; switch_units( unitstate.diffuse, false, false ); - auto const *lightcolor { + glm::vec3 const lightcolor { Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override - glm::value_ptr( Submodel->f4Diffuse ) : - glm::value_ptr( Submodel->DiffuseOverride ) }; + glm::vec3( Submodel->f4Diffuse ) : + Submodel->DiffuseOverride }; ::glColor4f( - lightcolor[ 0 ], - lightcolor[ 1 ], - lightcolor[ 2 ], + lightcolor.r, + lightcolor.g, + lightcolor.b, Submodel->fVisible * glarelevel ); // main draw call From 51521a135a8b83a63d0fa3c4c7fbad12ff203e3f Mon Sep 17 00:00:00 2001 From: pawelw Date: Sat, 3 Oct 2026 12:05:32 +0200 Subject: [PATCH 2/3] Text displays: accept characters above 127, in windows-1250 or utf-8 (#198) * Text displays: accept characters above 127, in windows-1250 or utf-8 A text display (sub-model of type "text") looks up the sub-model of each character in a 256 entry table, and indexed it with a plain char. With char being signed, any character above 127 gave a negative index: reading the text went outside the table, and so did filling the table if a character sub-model had such a name. In effect the displays could only show ASCII, and a text with national characters could crash. The lookup is now keyed by unicode code point. Both the displayed text and the names of the character sub-models are decoded as utf-8 if they are valid utf-8, and as windows-1250 otherwise, so legacy (ansi) sceneries and models work next to utf-8 ones, and the two can be mixed. A short ansi text can be valid utf-8 by accident (two upper case accented letters in a row will do); when both readings are possible the one which finds more of its characters in the model is used. The rest is unchanged: the first character of the text still isn't displayed, the characters are still drawn in the translucent pass only. A missing text pointer no longer gets dereferenced. * Text displays: prepare the characters once for each text The sequence of character sub-models was cached for the last text only, so displays of several instances drawn in turn decoded their texts again on every draw. It is now kept for each text the display shows; the texts of the instances don't change, so a draw costs a single lookup. Also the comments added in the renderers are now in English. * Text displays: move the text decoding to utilities utf8_to_utf32() and win1250_to_utf32(), with the windows-1250 table, now sit next to win1250_to_utf8(). The existing function maps only the Polish letters and passes other bytes above 127 through, so it can't serve the displays. * Text displays: address the SonarCloud findings std::array instead of the C-style arrays, the utf-8 sequence length worked out in a function of its own instead of nested conditional operators, std::byte for the bit operations, std::ranges::count_if and contains(). The character loop of the renderers moved to Render_Alpha_text(), which takes the nesting back to the limit, and the static pasText is accessed through the class. --- model/Model3d.cpp | 64 ++++++++++++++++++++++++++- model/Model3d.h | 7 ++- rendering/opengl33renderer.cpp | 39 +++++++--------- rendering/opengl33renderer.h | 1 + rendering/openglrenderer.cpp | 33 ++++++-------- rendering/openglrenderer.h | 3 ++ utilities/utilities.cpp | 81 ++++++++++++++++++++++++++++++++++ utilities/utilities.h | 4 ++ 8 files changed, 187 insertions(+), 45 deletions(-) diff --git a/model/Model3d.cpp b/model/Model3d.cpp index fba07446..e1a3a79e 100644 --- a/model/Model3d.cpp +++ b/model/Model3d.cpp @@ -17,6 +17,7 @@ Copyright (C) 2001-2004 Marcin Wozniak, Maciej Czapkiewicz and others #include "utilities/Globals.h" #include "utilities/Logs.h" +#include "utilities/utilities.h" #include "rendering/renderer.h" #include "utilities/Timer.h" #include "simulation/simulation.h" @@ -55,8 +56,6 @@ TSubModel::~TSubModel() SafeDelete(Child); delete fMatrix; // własny transform trzeba usunąć (zawsze jeden) } - delete[] smLetter; // używany tylko roboczo dla TP_TEXT, do przyspieszenia - // wyświetlania }; void TSubModel::Name_Material(std::string const &Name) @@ -892,6 +891,67 @@ void TSubModel::InitialRotate(bool doit) Next->InitialRotate(doit); }; +// text display (TP_TEXT): returns sub-models of the characters to draw for provided text, in drawing order. +// a character is matched with the first character of a child sub-model name. both the text and the names can be +// encoded either in utf-8 or in windows-1250, so the legacy (ansi) scenery and model files work along the utf-8 ones +std::vector const &TSubModel::text_letters(std::string const &Text) +{ + // the sequence is prepared once for each text the display shows. the texts of the model instances don't change, + // so drawing a display afterwards costs a single lookup, however many instances and passes take turns + auto const prepared{m_textlayouts.find(Text)}; + if (prepared != m_textlayouts.end()) + { + return prepared->second; + } + + std::u32string characters; + if (false == m_lettersready) + { + for (auto *letter = Child; letter != nullptr; letter = letter->Next) + { + if (letter->pName.empty()) + { + continue; + } + if (false == utf8_to_utf32(letter->pName, characters)) + { + win1250_to_utf32(letter->pName, characters); + } + if (characters.front() != 0) + { + m_letters[characters.front()] = letter; + } + } + m_lettersready = true; + } + + // the text is taken for utf-8 if it's valid as such, and for windows-1250 otherwise. a short ansi text can pass + // for utf-8 by accident (two upper case accented letters in a row are enough for this to happen), + // so if both readings are possible the one which finds more of its characters in the model wins + auto const known = [this](std::u32string const &Characters) { + return std::ranges::count_if(Characters, [this](char32_t const Character) { return m_letters.contains(Character); }); + }; + std::u32string ansi; + win1250_to_utf32(Text, ansi); + if (false == utf8_to_utf32(Text, characters) || known(ansi) > known(characters)) + { + characters = ansi; + } + + auto &textletters{m_textlayouts[Text]}; + // NOTE: the first character of the text isn't displayed. the displays always worked this way, and the existing + // content relies on it + for (std::size_t idx = 1; idx < characters.size(); ++idx) + { + auto const lookup{m_letters.find(characters[idx])}; + if (lookup != m_letters.end()) + { + textletters.emplace_back(lookup->second); + } + } + return textletters; +} + void TSubModel::ChildAdd(TSubModel *SubModel) { // dodanie submodelu potemnego (uzależnionego) // Ra: zmiana kolejności, żeby kolejne móc renderować po aktualnym (było diff --git a/model/Model3d.h b/model/Model3d.h index 0e053609..53e9084f 100644 --- a/model/Model3d.h +++ b/model/Model3d.h @@ -156,7 +156,10 @@ public: // chwilowo std::uintptr_t iAnimOwner{ 0 }; // roboczy numer egzemplarza, który ustawił animację TAnimType b_aAnim{ TAnimType::at_None }; // kody animacji oddzielnie, bo zerowane std::shared_ptr mAnimMatrix; // macierz do animacji kwaternionowych - TSubModel **smLetter{ nullptr }; // wskaźnik na tablicę submdeli do generoania tekstu (docelowo zapisać do E3D) + // text display (TP_TEXT) data, filled on first use + std::unordered_map m_letters; // sub-models of the available characters, by unicode code point + bool m_lettersready { false }; // whether the character lookup was built yet + std::unordered_map> m_textlayouts; // sub-models of consecutive characters, for each text shown TSubModel *Parent{ nullptr }; // nadrzędny, np. do wymnażania macierzy int iVisible { 1 }; // roboczy stan widoczności float fVisible { 1.f }; // visibility level @@ -202,6 +205,8 @@ public: void SetTranslate(float3 vNewTransVector); void SetRotateIK1(float3 vNewAngles); TSubModel * GetFromName( std::string const &search, bool i = true ); + // text display (TP_TEXT): returns sub-models of the characters to draw for provided text, in drawing order + std::vector const & text_letters( std::string const &Text ); inline float4x4 * GetMatrix() { return fMatrix; }; inline float4x4 const * GetMatrix() const { return fMatrix; }; // returns offset vector from root diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index c07efca4..2f587033 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -4634,6 +4634,21 @@ bool opengl33_renderer::Render_Alpha(TModel3d *Model, material_data const *Mater return result; } +// characters of a text display, laid out in a row. the text can be encoded in utf-8 or in windows-1250 +void opengl33_renderer::Render_Alpha_text(TSubModel *Submodel) +{ + if (TSubModel::pasText == nullptr) + { + return; + } + for (auto *p : Submodel->text_letters(*TSubModel::pasText)) + { // translucent only, for the time being + Render_Alpha(p); + if (p->fMatrix) + ::glMultMatrixf(p->fMatrix->readArray()); // move on to the place of the next character + } +} + void opengl33_renderer::Render_Alpha(TSubModel *Submodel) { // renderowanie przezroczystych przez DL @@ -4862,29 +4877,7 @@ void opengl33_renderer::Render_Alpha(TSubModel *Submodel) { if (Submodel->eType == TP_TEXT) { // tekst renderujemy w specjalny sposób, zamiast submodeli z łańcucha Child - int i, j = (int)Submodel->pasText->size(); - TSubModel *p; - if (!Submodel->smLetter) - { // jeśli nie ma tablicy, to ją stworzyć; miejsce nieodpowiednie, ale tymczasowo może być - Submodel->smLetter = new TSubModel *[256]; // tablica wskaźników submodeli dla wyświetlania tekstu - memset(Submodel->smLetter, 0, 256 * sizeof(TSubModel *)); // wypełnianie zerami - p = Submodel->Child; - while (p) - { - Submodel->smLetter[p->pName[0]] = p; - p = p->Next; // kolejny znak - } - } - for (i = 1; i <= j; ++i) - { - p = Submodel->smLetter[(*(Submodel->pasText))[i]]; // znak do wyświetlenia - if (p) - { // na razie tylko jako przezroczyste - Render_Alpha(p); - if (p->fMatrix) - ::glMultMatrixf(p->fMatrix->readArray()); // przesuwanie widoku - } - } + Render_Alpha_text(Submodel); } else if (Submodel->iAlpha & Submodel->iFlags & 0x002F0000) Render_Alpha(Submodel->Child); diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index 43cd24b0..562670d9 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -310,6 +310,7 @@ class opengl33_renderer : public gfx_renderer { bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle); bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance); void Render_Alpha(TSubModel *Submodel); + void Render_Alpha_text(TSubModel *Submodel); // characters of a text display void Update_Lights(light_array &Lights); glm::vec3 pick_color(std::size_t const Index); std::size_t pick_index(glm::ivec3 const &Color); diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index bc6c9d14..b675c38f 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -3813,6 +3813,19 @@ opengl_renderer::Render_Alpha( TModel3d *Model, material_data const *Material, f return result; } +// characters of a text display, laid out in a row. the text can be encoded in utf-8 or in windows-1250 +void +opengl_renderer::Render_Alpha_text( TSubModel *Submodel ) { + + if( TSubModel::pasText == nullptr ) { return; } + + for( auto *p : Submodel->text_letters( *TSubModel::pasText ) ) { // translucent only, for the time being + Render_Alpha( p ); + if( p->fMatrix ) + ::glMultMatrixf( p->fMatrix->readArray() ); // move on to the place of the next character + } +} + void opengl_renderer::Render_Alpha( TSubModel *Submodel ) { // renderowanie przezroczystych przez DL @@ -4028,25 +4041,7 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { if( Submodel->Child != nullptr ) { if( Submodel->eType == TP_TEXT ) { // tekst renderujemy w specjalny sposób, zamiast submodeli z łańcucha Child - int i, j = (int)Submodel->pasText->size(); - TSubModel *p; - if( !Submodel->smLetter ) { // jeśli nie ma tablicy, to ją stworzyć; miejsce nieodpowiednie, ale tymczasowo może być - Submodel->smLetter = new TSubModel *[ 256 ]; // tablica wskaźników submodeli dla wyświetlania tekstu - std::memset(Submodel->smLetter, 0, 256 * sizeof( TSubModel * )); // wypełnianie zerami - p = Submodel->Child; - while( p ) { - Submodel->smLetter[ p->pName[ 0 ] ] = p; - p = p->Next; // kolejny znak - } - } - for( i = 1; i <= j; ++i ) { - p = Submodel->smLetter[ ( *( Submodel->pasText) )[ i ] ]; // znak do wyświetlenia - if( p ) { // na razie tylko jako przezroczyste - Render_Alpha( p ); - if( p->fMatrix ) - ::glMultMatrixf( p->fMatrix->readArray() ); // przesuwanie widoku - } - } + Render_Alpha_text( Submodel ); } else if( Submodel->iAlpha & Submodel->iFlags & 0x002F0000 ) Render_Alpha( Submodel->Child ); diff --git a/rendering/openglrenderer.h b/rendering/openglrenderer.h index 820deb92..83dca483 100644 --- a/rendering/openglrenderer.h +++ b/rendering/openglrenderer.h @@ -270,6 +270,9 @@ private: Render_Alpha( TModel3d *Model, material_data const *Material, float const Squaredistance ); void Render_Alpha( TSubModel *Submodel ); + // characters of a text display + void + Render_Alpha_text( TSubModel *Submodel ); void Update_Lights( light_array &Lights ); bool diff --git a/utilities/utilities.cpp b/utilities/utilities.cpp index efaf3e6c..804ca66c 100644 --- a/utilities/utilities.cpp +++ b/utilities/utilities.cpp @@ -339,6 +339,87 @@ std::string win1250_to_utf8(const std::string &Input) return output; } +namespace +{ +// unicode code points of the upper half of the windows-1250 code page; 0 marks the unassigned bytes +std::array const win1250_upperhalf{ + 0x20AC, 0x0000, 0x201A, 0x0000, 0x201E, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0160, 0x2039, 0x015A, 0x0164, 0x017D, 0x0179, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0161, 0x203A, 0x015B, 0x0165, 0x017E, 0x017A, + 0x00A0, 0x02C7, 0x02D8, 0x0141, 0x00A4, 0x0104, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x015E, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x017B, + 0x00B0, 0x00B1, 0x02DB, 0x0142, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x0105, 0x015F, 0x00BB, 0x013D, 0x02DD, 0x013E, 0x017C, + 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E, + 0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7, 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF, + 0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F, + 0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7, 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9}; + +// returns number of bytes of the utf-8 sequence started by provided byte, or 0 if it can't start one +std::size_t utf8_sequence_length(std::byte const Lead) +{ + if ((Lead & std::byte{0x80}) == std::byte{0x00}) + { + return 1; + } + if ((Lead & std::byte{0xE0}) == std::byte{0xC0}) + { + return 2; + } + if ((Lead & std::byte{0xF0}) == std::byte{0xE0}) + { + return 3; + } + if ((Lead & std::byte{0xF8}) == std::byte{0xF0}) + { + return 4; + } + return 0; // a continuation byte, or a lead of a sequence utf-8 doesn't allow +} +} // namespace + +bool utf8_to_utf32(std::string const &Text, std::u32string &Output) +{ + // lowest code point which needs given number of bytes; shorter values are overlong forms + static std::array const minimum{0, 0, 0x80, 0x800, 0x10000}; + + Output.clear(); + auto const size{Text.size()}; + for (std::size_t idx = 0; idx < size;) + { + auto const lead{static_cast(Text[idx])}; + auto const length{utf8_sequence_length(lead)}; + if (length == 0 || idx + length > size) + { + return false; + } + auto codepoint{std::to_integer(lead & (std::byte{0xFF} >> (length > 1 ? length + 1 : 1)))}; + for (std::size_t offset = 1; offset < length; ++offset) + { + auto const next{static_cast(Text[idx + offset])}; + if ((next & std::byte{0xC0}) != std::byte{0x80}) + { + return false; + } + codepoint = (codepoint << 6) | std::to_integer(next & std::byte{0x3F}); + } + if (codepoint < minimum[length] || codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) + { + return false; + } + Output += codepoint; + idx += length; + } + return true; +} + +void win1250_to_utf32(std::string const &Text, std::u32string &Output) +{ + Output.clear(); + for (auto const character : Text) + { + auto const value{std::to_integer(static_cast(character))}; + Output += (value < 0x80 ? static_cast(value) : win1250_upperhalf[value - 0x80]); + } +} + // Ra: tymczasowe rozwiązanie kwestii zagranicznych (czeskich) napisów char charsetconversiontable[] = "E?,?\"_++?%Sstzz" " ^^L$A|S^CS<--RZo±,l'uP.,as>L\"lz" diff --git a/utilities/utilities.h b/utilities/utilities.h index 6e761380..6b8111e3 100644 --- a/utilities/utilities.h +++ b/utilities/utilities.h @@ -166,6 +166,10 @@ void win1250_to_ascii(std::string &Input); std::string Bezogonkow(std::string Input, bool const Underscorestospaces = false); std::string win1250_to_utf8(const std::string &input); +// decodes utf-8 encoded text into unicode code points. returns false if the text isn't valid utf-8 +bool utf8_to_utf32(std::string const &Text, std::u32string &Output); +// decodes windows-1250 (ansi) encoded text into unicode code points +void win1250_to_utf32(std::string const &Text, std::u32string &Output); inline std::string extract_value(std::string const &Key, std::string const &Input) { From 79bd0c8e9196a42fbb327b584ecccfaaf2676942 Mon Sep 17 00:00:00 2001 From: jerrrrycho Date: Sun, 4 Oct 2026 20:00:13 +0200 Subject: [PATCH 3/3] fix(sonar): addressing reliability/security issues (#202) --- .github/workflows/build.yml | 6 +- .github/workflows/build_bin.yml | 14 ++-- .github/workflows/sonar.yml | 37 +++++----- McZapkie/MOVER.h | 6 +- McZapkie/Mover.cpp | 34 +++++---- McZapkie/hamulce.cpp | 20 ++---- application/application.cpp | 2 +- application/drivermode.cpp | 2 +- application/scenarioloaderuilayer.cpp | 2 +- application/uitranscripts.cpp | 2 +- audio/audiorenderer.cpp | 15 ++-- launcher/deferred_image.h | 14 ++-- launcher/scenery_list.cpp | 2 +- launcher/vehicle_picker.cpp | 8 +-- launcher/vehicle_picker.h | 2 +- model/AnimModel.cpp | 2 +- model/Model3d.cpp | 13 ++-- model/Texture.cpp | 6 +- model/Texture.h | 4 +- network/message.h | 16 ++--- rendering/frustum.cpp | 30 ++------ rendering/lightarray.h | 2 +- rendering/opengl33renderer.cpp | 30 ++++++-- rendering/openglrenderer.cpp | 2 +- rendering/precipitation.cpp | 2 +- simulation/simulation.cpp | 6 +- simulation/simulationenvironment.cpp | 1 - utilities/Logs.cpp | 35 ++++----- utilities/parser.cpp | 5 +- utilities/parser.h | 2 +- vehicle/Driver.cpp | 100 +++++++++++++------------- vehicle/Driver.h | 21 +++++- vehicle/DynObj.cpp | 4 +- vehicle/Gauge.cpp | 3 +- vehicle/Train.cpp | 48 +++++++------ world/Segment.cpp | 2 +- 36 files changed, 254 insertions(+), 246 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e4a74ba..c9ce73bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,13 +23,13 @@ jobs: env: BUILD_WRAPPER_OUT_DIR: bw-output steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 with: fetch-depth: 0 submodules: recursive - name: Set up Python 3.14 - uses: actions/setup-python@v6 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: '3.14' @@ -106,7 +106,7 @@ jobs: -C "$GITHUB_WORKSPACE" . - name: Upload analysis input - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: sonar-input path: /tmp/workspace.tar.gz diff --git a/.github/workflows/build_bin.yml b/.github/workflows/build_bin.yml index f0b1fdec..ac1fa562 100644 --- a/.github/workflows/build_bin.yml +++ b/.github/workflows/build_bin.yml @@ -33,14 +33,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: fetch-depth: 0 submodules: recursive - name: Setup Python 3.14 id: python - uses: actions/setup-python@v6 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: "3.14" architecture: "x64" @@ -104,7 +104,7 @@ jobs: -Force - name: Upload Windows build - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: maszyna-windows-x64-${{ env.CONFIG_WIN }} path: | @@ -113,7 +113,7 @@ jobs: retention-days: 14 - name: Upload Windows debug symbols - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: maszyna-windows-x64-symbols-${{ env.CONFIG_WIN }} path: | @@ -133,14 +133,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: fetch-depth: 0 submodules: recursive - name: Setup Python 3.14 id: python - uses: actions/setup-python@v6 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: "3.14" @@ -244,7 +244,7 @@ jobs: cp -R shaders/. build/bin/shaders/ - name: Upload Linux build - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: maszyna-linux-x64-${{ env.CONFIG_LINUX }} path: | diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 66213c23..b2951189 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -64,15 +64,13 @@ jobs: if: github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-24.04 permissions: - contents: read # checkout trusted config + contents: read # fetch trusted config actions: read # download artifact from the Build run statuses: write # post the commit status pull-requests: read # verify PR head sha via the API env: BUILD_WRAPPER_OUT_DIR: bw-output - # .trusted/ holds the checkout of master used to restore the scanner config; - # it is not part of the analysed change. - SONAR_EXCLUSIONS: betterRenderer/**,ref/**,imgui/**,stb/**,build/**,bw-output/**,.trusted/** + SONAR_EXCLUSIONS: betterRenderer/**,ref/**,imgui/**,stb/**,build/**,bw-output/** SONAR_SCANNER_JAVA_OPTS: -Xmx5g steps: - name: Post pending status @@ -85,16 +83,23 @@ jobs: -f description="Analysis in progress" \ -f target_url="$RUN_URL" - # Trusted copy of the repo config, used to overwrite anything the PR - # might have tampered with (sonar-project.properties etc). - - name: Checkout trusted config from master - uses: actions/checkout@v5 - with: - ref: master - path: .trusted + # Trusted copy of the scanner config, used to overwrite anything the PR + # might have tampered with. Fetched as a single file through the API rather + # than with actions/checkout: nothing gets checked out in this privileged + # context. Kept outside the workspace, so the archive can't overwrite it. + - name: Fetch trusted config from master + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + url="repos/${{ github.repository }}/contents/sonar-project.properties?ref=master" + if ! gh api -H "Accept: application/vnd.github.raw" "$url" > /tmp/trusted-sonar-project.properties; then + echo "::warning::no sonar-project.properties on master" + rm -f /tmp/trusted-sonar-project.properties + fi - name: Download analysis input - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: sonar-input path: /tmp/sonar-input @@ -104,13 +109,7 @@ jobs: - name: Unpack analysis input run: | set -euo pipefail - # The archive holds the whole PR workspace and is unpacked over this one, - # .trusted/ included. Move the trusted config out of reach first, or a PR - # carrying its own .trusted/sonar-project.properties would have it planted - # here and restored below as if it came from master. - if [ -f "$GITHUB_WORKSPACE/.trusted/sonar-project.properties" ]; then - cp "$GITHUB_WORKSPACE/.trusted/sonar-project.properties" /tmp/trusted-sonar-project.properties - fi + # The archive holds the whole PR workspace and is unpacked over this one. # Nothing may be written outside the workspace: reject absolute members and # any member reaching through a parent directory before unpacking anything. tar -tzf /tmp/sonar-input/workspace.tar.gz > /tmp/archive-members.txt diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index e7bd1d23..accbbbaa 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1732,7 +1732,11 @@ class TMoverParameters // bounds-safe read access to MotorParam TMotorParameters const &motor_param(int const Position) const { - return MotorParam[std::clamp(Position, 0, MotorParametersArraySize)]; + if (Position < 0) + return MotorParam[0]; + if (Position > MotorParametersArraySize) + return MotorParam[MotorParametersArraySize]; + return MotorParam[Position]; } TTransmision Transmision; // record {liczba zebow przekladni} diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 82dd245f..ff60c27c 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -1752,6 +1752,11 @@ void TMoverParameters::LowVoltagePowerCheck(double const Deltatime) void TMoverParameters::PowerCouplersCheck(double const Deltatime, coupling const Coupling) { + if (Coupling != coupling::highvoltage && Coupling != coupling::power110v && Coupling != coupling::power24v) + { + // only power couplings carry voltage + return; + } auto localvoltage{0.0}; @@ -2403,16 +2408,14 @@ bool TMoverParameters::IncMainCtrl(int CtrlSpeed) if (CtrlSpeed > 1) { OK = IncMainCtrl(1) && IncMainCtrl(CtrlSpeed - 1); // a fail will propagate up the recursion chain. should this be || instead? + break; } - else + ++MainCtrlPos; + OK = true; + if (EIMCtrlType == 0 && ( SpeedCtrlAutoTurnOffFlag & 1 ) == 1 && MainCtrlActualPos != MainCtrlPos) { - ++MainCtrlPos; - OK = true; - if (EIMCtrlType == 0 && ( SpeedCtrlAutoTurnOffFlag & 1 ) == 1 && MainCtrlActualPos != MainCtrlPos) - { - DecScndCtrl(2); - SpeedCtrlUnit.IsActive = false; - } + DecScndCtrl(2); + SpeedCtrlUnit.IsActive = false; } break; } @@ -6392,10 +6395,7 @@ double TMoverParameters::TractionForce(double dt) // PosRatio = 1.0 * (PosRatio * 0 + 1) * PosRatio; // 1 * 1 * PosRatio = PosRatio Hamulec->SetED(0); // (Hamulec as TLSt).SetLBP(LocBrakePress); - if (PosRatio > eimv_pr) - tmp = 4; - else - tmp = 4; // szybkie malenie, powolne wzrastanie + tmp = 4; // szybkie malenie, powolne wzrastanie } dmoment = eimv[eimv_Fful]; // NOTE: the commands to operate the sandbox are likely to conflict with other similar ai decisions @@ -8022,8 +8022,6 @@ double TMoverParameters::dizel_fillcheck(int mcp, double dt) } if (enrot > nreg) // nad predkoscia regulatora zeruj dawke realfill = 0; - if (enrot < nreg) // pod predkoscia regulatora dawka zadana - realfill = realfill; if (enrot < dizel_nreg_min && RList[mcp].R > 0.001) // jesli ponizej biegu jalowego i niezerowa dawka, to dawaj pelna realfill = 1; } @@ -9118,7 +9116,7 @@ bool TMoverParameters::readMPT0(std::string const &line) } if (true == parser.getTokens(1, false)) { - int autoswitch; + int autoswitch{0}; parser >> autoswitch; MotorParam[idx].AutoSwitch = autoswitch == 1; } @@ -9174,7 +9172,7 @@ bool TMoverParameters::readMPTElectricSeries(std::string const &line) parser >> MotorParam[idx].mfi >> MotorParam[idx].mIsat >> MotorParam[idx].fi >> MotorParam[idx].Isat; if (true == parser.getTokens(1, false)) { - int autoswitch; + int autoswitch{0}; parser >> autoswitch; MotorParam[idx].AutoSwitch = autoswitch == 1; } @@ -9223,7 +9221,7 @@ bool TMoverParameters::readMPTDieselEngine(std::string const &line) parser >> MotorParam[idx].mIsat >> MotorParam[idx].fi >> MotorParam[idx].mfi; if (true == parser.getTokens(1, false)) { - int autoswitch; + int autoswitch{0}; parser >> autoswitch; MotorParam[idx].AutoSwitch = autoswitch == 1; } @@ -11048,7 +11046,7 @@ void TMoverParameters::LoadFIZ_DCEMUED(std::string const &line) void TMoverParameters::LoadFIZ_SpringBrake(std::string const &line) { - double vol; + double vol{1.0}; extract_value(vol, "Volume", line, "1"); if (!SpringBrake.Cylinder) SpringBrake.Cylinder = std::make_shared(); diff --git a/McZapkie/hamulce.cpp b/McZapkie/hamulce.cpp index 44e8be4b..64a7fb85 100644 --- a/McZapkie/hamulce.cpp +++ b/McZapkie/hamulce.cpp @@ -3516,10 +3516,7 @@ double TMHZ_K5P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) LimCP = std::min(LimCP, HP); // pozycja + czasowy lub zasilanie ActFlowSpeed = 4; - if (LimCP > CP) // podwyzszanie szybkie - CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy; - else - CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy + CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy double uop = UnbrakeOverPressure; // unbrake over pressure in actual state ManualOvrldActive = UniversalFlag & TUniversalBrake::ub_HighPressure; // button is pressed @@ -3546,10 +3543,9 @@ double TMHZ_K5P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) else dpMainValve = PFVd(PP, 0, ActFlowSpeed / LBDelay, dpPipe, 0.4); - if ((EQ(i_bcp, -1) && AutoOvrld) || (i_bcp < 0.5 && UniversalFlag & TUniversalBrake::ub_Overload)) + if (((EQ(i_bcp, -1) && AutoOvrld) || (i_bcp < 0.5 && UniversalFlag & TUniversalBrake::ub_Overload)) && TP < OverloadMaxPressure) { - if (TP < OverloadMaxPressure) - TP = TP + 0.03 * dt; + TP = TP + 0.03 * dt; } if (EQ(i_bcp, 3)) @@ -3699,10 +3695,7 @@ double TMHZ_6P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) LimCP = std::min(LimCP, HP); // pozycja + czasowy lub zasilanie ActFlowSpeed = 4; - if (LimCP > CP) // podwyzszanie szybkie - CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy; - else - CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy + CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy dpPipe = std::min(HP, CP + TP + RedAdj); @@ -3729,10 +3722,9 @@ double TMHZ_6P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) else dpMainValve = PFVd(PP, 0, ActFlowSpeed / LBDelay, dpPipe, 0.4); - if ((EQ(i_bcp, -1) && AutoOvrld) || (i_bcp < 0.5 && UniversalFlag & TUniversalBrake::ub_Overload)) + if (((EQ(i_bcp, -1) && AutoOvrld) || (i_bcp < 0.5 && UniversalFlag & TUniversalBrake::ub_Overload)) && TP < OverloadMaxPressure) { - if (TP < OverloadMaxPressure) - TP = TP + 0.03 * dt; + TP = TP + 0.03 * dt; } if (EQ(i_bcp, 4)) diff --git a/application/application.cpp b/application/application.cpp index ceb5ec6c..81e96c3d 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -498,7 +498,7 @@ int eu07_application::run() { command_queue::commands_map commands_to_exec; command_queue::commands_map local_commands = simulation::Commands.pop_intercept_queue(); - double slave_sync; + double slave_sync{0.0}; // if we're the server if (m_network && m_network->servers) diff --git a/application/drivermode.cpp b/application/drivermode.cpp index 80fd3b17..b5b78634 100644 --- a/application/drivermode.cpp +++ b/application/drivermode.cpp @@ -667,7 +667,7 @@ void driver_mode::update_camera(double const Deltatime) // reset window state, it'll be set again if applicable in a check below Global.CabWindowOpen = false; - if (simulation::Train != nullptr && Camera.m_owner != nullptr && false == DebugCameraFlag) + if (simulation::Train != nullptr && controlled != nullptr && Camera.m_owner != nullptr && false == DebugCameraFlag) { // jeśli jazda w kabinie, przeliczyć trzeba parametry kamery /* diff --git a/application/scenarioloaderuilayer.cpp b/application/scenarioloaderuilayer.cpp index c4e7df3d..de0d4e64 100644 --- a/application/scenarioloaderuilayer.cpp +++ b/application/scenarioloaderuilayer.cpp @@ -213,7 +213,7 @@ void scenarioloader_ui::render_() const float spacing = 10.0f * scale; // odstęp między ikoną a tekstem // Loading icon - const deferred_image *img = &m_loading_wheel_frames[38]; + deferred_image *img = &m_loading_wheel_frames[38]; const auto loading_tex = img->get(); const auto loading_size = glm::vec2(img->size()) * glm::vec2(scale); diff --git a/application/uitranscripts.cpp b/application/uitranscripts.cpp index 96c9f735..c9780e10 100644 --- a/application/uitranscripts.cpp +++ b/application/uitranscripts.cpp @@ -42,7 +42,7 @@ TTranscripts::Add( std::string const &txt, bool backgorund ) { cParser parser( asciitext ); while( true == parser.getTokens( 3, false, "[]\n" ) ) { - float begin, end; + float begin{ 0.f }, end{ 0.f }; std::string transcript; parser >> begin diff --git a/audio/audiorenderer.cpp b/audio/audiorenderer.cpp index 81f81570..50a6f727 100644 --- a/audio/audiorenderer.cpp +++ b/audio/audiorenderer.cpp @@ -563,15 +563,12 @@ openal_renderer::init_caps() { if( ::alcIsExtensionPresent( nullptr, "ALC_ENUMERATION_EXT" ) == AL_TRUE ) { // enumeration supported WriteLog( "available audio devices:" ); - auto const *devices { ::alcGetString( nullptr, ALC_DEVICE_SPECIFIER ) }; - auto const - *device { devices }, - *next { devices + 1 }; - while( device && *device != '\0' && next && *next != '\0' ) { - WriteLog( { device } ); - auto const len { std::strlen( device ) }; - device += len + 1; - next += len + 2; + // device list is a sequence of null-terminated names, ending with an empty name + auto const *device { ::alcGetString( nullptr, ALC_DEVICE_SPECIFIER ) }; + while( device != nullptr && *device != '\0' ) { + std::string const devicename { device }; + WriteLog( devicename ); + device += devicename.size() + 1; } } diff --git a/launcher/deferred_image.h b/launcher/deferred_image.h index fcd25fd6..85582264 100644 --- a/launcher/deferred_image.h +++ b/launcher/deferred_image.h @@ -1,10 +1,15 @@ #pragma once +#include + #include "model/Texture.h" #include "rendering/renderer.h" class deferred_image { public: + // value returned by get() when the image isn't (yet) available + static constexpr GLuint invalid_id { std::numeric_limits::max() }; + deferred_image() = default; deferred_image(const std::string &p) : path(p) { } deferred_image(const deferred_image&) = delete; @@ -15,7 +20,8 @@ public: return image != null_handle || !path.empty(); } - GLuint get() const + // loads the texture on first use + GLuint get() { if (!path.empty()) { image = GfxRenderer->Fetch_Texture(path, true); @@ -30,7 +36,7 @@ public: return tex.get_id(); } - return -1; + return invalid_id; } glm::ivec2 size() const @@ -43,6 +49,6 @@ public: } private: - mutable std::string path; - mutable texture_handle image = 0; + std::string path; + texture_handle image = 0; }; diff --git a/launcher/scenery_list.cpp b/launcher/scenery_list.cpp index 77f69a35..518669e5 100644 --- a/launcher/scenery_list.cpp +++ b/launcher/scenery_list.cpp @@ -281,7 +281,7 @@ void ui::scenerylist_panel::draw_trainset(trainset_desc &trainset) for (auto &dyn_desc : trainset.vehicles) { deferred_image *mini = nullptr; - if (dyn_desc.skin && dyn_desc.skin->mini.get() != (GLuint)-1) + if (dyn_desc.skin && dyn_desc.skin->mini.get() != deferred_image::invalid_id) mini = &dyn_desc.skin->mini; else mini = &placeholder_mini; diff --git a/launcher/vehicle_picker.cpp b/launcher/vehicle_picker.cpp index f7d3503f..9fe0a29a 100644 --- a/launcher/vehicle_picker.cpp +++ b/launcher/vehicle_picker.cpp @@ -37,7 +37,7 @@ void ui::vehiclepicker_panel::render_contents() if (ImGui::RadioButton(STR_C("texture group"), display_by_groups)) display_by_groups = true; - std::vector skinset_list; + std::vector skinset_list; if (display_by_groups) { std::vector model_list; @@ -114,7 +114,7 @@ void ui::vehiclepicker_panel::render_contents() for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { auto &desc = model_list[i]; - const deferred_image *image = nullptr; + deferred_image *image = nullptr; for (auto const &skinset : desc->matching_skinsets) { if (skinset->mini) { image = &skinset->mini; @@ -256,7 +256,7 @@ bool ui::vehiclepicker_panel::skin_filter(const skin_set *skin, std::vectorget(); - if (tex != (GLuint)-1) { + if (tex != deferred_image::invalid_id) { glm::ivec2 size = image->size(); float width = 30.0f / size.y * size.x; ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); diff --git a/launcher/vehicle_picker.h b/launcher/vehicle_picker.h index ad6e6dc2..ffdfb2a4 100644 --- a/launcher/vehicle_picker.h +++ b/launcher/vehicle_picker.h @@ -14,7 +14,7 @@ class vehiclepicker_panel : public ui_panel void render_contents() override; private: - bool selectable_image(const char *desc, bool selected, const deferred_image *image, const skin_set *pickable = nullptr); + bool selectable_image(const char *desc, bool selected, deferred_image *image, const skin_set *pickable = nullptr); vehicle_type selected_type = vehicle_type::none; std::shared_ptr selected_vehicle; diff --git a/model/AnimModel.cpp b/model/AnimModel.cpp index cc54a2a2..1d808676 100644 --- a/model/AnimModel.cpp +++ b/model/AnimModel.cpp @@ -598,11 +598,11 @@ void TAnimModel::on_season_update() { void TAnimModel::RaPrepare() { // ustawia światła i animacje we wzorcu modelu przed renderowaniem egzemplarza - bool state; // stan światła if (Global.UpdateMaterials) on_season_update(); for (int i = 0; i < iNumLights; ++i) { + bool state{ false }; // stan światła auto const lightmode { static_cast( std::abs( lsLights[ i ] ) ) }; switch( lightmode ) { case ls_On: diff --git a/model/Model3d.cpp b/model/Model3d.cpp index e1a3a79e..b67663f9 100644 --- a/model/Model3d.cpp +++ b/model/Model3d.cpp @@ -1529,7 +1529,9 @@ void TSubModel::ParentMatrix(float4x4 *m) const void TSubModel::ReplaceMatrix(const glm::mat4 &mat) { - *fMatrix = float4x4(glm::value_ptr(mat)); + for (int column = 0; column < 4; ++column) + for (int row = 0; row < 4; ++row) + fMatrix->e[column * 4 + row] = mat[column][row]; } void TSubModel::ReplaceMaterial(const std::string &name) @@ -1618,13 +1620,8 @@ TSubModel *TModel3d::GetFromName(std::string const &Name) const { // wyszukanie submodelu po nazwie if (Name.empty()) return Root; // potrzebne do terenu z E3D - if (iFlags & 0x0200) // wczytany z pliku tekstowego, wyszukiwanie rekurencyjne - return Root ? Root->GetFromName(Name) : nullptr; - else // wczytano z pliku binarnego, można wyszukać iteracyjnie - { - // for (int i=0;iGetFromName(Name) : nullptr; - } + // both text and binary models are searched recursively + return Root ? Root->GetFromName(Name) : nullptr; }; // locates particle source submodels and stores them on internal list diff --git a/model/Texture.cpp b/model/Texture.cpp index 5c1d852f..a4b9def4 100644 --- a/model/Texture.cpp +++ b/model/Texture.cpp @@ -394,12 +394,12 @@ opengl_texture::make_from_memory(size_t width, size_t height, const uint8_t *raw void opengl_texture::update_from_memory(size_t width, size_t height, const uint8_t *raw) { - if (id != (GLuint)-1 && (width != data_width || height != data_height || GL_SRGB8_ALPHA8 != data_format || GL_RGBA != data_components)) + if (id != invalid_id && (width != data_width || height != data_height || GL_SRGB8_ALPHA8 != data_format || GL_RGBA != data_components)) { glDeleteTextures(1, &id); - id = (GLuint)-1; + id = invalid_id; } - if (id == (GLuint)-1) + if (id == invalid_id) { data_width = width; data_height = height; diff --git a/model/Texture.h b/model/Texture.h index 1f582640..7b285ee3 100644 --- a/model/Texture.h +++ b/model/Texture.h @@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/. #pragma once #include +#include #include "winheaders.h" #include #include "model/ResourceManager.h" @@ -84,7 +85,8 @@ struct opengl_texture : public ITexture { virtual void update_from_memory(size_t width, size_t height, const uint8_t *data) override; // members - GLuint id{ (GLuint)-1 }; // associated GL resource + static constexpr GLuint invalid_id { std::numeric_limits::max() }; // id value of texture without GL resource + GLuint id{ invalid_id }; // associated GL resource bool has_alpha{ false }; // indicates the texture has alpha channel bool is_ready{ false }; // indicates the texture was processed and is ready for use std::string traits; // requested texture attributes: wrapping modes etc diff --git a/network/message.h b/network/message.h index 871e833f..5aa07d78 100644 --- a/network/message.h +++ b/network/message.h @@ -30,17 +30,17 @@ struct client_hello : public message virtual void serialize(std::ostream &stream) const override; virtual void deserialize(std::istream &stream) override; - int32_t version{}; - uint32_t start_packet{}; + int32_t version{0}; + uint32_t start_packet{0}; }; struct server_hello : public message { server_hello() : message(SERVER_HELLO) {} - uint32_t seed{}; - int64_t timestamp{}; - int64_t config{}; + uint32_t seed{0}; + int64_t timestamp{0}; + int64_t config{0}; std::string scenario; virtual void serialize(std::ostream &stream) const override; @@ -62,9 +62,9 @@ struct frame_info : public request_command { frame_info() : request_command(FRAME_INFO) {} - double render_dt{}; - double dt{}; - double sync{}; + double render_dt{0.0}; + double dt{0.0}; + double sync{0.0}; virtual void serialize(std::ostream &stream) const override; virtual void deserialize(std::istream &stream) override; diff --git a/rendering/frustum.cpp b/rendering/frustum.cpp index afe16441..f4677065 100644 --- a/rendering/frustum.cpp +++ b/rendering/frustum.cpp @@ -22,30 +22,14 @@ cFrustum::calculate() { void cFrustum::calculate( glm::mat4 const &Projection, glm::mat4 const &Modelview ) { - float const *proj = glm::value_ptr( Projection ); - float const *modl = glm::value_ptr( Modelview ); + // multiply the matrices to retrieve clipping planes (column-major, same layout as the old float[16] form) + auto const clipmatrix { Projection * Modelview }; float clip[ 16 ]; - - // multiply the matrices to retrieve clipping planes - clip[ 0 ] = modl[ 0 ] * proj[ 0 ] + modl[ 1 ] * proj[ 4 ] + modl[ 2 ] * proj[ 8 ] + modl[ 3 ] * proj[ 12 ]; - clip[ 1 ] = modl[ 0 ] * proj[ 1 ] + modl[ 1 ] * proj[ 5 ] + modl[ 2 ] * proj[ 9 ] + modl[ 3 ] * proj[ 13 ]; - clip[ 2 ] = modl[ 0 ] * proj[ 2 ] + modl[ 1 ] * proj[ 6 ] + modl[ 2 ] * proj[ 10 ] + modl[ 3 ] * proj[ 14 ]; - clip[ 3 ] = modl[ 0 ] * proj[ 3 ] + modl[ 1 ] * proj[ 7 ] + modl[ 2 ] * proj[ 11 ] + modl[ 3 ] * proj[ 15 ]; - - clip[ 4 ] = modl[ 4 ] * proj[ 0 ] + modl[ 5 ] * proj[ 4 ] + modl[ 6 ] * proj[ 8 ] + modl[ 7 ] * proj[ 12 ]; - clip[ 5 ] = modl[ 4 ] * proj[ 1 ] + modl[ 5 ] * proj[ 5 ] + modl[ 6 ] * proj[ 9 ] + modl[ 7 ] * proj[ 13 ]; - clip[ 6 ] = modl[ 4 ] * proj[ 2 ] + modl[ 5 ] * proj[ 6 ] + modl[ 6 ] * proj[ 10 ] + modl[ 7 ] * proj[ 14 ]; - clip[ 7 ] = modl[ 4 ] * proj[ 3 ] + modl[ 5 ] * proj[ 7 ] + modl[ 6 ] * proj[ 11 ] + modl[ 7 ] * proj[ 15 ]; - - clip[ 8 ] = modl[ 8 ] * proj[ 0 ] + modl[ 9 ] * proj[ 4 ] + modl[ 10 ] * proj[ 8 ] + modl[ 11 ] * proj[ 12 ]; - clip[ 9 ] = modl[ 8 ] * proj[ 1 ] + modl[ 9 ] * proj[ 5 ] + modl[ 10 ] * proj[ 9 ] + modl[ 11 ] * proj[ 13 ]; - clip[ 10 ] = modl[ 8 ] * proj[ 2 ] + modl[ 9 ] * proj[ 6 ] + modl[ 10 ] * proj[ 10 ] + modl[ 11 ] * proj[ 14 ]; - clip[ 11 ] = modl[ 8 ] * proj[ 3 ] + modl[ 9 ] * proj[ 7 ] + modl[ 10 ] * proj[ 11 ] + modl[ 11 ] * proj[ 15 ]; - - clip[ 12 ] = modl[ 12 ] * proj[ 0 ] + modl[ 13 ] * proj[ 4 ] + modl[ 14 ] * proj[ 8 ] + modl[ 15 ] * proj[ 12 ]; - clip[ 13 ] = modl[ 12 ] * proj[ 1 ] + modl[ 13 ] * proj[ 5 ] + modl[ 14 ] * proj[ 9 ] + modl[ 15 ] * proj[ 13 ]; - clip[ 14 ] = modl[ 12 ] * proj[ 2 ] + modl[ 13 ] * proj[ 6 ] + modl[ 14 ] * proj[ 10 ] + modl[ 15 ] * proj[ 14 ]; - clip[ 15 ] = modl[ 12 ] * proj[ 3 ] + modl[ 13 ] * proj[ 7 ] + modl[ 14 ] * proj[ 11 ] + modl[ 15 ] * proj[ 15 ]; + for( int column = 0; column < 4; ++column ) { + for( int row = 0; row < 4; ++row ) { + clip[ column * 4 + row ] = clipmatrix[ column ][ row ]; + } + } // get the sides of the frustum. m_frustum[ side_RIGHT ][ plane_A ] = clip[ 3 ] - clip[ 0 ]; diff --git a/rendering/lightarray.h b/rendering/lightarray.h index 670cbea8..d7459fe9 100644 --- a/rendering/lightarray.h +++ b/rendering/lightarray.h @@ -18,7 +18,7 @@ public: int index{ -1 }; // 0: front lights, 1: rear lights glm::dvec3 position; // position of the light in 3d scene glm::vec3 direction; // direction of the light in 3d scene - glm::vec3 color{ 255.0f / 255.0f, 241.0f / 255.0f, 224.0f / 255.0f }; // color of the light, default is halogen light + glm::vec3 color{ 1.0f, 241.0f / 255.0f, 224.0f / 255.0f }; // color of the light, default is halogen light float intensity{ 0.0f }; // (combined) intensity of the light(s) int count{ 0 }; // number (or pattern) of active light(s) glm::vec3 state{ 0.f }; // state of individual lights diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index 2f587033..fcd90aba 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -28,6 +28,23 @@ http://mozilla.org/MPL/2.0/. int constexpr EU07_PICKBUFFERSIZE{ 1024 }; // size of (square) textures bound with the pick framebuffer int constexpr EU07_REFLECTIONFIDELITYOFFSET { 250 }; // artificial increase of range for reflection pass detail reduction +namespace { + +// returns material assigned to the submodel, resolving replacable skins. null_handle if no skin set is bound +material_handle +submodel_material( TSubModel const *Submodel ) { + + if( Submodel->m_material >= 0 ) { + return Submodel->m_material; + } + return ( + TSubModel::ReplacableSkinId != nullptr ? + TSubModel::ReplacableSkinId[ -Submodel->m_material ] : + null_handle ); +} + +} // namespace + void GLAPIENTRY ErrorCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam ) { /* @@ -1935,7 +1952,7 @@ bool opengl33_renderer::Render(world_environment *Environment) auto const fogfactor{std::clamp(Global.fFogEnd / 2000.f, 0.f, 1.f)}; // stronger fog reduces opacity of the celestial bodies float const duskfactor = 1.0f - std::clamp(std::abs(Environment->m_sun.getAngle()), 0.0f, 12.0f) / 12.0f; - glm::vec3 suncolor = glm::mix(glm::vec3(255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f), glm::vec3(235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f), duskfactor); + glm::vec3 suncolor = glm::mix(glm::vec3(1.0f, 242.0f / 255.0f, 231.0f / 255.0f), glm::vec3(235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f), duskfactor); // sun { @@ -2341,8 +2358,7 @@ opengl_material &opengl33_renderer::Material(material_handle const Material) opengl_material const & opengl33_renderer::Material( TSubModel const * Submodel ) const { - auto const material { Submodel->m_material >= 0 ? Submodel->m_material : Submodel->ReplacableSkinId[ -Submodel->m_material ] }; - return m_materials.material( material ); + return m_materials.material( submodel_material( Submodel ) ); } texture_handle opengl33_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint) @@ -3767,7 +3783,7 @@ void opengl33_renderer::Render(TSubModel *Submodel) // textures... if (Submodel->m_material < 0) { // zmienialne skóry - Bind_Material(Submodel->ReplacableSkinId[-Submodel->m_material], Submodel); + Bind_Material(submodel_material(Submodel), Submodel); } else { @@ -3804,7 +3820,7 @@ void opengl33_renderer::Render(TSubModel *Submodel) if (Submodel->m_material < 0) { // zmienialne skóry - Bind_Material_Shadow(Submodel->ReplacableSkinId[-Submodel->m_material]); + Bind_Material_Shadow(submodel_material(Submodel)); } else { @@ -4689,7 +4705,7 @@ void opengl33_renderer::Render_Alpha(TSubModel *Submodel) // textures... if (Submodel->m_material < 0) { // zmienialne skóry - Bind_Material(Submodel->ReplacableSkinId[-Submodel->m_material], Submodel); + Bind_Material(submodel_material(Submodel), Submodel); } else { @@ -4721,7 +4737,7 @@ void opengl33_renderer::Render_Alpha(TSubModel *Submodel) if (Submodel->m_material < 0) { // zmienialne skóry - Bind_Material_Shadow(Submodel->ReplacableSkinId[-Submodel->m_material]); + Bind_Material_Shadow(submodel_material(Submodel)); } else { diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index b675c38f..27c94da8 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -1588,7 +1588,7 @@ opengl_renderer::Render( world_environment *Environment ) { auto const fogfactor { std::clamp( Global.fFogEnd / 2000.f, 0.f, 1.f ) }; // closer/denser fog reduces opacity of the celestial bodies float const duskfactor = 1.0f - std::clamp( std::abs( Environment->m_sun.getAngle() ), 0.0f, 12.0f ) / 12.0f; glm::vec3 suncolor = glm::mix( - glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ), + glm::vec3( 1.0f, 242.0f / 255.0f, 231.0f / 255.0f ), glm::vec3( 235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f ), duskfactor ); // sun diff --git a/rendering/precipitation.cpp b/rendering/precipitation.cpp index 3f9ecaec..02baaada 100644 --- a/rendering/precipitation.cpp +++ b/rendering/precipitation.cpp @@ -31,7 +31,7 @@ basic_precipitation::init() { void basic_precipitation::update() { - auto const timedelta { static_cast( ( DebugModeFlag ? Timer::GetDeltaTime() : Timer::GetDeltaTime() ) ) }; + auto const timedelta { static_cast( Timer::GetDeltaTime() ) }; if( timedelta == 0.0 ) { return; } diff --git a/simulation/simulation.cpp b/simulation/simulation.cpp index a1493f63..36edc4c7 100644 --- a/simulation/simulation.cpp +++ b/simulation/simulation.cpp @@ -137,6 +137,11 @@ state_manager::update_scripting_interface() { auto *time{ Memory.find( "__simulation.time" ) }; auto *date{ Memory.find( "__simulation.date" ) }; + if( weather == nullptr || time == nullptr || date == nullptr ) { + // scripting interface cells weren't created, nothing to synchronize + return; + } + if( simulation::is_ready ) { // potentially adjust weather if( weather->Value1() != m_scriptinginterface.weather->Value1() ) { @@ -396,7 +401,6 @@ void state_manager::process_commands() { pantograph.fLenL1 * std::sin( pantograph.fAngleL ) + pantograph.fLenU1 * std::sin( pantograph.fAngleU ) + pantograph.fHeight; - vehicle->MoverParameters->EnginePowerSource.CollectorParameters.CollectorsNo; } vehicle = vehicle->Prev(); diff --git a/simulation/simulationenvironment.cpp b/simulation/simulationenvironment.cpp index b96cea20..6c4061c4 100644 --- a/simulation/simulationenvironment.cpp +++ b/simulation/simulationenvironment.cpp @@ -78,7 +78,6 @@ world_environment::compute_weather() { Global.Overcast <= 0.70 ? 6 : Global.Overcast <= 0.80 ? 7 : Global.Overcast <= 0.90 ? 8 : - Global.Overcast > 0.90 ? 9 : 9; } diff --git a/utilities/Logs.cpp b/utilities/Logs.cpp index 737d25d7..5b4d1d9a 100644 --- a/utilities/Logs.cpp +++ b/utilities/Logs.cpp @@ -80,29 +80,20 @@ std::mutex logMutex; void LogService() { - // prevent crash if mutex is not initialized - while (true) - { - try - { - logMutex.lock(); - break; - } - catch (...) {} - } - logMutex.unlock(); - while (!Global.applicationQuitOrder) { { // --- Obsługa InfoStack --- while (!InfoStack.empty()) { - logMutex.lock(); - std::string msg = InfoStack.front().first; - bool isError = InfoStack.front().second; - InfoStack.pop_front(); - logMutex.unlock(); + std::string msg; + bool isError; + { + std::scoped_lock lock(logMutex); + msg = InfoStack.front().first; + isError = InfoStack.front().second; + InfoStack.pop_front(); + } // log to file if (Global.iWriteLogEnabled & 1) @@ -134,10 +125,12 @@ void LogService() // --- Obsługa ErrorStack --- while (!ErrorStack.empty()) { - logMutex.lock(); - std::string msg = ErrorStack.front(); - ErrorStack.pop_front(); - logMutex.unlock(); + std::string msg; + { + std::scoped_lock lock(logMutex); + msg = ErrorStack.front(); + ErrorStack.pop_front(); + } if (!(Global.iWriteLogEnabled & 1)) continue; diff --git a/utilities/parser.cpp b/utilities/parser.cpp index 7209df01..77f91ee3 100644 --- a/utilities/parser.cpp +++ b/utilities/parser.cpp @@ -272,10 +272,9 @@ void cParser::stripFirstTokenBOM(std::string& token, bool ToLower, const char* B } // if first "token" was standalone BOM, read the next real token (avoid recursion) - while (token.empty() && mStream->peek() != EOF) { - readToken(token, ToLower, Break); + if (token.empty() && mStream->peek() != EOF) { // readToken will not re-enter BOM stripping because mFirstToken is now false - break; + readToken(token, ToLower, Break); } } diff --git a/utilities/parser.h b/utilities/parser.h index 0cf51d8c..131efd8b 100644 --- a/utilities/parser.h +++ b/utilities/parser.h @@ -39,7 +39,7 @@ class cParser //: public std::stringstream Output_ getToken( bool const ToLower = true, char const *Break = "\n\r\t ;" ) { getTokens( 1, ToLower, Break ); - Output_ output; + Output_ output{}; *this >> output; return output; }; inline diff --git a/vehicle/Driver.cpp b/vehicle/Driver.cpp index 9f1b2586..4437950b 100644 --- a/vehicle/Driver.cpp +++ b/vehicle/Driver.cpp @@ -2231,7 +2231,7 @@ void TController::AutoRewident() BrakingInitialLevel = IsHeavyCargoTrain ? 1.25 : IsCargoTrain ? 1.25 : 1.00; - BrakingLevelIncrease = IsHeavyCargoTrain ? 0.25 : IsCargoTrain ? 0.25 : 0.25; + BrakingLevelIncrease = 0.25; if( is_emu() ) { auto ep_factor { ( BrakeSystem == TBrakeSystem::ElectroPneumatic ? 8 : 4 ) }; @@ -2320,7 +2320,8 @@ bool TController::CheckVehicles(TOrders user) p = pVehicle->FirstFind(d); // pojazd na czele składu if (!p) p = pVehicle; - pVehicles[end::front] = p; + auto *const frontvehicle { p }; // stays the consist head for the rest of this check + pVehicles[end::front] = frontvehicle; // liczenie pojazdów w składzie i ustalenie parametrów auto dir = d = 1 - d; // a dalej będziemy zliczać od czoła do tyłu fLength = 0.0; // długość składu do badania wyjechania za ograniczenie @@ -2369,7 +2370,7 @@ bool TController::CheckVehicles(TOrders user) if (iDrivigFlags & movePrimary) { // jeśli jest aktywnie prowadzącym pojazd, może zrobić własny porządek auto pantmask = 1; - p = pVehicles[end::front]; + p = frontvehicle; // establish ownership and vehicle order while (p) { @@ -2384,7 +2385,7 @@ bool TController::CheckVehicles(TOrders user) p = p->Next(); // pojazd podłączony od tyłu (licząc od czoła) } // with the order established the virtual train manager can do their work - p = pVehicles[ end::front ]; + p = frontvehicle; ControlledEnginesCount = p->MoverParameters->Power > 1.0 ? 1 : 0; auto hasheaters { false }; while (p) @@ -2457,7 +2458,7 @@ bool TController::CheckVehicles(TOrders user) } // detect push-pull train configurations and mark them accordingly - if( pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::control ) ) { + if( frontvehicle->is_connected( pVehicles[ end::rear ], coupling::control ) ) { // zmiana czoła przez zmianę kabiny iDrivigFlags |= movePushPull; } @@ -2482,7 +2483,7 @@ bool TController::CheckVehicles(TOrders user) } else { // jak dociska - pVehicles[end::front]->RaLightsSet(0, -1); + frontvehicle->RaLightsSet(0, -1); } } // enable door locks @@ -2493,7 +2494,7 @@ bool TController::CheckVehicles(TOrders user) // TODO: replace connection test with connection check between last engine and first car, specifically auto const isheatingcouplingactive { ( ControlledEnginesCount == 1 ? - pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::heating ) : + frontvehicle->is_connected( pVehicles[ end::rear ], coupling::heating ) : true ) }; auto const isheatingneeded { (is_emu() || is_dmu() ? true : @@ -4042,7 +4043,7 @@ void TController::SetTimeControllers() } } - if( std::abs( DizelPercentage_Speed - DizelActualPercentage ) > ( DizelPercentage > 1 ? 0 : 0 ) ) { + if( std::abs( DizelPercentage_Speed - DizelActualPercentage ) > 0 ) { if( PosDec > 0 && ( DizelActualPercentage - DizelPercentage_Speed > 50 @@ -4544,7 +4545,7 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N if (NewValue1 > 0.0 ? NewValue1 > fStopTime : false) fStopTime = NewValue1; // Ra: włączenie czekania bez zmiany komendy else - OrderList[OrderPos] = Wait_for_orders; // czekanie na komendę (albo dać OrderPos=0) + OrderCurrentSet( Wait_for_orders ); // czekanie na komendę (albo dać OrderPos=0) return true; } @@ -4986,13 +4987,13 @@ TController::PrepareDirection() { void TController::JumpToNextOrder( bool const Ignoremergedchangedirection ) { // wykonanie kolejnej komendy z tablicy rozkazów - if (OrderList[OrderPos] != Wait_for_orders) + if (auto const currentorder { OrderCurrentGet() }; currentorder != Wait_for_orders) { - if( (OrderList[OrderPos] & Change_direction) != 0 // jeśli zmiana kierunku - && OrderList[OrderPos] != Change_direction && false == Ignoremergedchangedirection ) { // ale nałożona na coś + if( (currentorder & Change_direction) != 0 // jeśli zmiana kierunku + && currentorder != Change_direction && false == Ignoremergedchangedirection ) { // ale nałożona na coś // usunięcie zmiany kierunku z innej komendy - OrderList[OrderPos] = TOrders(OrderList[OrderPos] & ~Change_direction); + OrderCurrentSet( TOrders(currentorder & ~Change_direction) ); OrderCheck(); return; } @@ -5032,37 +5033,43 @@ void TController::OrderCheck() // HACK: ensure consist doors will be closed on departure iDrivigFlags |= moveDoorOpened; } - if (OrderList[OrderPos] & Change_direction) // może być nałożona na inną i wtedy ma priorytet + auto const currentorder { OrderCurrentGet() }; + if (currentorder & Change_direction) // może być nałożona na inną i wtedy ma priorytet iDirectionOrder = -iDirection; // trzeba zmienić jawnie, bo się nie domyśli - else if (OrderList[OrderPos] == Obey_train) + else if (currentorder == Obey_train) iDrivigFlags |= moveStopPoint; // W4 są widziane - else if (OrderList[OrderPos] == Disconnect) + else if (currentorder == Disconnect) iVehicleCount = iVehicleCount < 0 ? 0 : iVehicleCount; // odczepianie lokomotywy - else if (OrderList[OrderPos] == Connect) + else if (currentorder == Connect) iDrivigFlags &= ~moveStopPoint; // podczas jazdy na połączenie nie zwracać uwagi na W4 - else if (OrderList[OrderPos] == Wait_for_orders) + else if (currentorder == Wait_for_orders) OrdersClear(); // czyszczenie rozkazów i przeskok do zerowej pozycji } void TController::OrderNext(TOrders NewOrder) { // ustawienie rozkazu do wykonania jako następny - if (OrderList[OrderPos] == NewOrder) + if (OrderCurrentGet() == NewOrder) return; // jeśli robi to, co trzeba, to koniec if (!OrderPos) OrderPos = 1; // na pozycji zerowej pozostaje czekanie OrderTop = OrderPos; // ale może jest czymś zajęty na razie if (NewOrder >= Shunt) // jeśli ma jechać { // ale może być zajęty chwilowymi operacjami - while (OrderList[OrderTop] != Wait_for_orders && OrderList[OrderTop] < Shunt) // jeśli coś robi + while (OrderTop >= 0 && OrderTop < maxorders - 1 + && OrderList[OrderTop] != Wait_for_orders && OrderList[OrderTop] < Shunt) // jeśli coś robi ++OrderTop; // pomijamy wszystkie tymczasowe prace } else { // jeśli ma ustawioną jazdę, to wyłączamy na rzecz operacji - while (OrderList[OrderTop] ? - OrderList[OrderTop] < Shunt && OrderList[OrderTop] != NewOrder : - false) // jeśli coś robi + while (OrderTop >= 0 && OrderTop < maxorders - 1 + && OrderList[OrderTop] != Wait_for_orders && OrderList[OrderTop] < Shunt && OrderList[OrderTop] != NewOrder) // jeśli coś robi ++OrderTop; // pomijamy wszystkie tymczasowe prace } + if (OrderTop < 0 || OrderTop >= maxorders) + { + ErrorLog("Commands overflow: order \"" + Order2Str(NewOrder) + "\" dropped"); + return; + } OrderList[OrderTop++] = NewOrder; // dodanie rozkazu jako następnego #if LOGORDERS OrdersDump( "OrderNext" ); // normalnie nie ma po co tego wypisywać @@ -5071,15 +5078,17 @@ void TController::OrderNext(TOrders NewOrder) void TController::OrderPush(TOrders NewOrder) { // zapisanie na stosie kolejnego rozkazu do wykonania - if (OrderPos == OrderTop && OrderList[OrderPos] < Shunt) // jeśli miałby być zapis na aktalnej pozycji + if (OrderPos == OrderTop && OrderCurrentGet() < Shunt) // jeśli miałby być zapis na aktalnej pozycji // ale nie jedzie ++OrderTop; // niektóre operacje muszą zostać najpierw dokończone => zapis na kolejnej + if (OrderTop < 0 || OrderTop >= maxorders) + { + ErrorLog("Commands overflow: order \"" + Order2Str(NewOrder) + "\" dropped"); + return; + } if (OrderList[OrderTop] != NewOrder) // jeśli jest to samo, to nie dodajemy OrderList[OrderTop++] = NewOrder; // dodanie rozkazu na stos - // if (OrderTop= maxorders) - ErrorLog("Commands overflow: The program will now crash"); #if LOGORDERS OrdersDump( "OrderPush: [" + Order2Str( NewOrder ) + "]" ); // normalnie nie ma po co tego wypisywać #endif @@ -5395,8 +5404,7 @@ TCommandType TController::BackwardScan( double const Range ) // najpierw sprawdzamy, czy semafor czy inny znak został przejechany auto const sl{e->input_location()}; // położenie komórki pamięci auto const pos{pVehicles[end::rear]->RearPosition()}; // pozycja tyłu - auto const sem{sl - pos}; // wektor do komórki pamięci od końca składu - if (dir.x * sem.x + dir.z * sem.z < 0) + if (auto const sem{sl - pos}; dir.x * sem.x + dir.z * sem.z < 0) // wektor do komórki pamięci od końca składu { // jeśli został minięty // iloczyn skalarny jest ujemny, gdy sygnał stoi z tyłu @@ -5406,13 +5414,10 @@ TCommandType TController::BackwardScan( double const Range ) return TCommandType::cm_Unknown; // nic } scanvel = e->input_value(1); // prędkość przy tym semaforze +#if LOGBACKSCAN // przeliczamy odległość od semafora - potrzebne by były współrzędne początku składu - scandist = glm::length(sem) - 2; // 2m luzu przy manewrach wystarczy - if (scandist < 0) - { - // ujemnych nie ma po co wysyłać - scandist = 0; - } + scandist = std::max(glm::length(sl - pos) - 2.0, 0.0); // 2m luzu przy manewrach wystarczy, ujemnych nie ma po co wysyłać +#endif } auto move{false}; // czy AI w trybie manewerowym ma dociągnąć pod S1 @@ -5425,32 +5430,23 @@ TCommandType TController::BackwardScan( double const Range ) } else { + // jeśli semafor jest daleko, a pojazd jedzie, to informujemy o zmianie prędkości + // jeśli jedzie manewrowo, musi dostać SetVelocity, żeby sie na pociągowy przełączył + // w przeciwnym razie ustawiamy prędkość tylko wtedy, gdy ma ruszyć, stanąć albo ma stać + // ruszać stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam +#if LOGBACKSCAN if (scandist > fMinProximityDist && mvOccupied->Vel > EU07_AI_NOMOVEMENT && (OrderCurrentGet() & (Shunt | Loose_shunt)) == 0) { - // jeśli semafor jest daleko, a pojazd jedzie, to informujemy o zmianie prędkości - // jeśli jedzie manewrowo, musi dostać SetVelocity, żeby sie na pociągowy przełączył -#if LOGBACKSCAN - // WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist) + - // AnsiString(scanvel)); WriteLog(edir); -#endif - // SetProximityVelocity(scandist,scanvel,&sl); - return scanvel > 0 ? TCommandType::cm_SetVelocity : TCommandType::cm_Unknown; } else { - // ustawiamy prędkość tylko wtedy, gdy ma ruszyć, stanąć albo ma stać - // if ((MoverParameters->Vel==0.0)||(scanvel==0.0)) //jeśli stoi lub ma stanąć/stać - // semafor na tym torze albo lokomtywa stoi, a ma ruszyć, albo ma stanąć, albo nie - // ruszać stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam - // PutCommand("SetVelocity",scanvel,e->Params[9].asMemCell->Value2(),&sl,stopSem); -#if LOGBACKSCAN WriteLog(edir + " - [SetVelocity] [" + to_string(scanvel, 2) + "] [" + to_string(e->input_value(2), 2) + "]"); -#endif - return scanvel > 0 ? TCommandType::cm_SetVelocity : TCommandType::cm_Unknown; } +#endif + return scanvel > 0 ? TCommandType::cm_SetVelocity : TCommandType::cm_Unknown; } } // reakcja AI w trybie manewrowym dodatkowo na sygnały manewrowe @@ -8118,7 +8114,7 @@ TController::check_route_behind( double const Range ) { } iDirectionOrder = -iDirection; // zmiana kierunku jazdy // zmiana kierunku bez psucia kolejnych komend - OrderList[ OrderPos ] = TOrders( OrderCurrentGet() | Change_direction ); + OrderCurrentSet( TOrders( OrderCurrentGet() | Change_direction ) ); } } } diff --git a/vehicle/Driver.h b/vehicle/Driver.h index b76dbf08..644b8122 100644 --- a/vehicle/Driver.h +++ b/vehicle/Driver.h @@ -482,6 +482,7 @@ private: void OrderPush( TOrders NewOrder ); void OrderNext( TOrders NewOrder ); inline TOrders OrderCurrentGet() const; + inline void OrderCurrentSet( TOrders const Order ); inline TOrders OrderNextGet() const; void OrderCheck(); void OrdersInit( double fVel ); @@ -542,7 +543,7 @@ private: double fLastVel = 0.0; // prędkość na poprzednio sprawdzonym torze TTrack *tLast = nullptr; // ostatni analizowany tor basic_event *eSignSkip = nullptr; // można pominąć ten SBL po zatrzymaniu - static constexpr std::size_t npos{ std::size_t( -1 ) }; + static constexpr std::size_t npos{ std::numeric_limits::max() }; std::size_t SemNextIndex{ npos }; std::size_t SemNextStopIndex{ npos }; double dMoveLen = 0.0; // odległość przejechana od ostatniego sprawdzenia tabelki @@ -645,9 +646,23 @@ private: }; inline TOrders TController::OrderCurrentGet() const { + if( OrderPos < 0 || OrderPos >= maxorders ) { + return Wait_for_orders; + } return OrderList[ OrderPos ]; } -inline TOrders TController::OrderNextGet() const { - return OrderList[ OrderPos + 1 ]; +inline void TController::OrderCurrentSet( TOrders const Order ) { + if( OrderPos < 0 || OrderPos >= maxorders ) { + return; + } + OrderList[ OrderPos ] = Order; +} + +inline TOrders TController::OrderNextGet() const { + if( OrderPos < 0 || OrderPos >= maxorders ) { + return Wait_for_orders; + } + // the order list wraps around, same as in JumpToNextOrder() + return OrderList[ ( OrderPos + 1 ) % maxorders ]; } diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index 873cc4d3..6af23f07 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -1408,7 +1408,7 @@ TDynamicObject * TDynamicObject::ABuScanNearestObject(glm::vec3 pos, TTrack *Tra TDynamicObject *FoundedObj; FoundedObj = ABuFindNearestObject(pos, Track, this, CouplNr); // zwraca numer sprzęgu znalezionego pojazdu - if (FoundedObj == nullptr) + if (FoundedObj == nullptr && Track != nullptr) { double ActDist; // Przeskanowana odleglosc. double CurrDist = 0; // Aktualna dlugosc toru. @@ -1448,7 +1448,7 @@ TDynamicObject * TDynamicObject::ABuScanNearestObject(glm::vec3 pos, TTrack *Tra ActDist = ScanDist; } else // Jesli nie ma, to wychodzimy. - ActDist = ScanDist; + break; } } // Koniec szukania najblizszego toru z jakims obiektem. return FoundedObj; diff --git a/vehicle/Gauge.cpp b/vehicle/Gauge.cpp index a510b05f..63c042d3 100644 --- a/vehicle/Gauge.cpp +++ b/vehicle/Gauge.cpp @@ -241,8 +241,7 @@ TGauge::Load_mapping( cParser &Input, TGauge::scratch_data &Scratchpad ) { gaugetype == "return" ? TGaugeType::push : gaugetype == "delayed" ? TGaugeType::push_delayed : gaugetype == "pushtoggle" ? TGaugeType::pushtoggle : - gaugetype == "toggle" ? TGaugeType::toggle : - TGaugeType::toggle; // default + TGaugeType::toggle; // "toggle" and default } else if( key == "soundinc:" ) { m_soundfxincrease.deserialize( Input, sound_type::single ); diff --git a/vehicle/Train.cpp b/vehicle/Train.cpp index 8d2117c4..a77c5749 100644 --- a/vehicle/Train.cpp +++ b/vehicle/Train.cpp @@ -31,6 +31,7 @@ http://mozilla.org/MPL/2.0/. #include "application/application.h" #include "rendering/renderer.h" #include +#include #include #include /* @@ -646,9 +647,8 @@ bool TTrain::Init(TDynamicObject *NewDynamicObject, bool e3d) } DynamicSet(NewDynamicObject); - if (!e3d) - if (DynamicObject->Mechanik == nullptr) - return false; + if (!e3d && DynamicObject->Mechanik == nullptr) + return false; DynamicObject->MechInside = true; @@ -1435,7 +1435,7 @@ void TTrain::OnCommand_secondcontrollerincrease(TTrain *Train, command_data cons // HACK: potentially animate push or pushtoggle control if (Train->ggScndCtrl.is_push()) { - auto const activeposition{Train->ggScndCtrl.is_toggle() ? 1.f : 1.f}; + auto const activeposition{1.f}; auto const neutralposition{Train->ggScndCtrl.is_toggle() ? 0.5f : 0.f}; Train->ggScndCtrl.UpdateValue(Command.action == GLFW_RELEASE ? neutralposition : activeposition, Train->dsbSwitch); } @@ -12398,8 +12398,18 @@ uint16_t TTrain::id() return vid; } -#include -#include +namespace +{ +// updates trains in range [Start, End) +void update_trains(std::deque const &Trains, std::size_t const Start, std::size_t const End, double const Dt) +{ + for (std::size_t j = Start; j < End; ++j) + { + if (Trains[j]) + Trains[j]->Update(Dt); + } +} +} // namespace void train_table::updateAsync(double dt) { @@ -12407,8 +12417,10 @@ void train_table::updateAsync(double dt) const size_t total = m_items.size(); const size_t chunkSize = (total + threads - 1) / threads; - std::vector> futures; - futures.reserve(threads); + std::vector workers; + std::vector> results; // przekazują wyjątki z wątków do głównego wątku + workers.reserve(threads); + results.reserve(threads); for (int i = 0; i < threads; ++i) { @@ -12418,21 +12430,17 @@ void train_table::updateAsync(double dt) if (start >= end) break; // brak więcej danych - futures.emplace_back(std::async(std::launch::async, - [this, start, end, dt]() - { - for (size_t j = start; j < end; ++j) - { - TTrain *train = m_items[j]; - if (train) - train->Update(dt); - } - })); + std::packaged_task task([this, start, end, dt]() { update_trains(m_items, start, end, dt); }); + results.emplace_back(task.get_future()); + workers.emplace_back(std::move(task)); } // Poczekaj aż wszystkie wątki skończą - for (auto &f : futures) - f.get(); + for (auto &worker : workers) + worker.join(); + + for (auto &result : results) + result.get(); // Teraz kasowanie (tylko w głównym wątku) for (TTrain *train : m_items) diff --git a/world/Segment.cpp b/world/Segment.cpp index 2afa264e..c963922a 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -294,7 +294,7 @@ TSegment::find_nearest_point( glm::dvec3 const &Point ) const { else { // for curves iterate through segment chunks, and find the one which gives us the least distance to the specified point double distance = std::numeric_limits::max(); - double nearest; + double nearest { 0.0 }; // NOTE: we're reusing already created segment chunks, which are created based on splinefidelity setting // this means depending on splinefidelity the results can be potentially slightly different for( int segmentidx = 0; segmentidx < iSegCount; ++segmentidx ) {