4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /** @file signal.cpp functions related to rail signals updating */
14 #include "station_map.h"
15 #include "tunnelbridge_map.h"
16 #include "vehicle_func.h"
17 #include "viewport_func.h"
19 #include "company_base.h"
21 #include "safeguards.h"
24 /** these are the maximums used for updating signal blocks */
25 static const uint SIG_TBU_SIZE
= 64; ///< number of signals entering to block
26 static const uint SIG_TBD_SIZE
= 256; ///< number of intersections - open nodes in current block
27 static const uint SIG_GLOB_SIZE
= 128; ///< number of open blocks (block can be opened more times until detected)
28 static const uint SIG_GLOB_UPDATE
= 64; ///< how many items need to be in _globset to force update
30 assert_compile(SIG_GLOB_UPDATE
<= SIG_GLOB_SIZE
);
32 /** incidating trackbits with given enterdir */
33 static const TrackBits _enterdir_to_trackbits
[DIAGDIR_END
] = {
40 /** incidating trackdirbits with given enterdir */
41 static const TrackdirBits _enterdir_to_trackdirbits
[DIAGDIR_END
] = {
42 TRACKDIR_BIT_X_SW
| TRACKDIR_BIT_UPPER_W
| TRACKDIR_BIT_RIGHT_S
,
43 TRACKDIR_BIT_Y_NW
| TRACKDIR_BIT_LOWER_W
| TRACKDIR_BIT_RIGHT_N
,
44 TRACKDIR_BIT_X_NE
| TRACKDIR_BIT_LOWER_E
| TRACKDIR_BIT_LEFT_N
,
45 TRACKDIR_BIT_Y_SE
| TRACKDIR_BIT_UPPER_E
| TRACKDIR_BIT_LEFT_S
49 * Set containing 'items' items of 'tile and Tdir'
50 * No tree structure is used because it would cause
51 * slowdowns in most usual cases
53 template <typename Tdir
, uint items
>
56 uint n
; // actual number of units
57 bool overflowed
; // did we try to overflow the set?
58 const char *name
; // name, used for debugging purposes...
67 /** Constructor - just set default values and 'name' */
68 SmallSet(const char *name
) : n(0), overflowed(false), name(name
) { }
70 /** Reset variables to default values */
74 this->overflowed
= false;
78 * Returns value of 'overflowed'
79 * @return did we try to overflow the set?
83 return this->overflowed
;
87 * Checks for empty set
88 * @return is the set empty?
97 * @return is the set full?
101 return this->n
== lengthof(data
);
105 * Reads the number of items
106 * @return current number of items
115 * Tries to remove first instance of given tile and dir
117 * @param dir and dir to remove
118 * @return element was found and removed
120 bool Remove(TileIndex tile
, Tdir dir
)
122 for (uint i
= 0; i
< this->n
; i
++) {
123 if (this->data
[i
].tile
== tile
&& this->data
[i
].dir
== dir
) {
124 this->data
[i
] = this->data
[--this->n
];
133 * Tries to find given tile and dir in the set
135 * @param dir and dir to find
136 * @return true iff the tile & dir element was found
138 bool IsIn(TileIndex tile
, Tdir dir
)
140 for (uint i
= 0; i
< this->n
; i
++) {
141 if (this->data
[i
].tile
== tile
&& this->data
[i
].dir
== dir
) return true;
148 * Adds tile & dir into the set, checks for full set
149 * Sets the 'overflowed' flag if the set was full
151 * @param dir and dir to add
152 * @return true iff the item could be added (set wasn't full)
154 bool Add(TileIndex tile
, Tdir dir
)
156 if (this->IsFull()) {
158 DEBUG(misc
, 0, "SignalSegment too complex. Set %s is full (maximum %d)", name
, items
);
159 return false; // set is full
162 this->data
[this->n
].tile
= tile
;
163 this->data
[this->n
].dir
= dir
;
170 * Reads the last added element into the set
171 * @param tile pointer where tile is written to
172 * @param dir pointer where dir is written to
173 * @return false iff the set was empty
175 bool Get(TileIndex
*tile
, Tdir
*dir
)
177 if (this->n
== 0) return false;
180 *tile
= this->data
[this->n
].tile
;
181 *dir
= this->data
[this->n
].dir
;
187 static SmallSet
<Trackdir
, SIG_TBU_SIZE
> _tbuset("_tbuset"); ///< set of signals that will be updated
188 static SmallSet
<DiagDirection
, SIG_TBD_SIZE
> _tbdset("_tbdset"); ///< set of open nodes in current signal block
189 static SmallSet
<DiagDirection
, SIG_GLOB_SIZE
> _globset("_globset"); ///< set of places to be updated in following runs
192 /** Check whether there is a train on rail, not in a depot */
193 static Vehicle
*TrainOnTileEnum(Vehicle
*v
, void *)
195 if (v
->type
!= VEH_TRAIN
|| Train::From(v
)->track
== TRACK_BIT_DEPOT
) return NULL
;
202 * Perform some operations before adding data into Todo set
203 * The new and reverse direction is removed from _globset, because we are sure
204 * it doesn't need to be checked again
205 * Also, remove reverse direction from _tbdset
206 * This is the 'core' part so the graph searching won't enter any tile twice
208 * @param t1 tile we are entering
209 * @param d1 direction (tile side) we are entering
210 * @param t2 tile we are leaving
211 * @param d2 direction (tile side) we are leaving
212 * @return false iff reverse direction was in Todo set
214 static inline bool CheckAddToTodoSet(TileIndex t1
, DiagDirection d1
, TileIndex t2
, DiagDirection d2
)
216 _globset
.Remove(t1
, d1
); // it can be in Global but not in Todo
217 _globset
.Remove(t2
, d2
); // remove in all cases
219 assert(!_tbdset
.IsIn(t1
, d1
)); // it really shouldn't be there already
221 if (_tbdset
.Remove(t2
, d2
)) return false;
228 * Perform some operations before adding data into Todo set
229 * The new and reverse direction is removed from Global set, because we are sure
230 * it doesn't need to be checked again
231 * Also, remove reverse direction from Todo set
232 * This is the 'core' part so the graph searching won't enter any tile twice
234 * @param t1 tile we are entering
235 * @param d1 direction (tile side) we are entering
236 * @param t2 tile we are leaving
237 * @param d2 direction (tile side) we are leaving
238 * @return false iff the Todo buffer would be overrun
240 static inline bool MaybeAddToTodoSet(TileIndex t1
, DiagDirection d1
, TileIndex t2
, DiagDirection d2
)
242 if (!CheckAddToTodoSet(t1
, d1
, t2
, d2
)) return true;
244 return _tbdset
.Add(t1
, d1
);
248 /** Current signal block state flags */
251 SF_TRAIN
= 1 << 0, ///< train found in segment
252 SF_EXIT
= 1 << 1, ///< exitsignal found
253 SF_EXIT2
= 1 << 2, ///< two or more exits found
254 SF_GREEN
= 1 << 3, ///< green exitsignal found
255 SF_GREEN2
= 1 << 4, ///< two or more green exits found
256 SF_FULL
= 1 << 5, ///< some of buffers was full, do not continue
257 SF_PBS
= 1 << 6, ///< pbs signal found
260 DECLARE_ENUM_AS_BIT_SET(SigFlags
)
264 * Search signal block
266 * @param owner owner whose signals we are updating
269 static SigFlags
ExploreSegment(Owner owner
)
271 SigFlags flags
= SF_NONE
;
274 DiagDirection enterdir
;
276 while (_tbdset
.Get(&tile
, &enterdir
)) {
277 TileIndex oldtile
= tile
; // tile we are leaving
278 DiagDirection exitdir
= enterdir
== INVALID_DIAGDIR
? INVALID_DIAGDIR
: ReverseDiagDir(enterdir
); // expected new exit direction (for straight line)
280 switch (GetTileType(tile
)) {
282 if (GetTileOwner(tile
) != owner
) continue; // do not propagate signals on others' tiles (remove for tracksharing)
284 if (IsRailDepot(tile
)) {
285 if (enterdir
== INVALID_DIAGDIR
) { // from 'inside' - train just entered or left the depot
286 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, NULL
, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
287 exitdir
= GetRailDepotDirection(tile
);
288 tile
+= TileOffsByDiagDir(exitdir
);
289 enterdir
= ReverseDiagDir(exitdir
);
291 } else if (enterdir
== GetRailDepotDirection(tile
)) { // entered a depot
292 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, NULL
, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
299 assert(IsValidDiagDirection(enterdir
));
300 TrackBits tracks
= GetTrackBits(tile
); // trackbits of tile
301 TrackBits tracks_masked
= (TrackBits
)(tracks
& _enterdir_to_trackbits
[enterdir
]); // only incidating trackbits
303 if (tracks
== TRACK_BIT_HORZ
|| tracks
== TRACK_BIT_VERT
) { // there is exactly one incidating track, no need to check
304 tracks
= tracks_masked
;
305 /* If no train detected yet, and there is not no train -> there is a train -> set the flag */
306 if (!(flags
& SF_TRAIN
) && EnsureNoTrainOnTrackBits(tile
, tracks
).Failed()) flags
|= SF_TRAIN
;
308 if (tracks_masked
== TRACK_BIT_NONE
) continue; // no incidating track
309 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, NULL
, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
312 if (HasSignals(tile
)) { // there is exactly one track - not zero, because there is exit from this tile
313 Track track
= TrackBitsToTrack(tracks_masked
); // mask TRACK_BIT_X and Y too
314 if (HasSignalOnTrack(tile
, track
)) { // now check whole track, not trackdir
315 SignalType sig
= GetSignalType(tile
, track
);
316 Trackdir trackdir
= (Trackdir
)FindFirstBit((tracks
* 0x101) & _enterdir_to_trackdirbits
[enterdir
]);
317 Trackdir reversedir
= ReverseTrackdir(trackdir
);
318 /* add (tile, reversetrackdir) to 'to-be-updated' set when there is
319 * ANY conventional signal in REVERSE direction
320 * (if it is a presignal EXIT and it changes, it will be added to 'to-be-done' set later) */
321 if (HasSignalOnTrackdir(tile
, reversedir
)) {
322 if (IsPbsSignal(sig
)) {
324 } else if (!_tbuset
.Add(tile
, reversedir
)) {
325 return flags
| SF_FULL
;
328 if (HasSignalOnTrackdir(tile
, trackdir
) && !IsOnewaySignal(tile
, track
)) flags
|= SF_PBS
;
330 /* if it is a presignal EXIT in OUR direction and we haven't found 2 green exits yes, do special check */
331 if (!(flags
& SF_GREEN2
) && IsPresignalExit(tile
, track
) && HasSignalOnTrackdir(tile
, trackdir
)) { // found presignal exit
332 if (flags
& SF_EXIT
) flags
|= SF_EXIT2
; // found two (or more) exits
333 flags
|= SF_EXIT
; // found at least one exit - allow for compiler optimizations
334 if (GetSignalStateByTrackdir(tile
, trackdir
) == SIGNAL_STATE_GREEN
) { // found green presignal exit
335 if (flags
& SF_GREEN
) flags
|= SF_GREEN2
;
344 for (DiagDirection dir
= DIAGDIR_BEGIN
; dir
< DIAGDIR_END
; dir
++) { // test all possible exit directions
345 if (dir
!= enterdir
&& (tracks
& _enterdir_to_trackbits
[dir
])) { // any track incidating?
346 TileIndex newtile
= tile
+ TileOffsByDiagDir(dir
); // new tile to check
347 DiagDirection newdir
= ReverseDiagDir(dir
); // direction we are entering from
348 if (!MaybeAddToTodoSet(newtile
, newdir
, tile
, dir
)) return flags
| SF_FULL
;
352 continue; // continue the while() loop
356 if (!HasStationRail(tile
)) continue;
357 if (GetTileOwner(tile
) != owner
) continue;
358 if (DiagDirToAxis(enterdir
) != GetRailStationAxis(tile
)) continue; // different axis
359 if (IsStationTileBlocked(tile
)) continue; // 'eye-candy' station tile
361 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, NULL
, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
362 tile
+= TileOffsByDiagDir(exitdir
);
366 if (!IsLevelCrossing(tile
)) continue;
367 if (GetTileOwner(tile
) != owner
) continue;
368 if (DiagDirToAxis(enterdir
) == GetCrossingRoadAxis(tile
)) continue; // different axis
370 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, NULL
, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
371 tile
+= TileOffsByDiagDir(exitdir
);
374 case MP_TUNNELBRIDGE
: {
375 if (GetTileOwner(tile
) != owner
) continue;
376 if (GetTunnelBridgeTransportType(tile
) != TRANSPORT_RAIL
) continue;
377 DiagDirection dir
= GetTunnelBridgeDirection(tile
);
379 if (enterdir
== INVALID_DIAGDIR
) { // incoming from the wormhole
380 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, NULL
, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
382 exitdir
= ReverseDiagDir(dir
);
383 tile
+= TileOffsByDiagDir(exitdir
); // just skip to next tile
384 } else { // NOT incoming from the wormhole!
385 if (ReverseDiagDir(enterdir
) != dir
) continue;
386 if (!(flags
& SF_TRAIN
) && HasVehicleOnPos(tile
, NULL
, &TrainOnTileEnum
)) flags
|= SF_TRAIN
;
387 tile
= GetOtherTunnelBridgeEnd(tile
); // just skip to exit tile
388 enterdir
= INVALID_DIAGDIR
;
389 exitdir
= INVALID_DIAGDIR
;
395 continue; // continue the while() loop
398 if (!MaybeAddToTodoSet(tile
, enterdir
, oldtile
, exitdir
)) return flags
| SF_FULL
;
406 * Update signals around segment in _tbuset
408 * @param flags info about segment
410 static void UpdateSignalsAroundSegment(SigFlags flags
)
415 while (_tbuset
.Get(&tile
, &trackdir
)) {
416 assert(HasSignalOnTrackdir(tile
, trackdir
));
418 SignalType sig
= GetSignalType(tile
, TrackdirToTrack(trackdir
));
419 SignalState newstate
= SIGNAL_STATE_GREEN
;
421 /* determine whether the new state is red */
422 if (flags
& SF_TRAIN
) {
423 /* train in the segment */
424 newstate
= SIGNAL_STATE_RED
;
426 /* is it a bidir combo? - then do not count its other signal direction as exit */
427 if (sig
== SIGTYPE_COMBO
&& HasSignalOnTrackdir(tile
, ReverseTrackdir(trackdir
))) {
428 /* at least one more exit */
429 if ((flags
& SF_EXIT2
) &&
431 (!(flags
& SF_GREEN
) ||
432 /* only one green exit, and it is this one - so all other exits are red */
433 (!(flags
& SF_GREEN2
) && GetSignalStateByTrackdir(tile
, ReverseTrackdir(trackdir
)) == SIGNAL_STATE_GREEN
))) {
434 newstate
= SIGNAL_STATE_RED
;
436 } else { // entry, at least one exit, no green exit
437 if (IsPresignalEntry(tile
, TrackdirToTrack(trackdir
)) && (flags
& SF_EXIT
) && !(flags
& SF_GREEN
)) newstate
= SIGNAL_STATE_RED
;
441 /* only when the state changes */
442 if (newstate
!= GetSignalStateByTrackdir(tile
, trackdir
)) {
443 if (IsPresignalExit(tile
, TrackdirToTrack(trackdir
))) {
444 /* for pre-signal exits, add block to the global set */
445 DiagDirection exitdir
= TrackdirToExitdir(ReverseTrackdir(trackdir
));
446 _globset
.Add(tile
, exitdir
); // do not check for full global set, first update all signals
448 SetSignalStateByTrackdir(tile
, trackdir
, newstate
);
449 MarkTileDirtyByTile(tile
);
456 /** Reset all sets after one set overflowed */
457 static inline void ResetSets()
466 * Updates blocks in _globset buffer
468 * @param owner company whose signals we are updating
469 * @return state of the first block from _globset
470 * @pre Company::IsValidID(owner)
472 static SigSegState
UpdateSignalsInBuffer(Owner owner
)
474 assert(Company::IsValidID(owner
));
476 bool first
= true; // first block?
477 SigSegState state
= SIGSEG_FREE
; // value to return
482 while (_globset
.Get(&tile
, &dir
)) {
483 assert(_tbuset
.IsEmpty());
484 assert(_tbdset
.IsEmpty());
486 /* After updating signal, data stored are always MP_RAILWAY with signals.
487 * Other situations happen when data are from outside functions -
488 * modification of railbits (including both rail building and removal),
489 * train entering/leaving block, train leaving depot...
491 switch (GetTileType(tile
)) {
492 case MP_TUNNELBRIDGE
:
493 /* 'optimization assert' - do not try to update signals when it is not needed */
494 assert(GetTunnelBridgeTransportType(tile
) == TRANSPORT_RAIL
);
495 assert(dir
== INVALID_DIAGDIR
|| dir
== ReverseDiagDir(GetTunnelBridgeDirection(tile
)));
496 _tbdset
.Add(tile
, INVALID_DIAGDIR
); // we can safely start from wormhole centre
497 _tbdset
.Add(GetOtherTunnelBridgeEnd(tile
), INVALID_DIAGDIR
);
501 if (IsRailDepot(tile
)) {
502 /* 'optimization assert' do not try to update signals in other cases */
503 assert(dir
== INVALID_DIAGDIR
|| dir
== GetRailDepotDirection(tile
));
504 _tbdset
.Add(tile
, INVALID_DIAGDIR
); // start from depot inside
511 if ((TrackStatusToTrackBits(GetTileTrackStatus(tile
, TRANSPORT_RAIL
, 0)) & _enterdir_to_trackbits
[dir
]) != TRACK_BIT_NONE
) {
512 /* only add to set when there is some 'interesting' track */
513 _tbdset
.Add(tile
, dir
);
514 _tbdset
.Add(tile
+ TileOffsByDiagDir(dir
), ReverseDiagDir(dir
));
520 /* jump to next tile */
521 tile
= tile
+ TileOffsByDiagDir(dir
);
522 dir
= ReverseDiagDir(dir
);
523 if ((TrackStatusToTrackBits(GetTileTrackStatus(tile
, TRANSPORT_RAIL
, 0)) & _enterdir_to_trackbits
[dir
]) != TRACK_BIT_NONE
) {
524 _tbdset
.Add(tile
, dir
);
527 /* happens when removing a rail that wasn't connected at one or both sides */
528 continue; // continue the while() loop
531 assert(!_tbdset
.Overflowed()); // it really shouldn't overflow by these one or two items
532 assert(!_tbdset
.IsEmpty()); // it wouldn't hurt anyone, but shouldn't happen too
534 SigFlags flags
= ExploreSegment(owner
);
538 /* SIGSEG_FREE is set by default */
539 if (flags
& SF_PBS
) {
541 } else if ((flags
& SF_TRAIN
) || ((flags
& SF_EXIT
) && !(flags
& SF_GREEN
)) || (flags
& SF_FULL
)) {
546 /* do not do anything when some buffer was full */
547 if (flags
& SF_FULL
) {
548 ResetSets(); // free all sets
552 UpdateSignalsAroundSegment(flags
);
559 static Owner _last_owner
= INVALID_OWNER
; ///< last owner whose track was put into _globset
563 * Update signals in buffer
564 * Called from 'outside'
566 void UpdateSignalsInBuffer()
568 if (!_globset
.IsEmpty()) {
569 UpdateSignalsInBuffer(_last_owner
);
570 _last_owner
= INVALID_OWNER
; // invalidate
576 * Add track to signal update buffer
578 * @param tile tile where we start
579 * @param track track at which ends we will update signals
580 * @param owner owner whose signals we will update
582 void AddTrackToSignalBuffer(TileIndex tile
, Track track
, Owner owner
)
584 static const DiagDirection _search_dir_1
[] = {
585 DIAGDIR_NE
, DIAGDIR_SE
, DIAGDIR_NE
, DIAGDIR_SE
, DIAGDIR_SW
, DIAGDIR_SE
587 static const DiagDirection _search_dir_2
[] = {
588 DIAGDIR_SW
, DIAGDIR_NW
, DIAGDIR_NW
, DIAGDIR_SW
, DIAGDIR_NW
, DIAGDIR_NE
591 /* do not allow signal updates for two companies in one run */
592 assert(_globset
.IsEmpty() || owner
== _last_owner
);
596 _globset
.Add(tile
, _search_dir_1
[track
]);
597 _globset
.Add(tile
, _search_dir_2
[track
]);
599 if (_globset
.Items() >= SIG_GLOB_UPDATE
) {
600 /* too many items, force update */
601 UpdateSignalsInBuffer(_last_owner
);
602 _last_owner
= INVALID_OWNER
;
608 * Add side of tile to signal update buffer
610 * @param tile tile where we start
611 * @param side side of tile
612 * @param owner owner whose signals we will update
614 void AddSideToSignalBuffer(TileIndex tile
, DiagDirection side
, Owner owner
)
616 /* do not allow signal updates for two companies in one run */
617 assert(_globset
.IsEmpty() || owner
== _last_owner
);
621 _globset
.Add(tile
, side
);
623 if (_globset
.Items() >= SIG_GLOB_UPDATE
) {
624 /* too many items, force update */
625 UpdateSignalsInBuffer(_last_owner
);
626 _last_owner
= INVALID_OWNER
;
631 * Update signals, starting at one side of a tile
632 * Will check tile next to this at opposite side too
634 * @see UpdateSignalsInBuffer()
635 * @param tile tile where we start
636 * @param side side of tile
637 * @param owner owner whose signals we will update
638 * @return the state of the signal segment
640 SigSegState
UpdateSignalsOnSegment(TileIndex tile
, DiagDirection side
, Owner owner
)
642 assert(_globset
.IsEmpty());
643 _globset
.Add(tile
, side
);
645 return UpdateSignalsInBuffer(owner
);
650 * Update signals at segments that are at both ends of
651 * given (existent or non-existent) track
653 * @see UpdateSignalsInBuffer()
654 * @param tile tile where we start
655 * @param track track at which ends we will update signals
656 * @param owner owner whose signals we will update
658 void SetSignalsOnBothDir(TileIndex tile
, Track track
, Owner owner
)
660 assert(_globset
.IsEmpty());
662 AddTrackToSignalBuffer(tile
, track
, owner
);
663 UpdateSignalsInBuffer(owner
);