# signal.cpp

Go to the documentation of this file.

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