(svn r10111) -Codechange: Add new vehicle hash table for collision detection and finding vehicles on a tile. The hash area scanned is far smaller than the old hash table, which is now used for viewport updates only. This should give a significant performance improvement for games with many vehicles. (Based on work by 'B. N. SmatZ!' and 'madman2003')
authorpeter1138
Tue, 12 Jun 2007 11:22:32 +0000
changeset 7367 a0499d5cb8e5
parent 7366 3a76f142e0ab
child 7368 c8585746a177
(svn r10111) -Codechange: Add new vehicle hash table for collision detection and finding vehicles on a tile. The hash area scanned is far smaller than the old hash table, which is now used for viewport updates only. This should give a significant performance improvement for games with many vehicles. (Based on work by 'B. N. SmatZ!' and 'madman2003')
src/roadveh_cmd.cpp
src/train_cmd.cpp
src/vehicle.cpp
src/vehicle.h
--- a/src/roadveh_cmd.cpp	Tue Jun 12 09:41:12 2007 +0000
+++ b/src/roadveh_cmd.cpp	Tue Jun 12 11:22:32 2007 +0000
@@ -709,7 +709,7 @@
 
 		if (!IsLevelCrossingTile(tile)) continue;
 
-		if (VehicleFromPos(tile, u, EnumCheckRoadVehCrashTrain) != NULL) {
+		if (VehicleFromPosXY(v->x_pos, v->y_pos, u, EnumCheckRoadVehCrashTrain) != NULL) {
 			RoadVehCrash(v);
 			return;
 		}
@@ -889,7 +889,7 @@
 	rvf.y = y;
 	rvf.dir = dir;
 	rvf.veh = v;
-	u = (Vehicle*)VehicleFromPos(TileVirtXY(x, y), &rvf, EnumCheckRoadVehClose);
+	u = (Vehicle*)VehicleFromPosXY(x, y, &rvf, EnumCheckRoadVehClose);
 
 	/* This code protects a roadvehicle from being blocked for ever
 	 * If more than 1480 / 74 days a road vehicle is blocked, it will
--- a/src/train_cmd.cpp	Tue Jun 12 09:41:12 2007 +0000
+++ b/src/train_cmd.cpp	Tue Jun 12 11:22:32 2007 +0000
@@ -2722,7 +2722,7 @@
 	tcc.v_skip = v->next;
 
 	/* find colliding vehicle */
-	Vehicle *realcoll = (Vehicle*)VehicleFromPos(TileVirtXY(v->x_pos, v->y_pos), &tcc, FindTrainCollideEnum);
+	Vehicle *realcoll = (Vehicle*)VehicleFromPosXY(v->x_pos, v->y_pos, &tcc, FindTrainCollideEnum);
 	if (realcoll == NULL) return;
 
 	Vehicle *coll = GetFirstVehicleInChain(realcoll);
@@ -2775,6 +2775,8 @@
 
 	/* For every vehicle after and including the given vehicle */
 	for (prev = GetPrevVehicleInChain(v); v != NULL; prev = v, v = v->next) {
+		DiagDirection enterdir = DIAGDIR_BEGIN;
+		bool update_signals = false;
 		BeginVehicleMove(v);
 
 		GetNewVehiclePosResult gp = GetNewVehiclePos(v);
@@ -2810,7 +2812,7 @@
 
 				/* Determine what direction we're entering the new tile from */
 				Direction dir = GetNewVehicleDirectionByTile(gp.new_tile, gp.old_tile);
-				DiagDirection enterdir = DirToDiagDir(dir);
+				enterdir = DirToDiagDir(dir);
 				assert(IsValidDiagDirection(enterdir));
 
 				/* Get the status of the tracks in the new tile and mask
@@ -2917,11 +2919,9 @@
 					assert(v->u.rail.track);
 				}
 
-				if (IsFrontEngine(v)) TrainMovedChangeSignals(gp.new_tile, enterdir);
-
-				/* Signals can only change when the first
-				 * (above) or the last vehicle moves. */
-				if (v->next == NULL) TrainMovedChangeSignals(gp.old_tile, ReverseDiagDir(enterdir));
+				/* We need to update signal status, but after the vehicle position hash
+				 * has been updated by AfterSetTrainPos() */
+				update_signals = true;
 
 				if (prev == NULL) AffectSpeedByDirChange(v, chosen_dir);
 
@@ -2958,6 +2958,14 @@
 			/* This is the first vehicle in the train */
 			AffectSpeedByZChange(v, old_z);
 		}
+
+		if (update_signals) {
+			if (IsFrontEngine(v)) TrainMovedChangeSignals(gp.new_tile, enterdir);
+
+			/* Signals can only change when the first
+			 * (above) or the last vehicle moves. */
+			if (v->next == NULL) TrainMovedChangeSignals(gp.old_tile, ReverseDiagDir(enterdir));
+		}
 	}
 	return;
 
