2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file signal.cpp functions related to rail signals updating */
12 #include "station_map.h"
13 #include "tunnelbridge_map.h"
14 #include "vehicle_func.h"
15 #include "viewport_func.h"
17 #include "company_base.h"
19 #include "safeguards.h"
22 /** these are the maximums used for updating signal blocks */
23 static const uint SIG_TBU_SIZE
= 64; ///< number of signals entering to block
24 static const uint SIG_TBD_SIZE
= 256; ///< number of intersections - open nodes in current block
25 static const uint SIG_GLOB_SIZE
= 128; ///< number of open blocks (block can be opened more times until detected)
26 static const uint SIG_GLOB_UPDATE
= 64; ///< how many items need to be in _globset to force update
28 static_assert(SIG_GLOB_UPDATE
<= SIG_GLOB_SIZE
);
30 /** incidating trackbits with given enterdir */
31 static const TrackBits _enterdir_to_trackbits
[DIAGDIR_END
] = {
38 /** incidating trackdirbits with given enterdir */
39 static const TrackdirBits _enterdir_to_trackdirbits
[DIAGDIR_END
] = {
40 TRACKDIR_BIT_X_SW
| TRACKDIR_BIT_UPPER_W
| TRACKDIR_BIT_RIGHT_S
,
41 TRACKDIR_BIT_Y_NW
| TRACKDIR_BIT_LOWER_W
| TRACKDIR_BIT_RIGHT_N
,
42 TRACKDIR_BIT_X_NE
| TRACKDIR_BIT_LOWER_E
| TRACKDIR_BIT_LEFT_N
,
43 TRACKDIR_BIT_Y_SE
| TRACKDIR_BIT_UPPER_E
| TRACKDIR_BIT_LEFT_S
47 * Set containing 'items' items of 'tile and Tdir'
48 * No tree structure is used because it would cause
49 * slowdowns in most usual cases
51 template <typename Tdir
, uint items
>
54 uint n
; // actual number of units
55 bool overflowed
; // did we try to overflow the set?
56 const char *name
; // name, used for debugging purposes...
65 /** Constructor - just set default values and 'name' */
66 SmallSet(const char *name
) : n(0), overflowed(false), name(name
) { }
68 /** Reset variables to default values */
72 this->overflowed
= false;
76 * Returns value of 'overflowed'
77 * @return did we try to overflow the set?
81 return this->overflowed
;
85 * Checks for empty set
86 * @return is the set empty?
95 * @return is the set full?
99 return this->n
== lengthof(data
);
103 * Reads the number of items
104 * @return current number of items
113 * Tries to remove first instance of given tile and dir
115 * @param dir and dir to remove
116 * @return element was found and removed
118 bool Remove(TileIndex tile
, Tdir dir
)
120 for (uint i
= 0; i
< this->n
; i
++) {
121 if (this->data
[i
].tile
== tile
&& this->data
[i
].dir
== dir
) {
122 this->data
[i
] = this->data
[--this->n
];
131 * Tries to find given tile and dir in the set
133 * @param dir and dir to find
134 * @return true iff the tile & dir element was found
136 bool IsIn(TileIndex tile
, Tdir dir
)
138 for (uint i
= 0; i
< this->n
; i
++) {
139 if (this->data
[i
].tile
== tile
&& this->data
[i
].dir
== dir
) return true;
146 * Adds tile & dir into the set, checks for full set
147 * Sets the 'overflowed' flag if the set was full
149 * @param dir and dir to add
150 * @return true iff the item could be added (set wasn't full)
152 bool Add(TileIndex tile
, Tdir dir
)
154 if (this->IsFull()) {
156 DEBUG(misc
, 0, "SignalSegment too complex. Set %s is full (maximum %d)", name
, items
);
157 return false; // set is full
160 this->data
[this->n
].tile
= tile
;
161 this->data
[this->n
].dir
= dir
;
168 * Reads the last added element into the set
169 * @param tile pointer where tile is written to
170 * @param dir pointer where dir is written to
171 * @return false iff the set was empty
173 bool Get(TileIndex
*tile
, Tdir
*dir
)
175 if (this->n
== 0) return false;
178 *tile
= this->data
[this->n
].tile
;
179 *dir
= this->data
[this->n
].dir
;
185 static SmallSet
<Trackdir
, SIG_TBU_SIZE
> _tbuset("_tbuset"); ///< set of signals that will be updated
186 static SmallSet
<DiagDirection
, SIG_TBD_SIZE
> _tbdset("_tbdset"); ///< set of open nodes in current signal block
187 static SmallSet
<DiagDirection
, SIG_GLOB_SIZE
> _globset("_globset"); ///< set of places to be updated in following runs
190 /** Check whether there is a train on rail, not in a depot */
191 static Vehicle
*TrainOnTileEnum(Vehicle
*v
, void *)
193 if (v
->type
!= VEH_TRAIN
|| Train::From(v
)->track
== TRACK_BIT_DEPOT
) return nullptr;
200 * Perform some operations before adding data into Todo set
201 * The new and reverse direction is removed from _globset, because we are sure
202 * it doesn't need to be checked again
203 * Also, remove reverse direction from _tbdset
204 * This is the 'core' part so the graph searching won't enter any tile twice
206 * @param t1 tile we are entering
207 * @param d1 direction (tile side) we are entering
208 * @param t2 tile we are leaving
209 * @param d2 direction (tile side) we are leaving
210 * @return false iff reverse direction was in Todo set
212 static inline bool CheckAddToTodoSet(TileIndex t1
, DiagDirection d1
, TileIndex t2
, DiagDirection d2
)
214 _globset
.Remove(t1
, d1
); // it can be in Global but not in Todo
215 _globset
.Remove(t2
, d2
); // remove in all cases
217 assert(!_tbdset
.IsIn(t1
, d1
)); // it really shouldn't be there already
219 if (_tbdset
.Remove(t2
, d2
)) return false;
226 * Perform some operations before adding data into Todo set
227 * The new and reverse direction is removed from Global set, because we are sure
228 * it doesn't need to be checked again
229 * Also, remove reverse direction from Todo set
230 * This is the 'core' part so the graph searching won't enter any tile twice
232 * @param t1 tile we are entering
233 * @param d1 direction (tile side) we are entering
234 * @param t2 tile we are leaving
235 * @param d2 direction (tile side) we are leaving
236 * @return false iff the Todo buffer would be overrun
238 static inline bool MaybeAddToTodoSet(TileIndex t1
, DiagDirection d1
, TileIndex t2
, DiagDirection d2
)
240 if (!CheckAddToTodoSet(t1
, d1
, t2
, d2
)) return true;
242 return _tbdset
.Add(t1
, d1
);
246 /** Current signal block state flags */
249 SF_TRAIN
= 1 << 0, ///< train found in segment
250 SF_EXIT
= 1 << 1, ///< exitsignal found
251 SF_EXIT2
= 1 << 2, ///< two or more exits found
252 SF_GREEN
= 1 << 3, ///< green exitsignal found
253 SF_GREEN2
= 1 << 4, ///< two or more green exits found
254 SF_FULL
= 1 << 5, ///< some of buffers was full, do not continue
255 SF_PBS
= 1 << 6, ///< pbs signal found
258 DECLARE_ENUM_AS_BIT_SET(SigFlags
)
262 * Search signal block
264 * @param owner owner whose signals we are updating
267 static SigFlags
ExploreSegment(Owner owner
)
269 SigFlags flags
= SF_NONE
;
271 TileIndex tile
= INVALID_TILE
; // Stop GCC from complaining about a possibly uninitialized variable (issue #8280).
272 DiagDirection enterdir
= INVALID_DIAGDIR
;
274 while (_tbdset
.Get(&tile
, &enterdir
)) { // tile and enterdir are initialized here, unless I'm mistaken.
275 TileIndex oldtile
= tile
; // tile we are leaving
276 DiagDirection exitdir
= enterdir
== INVALID_DIAGDIR
? INVALID_DIAGDIR
: ReverseDiagDir(enterdir
); // expected new exit direction (for straight line)
278 switch (GetTileType(tile
)) {
280 if (GetTileOwner(tile
) != owner
) continue; // do not propagate signals on others' tiles (remove for tracksharing)
282 if (IsRailDepot(tile
)) {
283 if (enterdir
== INVALID_DIAGDIR
) { // from 'inside' - train just entered or left the depot
284 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, nullptr, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
285 exitdir
= GetRailDepotDirection(tile
);
286 tile
+= TileOffsByDiagDir(exitdir
);
287 enterdir
= ReverseDiagDir(exitdir
);
289 } else if (enterdir
== GetRailDepotDirection(tile
)) { // entered a depot
290 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, nullptr, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
297 assert(IsValidDiagDirection(enterdir
));
298 TrackBits tracks
= GetTrackBits(tile
); // trackbits of tile
299 TrackBits tracks_masked
= (TrackBits
)(tracks
& _enterdir_to_trackbits
[enterdir
]); // only incidating trackbits
301 if (tracks
== TRACK_BIT_HORZ
|| tracks
== TRACK_BIT_VERT
) { // there is exactly one incidating track, no need to check
302 tracks
= tracks_masked
;
303 /* If no train detected yet, and there is not no train -> there is a train -> set the flag */
304 if (!(flags
& SF_TRAIN
) && EnsureNoTrainOnTrackBits(tile
, tracks
).Failed()) flags
|= SF_TRAIN
;
306 if (tracks_masked
== TRACK_BIT_NONE
) continue; // no incidating track
307 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, nullptr, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
310 if (HasSignals(tile
)) { // there is exactly one track - not zero, because there is exit from this tile
311 Track track
= TrackBitsToTrack(tracks_masked
); // mask TRACK_BIT_X and Y too
312 if (HasSignalOnTrack(tile
, track
)) { // now check whole track, not trackdir
313 SignalType sig
= GetSignalType(tile
, track
);
314 Trackdir trackdir
= (Trackdir
)FindFirstBit((tracks
* 0x101) & _enterdir_to_trackdirbits
[enterdir
]);
315 Trackdir reversedir
= ReverseTrackdir(trackdir
);
316 /* add (tile, reversetrackdir) to 'to-be-updated' set when there is
317 * ANY conventional signal in REVERSE direction
318 * (if it is a presignal EXIT and it changes, it will be added to 'to-be-done' set later) */
319 if (HasSignalOnTrackdir(tile
, reversedir
)) {
320 if (IsPbsSignal(sig
)) {
322 } else if (!_tbuset
.Add(tile
, reversedir
)) {
323 return flags
| SF_FULL
;
326 if (HasSignalOnTrackdir(tile
, trackdir
) && !IsOnewaySignal(tile
, track
)) flags
|= SF_PBS
;
328 /* if it is a presignal EXIT in OUR direction and we haven't found 2 green exits yes, do special check */
329 if (!(flags
& SF_GREEN2
) && IsPresignalExit(tile
, track
) && HasSignalOnTrackdir(tile
, trackdir
)) { // found presignal exit
330 if (flags
& SF_EXIT
) flags
|= SF_EXIT2
; // found two (or more) exits
331 flags
|= SF_EXIT
; // found at least one exit - allow for compiler optimizations
332 if (GetSignalStateByTrackdir(tile
, trackdir
) == SIGNAL_STATE_GREEN
) { // found green presignal exit
333 if (flags
& SF_GREEN
) flags
|= SF_GREEN2
;
342 for (DiagDirection dir
= DIAGDIR_BEGIN
; dir
< DIAGDIR_END
; dir
++) { // test all possible exit directions
343 if (dir
!= enterdir
&& (tracks
& _enterdir_to_trackbits
[dir
])) { // any track incidating?
344 TileIndex newtile
= tile
+ TileOffsByDiagDir(dir
); // new tile to check
345 DiagDirection newdir
= ReverseDiagDir(dir
); // direction we are entering from
346 if (!MaybeAddToTodoSet(newtile
, newdir
, tile
, dir
)) return flags
| SF_FULL
;
350 continue; // continue the while() loop
354 if (!HasStationRail(tile
)) continue;
355 if (GetTileOwner(tile
) != owner
) continue;
356 if (DiagDirToAxis(enterdir
) != GetRailStationAxis(tile
)) continue; // different axis
357 if (IsStationTileBlocked(tile
)) continue; // 'eye-candy' station tile
359 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, nullptr, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
360 tile
+= TileOffsByDiagDir(exitdir
);
364 if (!IsLevelCrossing(tile
)) continue;
365 if (GetTileOwner(tile
) != owner
) continue;
366 if (DiagDirToAxis(enterdir
) == GetCrossingRoadAxis(tile
)) continue; // different axis
368 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, nullptr, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
369 tile
+= TileOffsByDiagDir(exitdir
);
372 case MP_TUNNELBRIDGE
: {
373 if (GetTileOwner(tile
) != owner
) continue;
374 if (GetTunnelBridgeTransportType(tile
) != TRANSPORT_RAIL
) continue;
375 DiagDirection dir
= GetTunnelBridgeDirection(tile
);
377 if (enterdir
== INVALID_DIAGDIR
) { // incoming from the wormhole
378 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, nullptr, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
380 exitdir
= ReverseDiagDir(dir
);
381 tile
+= TileOffsByDiagDir(exitdir
); // just skip to next tile
382 } else { // NOT incoming from the wormhole!
383 if (ReverseDiagDir(enterdir
) != dir
) continue;
384 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, nullptr, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
385 tile
= GetOtherTunnelBridgeEnd(tile
); // just skip to exit tile
386 enterdir
= INVALID_DIAGDIR
;
387 exitdir
= INVALID_DIAGDIR
;
393 continue; // continue the while() loop
396 if (!MaybeAddToTodoSet(tile
, enterdir
, oldtile
, exitdir
)) return flags
| SF_FULL
;
404 * Update signals around segment in _tbuset
406 * @param flags info about segment
408 static void UpdateSignalsAroundSegment(SigFlags flags
)
410 TileIndex tile
= INVALID_TILE
; // Stop GCC from complaining about a possibly uninitialized variable (issue #8280).
411 Trackdir trackdir
= INVALID_TRACKDIR
;
413 while (_tbuset
.Get(&tile
, &trackdir
)) {
414 assert(HasSignalOnTrackdir(tile
, trackdir
));
416 SignalType sig
= GetSignalType(tile
, TrackdirToTrack(trackdir
));
417 SignalState newstate
= SIGNAL_STATE_GREEN
;
419 /* determine whether the new state is red */
420 if (flags
& SF_TRAIN
) {
421 /* train in the segment */
422 newstate
= SIGNAL_STATE_RED
;
424 /* is it a bidir combo? - then do not count its other signal direction as exit */
425 if (sig
== SIGTYPE_COMBO
&& HasSignalOnTrackdir(tile
, ReverseTrackdir(trackdir
))) {
426 /* at least one more exit */
427 if ((flags
& SF_EXIT2
) &&
429 (!(flags
& SF_GREEN
) ||
430 /* only one green exit, and it is this one - so all other exits are red */
431 (!(flags
& SF_GREEN2
) && GetSignalStateByTrackdir(tile
, ReverseTrackdir(trackdir
)) == SIGNAL_STATE_GREEN
))) {
432 newstate
= SIGNAL_STATE_RED
;
434 } else { // entry, at least one exit, no green exit
435 if (IsPresignalEntry(tile
, TrackdirToTrack(trackdir
)) && (flags
& SF_EXIT
) && !(flags
& SF_GREEN
)) newstate
= SIGNAL_STATE_RED
;
439 /* only when the state changes */
440 if (newstate
!= GetSignalStateByTrackdir(tile
, trackdir
)) {
441 if (IsPresignalExit(tile
, TrackdirToTrack(trackdir
))) {
442 /* for pre-signal exits, add block to the global set */
443 DiagDirection exitdir
= TrackdirToExitdir(ReverseTrackdir(trackdir
));
444 _globset
.Add(tile
, exitdir
); // do not check for full global set, first update all signals
446 SetSignalStateByTrackdir(tile
, trackdir
, newstate
);
447 MarkTileDirtyByTile(tile
);
454 /** Reset all sets after one set overflowed */
455 static inline void ResetSets()
464 * Updates blocks in _globset buffer
466 * @param owner company whose signals we are updating
467 * @return state of the first block from _globset
468 * @pre Company::IsValidID(owner)
470 static SigSegState
UpdateSignalsInBuffer(Owner owner
)
472 assert(Company::IsValidID(owner
));
474 bool first
= true; // first block?
475 SigSegState state
= SIGSEG_FREE
; // value to return
477 TileIndex tile
= INVALID_TILE
; // Stop GCC from complaining about a possibly uninitialized variable (issue #8280).
478 DiagDirection dir
= INVALID_DIAGDIR
;
480 while (_globset
.Get(&tile
, &dir
)) {
481 assert(_tbuset
.IsEmpty());
482 assert(_tbdset
.IsEmpty());
484 /* After updating signal, data stored are always MP_RAILWAY with signals.
485 * Other situations happen when data are from outside functions -
486 * modification of railbits (including both rail building and removal),
487 * train entering/leaving block, train leaving depot...
489 switch (GetTileType(tile
)) {
490 case MP_TUNNELBRIDGE
:
491 /* 'optimization assert' - do not try to update signals when it is not needed */
492 assert(GetTunnelBridgeTransportType(tile
) == TRANSPORT_RAIL
);
493 assert(dir
== INVALID_DIAGDIR
|| dir
== ReverseDiagDir(GetTunnelBridgeDirection(tile
)));
494 _tbdset
.Add(tile
, INVALID_DIAGDIR
); // we can safely start from wormhole centre
495 _tbdset
.Add(GetOtherTunnelBridgeEnd(tile
), INVALID_DIAGDIR
);
499 if (IsRailDepot(tile
)) {
500 /* 'optimization assert' do not try to update signals in other cases */
501 assert(dir
== INVALID_DIAGDIR
|| dir
== GetRailDepotDirection(tile
));
502 _tbdset
.Add(tile
, INVALID_DIAGDIR
); // start from depot inside
509 if ((TrackStatusToTrackBits(GetTileTrackStatus(tile
, TRANSPORT_RAIL
, 0)) & _enterdir_to_trackbits
[dir
]) != TRACK_BIT_NONE
) {
510 /* only add to set when there is some 'interesting' track */
511 _tbdset
.Add(tile
, dir
);
512 _tbdset
.Add(tile
+ TileOffsByDiagDir(dir
), ReverseDiagDir(dir
));
518 /* jump to next tile */
519 tile
= tile
+ TileOffsByDiagDir(dir
);
520 dir
= ReverseDiagDir(dir
);
521 if ((TrackStatusToTrackBits(GetTileTrackStatus(tile
, TRANSPORT_RAIL
, 0)) & _enterdir_to_trackbits
[dir
]) != TRACK_BIT_NONE
) {
522 _tbdset
.Add(tile
, dir
);
525 /* happens when removing a rail that wasn't connected at one or both sides */
526 continue; // continue the while() loop
529 assert(!_tbdset
.Overflowed()); // it really shouldn't overflow by these one or two items
530 assert(!_tbdset
.IsEmpty()); // it wouldn't hurt anyone, but shouldn't happen too
532 SigFlags flags
= ExploreSegment(owner
);
536 /* SIGSEG_FREE is set by default */
537 if (flags
& SF_PBS
) {
539 } else if ((flags
& SF_TRAIN
) || ((flags
& SF_EXIT
) && !(flags
& SF_GREEN
)) || (flags
& SF_FULL
)) {
544 /* do not do anything when some buffer was full */
545 if (flags
& SF_FULL
) {
546 ResetSets(); // free all sets
550 UpdateSignalsAroundSegment(flags
);
557 static Owner _last_owner
= INVALID_OWNER
; ///< last owner whose track was put into _globset
561 * Update signals in buffer
562 * Called from 'outside'
564 void UpdateSignalsInBuffer()
566 if (!_globset
.IsEmpty()) {
567 UpdateSignalsInBuffer(_last_owner
);
568 _last_owner
= INVALID_OWNER
; // invalidate
574 * Add track to signal update buffer
576 * @param tile tile where we start
577 * @param track track at which ends we will update signals
578 * @param owner owner whose signals we will update
580 void AddTrackToSignalBuffer(TileIndex tile
, Track track
, Owner owner
)
582 static const DiagDirection _search_dir_1
[] = {
583 DIAGDIR_NE
, DIAGDIR_SE
, DIAGDIR_NE
, DIAGDIR_SE
, DIAGDIR_SW
, DIAGDIR_SE
585 static const DiagDirection _search_dir_2
[] = {
586 DIAGDIR_SW
, DIAGDIR_NW
, DIAGDIR_NW
, DIAGDIR_SW
, DIAGDIR_NW
, DIAGDIR_NE
589 /* do not allow signal updates for two companies in one run */
590 assert(_globset
.IsEmpty() || owner
== _last_owner
);
594 _globset
.Add(tile
, _search_dir_1
[track
]);
595 _globset
.Add(tile
, _search_dir_2
[track
]);
597 if (_globset
.Items() >= SIG_GLOB_UPDATE
) {
598 /* too many items, force update */
599 UpdateSignalsInBuffer(_last_owner
);
600 _last_owner
= INVALID_OWNER
;
606 * Add side of tile to signal update buffer
608 * @param tile tile where we start
609 * @param side side of tile
610 * @param owner owner whose signals we will update
612 void AddSideToSignalBuffer(TileIndex tile
, DiagDirection side
, Owner owner
)
614 /* do not allow signal updates for two companies in one run */
615 assert(_globset
.IsEmpty() || owner
== _last_owner
);
619 _globset
.Add(tile
, side
);
621 if (_globset
.Items() >= SIG_GLOB_UPDATE
) {
622 /* too many items, force update */
623 UpdateSignalsInBuffer(_last_owner
);
624 _last_owner
= INVALID_OWNER
;
629 * Update signals, starting at one side of a tile
630 * Will check tile next to this at opposite side too
632 * @see UpdateSignalsInBuffer()
633 * @param tile tile where we start
634 * @param side side of tile
635 * @param owner owner whose signals we will update
636 * @return the state of the signal segment
638 SigSegState
UpdateSignalsOnSegment(TileIndex tile
, DiagDirection side
, Owner owner
)
640 assert(_globset
.IsEmpty());
641 _globset
.Add(tile
, side
);
643 return UpdateSignalsInBuffer(owner
);
648 * Update signals at segments that are at both ends of
649 * given (existent or non-existent) track
651 * @see UpdateSignalsInBuffer()
652 * @param tile tile where we start
653 * @param track track at which ends we will update signals
654 * @param owner owner whose signals we will update
656 void SetSignalsOnBothDir(TileIndex tile
, Track track
, Owner owner
)
658 assert(_globset
.IsEmpty());
660 AddTrackToSignalBuffer(tile
, track
, owner
);
661 UpdateSignalsInBuffer(owner
);