diff --git a/docs/landscape.html b/docs/landscape.html
index d96695241c..94b7795a68 100644
--- a/docs/landscape.html
+++ b/docs/landscape.html
@@ -740,6 +740,7 @@
+
m6: bits 1..0: animated tile state
m7 :
- If newhouses is activated
@@ -1026,6 +1027,7 @@
- m6 bits 6..3: the station type (rail, airport, truck, bus, oilrig, dock, buoy, waypoint, road waypoint)
- m6 bit 2: pbs reservation state for railway stations/waypoints
+ - m6 bits 1..0: animated tile state
- m7 bits 4..0: owner of road (road stops)
- m7: animation frame (railway stations/waypoints, airports)
@@ -1479,6 +1481,7 @@
m6 bits 5..3: random triggers (NewGRF)
m6 bit 2: bit 8 of type (see m5)
+ m6 bits 1..0: animated tile state
m7: animation frame
@@ -1651,6 +1654,7 @@
m2: index into the array of objects, bits 0 to 15 (upper bits in m5)
m3: random bits
m5: index into the array of objects, bits 16 to 23 (lower bits in m2)
+ m6 bits 1..0: animated tile state
m7: animation counter
diff --git a/docs/landscape_grid.html b/docs/landscape_grid.html
index 1fbf4b4bb3..23bd4980b8 100644
--- a/docs/landscape_grid.html
+++ b/docs/landscape_grid.html
@@ -159,7 +159,7 @@ the array so you can quickly see what is used and what is not.
1 XOXXXXX |
XXXX XXXX |
XXXX XXXX |
- XXXX XXOO |
+ XXXX XXXX |
XXXX XXXX |
OOOO OOOO OOOO OOOO |
@@ -188,7 +188,7 @@ the array so you can quickly see what is used and what is not.
XXXX OXXX |
XXXX XXXX |
XXXX XXXX |
- OXXX XXOO |
+ OXXX XXXX |
XXXX XXXX |
OOOO OOOO OOXX XXXX |
@@ -201,7 +201,7 @@ the array so you can quickly see what is used and what is not.
XXXX OOOO |
OOXX XXXX |
OOOO OXXX |
- OXXX XOOO |
+ OXXX XOXX |
OOOX XXXX |
OOOO XXXX XX XXXXXX |
@@ -280,7 +280,7 @@ the array so you can quickly see what is used and what is not.
XXXX XXXX |
XXXX XXXX |
XXXX XXXX |
- OOXXX XOO |
+ OOXXX XXX |
XXXX XXXX |
OOOO OOOO OOOO OOOO |
@@ -313,7 +313,7 @@ the array so you can quickly see what is used and what is not.
XXXX XXXX |
OOOO OOOO |
XXXX XXXX |
- OOOO OOOO |
+ OOOO OOXX |
XXXX XXXX |
OOOO OOOO OOOO OOOO |
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5f7847ff8a..8f7cfdcb2b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -50,6 +50,7 @@ add_files(
airport_gui.cpp
animated_tile.cpp
animated_tile_func.h
+ animated_tile_map.h
articulated_vehicles.cpp
articulated_vehicles.h
autocompletion.cpp
diff --git a/src/animated_tile.cpp b/src/animated_tile.cpp
index 86884d6ff9..694ba5f52d 100644
--- a/src/animated_tile.cpp
+++ b/src/animated_tile.cpp
@@ -8,7 +8,7 @@
/** @file animated_tile.cpp Everything related to animated tiles. */
#include "stdafx.h"
-#include "core/container_func.hpp"
+#include "animated_tile_map.h"
#include "tile_cmd.h"
#include "viewport_func.h"
#include "framerate_type.h"
@@ -19,16 +19,13 @@
std::vector _animated_tiles;
/**
- * Removes the given tile from the animated tile table.
+ * Stops animation on the given tile.
* @param tile the tile to remove
*/
void DeleteAnimatedTile(TileIndex tile)
{
- auto to_remove = std::ranges::find(_animated_tiles, tile);
- if (to_remove != _animated_tiles.end()) {
- /* The order of the remaining elements must stay the same, otherwise the animation loop may miss a tile. */
- _animated_tiles.erase(to_remove);
- }
+ /* If the tile was animated, mark it for deletion from the tile list on the next animation loop. */
+ if (GetAnimatedTileState(tile) == AnimatedTileState::Animated) SetAnimatedTileState(tile, AnimatedTileState::Deleted);
}
/**
@@ -39,7 +36,17 @@ void DeleteAnimatedTile(TileIndex tile)
void AddAnimatedTile(TileIndex tile, bool mark_dirty)
{
if (mark_dirty) MarkTileDirtyByTile(tile);
- include(_animated_tiles, tile);
+
+ const AnimatedTileState state = GetAnimatedTileState(tile);
+
+ /* Tile is already animated so nothing needs to happen. */
+ if (state == AnimatedTileState::Animated) return;
+
+ /* Tile has no previous animation state, so add to the tile list. If the state is anything
+ * other than None then the tile will still be in the list and does not need to be added again. */
+ if (state == AnimatedTileState::None) _animated_tiles.push_back(tile);
+
+ SetAnimatedTileState(tile, AnimatedTileState::Animated);
}
/**
@@ -47,22 +54,29 @@ void AddAnimatedTile(TileIndex tile, bool mark_dirty)
*/
void AnimateAnimatedTiles()
{
- PerformanceAccumulator framerate(PFE_GL_LANDSCAPE);
+ PerformanceAccumulator landscape_framerate(PFE_GL_LANDSCAPE);
- const TileIndex *ti = _animated_tiles.data();
- while (ti < _animated_tiles.data() + _animated_tiles.size()) {
- const TileIndex curr = *ti;
- AnimateTile(curr);
- /* During the AnimateTile call, DeleteAnimatedTile could have been called,
- * deleting an element we've already processed and pushing the rest one
- * slot to the left. We can detect this by checking whether the index
- * in the current slot has changed - if it has, an element has been deleted,
- * and we should process the current slot again instead of going forward.
- * NOTE: this will still break if more than one animated tile is being
- * deleted during the same AnimateTile call, but no code seems to
- * be doing this anyway.
- */
- if (*ti == curr) ++ti;
+ for (auto it = std::begin(_animated_tiles); it != std::end(_animated_tiles); /* nothing */) {
+ TileIndex &tile = *it;
+
+ if (GetAnimatedTileState(tile) != AnimatedTileState::Animated) {
+ /* Tile should not be animated any more, mark it as not animated and erase it from the list. */
+ SetAnimatedTileState(tile, AnimatedTileState::None);
+
+ /* Removing the last entry, no need to swap and continue. */
+ if (std::next(it) == std::end(_animated_tiles)) {
+ _animated_tiles.pop_back();
+ break;
+ }
+
+ /* Replace the current list entry with the back of the list to avoid moving elements. */
+ *it = _animated_tiles.back();
+ _animated_tiles.pop_back();
+ continue;
+ }
+
+ AnimateTile(tile);
+ ++it;
}
}
diff --git a/src/animated_tile_map.h b/src/animated_tile_map.h
new file mode 100644
index 0000000000..4103a948e4
--- /dev/null
+++ b/src/animated_tile_map.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of OpenTTD.
+ * 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.
+ * 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.
+ * 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 .
+ */
+
+/** @file animated_tile_map.h Maps accessors for animated tiles. */
+
+#ifndef ANIMATED_TILE_MAP_H
+#define ANIMATED_TILE_MAP_H
+
+#include "core/bitmath_func.hpp"
+#include "map_func.h"
+
+/**
+ * Animation state of a possibly-animated tile.
+ */
+enum class AnimatedTileState : uint8_t {
+ None = 0, ///< Tile is not animated.
+ Deleted = 1, ///< Tile was animated but should be removed.
+ Animated = 3, ///< Tile is animated.
+};
+
+/**
+ * Get the animated state of a tile.
+ * @param t The tile.
+ * @returns true iff the tile is animated.
+ */
+inline AnimatedTileState GetAnimatedTileState(Tile t)
+{
+ return static_cast(GB(t.m6(), 0, 2));
+}
+
+/**
+ * Set the animated state of a tile.
+ * @param t The tile.
+ */
+inline void SetAnimatedTileState(Tile t, AnimatedTileState state)
+{
+ SB(t.m6(), 0, 2, to_underlying(state));
+}
+
+#endif /* ANIMATED_TILE_MAP_H */
diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp
index 7c04967754..c39ccf6c46 100644
--- a/src/saveload/afterload.cpp
+++ b/src/saveload/afterload.cpp
@@ -43,6 +43,7 @@
#include "../game/game.hpp"
#include "../town.h"
#include "../economy_base.h"
+#include "../animated_tile_map.h"
#include "../animated_tile_func.h"
#include "../subsidy_base.h"
#include "../subsidy_func.h"
@@ -2225,6 +2226,23 @@ bool AfterLoadGame()
}
}
+ if (IsSavegameVersionBefore(SLV_ANIMATED_TILE_STATE_IN_MAP)) {
+ /* Animated tile state is stored in the map array, allowing
+ * quicker addition and deletion of animated tiles. */
+
+ extern std::vector _animated_tiles;
+
+ for (auto t : Map::Iterate()) {
+ /* Ensure there is no spurious animated tile state. */
+ if (MayAnimateTile(t)) SetAnimatedTileState(t, AnimatedTileState::None);
+ }
+
+ /* Set animated flag for all valid animated tiles. */
+ for (const TileIndex &tile : _animated_tiles) {
+ if (tile != INVALID_TILE) SetAnimatedTileState(tile, AnimatedTileState::Animated);
+ }
+ }
+
if (IsSavegameVersionBefore(SLV_124) && !IsSavegameVersionBefore(SLV_1)) {
/* The train station tile area was added, but for really old (TTDPatch) it's already valid. */
for (Waypoint *wp : Waypoint::Iterate()) {
diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h
index 7bb1c6d8d0..be0c3d9683 100644
--- a/src/saveload/saveload.h
+++ b/src/saveload/saveload.h
@@ -393,6 +393,7 @@ enum SaveLoadVersion : uint16_t {
SLV_NONFLOODING_WATER_TILES, ///< 345 PR#13013 Store water tile non-flooding state.
SLV_PATH_CACHE_FORMAT, ///< 346 PR#12345 Vehicle path cache format changed.
+ SLV_ANIMATED_TILE_STATE_IN_MAP, ///< 347 PR#13082 Animated tile state saved for improved performance.
SL_MAX_VERSION, ///< Highest possible saveload version
};