17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 18:29:40 +02:00

Editor checkpoint: turnout domain service, web->editor rename, Id-1 templates

Checkpoint of the editor branch so work isn't carried loose across a branch
switch. Builds.

- maj0sted turnout geometry moved into the domain: domain::lay_turnout lays the
  diverging path (pre-blade lead, blade at beta, arc of alfa-beta, frog rail) and
  solve_junction is a thin adapter onto it.
- the maj0sted web/ tree renamed to editor/ (namespace and paths) - there is no
  WASM here, it's plain native C++ the editor calls.
- click places the switch at its start (początek rozjazdu); the branch's leaving
  straight is locked tangent to the frog; switches can be slid along the through
  track and flipped L/P.
- Polish turnout templates carry catalogue length (Id-1-consistent lengths), plus
  blade-angle and pre-blade fields (left 0 until the catalogue figures are in).
- TURNOUT_SPEC.md: the Bezier-based runtime/model contract.
- also folds in other pending branch changes (scenery, tests) to leave a clean tree.
This commit is contained in:
maj00r
2026-08-01 19:55:33 +02:00
committed by maj00r
parent d069743265
commit 707b4de2f4
41 changed files with 2224 additions and 471 deletions

View File

@@ -136,11 +136,19 @@ bool http_get(std::string const &, std::vector<std::uint8_t> &, std::string &)
} // namespace
orthophoto_source::orthophoto_source()
orthophoto_source::orthophoto_source(maj0sted::editor::WmsConfig Config, int const Gridzoom, std::string Cachesubdir)
: m_service([&] {
if (Gridzoom >= maj0sted::editor::TileGrid::kCellZoomMin)
{
Config.tile_pixels = 512; // coarse cells do not need orto's 1024 px
}
return std::move(Config);
}()),
m_gridzoom(Gridzoom), m_cachesubdir(std::move(Cachesubdir))
{
// the library asks for a URL and expects the answer whenever it arrives; the transfer is queued
// here and handed back on the thread that drains it, so the service stays single threaded
m_service.set_fetcher([this](std::string const &Url, maj0sted::web::TileService::DoneFn Done) {
m_service.set_fetcher([this](std::string const &Url, maj0sted::editor::TileService::DoneFn Done) {
{
std::lock_guard<std::mutex> lock(m_mutex);
m_queued.push_back({Url, std::move(Done), false, {}, {}});
@@ -210,7 +218,7 @@ void orthophoto_source::worker()
}
}
std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::web::TileBBox const &View, int const Maxtiles)
std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::editor::TileBBox const &View, int const Maxtiles)
{
// hand finished transfers to the library here, on the caller's thread
std::deque<transfer> arrived;
@@ -227,8 +235,10 @@ std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::
--m_inflight;
}
std::vector<ready_tile> tiles;
for (auto const &key : maj0sted::web::TileGrid::tiles_for_view(View, maj0sted::web::TileGrid::kLevel, Maxtiles))
// fetch only what fits the budget for the current view. the window is recentred when the
// view is larger than the cap, and zoom-to-cursor moves the camera, so the set of keys asked
// for changes from frame to frame - that must not decide what is drawn
for (auto const &key : maj0sted::editor::TileGrid::tiles_for_view(View, m_gridzoom, Maxtiles))
{
// a tile kept from an earlier session seeds the cache, so no request goes out for it at all
if (false == m_service.has(key))
@@ -237,33 +247,51 @@ std::vector<orthophoto_source::ready_tile> orthophoto_source::collect(maj0sted::
}
auto const status = m_service.request(key);
if (status == maj0sted::web::TileStatus::Failed)
if (status == maj0sted::editor::TileStatus::Failed)
{
++m_failed;
// counted once per tile, not once per frame it stays unavailable
m_failedkeys.insert(key);
continue;
}
if (status != maj0sted::web::TileStatus::Ready)
if (status != maj0sted::editor::TileStatus::Ready)
{
continue;
}
auto found = m_textures.find(key);
if (found == m_textures.end())
if (m_textures.find(key) == m_textures.end())
{
// the first time a tile is drawn is also the moment its bytes are known to be good
// the first time a tile is ready is also the moment its bytes are known to be good
save_to_disk(key);
found = m_textures.emplace(key, upload(key)).first;
m_textures.emplace(key, upload(key));
}
if (found->second != 0)
}
// draw every texture we already hold that still covers the view. returning only the fetch
// window made tiles blink out during zoom as soon as they fell outside the cap, then blink
// back in once the window moved over them again
auto const overlaps = [](maj0sted::editor::TileBBox const &Left, maj0sted::editor::TileBBox const &Right) {
return Left.min_x < Right.max_x && Left.max_x > Right.min_x && Left.min_y < Right.max_y && Left.max_y > Right.min_y;
};
std::vector<ready_tile> tiles;
tiles.reserve(m_textures.size());
for (auto const &[key, texture] : m_textures)
{
if (texture == 0)
{
tiles.push_back({maj0sted::web::TileGrid::bbox(key), found->second});
continue;
}
auto const box = maj0sted::editor::TileGrid::bbox(key);
if (overlaps(box, View))
{
tiles.push_back({box, texture});
}
}
return tiles;
}
unsigned int orthophoto_source::upload(maj0sted::web::TileKey const &Key)
unsigned int orthophoto_source::upload(maj0sted::editor::TileKey const &Key)
{
auto const *image = m_service.peek(Key);
if (image == nullptr || image->bytes.empty())
@@ -296,12 +324,12 @@ unsigned int orthophoto_source::upload(maj0sted::web::TileKey const &Key)
return texture;
}
std::string orthophoto_source::tile_path(maj0sted::web::TileKey const &Key)
std::string orthophoto_source::tile_path(maj0sted::editor::TileKey const &Key) const
{
return "editor/cache/orto/" + std::to_string(Key.zoom) + "_" + std::to_string(Key.x) + "_" + std::to_string(Key.y) + ".img";
return "editor/cache/" + m_cachesubdir + "/" + std::to_string(Key.zoom) + "_" + std::to_string(Key.x) + "_" + std::to_string(Key.y) + ".img";
}
bool orthophoto_source::load_from_disk(maj0sted::web::TileKey const &Key)
bool orthophoto_source::load_from_disk(maj0sted::editor::TileKey const &Key)
{
std::ifstream file(tile_path(Key), std::ios::binary);
if (false == file.is_open())
@@ -320,7 +348,7 @@ bool orthophoto_source::load_from_disk(maj0sted::web::TileKey const &Key)
return true;
}
void orthophoto_source::save_to_disk(maj0sted::web::TileKey const &Key)
void orthophoto_source::save_to_disk(maj0sted::editor::TileKey const &Key)
{
auto const *image = m_service.peek(Key);
if (image == nullptr || image->bytes.empty())
@@ -350,7 +378,7 @@ std::size_t orthophoto_source::pending() const
std::size_t orthophoto_source::failed() const
{
return m_failed;
return m_failedkeys.size();
}
namespace
@@ -383,7 +411,7 @@ unsigned int upload_image(std::vector<std::uint8_t> const &Bytes)
return texture;
}
bool same_box(maj0sted::web::TileBBox const &Left, maj0sted::web::TileBBox const &Right)
bool same_box(maj0sted::editor::TileBBox const &Left, maj0sted::editor::TileBBox const &Right)
{
auto const close = [](double const A, double const B) { return std::abs(A - B) < 1.0; };
return close(Left.min_x, Right.min_x) && close(Left.min_y, Right.min_y) && close(Left.max_x, Right.max_x) && close(Left.max_y, Right.max_y);
@@ -391,7 +419,7 @@ bool same_box(maj0sted::web::TileBBox const &Left, maj0sted::web::TileBBox const
} // namespace
wms_image::wms_image(maj0sted::web::WmsConfig Config) : m_config(std::move(Config))
wms_image::wms_image(maj0sted::editor::WmsConfig Config) : m_config(std::move(Config))
{
m_worker = std::thread([this]() { worker(); });
}
@@ -446,7 +474,7 @@ void wms_image::worker()
}
}
unsigned int wms_image::texture_for(maj0sted::web::TileBBox const &Box, int const Pixels)
unsigned int wms_image::texture_for(maj0sted::editor::TileBBox const &Box, int const Pixels)
{
// a finished download becomes the texture on this thread, where the graphics API is safe to touch
{
@@ -482,7 +510,7 @@ unsigned int wms_image::texture_for(maj0sted::web::TileBBox const &Box, int cons
config.tile_pixels = Pixels;
{
std::lock_guard<std::mutex> lock(m_mutex);
m_url = maj0sted::web::wms_getmap_url(config, Box);
m_url = maj0sted::editor::wms_getmap_url(config, Box);
m_haswork = true;
}
m_loading = true;

View File

@@ -17,9 +17,10 @@ http://mozilla.org/MPL/2.0/.
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "maj0sted/web/tile_cache.hpp"
#include "maj0sted/editor/tile_cache.hpp"
namespace editor
{
@@ -37,18 +38,22 @@ class orthophoto_source
public:
struct ready_tile
{
maj0sted::web::TileBBox box;
maj0sted::editor::TileBBox box;
unsigned int texture{0};
};
orthophoto_source();
// default: Geoportal ortophoto on the fine 100 m grid. pass topo config and a coarse
// grid zoom (e.g. 1000) for the zoomed-out topographic underlay
explicit orthophoto_source(maj0sted::editor::WmsConfig Config = maj0sted::editor::WmsConfig::geoportal_ortho(), int const Gridzoom = maj0sted::editor::TileGrid::kLevel,
std::string Cachesubdir = "orto");
~orthophoto_source();
orthophoto_source(orthophoto_source const &) = delete;
orthophoto_source &operator=(orthophoto_source const &) = delete;
// asks for every tile covering the view and returns those that are already drawable. tiles still
// on their way simply aren't in the answer yet; call again next frame
std::vector<ready_tile> collect(maj0sted::web::TileBBox const &View, int const Maxtiles = 48);
// kicks off fetches for the view (capped) and returns every already-uploaded tile that still
// intersects it - including ones outside the fetch window, so a zoom cannot blink them away.
// tiles still on their way simply aren't in the answer yet; call again next frame
std::vector<ready_tile> collect(maj0sted::editor::TileBBox const &View, int const Maxtiles = 400);
// how many tiles are still being fetched, for the panel to report
std::size_t pending() const;
// how many tiles gave up after exhausting their retries
@@ -59,7 +64,7 @@ class orthophoto_source
struct transfer
{
std::string url;
maj0sted::web::TileService::DoneFn done;
maj0sted::editor::TileService::DoneFn done;
bool ok{false};
std::vector<std::uint8_t> bytes;
std::string content_type;
@@ -67,15 +72,17 @@ class orthophoto_source
// methods
void worker();
// decodes the cached bytes of a tile and uploads them; returns 0 when the image is unusable
unsigned int upload(maj0sted::web::TileKey const &Key);
unsigned int upload(maj0sted::editor::TileKey const &Key);
// imagery of a given piece of ground never changes, so a fetched tile is kept on disk and the
// next session starts with it instead of going back to a service that is slow and flaky
static std::string tile_path(maj0sted::web::TileKey const &Key);
bool load_from_disk(maj0sted::web::TileKey const &Key);
void save_to_disk(maj0sted::web::TileKey const &Key);
std::string tile_path(maj0sted::editor::TileKey const &Key) const;
bool load_from_disk(maj0sted::editor::TileKey const &Key);
void save_to_disk(maj0sted::editor::TileKey const &Key);
// members
maj0sted::web::TileService m_service;
std::unordered_map<maj0sted::web::TileKey, unsigned int, maj0sted::web::TileKeyHash> m_textures;
maj0sted::editor::TileService m_service;
int m_gridzoom{maj0sted::editor::TileGrid::kLevel};
std::string m_cachesubdir{"orto"};
std::unordered_map<maj0sted::editor::TileKey, unsigned int, maj0sted::editor::TileKeyHash> m_textures;
mutable std::mutex m_mutex;
std::condition_variable m_wakeup;
@@ -84,7 +91,7 @@ class orthophoto_source
std::vector<std::thread> m_workers;
std::atomic<bool> m_quitting{false};
std::atomic<std::size_t> m_inflight{0};
std::size_t m_failed{0};
std::unordered_set<maj0sted::editor::TileKey, maj0sted::editor::TileKeyHash> m_failedkeys;
};
// a single WMS image covering an arbitrary box, for views too coarse for the tile grid - the whole
@@ -94,25 +101,25 @@ class wms_image
{
public:
explicit wms_image(maj0sted::web::WmsConfig Config);
explicit wms_image(maj0sted::editor::WmsConfig Config);
~wms_image();
wms_image(wms_image const &) = delete;
wms_image &operator=(wms_image const &) = delete;
// asks for the given box at the given size and returns the texture currently available, which
// may still be the previous box's until the new one arrives; 0 when there is nothing to show yet
unsigned int texture_for(maj0sted::web::TileBBox const &Box, int const Pixels);
unsigned int texture_for(maj0sted::editor::TileBBox const &Box, int const Pixels);
// the box the returned texture actually covers
maj0sted::web::TileBBox const &covered() const { return m_covered; }
maj0sted::editor::TileBBox const &covered() const { return m_covered; }
bool loading() const { return m_loading.load(); }
private:
void worker();
maj0sted::web::WmsConfig m_config;
maj0sted::web::TileBBox m_requested{};
maj0sted::web::TileBBox m_inflightbox{};
maj0sted::web::TileBBox m_covered{};
maj0sted::editor::WmsConfig m_config;
maj0sted::editor::TileBBox m_requested{};
maj0sted::editor::TileBBox m_inflightbox{};
maj0sted::editor::TileBBox m_covered{};
unsigned int m_texture{0};
mutable std::mutex m_mutex;

View File

@@ -15,15 +15,21 @@ namespace editor
std::vector<turnout_preset> const &turnout_presets()
{
// the everyday PKP rozjazdy zwyczajne. the crossing mark and radius are the defining pair;
// the length the switch spans follows from them and is shown in the editor
// the standard PKP rozjazdy zwyczajne (49E1 / 60E1). crossing mark, radius and catalogue length
// (PR→KR along the through track). the crossing angles check out as atan(1/n); the lengths follow
// the type tables (metres). the diverging leaves not tangent but at the blade angle (beta) after a
// straight pre-blade lead, then arcs by (alfa - beta) to the frog.
//
// blade_angle and pre_blade are NOT yet from Id-1 - left at 0 (a tangent arc from PR) until the
// catalogue figures are in. Fill them per type when we have them; the geometry already supports it.
static std::vector<turnout_preset> const presets{
{"Rz 1:9 R190", 9.0, 190.0},
{"Rz 1:9 R300", 9.0, 300.0},
{"Rz 1:12 R300", 12.0, 300.0},
{"Rz 1:14 R500", 14.0, 500.0},
{"Rz 1:18,5 R1200", 18.5, 1200.0},
{"Rz 1:26,5 R2500", 26.5, 2500.0}};
{"Rz 1:7,5 R190", 7.5, 190.0, 25.222, 0.0, 0.0},
{"Rz 1:9 R190", 9.0, 190.0, 27.138, 0.0, 0.0},
{"Rz 1:9 R300", 9.0, 300.0, 33.230, 0.0, 0.0},
{"Rz 1:12 R500", 12.0, 500.0, 41.594, 0.0, 0.0},
{"Rz 1:14 R500", 14.0, 500.0, 42.371, 0.0, 0.0},
{"Rz 1:14 R760", 14.0, 760.0, 54.216, 0.0, 0.0},
{"Rz 1:18,5 R1200", 18.5, 1200.0, 64.818, 0.0, 0.0}};
return presets;
}

View File

@@ -22,8 +22,11 @@ namespace editor
struct turnout_preset
{
std::string name;
double crossing_n;
double radius;
double crossing_n; // skos 1:n
double radius; // metres
double length; // catalogue length PR->KR along the through track, metres
double blade_angle; // kąt nagięcia iglicy (beta), radians; the diverging leaves the straight here
double pre_blade; // odcinek przediglicowy: straight lead PR -> blade tip, metres
};
// typical Polish turnouts (rozjazdy zwyczajne). the (skos, R) pairs are the recognised standard;