roadstop.cpp

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