--- a/src/vehicle.cpp	Tue Jun 12 09:41:12 2007 +0000
+++ b/src/vehicle.cpp	Tue Jun 12 11:22:32 2007 +0000
@@ -394,44 +394,117 @@
 	return true;
 }
 
-
-static Vehicle *_vehicle_position_hash[0x1000];
+/* Size of the hash, 6 = 64 x 64, 7 = 128 x 128. Larger sizes will (in theory) reduce hash
+ * lookup times at the expense of memory usage. */
+const int HASH_BITS = 7;
+const int HASH_SIZE = 1 << HASH_BITS;
+const int HASH_MASK = HASH_SIZE - 1;
+const int TOTAL_HASH_SIZE = 1 << (HASH_BITS * 2);
+const int TOTAL_HASH_MASK = TOTAL_HASH_SIZE - 1;
+
+/* Resolution of the hash, 0 = 1*1 tile, 1 = 2*2 tiles, 2 = 4*4 tiles, etc.
+ * Profiling results show that 0 is fastest. */
+const int HASH_RES = 0;
+
+static Vehicle *_new_vehicle_position_hash[TOTAL_HASH_SIZE];
+
+static void *VehicleFromHash(int xl, int yl, int xu, int yu, void *data, VehicleFromPosProc *proc)
+{
+	for (int y = yl; ; y = (y + (1 << HASH_BITS)) & (HASH_MASK << HASH_BITS)) {
+		for (int x = xl; ; x = (x + 1) & HASH_MASK) {
+			Vehicle *v = _new_vehicle_position_hash[(x + y) & TOTAL_HASH_MASK];
+			for (; v != NULL; v = v->next_new_hash) {
+				void *a = proc(v, data);
+				if (a != NULL) return a;
+			}
+			if (x == xu) break;
+		}
+		if (y == yu) break;
+	}
+
+	return NULL;
+}
+
+
+void *VehicleFromPosXY(int x, int y, void *data, VehicleFromPosProc *proc)
+{
+	const int COLL_DIST = 6;
+
+	/* Hash area to scan is from xl,yl to xu,yu */
+	int xl = GB((x - COLL_DIST) / TILE_SIZE, HASH_RES, HASH_BITS);
+	int xu = GB((x + COLL_DIST) / TILE_SIZE, HASH_RES, HASH_BITS);
+	int yl = GB((y - COLL_DIST) / TILE_SIZE, HASH_RES, HASH_BITS) << HASH_BITS;
+	int yu = GB((y + COLL_DIST) / TILE_SIZE, HASH_RES, HASH_BITS) << HASH_BITS;
+
+	return VehicleFromHash(xl, yl, xu, yu, data, proc);
+}
+
 
 void *VehicleFromPos(TileIndex tile, void *data, VehicleFromPosProc *proc)
 {
-	Point pt = RemapCoords(TileX(tile) * TILE_SIZE, TileY(tile) * TILE_SIZE, 0);
-
-	/* The hash area to scan */
-	const int xl = GB(pt.x - 174, 7, 6);
-	const int xu = GB(pt.x + 104, 7, 6);
-	const int yl = GB(pt.y - 294, 6, 6) << 6;
-	const int yu = GB(pt.y +  56, 6, 6) << 6;
-
-	int x;
-	int y;
-
-	for (y = yl;; y = (y + (1 << 6)) & (0x3F << 6)) {
-		for (x = xl;; x = (x + 1) & 0x3F) {
-			Vehicle *v = _vehicle_position_hash[(x + y) & 0xFFFF];
-
-			while (v != NULL) {
-				void* a = proc(v, data);
-
-				if (a != NULL) return a;
-				v = v->next_hash;
-			}
-
-			if (x == xu) break;
-		}
-
-		if (y == yu) break;
+	int x = GB(TileX(tile), HASH_RES, HASH_BITS);
+	int y = GB(TileY(tile), HASH_RES, HASH_BITS) << HASH_BITS;
+
+	Vehicle *v = _new_vehicle_position_hash[(x + y) & TOTAL_HASH_MASK];
+	for (; v != NULL; v = v->next_new_hash) {
+		if (v->tile != tile) continue;
+
+		void *a = proc(v, data);
+		if (a != NULL) return a;
 	}
+
 	return NULL;
 }
 
+static void UpdateNewVehiclePosHash(Vehicle *v)
+{
+	Vehicle **old_hash = v->old_new_hash;
+	Vehicle **new_hash;
+
+	if (v->tile == INVALID_TILE || v->tile == 0) {
+		new_hash = NULL;
+	} else {
+		int x = GB(TileX(v->tile), HASH_RES, HASH_BITS);
+		int y = GB(TileY(v->tile), HASH_RES, HASH_BITS) << HASH_BITS;
+		new_hash = &_new_vehicle_position_hash[(x + y) & TOTAL_HASH_MASK];
+	}
+
+	if (old_hash == new_hash) return;
+
+	/* Remove from the old position in the hash table */
+	if (old_hash != NULL) {
+		Vehicle *last = NULL;
+		Vehicle *u = *old_hash;
+		while (u != v) {
+			last = u;
+			u = u->next_new_hash;
+			assert(u != NULL);
+		}
+
+		if (last == NULL) {
+			*old_hash = v->next_new_hash;
+		} else {
+			last->next_new_hash = v->next_new_hash;
+		}
+	}
+
+	/* Insert vehicle at beginning of the new position in the hash table */
+	if (new_hash != NULL) {
+		v->next_new_hash = *new_hash;
+		*new_hash = v;
+		assert(v != v->next_new_hash);
+	}
+
+	/* Remember current hash position */
+	v->old_new_hash = new_hash;
+}
+
+static Vehicle *_vehicle_position_hash[0x1000];
 
 static void UpdateVehiclePosHash(Vehicle* v, int x, int y)
 {
+	UpdateNewVehiclePosHash(v);
+
 	Vehicle **old_hash, **new_hash;
 	int old_x = v->left_coord;
 	int old_y = v->top_coord;
@@ -468,6 +541,7 @@
 void ResetVehiclePosHash()
 {
 	memset(_vehicle_position_hash, 0, sizeof(_vehicle_position_hash));
+	memset(_new_vehicle_position_hash, 0, sizeof(_new_vehicle_position_hash));
 }
 
 void InitializeVehicles()
@@ -611,8 +685,10 @@
 		InvalidateWindowData(WC_VEHICLE_DEPOT, v->tile);
 	}
 
+	v->tile = INVALID_TILE;
 	UpdateVehiclePosHash(v, INVALID_COORD, 0);
 	v->next_hash = NULL;
+	v->next_new_hash = NULL;
 	if (IsPlayerBuildableVehicleType(v)) DeleteVehicleOrders(v);
 
 	/* Now remove any artic part. This will trigger an other
--- a/src/vehicle.h	Tue Jun 12 09:41:12 2007 +0000
+++ b/src/vehicle.h	Tue Jun 12 11:22:32 2007 +0000
@@ -290,6 +290,8 @@
 	int32 right_coord;
 	int32 bottom_coord;
 	Vehicle *next_hash;
+	Vehicle *next_new_hash;
+	Vehicle **old_new_hash;
 
 	/* Related to age and service time */
 	Date age;     // Age in days
@@ -497,6 +499,7 @@
 bool IsEngineCountable(const Vehicle *v);
 void DeleteVehicleChain(Vehicle *v);
 void *VehicleFromPos(TileIndex tile, void *data, VehicleFromPosProc *proc);
+void *VehicleFromPosXY(int x, int y, void *data, VehicleFromPosProc *proc);
 void CallVehicleTicks();
 Vehicle *FindVehicleOnTileZ(TileIndex tile, byte z);