# signal.cpp

Go to the documentation of this file.

    00001 /* $Id: signal.cpp 18809 2010-01-15 16:41:15Z rubidium $ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * 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.
    00006  * 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.
    00007  * 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/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "debug.h"
    00014 #include "station_map.h"
    00015 #include "tunnelbridge_map.h"
    00016 #include "vehicle_func.h"
    00017 #include "functions.h"
    00018 #include "train.h"
    00019 #include "company_base.h"
    00020
    00021
    00023 enum {
    00024   SIG_TBU_SIZE    =  64,
    00025   SIG_TBD_SIZE    = 256,
    00026   SIG_GLOB_SIZE   = 128,
    00027   SIG_GLOB_UPDATE =  64,
    00028 };
    00029
    00030 assert_compile(SIG_GLOB_UPDATE <= SIG_GLOB_SIZE);
    00031
    00033 static const TrackBits _enterdir_to_trackbits[DIAGDIR_END] = {
    00034   TRACK_BIT_3WAY_NE,
    00035   TRACK_BIT_3WAY_SE,
    00036   TRACK_BIT_3WAY_SW,
    00037   TRACK_BIT_3WAY_NW
    00038 };
    00039
    00041 static const TrackdirBits _enterdir_to_trackdirbits[DIAGDIR_END] = {
    00042   TRACKDIR_BIT_X_SW | TRACKDIR_BIT_UPPER_W | TRACKDIR_BIT_RIGHT_S,
    00043   TRACKDIR_BIT_Y_NW | TRACKDIR_BIT_LOWER_W | TRACKDIR_BIT_RIGHT_N,
    00044   TRACKDIR_BIT_X_NE | TRACKDIR_BIT_LOWER_E | TRACKDIR_BIT_LEFT_N,
    00045   TRACKDIR_BIT_Y_SE | TRACKDIR_BIT_UPPER_E | TRACKDIR_BIT_LEFT_S
    00046 };
    00047
    00053 template <typename Tdir, uint items>
    00054 struct SmallSet {
    00055 private:
    00056   uint n;           // actual number of units
    00057   bool overflowed;  // did we try to oveflow the set?
    00058   const char *name; // name, used for debugging purposes...
    00059
    00061   struct SSdata {
    00062     TileIndex tile;
    00063     Tdir dir;
    00064   } data[items];
    00065
    00066 public:
    00068   SmallSet(const char *name) : n(0), overflowed(false), name(name) { }
    00069
    00071   void Reset()
    00072   {
    00073     this->n = 0;
    00074     this->overflowed = false;
    00075   }
    00076
    00081   bool Overflowed()
    00082   {
    00083     return this->overflowed;
    00084   }
    00085
    00090   bool IsEmpty()
    00091   {
    00092     return this->n == 0;
    00093   }
    00094
    00099   bool IsFull()
    00100   {
    00101     return this->n == lengthof(data);
    00102   }
    00103
    00108   uint Items()
    00109   {
    00110     return this->n;
    00111   }
    00112
    00113
    00120   bool Remove(TileIndex tile, Tdir dir)
    00121   {
    00122     for (uint i = 0; i < this->n; i++) {
    00123       if (this->data[i].tile == tile && this->data[i].dir == dir) {
    00124         this->data[i] = this->data[--this->n];
    00125         return true;
    00126       }
    00127     }
    00128
    00129     return false;
    00130   }
    00131
    00138   bool IsIn(TileIndex tile, Tdir dir)
    00139   {
    00140     for (uint i = 0; i < this->n; i++) {
    00141       if (this->data[i].tile == tile && this->data[i].dir == dir) return true;
    00142     }
    00143
    00144     return false;
    00145   }
    00146
    00154   bool Add(TileIndex tile, Tdir dir)
    00155   {
    00156     if (this->IsFull()) {
    00157       overflowed = true;
    00158       DEBUG(misc, 0, "SignalSegment too complex. Set %s is full (maximum %d)", name, items);
    00159       return false; // set is full
    00160     }
    00161
    00162     this->data[this->n].tile = tile;
    00163     this->data[this->n].dir = dir;
    00164     this->n++;
    00165
    00166     return true;
    00167   }
    00168
    00175   bool Get(TileIndex *tile, Tdir *dir)
    00176   {
    00177     if (this->n == 0) return false;
    00178
    00179     this->n--;
    00180     *tile = this->data[this->n].tile;
    00181     *dir = this->data[this->n].dir;
    00182
    00183     return true;
    00184   }
    00185 };
    00186
    00187 static SmallSet<Trackdir, SIG_TBU_SIZE> _tbuset("_tbuset");
    00188 static SmallSet<DiagDirection, SIG_TBD_SIZE> _tbdset("_tbdset");
    00189 static SmallSet<DiagDirection, SIG_GLOB_SIZE> _globset("_globset");
    00190
    00191
    00193 static Vehicle *TrainOnTileEnum(Vehicle *v, void *)
    00194 {
    00195   if (v->type != VEH_TRAIN || Train::From(v)->track == TRACK_BIT_DEPOT) return NULL;
    00196
    00197   return v;
    00198 }
    00199
    00200
    00214 static inline bool CheckAddToTodoSet(TileIndex t1, DiagDirection d1, TileIndex t2, DiagDirection d2)
    00215 {
    00216   _globset.Remove(t1, d1); // it can be in Global but not in Todo
    00217   _globset.Remove(t2, d2); // remove in all cases
    00218
    00219   assert(!_tbdset.IsIn(t1, d1)); // it really shouldn't be there already
    00220
    00221   if (_tbdset.Remove(t2, d2)) return false;
    00222
    00223   return true;
    00224 }
    00225
    00226
    00240 static inline bool MaybeAddToTodoSet(TileIndex t1, DiagDirection d1, TileIndex t2, DiagDirection d2)
    00241 {
    00242   if (!CheckAddToTodoSet(t1, d1, t2, d2)) return true;
    00243
    00244   return _tbdset.Add(t1, d1);
    00245 }
    00246
    00247
    00249 enum SigFlags {
    00250   SF_NONE   = 0,
    00251   SF_TRAIN  = 1 << 0,
    00252   SF_EXIT   = 1 << 1,
    00253   SF_EXIT2  = 1 << 2,
    00254   SF_GREEN  = 1 << 3,
    00255   SF_GREEN2 = 1 << 4,
    00256   SF_FULL   = 1 << 5,
    00257   SF_PBS    = 1 << 6,
    00258 };
    00259
    00260 DECLARE_ENUM_AS_BIT_SET(SigFlags)
    00261
    00262
    00263
    00269 static SigFlags ExploreSegment(Owner owner)
    00270 {
    00271   SigFlags flags = SF_NONE;
    00272
    00273   TileIndex tile;
    00274   DiagDirection enterdir;
    00275
    00276   while (_tbdset.Get(&tile, &enterdir)) {
    00277     TileIndex oldtile = tile; // tile we are leaving
    00278     DiagDirection exitdir = enterdir == INVALID_DIAGDIR ? INVALID_DIAGDIR : ReverseDiagDir(enterdir); // expected new exit direction (for straight line)
    00279
    00280     switch (GetTileType(tile)) {
    00281       case MP_RAILWAY: {
    00282         if (GetTileOwner(tile) != owner) continue; // do not propagate signals on others' tiles (remove for tracksharing)
    00283
    00284         if (IsRailDepot(tile)) {
    00285           if (enterdir == INVALID_DIAGDIR) { // from 'inside' - train just entered or left the depot
    00286             if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
    00287             exitdir = GetRailDepotDirection(tile);
    00288             tile += TileOffsByDiagDir(exitdir);
    00289             enterdir = ReverseDiagDir(exitdir);
    00290             break;
    00291           } else if (enterdir == GetRailDepotDirection(tile)) { // entered a depot
    00292             if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
    00293             continue;
    00294           } else {
    00295             continue;
    00296           }
    00297         }
    00298
    00299         TrackBits tracks = GetTrackBits(tile); // trackbits of tile
    00300         TrackBits tracks_masked = (TrackBits)(tracks & _enterdir_to_trackbits[enterdir]); // only incidating trackbits
    00301
    00302         if (tracks == TRACK_BIT_HORZ || tracks == TRACK_BIT_VERT) { // there is exactly one incidating track, no need to check
    00303           tracks = tracks_masked;
    00304           if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, &tracks, &EnsureNoTrainOnTrackProc)) flags |= SF_TRAIN;
    00305         } else {
    00306           if (tracks_masked == TRACK_BIT_NONE) continue; // no incidating track
    00307           if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
    00308         }
    00309
    00310         if (HasSignals(tile)) { // there is exactly one track - not zero, because there is exit from this tile
    00311           Track track = TrackBitsToTrack(tracks_masked); // mask TRACK_BIT_X and Y too
    00312           if (HasSignalOnTrack(tile, track)) { // now check whole track, not trackdir
    00313             SignalType sig = GetSignalType(tile, track);
    00314             Trackdir trackdir = (Trackdir)FindFirstBit((tracks * 0x101) & _enterdir_to_trackdirbits[enterdir]);
    00315             Trackdir reversedir = ReverseTrackdir(trackdir);
    00316             /* add (tile, reversetrackdir) to 'to-be-updated' set when there is
    00317              * ANY conventional signal in REVERSE direction
    00318              * (if it is a presignal EXIT and it changes, it will be added to 'to-be-done' set later) */
    00319             if (HasSignalOnTrackdir(tile, reversedir)) {
    00320               if (IsPbsSignal(sig)) {
    00321                 flags |= SF_PBS;
    00322               } else if (!_tbuset.Add(tile, reversedir)) {
    00323                 return flags | SF_FULL;
    00324               }
    00325             }
    00326             if (HasSignalOnTrackdir(tile, trackdir) && !IsOnewaySignal(tile, track)) flags |= SF_PBS;
    00327
    00328             /* if it is a presignal EXIT in OUR direction and we haven't found 2 green exits yes, do special check */
    00329             if (!(flags & SF_GREEN2) && IsPresignalExit(tile, track) && HasSignalOnTrackdir(tile, trackdir)) { // found presignal exit
    00330               if (flags & SF_EXIT) flags |= SF_EXIT2; // found two (or more) exits
    00331               flags |= SF_EXIT; // found at least one exit - allow for compiler optimizations
    00332               if (GetSignalStateByTrackdir(tile, trackdir) == SIGNAL_STATE_GREEN) { // found green presignal exit
    00333                 if (flags & SF_GREEN) flags |= SF_GREEN2;
    00334                 flags |= SF_GREEN;
    00335               }
    00336             }
    00337
    00338             continue;
    00339           }
    00340         }
    00341
    00342         for (DiagDirection dir = DIAGDIR_BEGIN; dir < DIAGDIR_END; dir++) { // test all possible exit directions
    00343           if (dir != enterdir && (tracks & _enterdir_to_trackbits[dir])) { // any track incidating?
    00344             TileIndex newtile = tile + TileOffsByDiagDir(dir);  // new tile to check
    00345             DiagDirection newdir = ReverseDiagDir(dir); // direction we are entering from
    00346             if (!MaybeAddToTodoSet(newtile, newdir, tile, dir)) return flags | SF_FULL;
    00347           }
    00348         }
    00349
    00350         continue; // continue the while() loop
    00351         }
    00352
    00353       case MP_STATION:
    00354         if (!HasStationRail(tile)) continue;
    00355         if (GetTileOwner(tile) != owner) continue;
    00356         if (DiagDirToAxis(enterdir) != GetRailStationAxis(tile)) continue; // different axis
    00357         if (IsStationTileBlocked(tile)) continue; // 'eye-candy' station tile
    00358
    00359         if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
    00360         tile += TileOffsByDiagDir(exitdir);
    00361         break;
    00362
    00363       case MP_ROAD:
    00364         if (!IsLevelCrossing(tile)) continue;
    00365         if (GetTileOwner(tile) != owner) continue;
    00366         if (DiagDirToAxis(enterdir) == GetCrossingRoadAxis(tile)) continue; // different axis
    00367
    00368         if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
    00369         tile += TileOffsByDiagDir(exitdir);
    00370         break;
    00371
    00372       case MP_TUNNELBRIDGE: {
    00373         if (GetTileOwner(tile) != owner) continue;
    00374         if (GetTunnelBridgeTransportType(tile) != TRANSPORT_RAIL) continue;
    00375         DiagDirection dir = GetTunnelBridgeDirection(tile);
    00376
    00377         if (enterdir == INVALID_DIAGDIR) { // incoming from the wormhole
    00378           if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
    00379           enterdir = dir;
    00380           exitdir = ReverseDiagDir(dir);
    00381           tile += TileOffsByDiagDir(exitdir); // just skip to next tile
    00382         } else { // NOT incoming from the wormhole!
    00383           if (ReverseDiagDir(enterdir) != dir) continue;
    00384           if (!(flags & SF_TRAIN) && HasVehicleOnPos(tile, NULL, &TrainOnTileEnum)) flags |= SF_TRAIN;
    00385           tile = GetOtherTunnelBridgeEnd(tile); // just skip to exit tile
    00386           enterdir = INVALID_DIAGDIR;
    00387           exitdir = INVALID_DIAGDIR;
    00388         }
    00389         }
    00390         break;
    00391
    00392       default:
    00393         continue; // continue the while() loop
    00394     }
    00395
    00396     if (!MaybeAddToTodoSet(tile, enterdir, oldtile, exitdir)) return flags | SF_FULL;
    00397   }
    00398
    00399   return flags;
    00400 }
    00401
    00402
    00408 static void UpdateSignalsAroundSegment(SigFlags flags)
    00409 {
    00410   TileIndex tile;
    00411   Trackdir trackdir;
    00412
    00413   while (_tbuset.Get(&tile, &trackdir)) {
    00414     assert(HasSignalOnTrackdir(tile, trackdir));
    00415
    00416     SignalType sig = GetSignalType(tile, TrackdirToTrack(trackdir));
    00417     SignalState newstate = SIGNAL_STATE_GREEN;
    00418
    00419     /* determine whether the new state is red */
    00420     if (flags & SF_TRAIN) {
    00421       /* train in the segment */
    00422       newstate = SIGNAL_STATE_RED;
    00423     } else {
    00424       /* is it a bidir combo? - then do not count its other signal direction as exit */
    00425       if (sig == SIGTYPE_COMBO && HasSignalOnTrackdir(tile, ReverseTrackdir(trackdir))) {
    00426         /* at least one more exit */
    00427         if ((flags & SF_EXIT2) &&
    00428             /* no green exit */
    00429             (!(flags & SF_GREEN) ||
    00430             /* only one green exit, and it is this one - so all other exits are red */
    00431             (!(flags & SF_GREEN2) && GetSignalStateByTrackdir(tile, ReverseTrackdir(trackdir)) == SIGNAL_STATE_GREEN))) {
    00432           newstate = SIGNAL_STATE_RED;
    00433         }
    00434       } else { // entry, at least one exit, no green exit
    00435         if (IsPresignalEntry(tile, TrackdirToTrack(trackdir)) && (flags & SF_EXIT) && !(flags & SF_GREEN)) newstate = SIGNAL_STATE_RED;
    00436       }
    00437     }
    00438
    00439     /* only when the state changes */
    00440     if (newstate != GetSignalStateByTrackdir(tile, trackdir)) {
    00441       if (IsPresignalExit(tile, TrackdirToTrack(trackdir))) {
    00442         /* for pre-signal exits, add block to the global set */
    00443         DiagDirection exitdir = TrackdirToExitdir(ReverseTrackdir(trackdir));
    00444         _globset.Add(tile, exitdir); // do not check for full global set, first update all signals
    00445       }
    00446       SetSignalStateByTrackdir(tile, trackdir, newstate);
    00447       MarkTileDirtyByTile(tile);
    00448     }
    00449   }
    00450
    00451 }
    00452
    00453
    00455 static inline void ResetSets()
    00456 {
    00457   _tbuset.Reset();
    00458   _tbdset.Reset();
    00459   _globset.Reset();
    00460 }
    00461
    00462
    00470 static SigSegState UpdateSignalsInBuffer(Owner owner)
    00471 {
    00472   assert(Company::IsValidID(owner));
    00473
    00474   bool first = true;  // first block?
    00475   SigSegState state = SIGSEG_FREE; // value to return
    00476
    00477   TileIndex tile;
    00478   DiagDirection dir;
    00479
    00480   while (_globset.Get(&tile, &dir)) {
    00481     assert(_tbuset.IsEmpty());
    00482     assert(_tbdset.IsEmpty());
    00483
    00484     /* After updating signal, data stored are always MP_RAILWAY with signals.
    00485      * Other situations happen when data are from outside functions -
    00486      * modification of railbits (including both rail building and removal),
    00487      * train entering/leaving block, train leaving depot...
    00488      */
    00489     switch (GetTileType(tile)) {
    00490       case MP_TUNNELBRIDGE:
    00491         /* 'optimization assert' - do not try to update signals when it is not needed */
    00492         assert(GetTunnelBridgeTransportType(tile) == TRANSPORT_RAIL);
    00493         assert(dir == INVALID_DIAGDIR || dir == ReverseDiagDir(GetTunnelBridgeDirection(tile)));
    00494         _tbdset.Add(tile, INVALID_DIAGDIR);  // we can safely start from wormhole centre
    00495         _tbdset.Add(GetOtherTunnelBridgeEnd(tile), INVALID_DIAGDIR);
    00496         break;
    00497
    00498       case MP_RAILWAY:
    00499         if (IsRailDepot(tile)) {
    00500           /* 'optimization assert' do not try to update signals in other cases */
    00501           assert(dir == INVALID_DIAGDIR || dir == GetRailDepotDirection(tile));
    00502           _tbdset.Add(tile, INVALID_DIAGDIR); // start from depot inside
    00503           break;
    00504         }
    00505         /* FALLTHROUGH */
    00506       case MP_STATION:
    00507       case MP_ROAD:
    00508         if ((TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, 0)) & _enterdir_to_trackbits[dir]) != TRACK_BIT_NONE) {
    00509           /* only add to set when there is some 'interesting' track */
    00510           _tbdset.Add(tile, dir);
    00511           _tbdset.Add(tile + TileOffsByDiagDir(dir), ReverseDiagDir(dir));
    00512           break;
    00513         }
    00514         /* FALLTHROUGH */
    00515       default:
    00516         /* jump to next tile */
    00517         tile = tile + TileOffsByDiagDir(dir);
    00518         dir = ReverseDiagDir(dir);
    00519         if ((TrackStatusToTrackBits(GetTileTrackStatus(tile, TRANSPORT_RAIL, 0)) & _enterdir_to_trackbits[dir]) != TRACK_BIT_NONE) {
    00520           _tbdset.Add(tile, dir);
    00521           break;
    00522         }
    00523         /* happens when removing a rail that wasn't connected at one or both sides */
    00524         continue; // continue the while() loop
    00525     }
    00526
    00527     assert(!_tbdset.Overflowed()); // it really shouldn't overflow by these one or two items
    00528     assert(!_tbdset.IsEmpty()); // it wouldn't hurt anyone, but shouldn't happen too
    00529
    00530     SigFlags flags = ExploreSegment(owner);
    00531
    00532     if (first) {
    00533       first = false;
    00534       /* SIGSEG_FREE is set by default */
    00535       if (flags & SF_PBS) {
    00536         state = SIGSEG_PBS;
    00537       } else if ((flags & SF_TRAIN) || ((flags & SF_EXIT) && !(flags & SF_GREEN)) || (flags & SF_FULL)) {
    00538         state = SIGSEG_FULL;
    00539       }
    00540     }
    00541
    00542     /* do not do anything when some buffer was full */
    00543     if (flags & SF_FULL) {
    00544       ResetSets(); // free all sets
    00545       break;
    00546     }
    00547
    00548     UpdateSignalsAroundSegment(flags);
    00549   }
    00550
    00551   return state;
    00552 }
    00553
    00554
    00555 static Owner _last_owner = INVALID_OWNER;
    00556
    00557
    00562 void UpdateSignalsInBuffer()
    00563 {
    00564   if (!_globset.IsEmpty()) {
    00565     UpdateSignalsInBuffer(_last_owner);
    00566     _last_owner = INVALID_OWNER; // invalidate
    00567   }
    00568 }
    00569
    00570
    00578 void AddTrackToSignalBuffer(TileIndex tile, Track track, Owner owner)
    00579 {
    00580   static const DiagDirection _search_dir_1[] = {
    00581     DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_NE, DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_SE
    00582   };
    00583   static const DiagDirection _search_dir_2[] = {
    00584     DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NW, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_NE
    00585   };
    00586
    00587   /* do not allow signal updates for two companies in one run */
    00588   assert(_globset.IsEmpty() || owner == _last_owner);
    00589
    00590   _last_owner = owner;
    00591
    00592   _globset.Add(tile, _search_dir_1[track]);
    00593   _globset.Add(tile, _search_dir_2[track]);
    00594
    00595   if (_globset.Items() >= SIG_GLOB_UPDATE) {
    00596     /* too many items, force update */
    00597     UpdateSignalsInBuffer(_last_owner);
    00598     _last_owner = INVALID_OWNER;
    00599   }
    00600 }
    00601
    00602
    00610 void AddSideToSignalBuffer(TileIndex tile, DiagDirection side, Owner owner)
    00611 {
    00612   /* do not allow signal updates for two companies in one run */
    00613   assert(_globset.IsEmpty() || owner == _last_owner);
    00614
    00615   _last_owner = owner;
    00616
    00617   _globset.Add(tile, side);
    00618
    00619   if (_globset.Items() >= SIG_GLOB_UPDATE) {
    00620     /* too many items, force update */
    00621     UpdateSignalsInBuffer(_last_owner);
    00622     _last_owner = INVALID_OWNER;
    00623   }
    00624 }
    00625
    00636 SigSegState UpdateSignalsOnSegment(TileIndex tile, DiagDirection side, Owner owner)
    00637 {
    00638   assert(_globset.IsEmpty());
    00639   _globset.Add(tile, side);
    00640
    00641   return UpdateSignalsInBuffer(owner);
    00642 }
    00643
    00644
    00654 void SetSignalsOnBothDir(TileIndex tile, Track track, Owner owner)
    00655 {
    00656   assert(_globset.IsEmpty());
    00657
    00658   AddTrackToSignalBuffer(tile, track, owner);
    00659   UpdateSignalsInBuffer(owner);
    00660 }