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 }

Generated on Mon Feb 16 23:12:10 2009 for openttd by  doxygen 1.5.6