mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 03:59:19 +02:00
Speed table: keep it sorted and keep the signal indices valid (#178)
* Fix the speed table index remap in TablePurger() TablePurger() rebuilds the speed table and has to move SemNextIndex and SemNextStopIndex along with it. Two cases came out wrong. A trimmed point was never invalidated: the index was first overwritten with the position the next kept point would take, and the guard that should reset it to -1 compared the loop counter against that new value, so it only matched when nothing had been trimmed before it. The index then pointed at an unrelated record, and if that record happens to sit at zero velocity the placement can no longer be updated either, because the update requires the recorded one to be non-zero. An index pointing at the last entry was left untouched, because the loop stops one short of it and that entry is appended afterwards. The stale value ended up past the end of the trimmed table, which is what the provisory check in TableTraceRoute() catches. The placements are now recorded separately and assigned once the new table is complete, with the always-kept last entry handled too. The trim condition is shared by the counting and the copying pass instead of being spelled out twice. TableCheck() drops entries beyond a switch that changed under the scan without adjusting the placements either, so they are invalidated there as well. The check in TableTraceRoute() stays as a safety net, without the TODO. * Sort the whole speed table in TableSort() TableSort() made a single bubble pass over the table. That only moves an entry one place towards the front, but TableTraceRoute() adds the scanned events of a track before the track itself, so a track carrying k scanned events lands k places too far and needs k passes. With two or more events in one slot - a cab signal resonator next to a signal, a signal and a W4 - the track entry stays behind entries that are farther away, and TableUpdate() walks the table out of distance order until enough later scans have shifted it back. The range before the last slot is now sorted with std::stable_sort, and the semaphor placements are moved along by looking their events up again. --------- Co-authored-by: pwarszawski <pwarszawski@gmail.com>
This commit is contained in:
@@ -540,8 +540,7 @@ void TController::TableTraceRoute(double fDistance, TDynamicObject *pVehicle)
|
||||
}
|
||||
else {
|
||||
if( iTableDirection == 0 ) { return; }
|
||||
// NOTE: provisory fix for BUG: sempahor indices no longer matching table size
|
||||
// TODO: find and really fix the reason it happens
|
||||
// NOTE: belt and braces; TablePurger() used to leave these pointing past the end of the table
|
||||
if( SemNextIndex != -1
|
||||
&& SemNextIndex >= sSpeedTable.size() ) {
|
||||
SemNextIndex = -1;
|
||||
@@ -800,6 +799,15 @@ void TController::TableCheck(double fDistance)
|
||||
sSpeedTable.pop_back();
|
||||
--iLast;
|
||||
}
|
||||
// a semaphor recorded beyond the switch went away with the discarded entries
|
||||
if( SemNextIndex != -1
|
||||
&& SemNextIndex >= sSpeedTable.size() ) {
|
||||
SemNextIndex = -1;
|
||||
}
|
||||
if( SemNextStopIndex != -1
|
||||
&& SemNextStopIndex >= sSpeedTable.size() ) {
|
||||
SemNextStopIndex = -1;
|
||||
}
|
||||
tLast = sSpeedTable[ i ].trTrack;
|
||||
TableTraceRoute( fDistance, pVehicles[ end::rear ] );
|
||||
TableSort();
|
||||
@@ -1739,14 +1747,16 @@ void TController::TablePurger()
|
||||
return;
|
||||
}
|
||||
// simplest approach should be good enough for start -- just copy whatever is still relevant, then swap
|
||||
auto const isexpendable = []( TSpeedPos const &Speedpoint ) {
|
||||
return
|
||||
0 == ( Speedpoint.iFlags & spEnabled )
|
||||
|| ( ( Speedpoint.iFlags & ( spElapsed | spTrack | spCurve | spSwitch ) ) == ( spElapsed | spTrack | spCurve )
|
||||
&& Speedpoint.fVelNext < 0.0 ); };
|
||||
// do a trial run first, to see if we need to bother at all
|
||||
std::size_t trimcount{ 0 };
|
||||
for( std::size_t idx = 0; idx < sSpeedTable.size() - 1; ++idx ) {
|
||||
auto const &speedpoint = sSpeedTable[ idx ];
|
||||
if( 0 == (speedpoint.iFlags & spEnabled)
|
||||
|| ( (speedpoint.iFlags & (spElapsed | spTrack | spCurve | spSwitch)) == (spElapsed | spTrack | spCurve)
|
||||
&& speedpoint.fVelNext < 0.0 ) ) {
|
||||
// NOTE: we could break out early here, but running through entire thing gives us exact size needed for new table
|
||||
// NOTE: we could break out early here, but running through entire thing gives us exact size needed for new table
|
||||
if( isexpendable( sSpeedTable[ idx ] ) ) {
|
||||
++trimcount;
|
||||
}
|
||||
}
|
||||
@@ -1755,34 +1765,34 @@ void TController::TablePurger()
|
||||
return;
|
||||
}
|
||||
std::vector<TSpeedPos> trimmedtable; trimmedtable.reserve( sSpeedTable.size() - trimcount );
|
||||
// we can only update pointers safely after new table is finalized, so record their indices until then
|
||||
// the indices are only valid for the old table, so the new placements are recorded separately
|
||||
// and assigned once the new table is complete
|
||||
auto semnextindex { std::size_t( -1 ) };
|
||||
auto semnextstopindex { std::size_t( -1 ) };
|
||||
auto const trackplacement = [&]( std::size_t const Index, bool const Kept ) {
|
||||
// a trimmed point leaves the index invalid, a kept one lands where the new table ends
|
||||
if( Index == SemNextIndex ) {
|
||||
semnextindex = ( Kept ? trimmedtable.size() : std::size_t( -1 ) );
|
||||
}
|
||||
if( Index == SemNextStopIndex ) {
|
||||
semnextstopindex = ( Kept ? trimmedtable.size() : std::size_t( -1 ) );
|
||||
} };
|
||||
|
||||
for( std::size_t idx = 0; idx < sSpeedTable.size() - 1; ++idx ) {
|
||||
// cache placement of semaphors in the new table, if we encounter them
|
||||
if( idx == SemNextIndex ) {
|
||||
SemNextIndex = trimmedtable.size();
|
||||
}
|
||||
if( idx == SemNextStopIndex ) {
|
||||
SemNextStopIndex = trimmedtable.size();
|
||||
}
|
||||
auto const &speedpoint = sSpeedTable[ idx ];
|
||||
if( 0 == (speedpoint.iFlags & spEnabled)
|
||||
|| ( (speedpoint.iFlags & (spElapsed | spTrack | spCurve | spSwitch)) == (spElapsed | spTrack | spCurve)
|
||||
&& speedpoint.fVelNext < 0.0 ) ) {
|
||||
// if the trimmed point happens to be currently active semaphor we need to invalidate their placements
|
||||
if( idx == SemNextIndex ) {
|
||||
SemNextIndex = -1;
|
||||
}
|
||||
if( idx == SemNextStopIndex ) {
|
||||
SemNextStopIndex = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto const kept { false == isexpendable( speedpoint ) };
|
||||
trackplacement( idx, kept );
|
||||
if( false == kept ) { continue; }
|
||||
// we're left with useful speed point record we should copy
|
||||
trimmedtable.emplace_back( speedpoint );
|
||||
}
|
||||
// always copy the last entry
|
||||
// always copy the last entry; an index pointing at it has to follow it as well
|
||||
trackplacement( sSpeedTable.size() - 1, true );
|
||||
trimmedtable.emplace_back( sSpeedTable.back() );
|
||||
|
||||
SemNextIndex = semnextindex;
|
||||
SemNextStopIndex = semnextstopindex;
|
||||
|
||||
if( Global.iWriteLogEnabled & 8 ) {
|
||||
WriteLog( "Speed table garbage collection for " + OwnerName() + " cut away " + std::to_string( trimcount ) + ( trimcount == 1 ? " record" : " records" ) );
|
||||
}
|
||||
@@ -1797,25 +1807,35 @@ void TController::TableSort() {
|
||||
// we skip last slot and no point in checking if there's only one other entry
|
||||
return;
|
||||
}
|
||||
TSpeedPos sp_temp = TSpeedPos(); // uzywany do przenoszenia
|
||||
for( int i = 0; i < iLast - 1; ++i ) {
|
||||
// pętla tylko do dwóch pozycji od końca bo ostatniej nie modyfikujemy
|
||||
if (sSpeedTable[i].fDist > sSpeedTable[i + 1].fDist)
|
||||
{ // jesli pozycja wcześniejsza jest dalej to źle
|
||||
sp_temp = sSpeedTable[i + 1];
|
||||
sSpeedTable[i + 1] = sSpeedTable[i]; // zamiana
|
||||
sSpeedTable[i] = sp_temp;
|
||||
// jeszcze sprawdzenie czy pozycja nie była indeksowana dla eventów
|
||||
if (SemNextIndex == i)
|
||||
++SemNextIndex;
|
||||
else if (SemNextIndex == i + 1)
|
||||
--SemNextIndex;
|
||||
if (SemNextStopIndex == i)
|
||||
++SemNextStopIndex;
|
||||
else if (SemNextStopIndex == i + 1)
|
||||
--SemNextStopIndex;
|
||||
}
|
||||
}
|
||||
// the last slot holds the track the scan stopped at and stays where it is.
|
||||
// a single bubble pass is not enough: TableTraceRoute() adds the events of a track before the
|
||||
// track itself, so a track with k scanned events lands k places too far and needs k passes
|
||||
auto const sortedcount { static_cast<std::size_t>( iLast ) };
|
||||
auto const inrange = [&]( std::size_t const Index ) {
|
||||
return Index < sortedcount; };
|
||||
// the placements point at semaphor entries; remember which ones, the sort moves them around
|
||||
auto const *semnextevent { inrange( SemNextIndex ) ? sSpeedTable[ SemNextIndex ].evEvent : nullptr };
|
||||
auto const *semnextstopevent { inrange( SemNextStopIndex ) ? sSpeedTable[ SemNextStopIndex ].evEvent : nullptr };
|
||||
|
||||
std::stable_sort(
|
||||
std::begin( sSpeedTable ), std::begin( sSpeedTable ) + sortedcount,
|
||||
[]( TSpeedPos const &Left, TSpeedPos const &Right ) {
|
||||
return Left.fDist < Right.fDist; } );
|
||||
|
||||
auto const relocate = [&]( basic_event const *Event, std::size_t &Index ) {
|
||||
if( Event == nullptr ) { return; }
|
||||
auto const lookup {
|
||||
std::find_if(
|
||||
std::begin( sSpeedTable ), std::begin( sSpeedTable ) + sortedcount,
|
||||
[Event]( TSpeedPos const &Point ) {
|
||||
return Point.evEvent == Event; } ) };
|
||||
Index = (
|
||||
lookup != std::begin( sSpeedTable ) + sortedcount ?
|
||||
static_cast<std::size_t>( std::distance( std::begin( sSpeedTable ), lookup ) ) :
|
||||
std::size_t( -1 ) );
|
||||
};
|
||||
relocate( semnextevent, SemNextIndex );
|
||||
relocate( semnextstopevent, SemNextStopIndex );
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user