17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 09:49:19 +02:00

Editor: fold the plan library into the tree as modules, drop its own name

The track layout library came in as a subproject of its own -- its own
CMakeLists, its own install and export rules, its own include tree -- which only
made the build harder to follow now that everything else is a module. It is part
of the editor, so it lives where the editor lives.

- editor/plan_*.cppm / plan_*.cpp: the fifteen interface units and twelve
  implementation units, flat in editor/ beside editorTerrain and orthophoto, with
  the plan_ prefix keeping them clear of what was already there (editor/turnouts
  is the preset catalogue, so the geometry is plan_turnout_geometry).
- modules are named eu07.editor.plan_*, like every other module in the tree.
- namespaces move under the editor's own: editor::plan, editor::plan::geometry,
  ::io, ::render. The library's old name is gone from the sources and the docs.
- every moved file carries the MPL header the rest of the tree carries; it had
  none. version.hpp went with the subproject: macros only, and unused.
- the umbrella header is dropped -- nothing imported it.
- the unit tests survive the fold: editor/tests, behind EU07_EDITOR_PLAN_TESTS
  (off by default), compiles the interfaces a second time into a small library of
  its own, because a test can only import modules from a target it links and
  linking the game would drag the whole simulator in. The code under test needs
  nothing but a compiler. 7/7 pass.
- a switch may now be written with two dashes as well: --editor beside -editor.

Builds with clang 22 (-DWITH_BETTER_RENDERER=OFF); -editor and --editor both come
up in the editor on empty ground.
This commit is contained in:
maj00r
2026-09-13 13:31:01 +02:00
parent c15e200e37
commit 321c9dd7ab
56 changed files with 931 additions and 952 deletions

195
editor/plan_tilecache.cppm Normal file
View File

