signal.cpp

Go to the documentation of this file.
00001 /* $Id$ */
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 "infrastructure_func.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 (!IsOneSignalBlock(owner, GetTileOwner(tile))) continue;
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 (!IsOneSignalBlock(owner, GetTileOwner(tile))) 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 (!IsOneSignalBlock(owner, GetTileOwner(tile))) 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 (!IsOneSignalBlock(owner, GetTileOwner(tile))) 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    * if these companies are not part of the same signal block */
00589   assert(_globset.IsEmpty() || IsOneSignalBlock(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    * if these companies are not part of the same signal block */
00615   assert(_globset.IsEmpty() || IsOneSignalBlock(owner, _last_owner));
00616 
00617   _last_owner = owner;
00618 
00619   _globset.Add(tile, side);
00620 
00621   if (_globset.Items() >= SIG_GLOB_UPDATE) {
00622     /* too many items, force update */
00623     UpdateSignalsInBuffer(_last_owner);
00624     _last_owner = INVALID_OWNER;
00625   }
00626 }
00627 
00638 SigSegState UpdateSignalsOnSegment(TileIndex tile, DiagDirection side, Owner owner)
00639 {
00640   assert(_globset.IsEmpty());
00641   _globset.Add(tile, side);
00642 
00643   return UpdateSignalsInBuffer(owner);
00644 }
00645 
00646 
00656 void SetSignalsOnBothDir(TileIndex tile, Track track, Owner owner)
00657 {
00658   assert(_globset.IsEmpty());
00659 
00660   AddTrackToSignalBuffer(tile, track, owner);
00661   UpdateSignalsInBuffer(owner);
00662 }

Generated on Wed Dec 30 20:40:06 2009 for OpenTTD by  doxygen 1.5.6