From 0878f7105188bcee27f5e4ee8856325c9a582d24 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Wed, 13 Aug 2025 17:23:19 +0100 Subject: [PATCH] Codechange: Add RailStationTileLayout iterator. (#14510) This allows iterating a predefined or calculated rail station layout without allocating extra memory and copying. --- src/CMakeLists.txt | 1 + src/station_cmd.cpp | 73 +++++++++++++++------------------------ src/station_layout_type.h | 59 +++++++++++++++++++++++++++++++ src/waypoint_cmd.cpp | 12 +++---- 4 files changed, 93 insertions(+), 52 deletions(-) create mode 100644 src/station_layout_type.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d41ce2ea14..74fb319399 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -462,6 +462,7 @@ add_files( station_gui.cpp station_gui.h station_kdtree.h + station_layout_type.h station_map.h station_type.h statusbar_gui.cpp diff --git a/src/station_cmd.cpp b/src/station_cmd.cpp index 35b3693bb5..3ddb4b7d3a 100644 --- a/src/station_cmd.cpp +++ b/src/station_cmd.cpp @@ -68,6 +68,7 @@ #include "timer/timer_game_tick.h" #include "cheat_type.h" #include "road_func.h" +#include "station_layout_type.h" #include "widgets/station_widget.h" @@ -1094,54 +1095,37 @@ CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta) return CommandCost(); } -static inline uint8_t *CreateSingle(uint8_t *layout, int n) +RailStationTileLayout::RailStationTileLayout(const StationSpec *spec, uint8_t platforms, uint8_t length) : platforms(platforms), length(length) { - int i = n; - do *layout++ = 0; while (--i); - layout[((n - 1) >> 1) - n] = 2; - return layout; + if (spec == nullptr) return; + + /* Look for a predefined layout for the required size. */ + auto found = spec->layouts.find(GetStationLayoutKey(platforms, length)); + if (found != std::end(spec->layouts)) this->layout = found->second; } -static inline uint8_t *CreateMulti(uint8_t *layout, int n, uint8_t b) +StationGfx RailStationTileLayout::Iterator::operator*() const { - int i = n; - do *layout++ = b; while (--i); - if (n > 4) { - layout[0 - n] = 0; - layout[n - 1 - n] = 0; - } - return layout; -} + /* Use predefined layout if it exists. Mask bit zero which will indicate axis. */ + if (!stl.layout.empty()) return this->stl.layout[this->position] & ~1; -/** - * Create the station layout for the given number of tracks and platform length. - * @param layout The layout to write to. - * @param numtracks The number of tracks to write. - * @param plat_len The length of the platforms. - * @param statspec The specification of the station to (possibly) get the layout from. - */ -void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const StationSpec *statspec) -{ - if (statspec != nullptr) { - auto found = statspec->layouts.find(GetStationLayoutKey(numtracks, plat_len)); - if (found != std::end(statspec->layouts)) { - /* Custom layout defined, copy to buffer. */ - std::copy(std::begin(found->second), std::end(found->second), layout); - return; - } + if (this->stl.length == 1) { + /* Special case for 1-long platforms, all bare platforms except one small building. */ + return this->position == ((this->stl.platforms - 1) / 2) ? 2 : 0; } - if (plat_len == 1) { - CreateSingle(layout, numtracks); - } else { - if (numtracks & 1) layout = CreateSingle(layout, plat_len); - int n = numtracks >> 1; - - while (--n >= 0) { - layout = CreateMulti(layout, plat_len, 4); - layout = CreateMulti(layout, plat_len, 6); - } + if ((this->position < this->stl.length && (this->stl.platforms % 2 == 1))) { + /* Number of tracks is odd, make the first platform bare with a small building. */ + return this->position == ((this->stl.length - 1) / 2) ? 2 : 0; } + + if (this->stl.length > 4 && ((this->position % this->stl.length) == 0 || (this->position % this->stl.length) == this->stl.length - 1)) { + /* Station is longer than 4 tiles, place bare platforms at either end. */ + return 0; + } + + /* None of the above so must be north or south part of larger station. */ + return ((this->position / this->stl.length) & (this->stl.platforms % 2)) ? 4 : 6; } /** @@ -1427,19 +1411,17 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy TileIndexDiff track_delta = TileOffsByAxis(OtherAxis(axis)); // offset to go to the next track Track track = AxisToTrack(axis); - std::vector layouts(numtracks * plat_len); - GetStationLayout(layouts.data(), numtracks, plat_len, statspec); + RailStationTileLayout stl{statspec, numtracks, plat_len}; + auto it = stl.begin(); uint8_t numtracks_orig = numtracks; Company *c = Company::Get(st->owner); - size_t layout_idx = 0; TileIndex tile_track = tile_org; do { TileIndex tile = tile_track; int w = plat_len; do { - uint8_t layout = layouts[layout_idx++]; if (IsRailStationTile(tile) && HasStationReservation(tile)) { /* Check for trains having a reservation for this tile. */ Train *v = GetTrainForReservation(tile, AxisToTrack(GetRailStationAxis(tile))); @@ -1458,7 +1440,8 @@ CommandCost CmdBuildRailStation(DoCommandFlags flags, TileIndex tile_org, RailTy /* Remove animation if overbuilding */ DeleteAnimatedTile(tile); uint8_t old_specindex = HasStationTileRail(tile) ? GetCustomStationSpecIndex(tile) : 0; - MakeRailStation(tile, st->owner, st->index, axis, layout & ~1, rt); + + MakeRailStation(tile, st->owner, st->index, axis, *it++, rt); /* Free the spec if we overbuild something */ DeallocateSpecFromStation(st, old_specindex); diff --git a/src/station_layout_type.h b/src/station_layout_type.h new file mode 100644 index 0000000000..3f2c70ff20 --- /dev/null +++ b/src/station_layout_type.h @@ -0,0 +1,59 @@ +/* + * 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 station_layout_type.h Functions related to station layouts. */ + +#ifndef STATION_LAYOUT_TYPE_H +#define STATION_LAYOUT_TYPE_H + +#include "newgrf_station.h" +#include "station_map.h" + +class RailStationTileLayout { +private: + std::span layout{}; ///< Predefined tile layout. + uint platforms; ///< Number of platforms. + uint length; ///< Length of platforms. +public: + RailStationTileLayout(const StationSpec *spec, uint8_t platforms, uint8_t length); + + class Iterator { + const RailStationTileLayout &stl; ///< Station tile layout being iterated. + uint position = 0; ///< Position within iterator. + public: + using value_type = StationGfx; + using difference_type = std::ptrdiff_t; + using iterator_category = std::forward_iterator_tag; + using pointer = void; + using reference = void; + + Iterator(const RailStationTileLayout &stl) : stl(stl) {} + + bool operator==(const Iterator &rhs) const { return this->position == rhs.position; } + bool operator==(const std::default_sentinel_t &) const { return this->position == this->stl.platforms * this->stl.length; } + + StationGfx operator*() const; + + Iterator &operator++() + { + ++this->position; + return *this; + } + + Iterator operator++(int) + { + Iterator result = *this; + ++*this; + return result; + } + }; + + Iterator begin() const { return Iterator(*this); } + std::default_sentinel_t end() const { return std::default_sentinel_t(); } +}; + +#endif /* STATION_LAYOUT_TYPE_H */ diff --git a/src/waypoint_cmd.cpp b/src/waypoint_cmd.cpp index 8814cfc0a7..4780ee2cbf 100644 --- a/src/waypoint_cmd.cpp +++ b/src/waypoint_cmd.cpp @@ -31,6 +31,7 @@ #include "company_gui.h" #include "waypoint_cmd.h" #include "landscape_cmd.h" +#include "station_layout_type.h" #include "table/strings.h" @@ -179,7 +180,6 @@ static CommandCost IsValidTileForWaypoint(TileIndex tile, Axis axis, StationID * return CommandCost(); } -extern void GetStationLayout(uint8_t *layout, uint numtracks, uint plat_len, const StationSpec *statspec); extern CommandCost FindJoiningWaypoint(StationID existing_station, StationID station_to_join, bool adjacent, TileArea ta, Waypoint **wp, bool is_road); extern CommandCost CanExpandRailStation(const BaseStation *st, TileArea &new_ta); extern CommandCost CalculateRoadStopCost(TileArea tile_area, DoCommandFlags flags, bool is_drive_through, StationType station_type, Axis axis, DiagDirection ddir, StationID *est, RoadType rt, Money unit_cost); @@ -286,13 +286,11 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi wp->UpdateVirtCoord(); const StationSpec *spec = StationClass::Get(spec_class)->GetSpec(spec_index); - std::vector layout(count); - if (spec != nullptr) { - /* For NewGRF waypoints we like to have their style. */ - GetStationLayout(layout.data(), count, 1, spec); - } uint8_t map_spec_index = AllocateSpecToStation(spec, wp, true); + RailStationTileLayout stl{spec, count, 1}; + auto it = stl.begin(); + Company *c = Company::Get(wp->owner); for (int i = 0; i < count; i++) { TileIndex tile = start_tile + i * offset; @@ -301,7 +299,7 @@ CommandCost CmdBuildRailWaypoint(DoCommandFlags flags, TileIndex start_tile, Axi bool reserved = IsTileType(tile, MP_RAILWAY) ? HasBit(GetRailReservationTrackBits(tile), AxisToTrack(axis)) : HasStationReservation(tile); - MakeRailWaypoint(tile, wp->owner, wp->index, axis, layout[i], GetRailType(tile)); + MakeRailWaypoint(tile, wp->owner, wp->index, axis, *it++, GetRailType(tile)); SetCustomStationSpecIndex(tile, map_spec_index); SetRailStationTileFlags(tile, spec);