@@ -0,0 +1,195 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
module;
#include <cstdint>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
export module eu07.editor.plan_tilecache;
export {
namespace editor::plan {
/// A stable tile in the EPSG:2180 tiling pyramid. Tiles are addressed by their
/// zoom level and integer column/row, so the same ground square always maps to
/// the same key regardless of the current viewport — that is what makes the
/// cache reusable across pans and zooms.
struct TileKey {
int zoom{0};
int x{0}; ///< column (increases with easting)
int y{0}; ///< row (increases with northing)
friend bool operator==(const TileKey&, const TileKey&) = default;
};
struct TileKeyHash {
std::size_t operator()(const TileKey& k) const noexcept {
// Mix the three small integers into one hash.
std::uint64_t h = static_cast<std::uint64_t>(static_cast<std::uint32_t>(k.x));
h = h * 1099511628211ULL ^
static_cast<std::uint64_t>(static_cast<std::uint32_t>(k.y));
h = h * 1099511628211ULL ^ static_cast<std::uint64_t>(k.zoom);
return static_cast<std::size_t>(h);
}
};
/// A bounding box in EPSG:2180 metres (easting = x, northing = y).
struct TileBBox {
double min_x{0.0};
double min_y{0.0};
double max_x{0.0};
double max_y{0.0};
};
/// The tiling scheme. A pure, static value type defining a square grid over the
/// EPSG:2180 plane. The default grid is fixed 100 m × 100 m cells (key.zoom =
/// kLevel). Coarser overview grids encode the cell size in metres in
/// @c TileKey::zoom when that value is >= 100 (e.g. zoom 1000 → 1 km cells for
/// topo underlays). Nothing here touches the network.
class TileGrid {
public:
static constexpr int kTilePixels = 1024; ///< pixels per cell edge (GetMap WIDTH/HEIGHT)
static constexpr double kOriginX = 0.0; ///< easting of cell (0,0)'s left edge
static constexpr double kOriginY = 0.0; ///< northing of cell (0,0)'s bottom edge
/// Default cache cell size: 100 m × 100 m ground square.
static constexpr double kCellMetres = 100.0;
/// Default grid level stored in TileKey::zoom for the 100 m grid.
static constexpr int kLevel = 0;
/// Zoom values at or above this are treated as the cell edge in metres.
static constexpr int kCellZoomMin = 100;
/// Cell edge in metres for @p key (100 m default, or key.zoom when coarse).
[[nodiscard]] static double cell_metres(const TileKey& key) noexcept;
/// Ground resolution (metres per pixel) of a cell.
[[nodiscard]] static double resolution(int zoom = kLevel) noexcept;
/// Edge length of a cell in metres for the given zoom/cell encoding.
[[nodiscard]] static double tile_span(int zoom = kLevel) noexcept;
/// The cell whose ground square contains (@p x, @p y).
[[nodiscard]] static TileKey tile_at(double x, double y, int zoom = kLevel) noexcept;
/// The ground bounding box (EPSG:2180) of @p key.
[[nodiscard]] static TileBBox bbox(const TileKey& key) noexcept;
/// The grid level for a target resolution. Always kLevel for the fine grid.
[[nodiscard]] static int zoom_for_resolution(double metres_per_pixel) noexcept;
/// Every cell whose ground square intersects @p view, capped at @p max_tiles.
/// Pass a coarse cell size (e.g. 1000) as @p zoom to get a 1 km overview grid.
[[nodiscard]] static std::vector<TileKey> tiles_for_view(const TileBBox& view,
int zoom = kLevel,
int max_tiles = 64);
};
/// Configuration for a WMS GetMap source. Defaults target a public EPSG:2180
/// endpoint (Polish Geoportal). Every field is data, so a different backend is a
/// pure configuration change with no code edits.
struct WmsConfig {
std::string base_url;
std::string layers;
std::string format{"image/jpeg"};
std::string version{"1.1.1"}; ///< 1.1.1 keeps BBOX as minx,miny,maxx,maxy
std::string srs{"EPSG:2180"};
int tile_pixels{TileGrid::kTilePixels};
/// Geoportal ortophoto (ortofotomapa), EPSG:2180, JPEG.
[[nodiscard]] static WmsConfig geoportal_ortho();
/// Geoportal topographic base map (BDOT/raster), EPSG:2180, PNG.
[[nodiscard]] static WmsConfig geoportal_topo();
};
/// Builds the full WMS 1.1.1 GetMap URL for @p box using @p cfg. Numbers are
/// formatted locale-independently (std::to_chars), matching the rest of the
/// library's serialization.
[[nodiscard]] std::string wms_getmap_url(const WmsConfig& cfg, const TileBBox& box);
enum class TileStatus { Missing, Loading, Ready, Failed };
/// A cached tile image: raw encoded bytes (JPEG/PNG) plus their MIME type.
struct TileImage {
TileStatus status{TileStatus::Missing};
std::vector<std::uint8_t> bytes;
std::string content_type;
int attempts{0}; ///< failed fetch attempts so far (see TileService retry cap)
};
/// Owns the tile grid, the tile cache and the WMS fetch. This is the single
/// place that decides which tile is needed and whether it is already cached. The
/// download itself is the host's: it injects a Fetcher, so the tiling and caching
/// logic here stays transport-free and is exercised in tests without a network.
class TileService {
public:
/// Called when a fetch finishes: (success, bytes, content_type).
using DoneFn =
std::function<void(bool, std::vector<std::uint8_t>, std::string)>;
/// Injectable network backend: given a URL, eventually invoke DoneFn.
using Fetcher = std::function<void(const std::string& url, DoneFn)>;
explicit TileService(WmsConfig cfg = WmsConfig::geoportal_ortho());
[[nodiscard]] const WmsConfig& config() const noexcept { return cfg_; }
void set_config(WmsConfig cfg) { cfg_ = std::move(cfg); }
/// Sets the transport. When unset, a requested tile fails at once (there is
/// no built-in downloader — the host owns that).
void set_fetcher(Fetcher fetcher) { fetcher_ = std::move(fetcher); }
/// Notified (with the tile's key) whenever a tile transitions to Ready or
/// Failed. Lets the renderer schedule a redraw when an async tile arrives.
void set_on_ready(std::function<void(TileKey)> cb) { on_ready_ = std::move(cb); }
/// Ensures @p key is cached or being fetched, and returns its current status.
/// A cache hit returns Ready immediately and issues no network request.
TileStatus request(const TileKey& key);
/// The cached image for @p key, or nullptr if it is not Ready.
[[nodiscard]] const TileImage* peek(const TileKey& key) const;
/// Stores bytes for @p key directly, marking it Ready. Exposed so a caller
/// (or a test) can seed the cache without going through the fetcher.
void store(const TileKey& key, std::vector<std::uint8_t> bytes,
std::string content_type = {});
[[nodiscard]] bool has(const TileKey& key) const;
[[nodiscard]] std::size_t size() const noexcept { return cache_.size(); }
void clear() { cache_.clear(); }
/// The GetMap URL this service would use for @p key.
[[nodiscard]] std::string url_for(const TileKey& key) const;
/// Finalises an in-flight fetch: on success stores the bytes (Ready), on
/// failure marks the tile Failed, then fires the on_ready callback. Public so
/// the host's fetcher callback can hand the result back here.
void complete_fetch(const TileKey& key, bool ok, std::vector<std::uint8_t> bytes,
std::string content_type);
/// Re-issues the fetch for @p key.
void retry_now(const TileKey& key);
private:
void begin_fetch(const TileKey& key);
/// Re-issues a fetch for @p key after a failure — the service retries up to
/// kMaxRetries times, then marks the tile Failed. The fetcher owns the timing.
void schedule_retry(const TileKey& key);
WmsConfig cfg_;
std::unordered_map<TileKey, TileImage, TileKeyHash> cache_;
Fetcher fetcher_;
std::function<void(TileKey)> on_ready_;
};
} // namespace editor::plan
} // export