(svn r11011) -Fix [FS#1129]: GetFirstVehicleInChain did change the game state while being marked const.
authorrubidium
Thu, 30 Aug 2007 21:11:12 +0000
changeset 7993 76b0eb3e19c9
parent 7992 8ac3fcd8d570
child 7994 d5e8a6a01c4d
(svn r11011) -Fix [FS#1129]: GetFirstVehicleInChain did change the game state while being marked const.
-Codechange: do not brute force determine the first vehicle in the chain or previous vehicle, but do it by properly accounting the previous and first pointers when updating the next pointer. This gives a performance increase of about 15% when there are a lot of vehicles in the game.
src/autoreplace_cmd.cpp
src/depot_gui.cpp
src/newgrf_engine.cpp
src/order_gui.cpp
src/roadveh_cmd.cpp
src/train_cmd.cpp
src/vehicle.cpp
src/vehicle.h
src/viewport.cpp
src/water_cmd.cpp
--- a/src/autoreplace_cmd.cpp	Thu Aug 30 20:40:33 2007 +0000
+++ b/src/autoreplace_cmd.cpp	Thu Aug 30 21:11:12 2007 +0000
@@ -52,7 +52,7 @@
 	 * the complete train, which is without the weight of cargo we just
 	 * moved back into some (of the) new wagon(s).
 	 */
-	if (dest->type == VEH_TRAIN) TrainConsistChanged(dest->first);
+	if (dest->type == VEH_TRAIN) TrainConsistChanged(dest->First());
 }
 
 static bool VerifyAutoreplaceRefitForOrders(const Vehicle *v, const EngineID engine_type)
@@ -61,7 +61,7 @@
 	const Vehicle *u;
 
 	if (v->type == VEH_TRAIN) {
-		u = GetFirstVehicleInChain(v);
+		u = v->First();
 	} else {
 		u = v;
 	}
@@ -104,7 +104,7 @@
 
 	/* the old engine didn't have cargo capacity, but the new one does
 	 * now we will figure out what cargo the train is carrying and refit to fit this */
-	v = GetFirstVehicleInChain(v);
+	v = v->First();
 	do {
 		if (v->cargo_cap == 0) continue;
 		/* Now we found a cargo type being carried on the train and we will see if it is possible to carry to this one */
@@ -200,9 +200,9 @@
 			 * sell the old engine in a moment
 			 */
 			/* Get the vehicle in front of the one we move out */
-			Vehicle *front = GetPrevVehicleInChain(old_v);
+			Vehicle *front = old_v->Previous();
 			/* If the vehicle in front is the rear end of a dualheaded engine, then we need to use the one in front of that one */
-			if (IsMultiheaded(front) && !IsTrainEngine(front)) front = GetPrevVehicleInChain(front);
+			if (IsMultiheaded(front) && !IsTrainEngine(front)) front = front->Previous();
 			/* Now we move the old one out of the train */
 			DoCommand(0, (INVALID_VEHICLE << 16) | old_v->index, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE);
 			/* Add the new vehicle */
@@ -236,7 +236,7 @@
 			}
 		}
 		/* We are done setting up the new vehicle. Now we move the cargo from the old one to the new one */
