mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-03 06:06:28 +01:00
fix(sonar): addressing reliability/security issues (#202)
This commit is contained in:
@@ -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<OrderPos) OrderTop=OrderPos;
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<std::size_t>::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 ];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -31,6 +31,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "application/application.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
/*
|
||||
@@ -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 <future>
|
||||
#include <algorithm>
|
||||
namespace
|
||||
{
|
||||
// updates trains in range [Start, End)
|
||||
void update_trains(std::deque<TTrain *> 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<std::future<void>> futures;
|
||||
futures.reserve(threads);
|
||||
std::vector<std::jthread> workers;
|
||||
std::vector<std::future<void>> 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<void()> 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)
|
||||
|
||||
Reference in New Issue
Block a user