17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-02 17:29:14 +01:00

Merge the track layout editor into cpp20-modules, ported to modules

The editor branch was written against headers and the PCH, both of which this
branch has done away with, so its own files were ported rather than merged: the
five headers it added are now interface units and their .cpp files module
implementation units.

- editor/{puwg1992,polandmap,turnouts,orthophoto} become eu07.editor.* modules,
  application/planpanel becomes eu07.application.planpanel. The plan tool keeps
  to its own module rather than joining the eu07.simcore partitions: it stands on
  the editor's libraries and eu07.application.uilayer, not on the simulation core,
  so :editoruilayer can export import it without a cycle.
- glm arrives by import eu07.glm, never by include, as the rest of the tree does.
  maj0sted is header-only and glm-free, so its headers stay includes in the global
  module fragment - every unit that names those types includes them itself, since
  a global fragment is not visible across units.
- the one textual conflict was :editoruilayer's include block against this
  branch's imports; resolved this branch's way plus the planpanel import.

Builds with clang 22 (-DWITH_BETTER_RENDERER=OFF) and starts into the editor on
empty ground with -editor alone.
This commit is contained in:
maj00r
2026-09-13 12:36:21 +02:00
74 changed files with 13615 additions and 81 deletions

View File

@@ -75,6 +75,16 @@ node_groups::close()
return handle();
}
void
node_groups::clear()
{
// nodes/events are owned by the region (or already gone with it); do not dereference them
m_groupmap.clear();
while( false == m_activegroup.empty() ) {
m_activegroup.pop();
}
}
bool node_groups::assign_cross_switch(map::track_switch& sw, std::string &sw_name, std::string const &id, size_t idx)
{
sw.action[idx] = simulation::Events.FindEvent(sw_name + ":" + id);

View File

@@ -54,6 +54,10 @@ public:
// indicates creation of current group ended. returns: handle to the parent group or null_handle if group stack is empty
group_handle
close();
// drops every group without touching node pointers — call when the region that owned
// those nodes is about to be (or has just been) destroyed, before a scenery reload
void
clear();
// update minimap objects
void
update_map();

View File

@@ -0,0 +1,228 @@
# Binarny format scenerii („eu7") — specyfikacja zapisu na dysku
Źródło autorytatywne: [`scene/scenerybinary.h`](scenerybinary.h) i
[`scene/scenerybinary.cpp`](scenerybinary.cpp). Ten dokument opisuje format tak, jak kod go
aktualnie czyta i zapisuje (wersja na dysku: **10**). Jeśli kiedykolwiek się rozjadą,
rozstrzyga kod — wtedy zaktualizuj ten plik.
## 1. Cel
Format binarny zastępuje stare formaty tekstowe (`.scn` / `.scm` / `.inc` / `.ctr`) oraz dawny
blob terenu `.sbt`. To **przezroczysty, modularny cache**: każdy tekstowy plik scenerii
kompiluje się do dokładnie jednego binarnego „twina", konsumowanego na warstwie plikowej
`cParser`, więc deserializer scenerii nigdy nie widzi formatu. Twin przechowuje własny, w pełni
rozwinięty strumień tokenów pliku źródłowego (komentarze usunięte, liczby otypowane, stringi
zinternowane) i zachowuje dyrektywy `include` jako referencje — twin jednego pliku wskazuje na
twiny plików, które tamten dołącza.
Cele projektowe: małe pliki, **odczyt strumieniowy z bufora** (cały twin wczytany raz, potem
parsowany wskaźnikiem, a tokeny-stringi serwowane jako widoki — bez wywołań strumienia
per-bajt, bez materializacji pośredniej), równoległy/offline'owy baking oraz **progresywne
ładowanie** (najpierw infrastruktura, żeby gra mogła ruszyć, potem strumieniowanie węzłów
wizualnych w kolejności odległości od kamery).
## 2. Nazewnictwo plików (mapowanie twina)
Jeden plik źródłowy → jeden twin; rozszerzenie binarne wynika z rodzaju źródła
(`scenerybinary_extension_for()`):
| źródło | twin | `scenery_file_kind` |
|---------|---------|---------------------|
| `.scn` | `.scnb` | `scn` (0) |
| `.inc` | `.incb` | `inc` (1) |
| `.scm` | `.scmb` | `scm` (2) |
| `.ctr` | `.ctrb` | `scn` (0)¹ |
¹ `.ctr` dostaje własne rozszerzenie `.ctrb`, ale jest tokenizowany jako rodzaj `scn` (enum
`kind` nie ma osobnej wartości dla `ctr`).
Twin jest **odtwarzany tylko, jeśli jest co najmniej tak nowy jak źródło tekstowe** (porównanie
czasu modyfikacji); edycja pliku scenerii wymusza rekompilację zamiast odtworzenia
nieaktualnego twina.
## 3. Ogólny układ pliku
```
+-----------------------------------------------------------+
| Nagłówek |
| magic : uint32 LE = MAKE_ID4('e','u','7',10) | 4 bajty
| kind : uint8 = scenery_file_kind | 1 bajt
| flags : uint32 LE (bit0 = infra-relevant) | 4 bajty
+-----------------------------------------------------------+
| Tablica stringów |
| count : varint |
| powtórz count razy: |
| length : varint |
| bytes : <length> surowych bajtów (bez NUL) |
+-----------------------------------------------------------+
| Strumień wpisów |
| wpisy jeden po drugim aż do końca pliku |
+-----------------------------------------------------------+
```
* **magic** — `"eu7"` plus jednobajtowa wersja (obecnie `10`), little-endian przez `MAKE_ID4`.
Reader odrzuca każdy bufor, którego magic/wersja nie zgadza się dokładnie (starsze twiny są
rekompilowane, nie odczytywane błędnie). Minimalny poprawny plik to 9 bajtów (nagłówek).
* **kind** — z jakiego formatu tekstowego ten twin został skompilowany (tabela wyżej).
* **flags bit0 (`infra-relevant`)** — ustawiony, jeśli plik ma co najmniej jeden węzeł
infrastruktury lub jakikolwiek `include`. Czysto wizualny liść (np. `.incb` z florą: same
kształty + dyrektywy transformacji) ma go wyzerowany, więc pass infrastrukturalny może w ogóle
nie otwierać pliku, zamiast przechodzić go po to, żeby odrzucić jego treść wizualną.
* **tablica stringów** — każdy odrębny tekst tokenu jest internowany raz i referowany dalej
przez swój zerobazowy indeks. Stringi mają prefiks długości (bez terminatora NUL), więc reader
może serwować `std::string_view` wprost w bufor.
## 4. Kodowania prymitywów
* **varint** — LEB128 bez znaku: 7 bitów danych na bajt, najwyższy bit = „kolejny bajt
następuje". Jeden bajt obejmuje wartości < 128 — to przypadek typowy dla indeksów tablicy
stringów.
* **zig-zag** — liczby ze znakiem są mapowane na bez znaku przed varintem
(`(n << 1) ^ (n >> 63)`), więc małe wartości ujemne pozostają krótkie.
* **f32 / f64** — IEEE-754, little-endian (4 / 8 bajtów), zgodnie z `sn_utils`.
Wszystkie skalary wielobajtowe są little-endian.
## 5. Strumień wpisów
Każdy wpis zaczyna się od **head varint**. Jego dolne 3 bity to tag (`TAG_MASK = 0x7`); pozostałe
górne bity (`head >> 3`) niosą ładunek wpisów-tokenów (indeks w tablicy stringów). Przypadek
typowy — token — kosztuje więc tylko ten jeden varint.
| wartość tagu | nazwa | ładunek po head |
|--------------|-----------|-------------------------------------------------------------------|
| 0 | `TOKEN` | brak — `head >> 3` to indeks w tablicy stringów. Małymi literami per-konsument przy odtwarzaniu. |
| 1 | `INCLUDE` | `varint(fileexpr_count)` + tyle `varint(string_idx)`, potem `varint(param_count)` + tyle `varint(string_idx)`. |
| 2 | `INT` | jeden zig-zag varint (wartość całkowita ze znakiem). |
| 3 | `F32` | 4 bajty, float little-endian. |
| 4 | `F64` | 8 bajtów, double little-endian. |
| 5 | `QTOKEN` | brak — `head >> 3` to indeks w tablicy stringów. **Token cytowany: case zachowany dosłownie** przy odtwarzaniu (nigdy nie zmniejszany). |
| 6 | `NODE` | marker węzła — patrz §6. Po nim następuje ciało węzła. |
Logiczne typy wpisów widziane przez konsumenta (`scenery_entry_type`): `token`, `qtoken`,
`number` (dowolny z INT/F32/F64), `include`. Podział INT/F32/F64 to szczegół kodowania ukryty
wewnątrz readera/writera.
### Kodowanie liczb (`write_number`)
Token liczbowy zapisywany jest w najbardziej zwartej formie, która wystarczająco dobrze
round-trip'uje:
1. Wartość **całkowita** w zakresie ±2⁵³ (`±9007199254740992`) → `INT` (zig-zag varint). Pokrywa
wiele małych liczb w jednym–dwóch bajtach.
2. W przeciwnym razie **`F32`**, jeśli float jest skończony i reprezentuje wartość albo dokładnie,
albo z błędem względnym ≤ `1e-6` (`|float(v) − v| ≤ 1e-6·|v|`).
3. W przeciwnym razie pełny **`F64`**.
### Wpisy include
Include zapisuje **dosłowne wyrażenie nazwy pliku** (pojedynczy token albo zbiór losowy, np.
`[ a.inc b.inc ]` — z tokenami nawiasów) plus listę parametrów, wszystko jako indeksy
zinternowanych stringów. Przy odtwarzaniu wyrażenie jest re-ewaluowane (z ponownym losowaniem
zbioru losowego), a plik-dziecko wchodzony przezroczyście; własne tokeny dziecka pochodzą z jego
twina (lub tekstu). To właśnie zachowuje `cParser::Name()`, grupowanie węzłów per-`.inc` oraz
substytucję parametrów `(pN)` dokładnie jak ścieżka tekstowa.
## 6. Markery węzłów (progresywne ładowanie)
Każdy **węzeł najwyższego poziomu** (blok `node … end<typ>`; węzły nigdy nie są zagnieżdżone)
jest opakowany w marker `NODE`, żeby reader mógł serwować lub pomijać cały węzeł per-pass.
Układ markera:
```
varint(TAG_NODE) # head, tag = 6
varint(class) # NODECLASS_* (poniżej)
varint(byte_span) # długość w bajtach buforowanego ciała następującego po markerze
[ f32 X, f32 Y, f32 Z, f32 Range ] # TYLKO gdy class == NODECLASS_VISUAL_POS (4 floaty)
<body> # `byte_span` bajtów: własne wpisy węzła (tokeny/liczby/...)
```
Klasy węzłów (`NODECLASS_*`):
| wartość | nazwa | znaczenie |
|---------|----------------|------------------------------------------------------------------------|
| 0 | `INFRA` | węzeł infrastruktury (tor, trakcja, memcell, event, dźwięk, …). |
| 1 | `VISUAL` | węzeł wizualny bez pozycji w markerze (kształty / starsze twiny). |
| 2 | `VISUAL_POS` | wizualny węzeł modelu, którego marker niesie pozycję lokalną **oraz zasięg widoczności** — 4 f32: `X, Y, Z, Range` (`Range` = `range_max`, `-1` = nieograniczony). |
> Uwaga: komentarz w kodzie przy `NODECLASS_VISUAL_POS` wciąż mówi „3 f32"; writer realnie
> emituje **4** floaty (X, Y, Z, Range). Na dysku jest układ 4-floatowy powyżej.
`Range`/pozycja pozwalają ładowaniu wizualnemu sterowanemu odległością od kamery („ring")
**przetestować dystans i pominąć węzeł w O(1)** — bez dekodowania żadnego z jego tokenów — oraz
budować eagerly modele dalekie, ale o dużym zasięgu (range poza promieniem strumieniowania),
zamiast porzucać je na dystansie. Kształty (triangles/lines) nie mają pozycji w markerze i
spadają do testu sekcyjnego w deserializerze.
## 7. Passy ładowania (`scenery_load_pass`)
Readerowi mówi się, którą klasę węzłów serwować; `next()` pomija markery węzłów i ciała węzłów
spoza passu (przesuwając kursor o `byte_span` w O(1)), aż dojdzie do wpisu do zaserwowania.
**Wpisy nie-węzłowe (dyrektywy, include'y) są serwowane zawsze**, w każdym passie, by utrzymać
spójny stan transformacji/grup.
| pass | serwuje |
|------------------|-------------------------------------------------------------------|
| `all` (0) | wszystko (ładowanie jednoprzebiegowe, == zachowanie sprzed v6). |
| `infrastructure` | dyrektywy/include + węzły `INFRA`; węzły wizualne pominięte. |
| `visual` (2) | dyrektywy/include + węzły `VISUAL`/`VISUAL_POS`; infra pominięta. |
Ten sam twin jest otwierany raz, potem przechodzony ponownie: najpierw infrastruktura eagerly
(gra może ruszyć), potem wizuale strumieniowane po sekcjach kamery.
## 8. Powierzchnia API readera (semantyka)
`scenery_binary_reader` (bufor musi przeżyć reader):
* `open(string_view)` — waliduje nagłówek, indeksuje tablicę stringów, ustawia na pierwszym
wpisie.
* `set_pass(pass)` / `next(out)` — strumieniuje kolejny serwowany wpis (§7).
* `node_position(x, y, z, range)` — pozycja lokalna + `range_max` aktualnie serwowanego węzła,
jeśli marker je niósł (`VISUAL_POS`); w przeciwnym razie false.
* `skip_to_node_end()` — przeskakuje kursor na koniec aktualnie serwowanego węzła (O(1)), by
porzucić węzeł po podejrzeniu tylko jego pierwszych wpisów (ring skip kamery).
* `node_offset()` / `seek_node(offset)` — bajtowy offset markera bieżącego węzła oraz
repozycjonowanie tam, żeby kolejne `next()` zaserwowało ten węzeł (odbudowa jednego węzła na
żądanie bez re-skanowania twina).
* `infra_relevant()` — flaga z nagłówka (§3); pozwala passowi infrastrukturalnemu w ogóle nie
otwierać czysto wizualnych liści.
* `progress()` — ułamek skonsumowanej sekcji wpisów (0..100), dla paska ładowania.
Odczyt jest **kontrolowany pod kątem granic**: przy przekroczeniu kursor parkuje na końcu i
zwracane jest zero, więc obcięty twin dekoduje się do pustki zamiast czytać poza bufor.
## 9. Wersjonowanie
`SCENERYBINARY_MAGIC` osadza wersję w 4. bajcie magic (obecnie `10`). Podbicie unieważnia
wszystkie starsze twiny (nie przechodzą `open()` i są rekompilowane) — tak bezpiecznie wdraża
się zmiany formatu. Skrócona historia (szczegóły w nagłówku):
* **v4** — reader strumieniowy z bufora, spakowane tagi wpisów, liczby całkowite jako zig-zag
varint.
* **v5** — tokeny w oryginalnym case + flaga cytowania, więc baking jest niezależny od gramatyki
(umożliwia headless/standalone baker).
* **v6** — węzły najwyższego poziomu opakowane w marker (klasa + byte span) → progresywne
ładowanie.
* **v7** — marker modelu wizualnego niesie też pozycję lokalną modelu.
* **v8+** — poprawka terminatora eventlauncher; marker niesie też zasięg widoczności modelu
(`range_max`), dając obecny 4-floatowy ładunek `VISUAL_POS`. Aktualna wersja na dysku: **10**.
## 10. Baking
Serializacja i zapis twina to praca samodzielna, zdejmowana ze ścieżki budowy sceny:
* **Asynchronicznie** (`scenerybinary_write_async` / `scenerybinary_wait_all`) — podczas
normalnego ładowania tekstowego gotowe writery trafiają do małej puli wątków, więc I/O nakłada
się na resztę budowy sceny; wyniki logowane są na wątku głównym po zakończeniu ładowania.
* **Headless** (`scenerybinary_bake_headless`, tryb linii poleceń `-bake`) — tokenizuje
scenariusz i, rekurencyjnie, każdy dołączany plik, kompilując każdy twin **bez sceny, renderera
i okna**. Graf plików odkrywany jest przez tokenizację każdego pliku w izolacji (include'y
zapisane, ale nieotwierane), a każdy twin kompilowany na wątku roboczym; każdy plik bakowany
jest dokładnie raz (deduplikacja), więc zapisy twinów nigdy się nie ścigają. Świeże twiny są
nietknięte; rekompilowane są tylko brakujące/nieaktualne.
## 11. Kto używa maszynerii odtwarzania
Tylko deserializer scenerii (`simulation/simulationstateserializer.cpp`) steruje API
replay/pass/streaming binarnego twina. Każdy inny konsument `cParser` (modele, eventy, pojazdy,
audio, config, …) korzysta ze zwykłego tokenizera tekstu i nie wie nic o formacie binarnym.