# roadstop.cpp

Go to the documentation of this file.

    00001 /* $Id: roadstop.cpp 24900 2013-01-08 22:46:42Z planetmaker $ */
    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 "roadveh.h"
    00014 #include "core/pool_func.hpp"
    00015 #include "roadstop_base.h"
    00016 #include "station_base.h"
    00017 #include "vehicle_func.h"
    00018
    00020 RoadStopPool _roadstop_pool("RoadStop");
    00021 INSTANTIATE_POOL_METHODS(RoadStop)
    00022
    00023
    00026 RoadStop::~RoadStop()
    00027 {
    00028   /* When we are the head we need to free the entries */
    00029   if (HasBit(this->status, RSSFB_BASE_ENTRY)) {
    00030     delete this->east;
    00031     delete this->west;
    00032   }
    00033
    00034   if (CleaningPool()) return;
    00035 }
    00036
    00042 RoadStop *RoadStop::GetNextRoadStop(const RoadVehicle *v) const
    00043 {
    00044   for (RoadStop *rs = this->next; rs != NULL; rs = rs->next) {
    00045     /* The vehicle cannot go to this roadstop (different roadtype) */
    00046     if ((GetRoadTypes(rs->xy) & v->compatible_roadtypes) == ROADTYPES_NONE) continue;
    00047     /* The vehicle is articulated and can therefore not go to a standard road stop. */
    00048     if (IsStandardRoadStopTile(rs->xy) && v->HasArticulatedPart()) continue;
    00049
    00050     /* The vehicle can actually go to this road stop. So, return it! */
    00051     return rs;
    00052   }
    00053
    00054   return NULL;
    00055 }
    00056
    00062 void RoadStop::MakeDriveThrough()
    00063 {
    00064   assert(this->east == NULL && this->west == NULL);
    00065
    00066   RoadStopType rst = GetRoadStopType(this->xy);
    00067   DiagDirection dir = GetRoadStopDir(this->xy);
    00068   /* Use absolute so we always go towards the northern tile */
    00069   TileIndexDiff offset = abs(TileOffsByDiagDir(dir));
    00070
    00071   /* Information about the tile north of us */
    00072   TileIndex north_tile = this->xy - offset;
    00073   bool north = IsDriveThroughRoadStopContinuation(this->xy, north_tile);
    00074   RoadStop *rs_north = north ? RoadStop::GetByTile(north_tile, rst) : NULL;
    00075
    00076   /* Information about the tile south of us */
    00077   TileIndex south_tile = this->xy + offset;
    00078   bool south = IsDriveThroughRoadStopContinuation(this->xy, south_tile);
    00079   RoadStop *rs_south = south ? RoadStop::GetByTile(south_tile, rst) : NULL;
    00080
    00081   /* Amount of road stops that will be added to the 'northern' head */
    00082   int added = 1;
    00083   if (north && rs_north->east != NULL) { // (east != NULL) == (west != NULL)
    00084     /* There is a more northern one, so this can join them */
    00085     this->east = rs_north->east;
    00086     this->west = rs_north->west;
    00087
    00088     if (south && rs_south->east != NULL) { // (east != NULL) == (west != NULL)
    00089       /* There more southern tiles too, they must 'join' us too */
    00090       ClrBit(rs_south->status, RSSFB_BASE_ENTRY);
    00091       this->east->occupied += rs_south->east->occupied;
    00092       this->west->occupied += rs_south->west->occupied;
    00093
    00094       /* Free the now unneeded entry structs */
    00095       delete rs_south->east;
    00096       delete rs_south->west;
    00097
    00098       /* Make all 'children' of the southern tile take the new master */
    00099       for (; IsDriveThroughRoadStopContinuation(this->xy, south_tile); south_tile += offset) {
    00100         rs_south = RoadStop::GetByTile(south_tile, rst);
    00101         if (rs_south->east == NULL) break;
    00102         rs_south->east = rs_north->east;
    00103         rs_south->west = rs_north->west;
    00104         added++;
    00105       }
    00106     }
    00107   } else if (south && rs_south->east != NULL) { // (east != NULL) == (west != NULL)
    00108     /* There is one to the south, but not to the north... so we become 'parent' */
    00109     this->east = rs_south->east;
    00110     this->west = rs_south->west;
    00111     SetBit(this->status, RSSFB_BASE_ENTRY);
    00112     ClrBit(rs_south->status, RSSFB_BASE_ENTRY);
    00113   } else {
    00114     /* We are the only... so we are automatically the master */
    00115     this->east = new Entry();
    00116     this->west = new Entry();
    00117     SetBit(this->status, RSSFB_BASE_ENTRY);
    00118   }
    00119
    00120   /* Now update the lengths */
    00121   added *= TILE_SIZE;
    00122   this->east->length += added;
    00123   this->west->length += added;
    00124 }
    00125
    00130 void RoadStop::ClearDriveThrough()
    00131 {
    00132   assert(this->east != NULL && this->west != NULL);
    00133
    00134   RoadStopType rst = GetRoadStopType(this->xy);
    00135   DiagDirection dir = GetRoadStopDir(this->xy);
    00136   /* Use absolute so we always go towards the northern tile */
    00137   TileIndexDiff offset = abs(TileOffsByDiagDir(dir));
    00138
    00139   /* Information about the tile north of us */
    00140   TileIndex north_tile = this->xy - offset;
    00141   bool north = IsDriveThroughRoadStopContinuation(this->xy, north_tile);
    00142   RoadStop *rs_north = north ? RoadStop::GetByTile(north_tile, rst) : NULL;
    00143
    00144   /* Information about the tile south of us */
    00145   TileIndex south_tile = this->xy + offset;
    00146   bool south = IsDriveThroughRoadStopContinuation(this->xy, south_tile);
    00147   RoadStop *rs_south = south ? RoadStop::GetByTile(south_tile, rst) : NULL;
    00148
    00149   /* Must only be cleared after we determined which neighbours are
    00150    * part of our little entry 'queue' */
    00151   DoClearSquare(this->xy);
    00152
    00153   if (north) {
    00154     /* There is a tile to the north, so we can't clear ourselves. */
    00155     if (south) {
    00156       /* There are more southern tiles too, they must be split;
    00157        * first make the new southern 'base' */
    00158       SetBit(rs_south->status, RSSFB_BASE_ENTRY);
    00159       rs_south->east = new Entry();
    00160       rs_south->west = new Entry();
    00161
    00162       /* Keep track of the base because we need it later on */
    00163       RoadStop *rs_south_base = rs_south;
    00164       TileIndex base_tile = south_tile;
    00165
    00166       /* Make all (even more) southern stops part of the new entry queue */
    00167       for (south_tile += offset; IsDriveThroughRoadStopContinuation(base_tile, south_tile); south_tile += offset) {
    00168         rs_south = RoadStop::GetByTile(south_tile, rst);
    00169         rs_south->east = rs_south_base->east;
    00170         rs_south->west = rs_south_base->west;
    00171       }
    00172
    00173       /* Find the other end; the northern most tile */
    00174       for (; IsDriveThroughRoadStopContinuation(base_tile, north_tile); north_tile -= offset) {
    00175         rs_north = RoadStop::GetByTile(north_tile, rst);
    00176       }
    00177
    00178       /* We have to rebuild the entries because we cannot easily determine
    00179        * how full each part is. So instead of keeping and maintaining a list
    00180        * of vehicles and using that to 'rebuild' the occupied state we just
    00181        * rebuild it from scratch as that removes lots of maintenance code
    00182        * for the vehicle list and it's faster in real games as long as you
    00183        * do not keep split and merge road stop every tick by the millions. */
    00184       rs_south_base->east->Rebuild(rs_south_base);
    00185       rs_south_base->west->Rebuild(rs_south_base);
    00186
    00187       assert(HasBit(rs_north->status, RSSFB_BASE_ENTRY));
    00188       rs_north->east->Rebuild(rs_north);
    00189       rs_north->west->Rebuild(rs_north);
    00190     } else {
    00191       /* Only we left, so simple update the length. */
    00192       rs_north->east->length -= TILE_SIZE;
    00193       rs_north->west->length -= TILE_SIZE;
    00194     }
    00195   } else if (south) {
    00196     /* There is only something to the south. Hand over the base entry */
    00197     SetBit(rs_south->status, RSSFB_BASE_ENTRY);
    00198     rs_south->east->length -= TILE_SIZE;
    00199     rs_south->west->length -= TILE_SIZE;
    00200   } else {
    00201     /* We were the last */
    00202     delete this->east;
    00203     delete this->west;
    00204   }
    00205
    00206   /* Make sure we don't get used for something 'incorrect' */
    00207   ClrBit(this->status, RSSFB_BASE_ENTRY);
    00208   this->east = NULL;
    00209   this->west = NULL;
    00210 }
    00211
    00216 void RoadStop::Leave(RoadVehicle *rv)
    00217 {
    00218   if (IsStandardRoadStopTile(rv->tile)) {
    00219     /* Vehicle is leaving a road stop tile, mark bay as free */
    00220     this->FreeBay(HasBit(rv->state, RVS_USING_SECOND_BAY));
    00221     this->SetEntranceBusy(false);
    00222   } else {
    00223     /* Otherwise just leave the drive through's entry cache. */
    00224     this->GetEntry(DirToDiagDir(rv->direction))->Leave(rv);
    00225   }
    00226 }
    00227
    00233 bool RoadStop::Enter(RoadVehicle *rv)
    00234 {
    00235   if (IsStandardRoadStopTile(this->xy)) {
    00236     /* For normal (non drive-through) road stops
    00237      * Check if station is busy or if there are no free bays or whether it is a articulated vehicle. */
    00238     if (this->IsEntranceBusy() || !this->HasFreeBay() || rv->HasArticulatedPart()) return false;
    00239
    00240     SetBit(rv->state, RVS_IN_ROAD_STOP);
    00241
    00242     /* Allocate a bay and update the road state */
    00243     uint bay_nr = this->AllocateBay();
    00244     SB(rv->state, RVS_USING_SECOND_BAY, 1, bay_nr);
    00245
    00246     /* Mark the station entrance as busy */
    00247     this->SetEntranceBusy(true);
    00248     return true;
    00249   }
    00250
    00251   /* Vehicles entering a drive-through stop from the 'normal' side use first bay (bay 0). */
    00252   this->GetEntry(DirToDiagDir(rv->direction))->Enter(rv);
    00253
    00254   /* Indicate a drive-through stop */
    00255   SetBit(rv->state, RVS_IN_DT_ROAD_STOP);
    00256   return true;
    00257 }
    00258
    00266 /* static */ RoadStop *RoadStop::GetByTile(TileIndex tile, RoadStopType type)
    00267 {
    00268   const Station *st = Station::GetByTile(tile);
    00269
    00270   for (RoadStop *rs = st->GetPrimaryRoadStop(type);; rs = rs->next) {
    00271     if (rs->xy == tile) return rs;
    00272     assert(rs->next != NULL);
    00273   }
    00274 }
    00275
    00280 void RoadStop::Entry::Leave(const RoadVehicle *rv)
    00281 {
    00282   this->occupied -= rv->gcache.cached_total_length;
    00283   assert(this->occupied >= 0);
    00284 }
    00285
    00290 void RoadStop::Entry::Enter(const RoadVehicle *rv)
    00291 {
    00292   /* we cannot assert on this->occupied < this->length because of the
    00293    * remote possibility that RVs are running through each other when
    00294    * trying to prevention an infinite jam. */
    00295   this->occupied += rv->gcache.cached_total_length;
    00296 }
    00297
    00305 /* static */ bool RoadStop::IsDriveThroughRoadStopContinuation(TileIndex rs, TileIndex next)
    00306 {
    00307   return IsTileType(next, MP_STATION) &&
    00308       GetStationIndex(next) == GetStationIndex(rs) &&
    00309       GetStationType(next) == GetStationType(rs) &&
    00310       GetRoadStopDir(next) == GetRoadStopDir(rs) &&
    00311       IsDriveThroughStopTile(next);
    00312 }
    00313
    00314 typedef std::list<const RoadVehicle *> RVList;
    00315
    00317 struct RoadStopEntryRebuilderHelper {
    00318   RVList vehicles;
    00319   DiagDirection dir;
    00320 };
    00321
    00328 Vehicle *FindVehiclesInRoadStop(Vehicle *v, void *data)
    00329 {
    00330   RoadStopEntryRebuilderHelper *rserh = (RoadStopEntryRebuilderHelper*)data;
    00331   /* Not a RV or not in the right direction or crashed :( */
    00332   if (v->type != VEH_ROAD || DirToDiagDir(v->direction) != rserh->dir || !v->IsPrimaryVehicle() || (v->vehstatus & VS_CRASHED) != 0) return NULL;
    00333
    00334   RoadVehicle *rv = RoadVehicle::From(v);
    00335   /* Don't add ones not in a road stop */
    00336   if (rv->state < RVSB_IN_ROAD_STOP) return NULL;
    00337
    00338   /* Do not add duplicates! */
    00339   for (RVList::iterator it = rserh->vehicles.begin(); it != rserh->vehicles.end(); it++) {
    00340     if (rv == *it) return NULL;
    00341   }
    00342
    00343   rserh->vehicles.push_back(rv);
    00344   return NULL;
    00345 }
    00346
    00352 void RoadStop::Entry::Rebuild(const RoadStop *rs, int side)
    00353 {
    00354   assert(HasBit(rs->status, RSSFB_BASE_ENTRY));
    00355
    00356   DiagDirection dir = GetRoadStopDir(rs->xy);
    00357   if (side == -1) side = (rs->east == this);
    00358
    00359   RoadStopEntryRebuilderHelper rserh;
    00360   rserh.dir = side ? dir : ReverseDiagDir(dir);
    00361
    00362   this->length = 0;
    00363   TileIndexDiff offset = abs(TileOffsByDiagDir(dir));
    00364   for (TileIndex tile = rs->xy; IsDriveThroughRoadStopContinuation(rs->xy, tile); tile += offset) {
    00365     this->length += TILE_SIZE;
    00366     FindVehicleOnPos(tile, &rserh, FindVehiclesInRoadStop);
    00367   }
    00368
    00369   this->occupied = 0;
    00370   for (RVList::iterator it = rserh.vehicles.begin(); it != rserh.vehicles.end(); it++) {
    00371     this->occupied += (*it)->gcache.cached_total_length;
    00372   }
    00373 }
    00374
    00375
    00380 void RoadStop::Entry::CheckIntegrity(const RoadStop *rs) const
    00381 {
    00382   if (!HasBit(rs->status, RSSFB_BASE_ENTRY)) return;
    00383
    00384   /* The tile 'before' the road stop must not be part of this 'line' */
    00385   assert(!IsDriveThroughRoadStopContinuation(rs->xy, rs->xy - abs(TileOffsByDiagDir(GetRoadStopDir(rs->xy)))));
    00386
    00387   Entry temp;
    00388   temp.Rebuild(rs, rs->east == this);
    00389   if (temp.length != this->length || temp.occupied != this->occupied) NOT_REACHED();
    00390 }