-		MoveVehicleCargo(new_v->type == VEH_TRAIN ? GetFirstVehicleInChain(new_v) : new_v, old_v);
+		MoveVehicleCargo(new_v->type == VEH_TRAIN ? new_v->First() : new_v, old_v);
 
 		// Get the name of the old vehicle if it has a custom name.
 		if (!IsCustomName(old_v->string_id)) {
--- a/src/depot_gui.cpp	Thu Aug 30 20:40:33 2007 +0000
+++ b/src/depot_gui.cpp	Thu Aug 30 21:11:12 2007 +0000
@@ -359,7 +359,7 @@
 			while (v != NULL && (x -= v->u.rail.cached_veh_length) >= 0) v = v->Next();
 
 			/* if an articulated part was selected, find its parent */
-			while (v != NULL && IsArticulatedPart(v)) v = GetPrevVehicleInChain(v);
+			while (v != NULL && IsArticulatedPart(v)) v = v->Previous();
 
 			d->wagon = v;
 
@@ -398,7 +398,7 @@
 	if (wagon == NULL) {
 		if (head != NULL) wagon = GetLastVehicleInChain(head);
 	} else  {
-		wagon = GetPrevVehicleInChain(wagon);
+		wagon = wagon->Previous();
 		if (wagon == NULL) return;
 	}
 
@@ -475,7 +475,7 @@
 	if (v == NULL) return;
 
 	if (v->HasFront() && !v->IsPrimaryVehicle()) {
-		v = GetFirstVehicleInChain(v);
+		v = v->First();
 		/* Do nothing when clicking on a train in depot with no loc attached */
 		if (v->type == VEH_TRAIN && !IsFrontEngine(v)) return;
 	}
--- a/src/newgrf_engine.cpp	Thu Aug 30 20:40:33 2007 +0000
+++ b/src/newgrf_engine.cpp	Thu Aug 30 21:11:12 2007 +0000
@@ -516,7 +516,7 @@
 				byte chain_before = 0;
 				byte chain_after  = 0;
 
-				for (u = GetFirstVehicleInChain(v); u != v; u = u->Next()) {
+				for (u = v->First(); u != v; u = u->Next()) {
 					chain_before++;
 					if (variable == 0x41 && u->engine_type != v->engine_type) chain_before = 0;
 				}
@@ -610,7 +610,7 @@
 			 */
 			if (v->type != VEH_TRAIN) return 0;
 
-			const Vehicle *u_p = GetPrevVehicleInChain(v);
+			const Vehicle *u_p = v->Previous();
 			const Vehicle *u_n = v->Next();
 			DirDiff f = (u_p == NULL) ?  DIRDIFF_SAME : DirDifference(u_p->direction, v->direction);
 			DirDiff b = (u_n == NULL) ?  DIRDIFF_SAME : DirDifference(v->direction, u_n->direction);
@@ -758,8 +758,8 @@
 				case 0x75: return GB(v->u.rail.cached_power,  8, 24);
 				case 0x76: return GB(v->u.rail.cached_power, 16, 16);
 				case 0x77: return GB(v->u.rail.cached_power, 24,  8);
-				case 0x7C: return v->first->index;
-				case 0x7D: return GB(v->first->index, 8, 8);
+				case 0x7C: return v->First()->index;
+				case 0x7D: return GB(v->First()->index, 8, 8);
 				case 0x7F: return 0; // Used for vehicle reversing hack in TTDP
 			}
 			break;
@@ -804,7 +804,7 @@
 	if (v == NULL) return group->g.real.loading[0];
 
 	if (v->type == VEH_TRAIN) {
-		in_motion = GetFirstVehicleInChain(v)->current_order.type != OT_LOADING;
+		in_motion = v->First()->current_order.type != OT_LOADING;
 	} else {
 		in_motion = v->current_order.type != OT_LOADING;
 	}
@@ -832,7 +832,7 @@
 	res->ResolveReal   = &VehicleResolveReal;
 
 	res->u.vehicle.self   = v;
-	res->u.vehicle.parent = (v != NULL && v->HasFront()) ? GetFirstVehicleInChain(v) : v;
+	res->u.vehicle.parent = (v != NULL && v->HasFront()) ? v->First() : v;
 
 	res->u.vehicle.self_type = engine_type;
 
@@ -1033,7 +1033,7 @@
 			 * i.e.), so we give them all the NEW_CARGO triggered
 			 * vehicle's portion of random bits. */
 			assert(first);
-			DoTriggerVehicle((v->type == VEH_TRAIN) ? GetFirstVehicleInChain(v) : v, VEHICLE_TRIGGER_ANY_NEW_CARGO, new_random_bits, false);
+			DoTriggerVehicle((v->type == VEH_TRAIN) ? v->First() : v, VEHICLE_TRIGGER_ANY_NEW_CARGO, new_random_bits, false);
 			break;
 
 		case VEHICLE_TRIGGER_DEPOT:
