# signal.cpp

Go to the documentation of this file.

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