17
0
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:
pawelw
2026-09-23 01:02:11 +02:00
committed by GitHub
parent 1d9c608396
commit 95f41f069f

View File

@@ -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 );
}
//---------------------------------------------------------------------------