--- a/src/order_gui.cpp	Thu Aug 30 20:40:33 2007 +0000
+++ b/src/order_gui.cpp	Thu Aug 30 21:11:12 2007 +0000
@@ -356,7 +356,7 @@
 	if (u->type != v->type) return false;
 
 	if (u->HasFront() && !u->IsPrimaryVehicle()) {
-		u = GetFirstVehicleInChain(u);
+		u = u->First();
 		if (!u->IsPrimaryVehicle()) return false;
 	}
 
--- a/src/roadveh_cmd.cpp	Thu Aug 30 20:40:33 2007 +0000
+++ b/src/roadveh_cmd.cpp	Thu Aug 30 21:11:12 2007 +0000
@@ -141,8 +141,8 @@
 	assert(IsRoadVehFront(v));
 
 	for (Vehicle *u = v; u != NULL; u = u->Next()) {
-		/* Update the v->first cache. */
-		if (u->first == NULL) u->first = v;
+		/* Check the v->first cache. */
+		assert(u->First() == v);
 
 		/* Update the 'first engine' */
 		u->u.road.first_engine = (v == u) ? INVALID_ENGINE : v->engine_type;
@@ -259,7 +259,6 @@
 		v->vehicle_flags = 0;
 		if (e->flags & ENGINE_EXCLUSIVE_PREVIEW) SETBIT(v->vehicle_flags, VF_BUILT_AS_PROTOTYPE);
 
-		v->first = NULL;
 		v->cargo_cap = GetVehicleProperty(v, 0x0F, rvi->capacity);
 
 		AddArticulatedParts(vl, VEH_ROAD);
@@ -868,7 +867,7 @@
 		!v->IsInDepot() &&
 		myabs(v->z_pos - rvf->veh->z_pos) < 6 &&
 		v->direction == rvf->dir &&
-		GetFirstVehicleInChain(rvf->veh) != GetFirstVehicleInChain(v) &&
+		rvf->veh->First() != v->First() &&
 		(dist_x[v->direction] >= 0 || (x_diff > dist_x[v->direction] && x_diff <= 0)) &&
 		(dist_x[v->direction] <= 0 || (x_diff < dist_x[v->direction] && x_diff >= 0)) &&
 		(dist_y[v->direction] >= 0 || (y_diff > dist_y[v->direction] && y_diff <= 0)) &&
--- a/src/train_cmd.cpp	Thu Aug 30 20:40:33 2007 +0000
+++ b/src/train_cmd.cpp	Thu Aug 30 21:11:12 2007 +0000
@@ -161,8 +161,8 @@
 	for (Vehicle *u = v; u != NULL; u = u->Next()) {
 		const RailVehicleInfo *rvi_u = RailVehInfo(u->engine_type);
 
-		/* Update the v->first cache. This is faster than having to brute force it later. */
-		if (u->first == NULL) u->first = v;
+		/* Check the v->first cache. */
+		assert(u->First() == v);
 
 		/* update the 'first engine' */
 		u->u.rail.first_engine = v == u ? INVALID_ENGINE : first_engine;
@@ -583,8 +583,8 @@
 			_new_vehicle_id = v->index;
 
 			VehiclePositionChanged(v);
-			TrainConsistChanged(GetFirstVehicleInChain(v));
-			UpdateTrainGroupID(GetFirstVehicleInChain(v));
+			TrainConsistChanged(v->First());
+			UpdateTrainGroupID(v->First());
 
 			InvalidateWindow(WC_VEHICLE_DEPOT, v->tile);
 			if (IsLocalPlayer()) {
@@ -620,6 +620,7 @@
 
 static void AddRearEngineToMultiheadedTrain(Vehicle* v, Vehicle* u, bool building)
 {
+	u = new (u) Train();
 	u->direction = v->direction;
 	u->owner = v->owner;
 	u->tile = v->tile;
@@ -628,7 +629,6 @@
 	u->z_pos = v->z_pos;
 	u->u.rail.track = TRACK_BIT_DEPOT;
 	u->vehstatus = v->vehstatus & ~VS_STOPPED;
-	u = new (u) Train();
 	u->subtype = 0;
 	SetMultiheaded(u);
 	u->spritenum = v->spritenum + 1;
@@ -841,7 +841,6 @@
 	Vehicle *u;
 	for (u = first; GetNextVehicle(u) != v; u = GetNextVehicle(u)) {}
 	GetLastEnginePart(u)->SetNext(GetNextVehicle(v));
-	v->first = NULL; // we shouldn't point to the old first, since the vehicle isn't in that chain anymore
 	return first;
 }
 
@@ -872,11 +871,12 @@
  */
 static void AddWagonToConsist(Vehicle *v, Vehicle *dest)
 {
-	UnlinkWagon(v, GetFirstVehicleInChain(v));
+	UnlinkWagon(v, v->First());
 	if (dest == NULL) return;
 
-	v->SetNext(dest->Next());
+	Vehicle *next = dest->Next();
 	dest->SetNext(v);
+	v->SetNext(next);
 	ClearFreeWagon(v);
 	ClearFrontEngine(v);
 }
@@ -933,19 +933,19 @@
 	}
 
 	/* if an articulated part is being handled, deal with its parent vehicle */
-	while (IsArticulatedPart(src)) src = GetPrevVehicleInChain(src);
+	while (IsArticulatedPart(src)) src = src->Previous();
 	if (dst != NULL) {
-		while (IsArticulatedPart(dst)) dst = GetPrevVehicleInChain(dst);
+		while (IsArticulatedPart(dst)) dst = dst->Previous();
 	}
 
 	/* don't move the same vehicle.. */
 	if (src == dst) return CommandCost();
 
 	/* locate the head of the two chains */
-	Vehicle *src_head = GetFirstVehicleInChain(src);
+	Vehicle *src_head = src->First();
 	Vehicle *dst_head;
 	if (dst != NULL) {
-		dst_head = GetFirstVehicleInChain(dst);
+		dst_head = dst->First();
 		if (dst_head->tile != src_head->tile) return CMD_ERROR;
 		/* Now deal with articulated part of destination wagon */
 		dst = GetLastEnginePart(dst);
@@ -1022,10 +1022,6 @@
 
 	/* do it? */
 	if (flags & DC_EXEC) {
-		/* clear the ->first cache */
-		for (Vehicle *u = src_head; u != NULL; u = u->Next()) u->first = NULL;
-		for (Vehicle *u = dst_head; u != NULL; u = u->Next()) u->first = NULL;
-
 		/* If we move the front Engine and if the second vehicle is not an engine
 		   add the whole vehicle to the DEFAULT_GROUP */
 		if (IsFrontEngine(src) && !IsDefaultGroupID(src->group_id)) {
@@ -1101,13 +1097,12 @@
 			}
 			dst->SetNext(src);
 		}
+
 		if (src->u.rail.other_multiheaded_part != NULL) {
 			if (src->u.rail.other_multiheaded_part == src_head) {
 				src_head = src_head->Next();
 			}
 			AddWagonToConsist(src->u.rail.other_multiheaded_part, src);
-			/* previous line set the front engine to the old front. We need to clear that */
-			src->u.rail.other_multiheaded_part->first = NULL;
 		}
 
 		/* If there is an engine behind first_engine we moved away, it should become new first_engine
@@ -1218,8 +1213,8 @@
 
 	SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES);
 
-	while (IsArticulatedPart(v)) v = GetPrevVehicleInChain(v);
-	Vehicle *first = GetFirstVehicleInChain(v);
+	while (IsArticulatedPart(v)) v = v->Previous();
+	Vehicle *first = v->First();
 
 	/* make sure the vehicle is stopped in the depot */
 	if (CheckTrainStoppedInDepot(first) < 0) {
@@ -1262,9 +1257,6 @@
 			if ((flags & DC_EXEC) && v == first) {
 				Vehicle *new_f = GetNextVehicle(first);
 
-				/* 2.1 If the first wagon is sold, update the first-> pointers to NULL */
-				for (Vehicle *tmp = first; tmp != NULL; tmp = tmp->Next()) tmp->first = NULL;
-
 				/* 2.2 If there are wagons present after the deleted front engine, check
 				 * if the second wagon (which will be first) is an engine. If it is one,
 				 * promote it as a new train, retaining the unitnumber, orders */
@@ -1633,7 +1625,7 @@
 			return_cmd_error(STR_ONLY_TURN_SINGLE_UNIT);
 		}
 
-		Vehicle *front = GetFirstVehicleInChain(v);
+		Vehicle *front = v->First();
 		/* make sure the vehicle is stopped in the depot */
 		if (CheckTrainStoppedInDepot(front) < 0) {
 			return_cmd_error(STR_881A_TRAINS_CAN_ONLY_BE_ALTERED);
@@ -1777,7 +1769,7 @@
 	_returned_refit_capacity = num;
 
 	/* Update the train's cached variables */
-	if (flags & DC_EXEC) TrainConsistChanged(GetFirstVehicleInChain(GetVehicle(p1)));
+	if (flags & DC_EXEC) TrainConsistChanged(GetVehicle(p1)->First());
 
 	return cost;
 }
@@ -2718,7 +2710,7 @@
 			myabs(v->x_pos - tcc->v->x_pos) < 6 &&
 			myabs(v->y_pos - tcc->v->y_pos) < 6 ) {
 
-		Vehicle *coll = GetFirstVehicleInChain(v);
+		Vehicle *coll = v->First();
 
 		/* it can't collide with its own wagons */
 		if (tcc->v == coll ||
@@ -2807,7 +2799,7 @@
 	Vehicle *prev;
 
 	/* For every vehicle after and including the given vehicle */
-	for (prev = GetPrevVehicleInChain(v); v != NULL; prev = v, v = v->Next()) {
+	for (prev = v->Previous(); v != NULL; prev = v, v = v->Next()) {
 		DiagDirection enterdir = DIAGDIR_BEGIN;
 		bool update_signals = false;
 		BeginVehicleMove(v);
@@ -2945,7 +2937,7 @@
 					v->tile = gp.new_tile;
 
 					if (GetTileRailType(gp.new_tile) != GetTileRailType(gp.old_tile)) {
-						TrainPowerChanged(GetFirstVehicleInChain(v));
+						TrainPowerChanged(v->First());
 					}
 
 					v->u.rail.track = chosen_track;
--- a/src/vehicle.cpp	Thu Aug 30 20:40:33 2007 +0000
+++ b/src/vehicle.cpp	Thu Aug 30 21:11:12 2007 +0000
@@ -209,6 +209,9 @@
 	Vehicle *v;
 
 	FOR_ALL_VEHICLES(v) {
+		/* Reinstate the previous pointer */
+		if (v->Next() != NULL) v->Next()->previous = v;
+
 		v->UpdateDeltaXY(v->direction);
 
 		v->fill_percent_te_id = INVALID_TE_ID;
@@ -220,6 +223,17 @@
 	}
 
 	FOR_ALL_VEHICLES(v) {
+		/* Fill the first pointers */
+		if (v->Previous() == NULL) {
+			for (Vehicle *u = v; u != NULL; u = u->Next()) {
+				u->first = v;
+			}
+		}
+	}
+
+	FOR_ALL_VEHICLES(v) {
+		assert(v->first != NULL);
+
 		if (v->type == VEH_TRAIN && (IsFrontEngine(v) || IsFreeWagon(v))) {
 			TrainConsistChanged(v);
 		} else if (v->type == VEH_ROAD && IsRoadVehFront(v)) {
@@ -269,6 +283,7 @@
 	this->left_coord         = INVALID_COORD;
 	this->group_id           = DEFAULT_GROUP;
 	this->fill_percent_te_id = INVALID_TE_ID;
+	this->first              = this;
 }
 
 /**
@@ -466,78 +481,6 @@
 	return v;
 }
 
-/** Finds the previous vehicle in a chain, by a brute force search.
- * This old function is REALLY slow because it searches through all vehicles to
- * find the previous vehicle, but if v->first has not been set, then this function
- * will need to be used to find the previous one. This function should never be
- * called by anything but GetFirstVehicleInChain
- */
-static Vehicle *GetPrevVehicleInChain_bruteforce(const Vehicle *v)
-{
-	Vehicle *u;
-
-	FOR_ALL_VEHICLES(u) if (u->type == v->type && u->Next() == v) return u;
-
-	return NULL;
-}
-
-/** Find the previous vehicle in a chain, by using the v->first cache.
- * While this function is fast, it cannot be used in the GetFirstVehicleInChain
- * function, otherwise you'll end up in an infinite loop call
- */
-Vehicle *GetPrevVehicleInChain(const Vehicle *v)
-{
-	Vehicle *u;
-	assert(v != NULL);
-
-	u = GetFirstVehicleInChain(v);
-
-	/* Check to see if this is the first */
-	if (v == u) return NULL;
-
-	for (; u->Next() != v; u = u->Next()) assert(u->Next() != NULL);
-
-	return u;
-}
-
-/** Finds the first vehicle in a chain.
- * This function reads out the v->first cache. Should the cache be dirty,
- * it determines the first vehicle in a chain, and updates the cache.
- */
-Vehicle *GetFirstVehicleInChain(const Vehicle *v)
-{
-	Vehicle* u;
-
-	assert(v != NULL);
-	assert(v->type == VEH_TRAIN || v->type == VEH_ROAD);
-
-	if (v->first != NULL) {
-		if (v->type == VEH_TRAIN) {
-			if (IsFrontEngine(v->first) || IsFreeWagon(v->first)) return v->first;
-		} else {
-			if (IsRoadVehFront(v->first)) return v->first;
-		}
-
-		DEBUG(misc, 0, "v->first cache faulty. We shouldn't be here, rebuilding cache!");
-	}
-
-	/* It is the fact (currently) that newly built vehicles do not have
-	 * their ->first pointer set. When this is the case, go up to the
-	 * first engine and set the pointers correctly. Also the first pointer
-	 * is not saved in a savegame, so this has to be fixed up after loading */
-
-	/* Find the 'locomotive' or the first wagon in a chain */
-	while ((u = GetPrevVehicleInChain_bruteforce(v)) != NULL) v = u;
-
-	/* Set the first pointer of all vehicles in that chain to the first wagon */
-	if ((v->type == VEH_TRAIN && (IsFrontEngine(v) || IsFreeWagon(v))) ||
-			(v->type == VEH_ROAD && IsRoadVehFront(v))) {
-		for (u = (Vehicle *)v; u != NULL; u = u->Next()) u->first = (Vehicle *)v;
-	}
-
-	return (Vehicle*)v;
-}
-
 uint CountVehiclesInChain(const Vehicle* v)
 {
 	uint count = 0;
@@ -2179,14 +2122,14 @@
 	switch (v->type) {
 		case VEH_TRAIN:
 			InvalidateWindowClasses(WC_TRAINS_LIST);
-			if (!IsFrontEngine(v)) v = GetFirstVehicleInChain(v);
+			if (!IsFrontEngine(v)) v = v->First();
 			UpdateSignalsOnSegment(v->tile, GetRailDepotDirection(v->tile));
 			v->load_unload_time_rem = 0;
 			break;
 
 		case VEH_ROAD:
 			InvalidateWindowClasses(WC_ROADVEH_LIST);
-			if (!IsRoadVehFront(v)) v = GetFirstVehicleInChain(v);
+			if (!IsRoadVehFront(v)) v = v->First();
 			break;
 
 		case VEH_SHIP:
@@ -3168,6 +3111,28 @@
 	InvalidateVehicleOrder(this);
 }
 
+void Vehicle::SetNext(Vehicle *next)
+{
+	if (this->next != NULL) {
+		/* We had an old next vehicle. Update the first and previous pointers */
+		for (Vehicle *v = this->next; v != NULL; v = v->Next()) {
+			v->first = this->next;
+		}
+		this->next->previous = NULL;
+	}
+
+	this->next = next;
+
+	if (this->next != NULL) {
+		/* A new next vehicle. Update the first and previous pointers */
+		if (this->next->previous != NULL) this->next->previous->next = NULL;
+		this->next->previous = this;
+		for (Vehicle *v = this->next; v != NULL; v = v->Next()) {
+			v->first = this->first;
+		}
+	}
+}
+
 void SpecialVehicle::UpdateDeltaXY(Direction direction)
 {
 	this->x_offs        = 0;
--- a/src/vehicle.h	Thu Aug 30 20:40:33 2007 +0000
+++ b/src/vehicle.h	Thu Aug 30 21:11:12 2007 +0000
@@ -219,7 +219,8 @@
 DECLARE_OLD_POOL(Vehicle, Vehicle, 9, 125)
 
 struct SaveLoad;
-extern const SaveLoad *GetVehicleDescription(VehicleType vt);
+const SaveLoad *GetVehicleDescription(VehicleType vt);
+void AfterLoadVehicles();
 
 struct Vehicle : PoolItem<Vehicle, VehicleID, &_Vehicle_pool> {
 	VehicleTypeByte type;    ///< Type of vehicle
@@ -227,10 +228,12 @@
 
 private:
 	Vehicle *next;           // pointer to the next vehicle in the chain
+	Vehicle *previous;       // NOSAVE: pointer to the previous vehicle in the chain
+	Vehicle *first;          // NOSAVE: pointer to the first vehicle in the chain
 public:
 	friend const SaveLoad *GetVehicleDescription(VehicleType vt); // So we can use private/protected variables in the saveload code
+	friend void AfterLoadVehicles();
 
-	Vehicle *first;          // NOSAVE: pointer to the first vehicle in the chain
 	Vehicle *depot_list;     // NOSAVE: linked list to tell what vehicles entered a depot during the last tick. Used by autoreplace
 
 	StringID string_id;      // Displayed string
@@ -472,7 +475,7 @@
 	 * Set the next vehicle of this vehicle.
 	 * @param next the next vehicle. NULL removes the next vehicle.
 	 */
-	void SetNext(Vehicle *next) { this->next = next; }
+	void SetNext(Vehicle *next);
 
 	/**
 	 * Get the next vehicle of this vehicle.
@@ -480,6 +483,19 @@
 	 * @return the next vehicle or NULL when there isn't a next vehicle.
 	 */
 	inline Vehicle *Next() const { return this->next; }
+
+	/**
+	 * Get the previous vehicle of this vehicle.
+	 * @note articulated parts are also counted as vehicles.
+	 * @return the previous vehicle or NULL when there isn't a previous vehicle.
+	 */
+	inline Vehicle *Previous() const { return this->previous; }
+
+	/**
+	 * Get the first vehicle of this vehicle chain.
+	 * @return the first vehicle of the chain.
+	 */
+	inline Vehicle *First() const { return this->first; }
 };
 
 /**
@@ -556,10 +572,7 @@
 
 void VehicleServiceInDepot(Vehicle *v);
 void VehiclePositionChanged(Vehicle *v);
-void AfterLoadVehicles();
 Vehicle *GetLastVehicleInChain(Vehicle *v);
-Vehicle *GetPrevVehicleInChain(const Vehicle *v);
-Vehicle *GetFirstVehicleInChain(const Vehicle *v);
 uint CountVehiclesInChain(const Vehicle *v);
 bool IsEngineCountable(const Vehicle *v);
 void DeleteVehicleChain(Vehicle *v);
--- a/src/viewport.cpp	Thu Aug 30 20:40:33 2007 +0000
+++ b/src/viewport.cpp	Thu Aug 30 21:11:12 2007 +0000
@@ -1786,13 +1786,13 @@
 
 static void SafeShowTrainViewWindow(const Vehicle* v)
 {
-	if (!IsFrontEngine(v)) v = GetFirstVehicleInChain(v);
+	if (!IsFrontEngine(v)) v = v->First();
 	ShowVehicleViewWindow(v);
 }
 
 static void SafeShowRoadVehViewWindow(const Vehicle *v)
 {
-	if (!IsRoadVehFront(v)) v = GetFirstVehicleInChain(v);
+	if (!IsRoadVehFront(v)) v = v->First();
 	ShowVehicleViewWindow(v);
 }
 
--- a/src/water_cmd.cpp	Thu Aug 30 20:40:33 2007 +0000
+++ b/src/water_cmd.cpp	Thu Aug 30 21:11:12 2007 +0000
@@ -658,7 +658,7 @@
 			}
 			Vehicle *u;
 
-			if (v->type != VEH_AIRCRAFT) v = GetFirstVehicleInChain(v);
+			if (v->type != VEH_AIRCRAFT) v = v->First();
 			u = v;
 
 			/* crash all wagons, and count passengers */