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

Generated on Sun Jan 23 01:49:09 2011 for OpenTTD by  doxygen 1.6.1