16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-18 21:29:18 +02:00
Commit Graph

71 Commits

Author SHA1 Message Date
maj00r
ef4e99a582 Skip pure-visual leaf includes in the infrastructure pass
On a million-instance scenery (tomaszewo) the infra pass was spending ~24s
reopening pure-visual leaf twins (grass.incb etc.) just to skip their content --
~200k cParser constructions, because every flora include reopens the same leaf.

Twin header now flags whether a file has any infrastructure node or include
(format bumped to v10). A pure-visual leaf (flora .incb: triangles + transform
directives only) has it clear, so the infra pass skips opening it: the first open
of each file caches the verdict, later opens are dropped before construction.

Result on tomaszewo: infra 55s -> 31s, getToken 1.06M -> 89k. Also adds a load
profiler (per-type build time, dispatch time, getToken count) behind WriteLog so
the next bottleneck is measured, not guessed (it's now the 25s of decorative
vehicle media loading).
2026-06-25 00:53:24 +02:00
maj00r
4a9bfdc0aa Section-index visual streaming (no per-cycle twin re-scan)
Replaces the camera-distance ring passes with section-following streaming and a
persistent section index, so a million-node scenery no longer re-scans the whole
twin every time the camera moves into new ground.

- v9 node marker also stores range_max; a model visible from beyond the stream
  radius (or unlimited) is built once up front, the rest stream by section so
  distant landmarks/traction/buildings don't pop out while flora stays local.
- Reader gains node_offset()/seek_node(); cParser exposes the deepest twin's
  file/path/offset/params and seekReplayNode/setReplayParams to rebuild one node.
- First visual pass indexes every deferred node (models via the dispatch fast
  path, origin-placed flora/shapes in deserialize_node) under its region section
  while building the spawn area; later cycles rebuild only the newly-wanted
  sections by seeking straight to their nodes -- O(visible), not O(whole twin).
- Build-all fallback retained for ghostview with no camera centre.

Known: absolute terrain triangles (no origin) still build in the first pass; only
origin-placed content section-streams. Untested in-game (format bump needs a
rebake).
2026-06-24 23:56:53 +02:00
maj00r
16b39a21b4 Skip text (un-baked) includes on the visual load pass
Pass filtering (infrastructure eager / visual deferred) relies on the per-node
markers that only a binary twin carries. An un-baked text include has no markers,
so it can't be split: it loads in full during the infrastructure pass. Reopening
it on the visual pass reprocessed every node a second time and, for parameterized
text includes, re-ran their models against a parser whose parameters no longer
applied, leaking a literal "(pN)" into TAnimModel::Load -> std::stoi throw ->
crash while streaming visuals. Drop the text include on the visual pass; its
content already loaded on the first pass. Fully-baked include trees are unaffected
(their children replay as twins and defer normally).
2026-06-24 18:31:39 +02:00
maj00r
06d44b50b0 fixed eventlauncher parsing
ringall mode in ghostmode
2026-06-23 23:00:54 +02:00
maj00r
06409a54ea Store model position in the v7 node marker for O(1) ring skipping
The camera-ring visual load replays the twin once per distance ring, and each pass
had to read every node's header + X Y Z tokens (~8 tokens through the cParser pipeline)
just to decide whether the node is in the current ring -- expensive when a scenery has
a million flora instances.

Bump the twin format to v7: a visual model node's marker now carries its local position
(3 f32), captured at bake time from the node's first three numbers. The reader hands the
position out (scenery_binary_reader::node_position), and the deserializer ring-tests a
model and skips it in O(1) over the marker span, straight from the dispatch loop, without
deserialize_node decoding any of its tokens. deserialize_model uses the same marker
position for its own ring test so the two decisions agree exactly (a node near a ring
boundary can't be dropped by both adjacent rings). Shapes/older twins have no marker
position and fall back to the token-based test. The per-frame visual budget is factored
into VISUAL_BUDGET_MS.

Verified: td.scn rebakes to v7, no duplicates, no unexpected tokens, completes ~1s.
tomaszewo (1M+ flora) stays memory-bounded with no duplicates; full streaming is still
paced by walking every node per ring (the remaining cost is the replay pipeline itself,
which a spatial section index would address).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 18:09:03 +02:00
maj00r
d7a3e1310a Replace per-node text capture with camera-distance ring multi-pass
The previous nearest-first build captured every deferred visual node as text into a
sorted vector, which does not scale: one tomaszewo flora file alone holds 440k model
nodes (the scenery has 1M+), so the capture ran the process to ~7 GB and its
enumeration never finished.

Stream the visual nodes in camera-distance rings instead, with no per-node capture
(O(1) memory). The visual pass replays the twin once per ring (nearest first); a node
is built only when its squared distance to the camera -- sampled once when the visual
phase starts, so the partition is stable across passes -- falls in the current ring,
otherwise the rest of its body is skipped in O(1) by jumping over the v6 marker span.
Each node is therefore built exactly once, in roughly nearest-first order, through the
normal node path (instancing buckets unchanged). Explicit triangles/lines shapes have
no single position to ring-test by, so they build in the nearest ring pass only.

Reader gains skip_to_node_end() (remembers the served node's end and jumps the cursor
there); cParser::skipReplayNode() delegates it down the active include child.

Verified: td.scn builds 4 rings, no duplicates, no unexpected tokens, complete ~1s
after the infrastructure pass. tomaszewo stays memory-bounded (no OOM, no duplicates)
where the capture approach previously hung.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:41:46 +02:00
maj00r
1160bfecac Stream deferred visual nodes in camera-distance order (nearest first)
The progressive load previously streamed the deferred visual nodes (3d model
instances + terrain shapes/lines) in file order, so distant scenery could load
before the player's surroundings. This builds them nearest-camera first instead.

The visual pass now runs in two steps. Enumeration replays the twin and captures
each visual node verbatim (its resolved tokens as text -- numbers round-trip
losslessly through cParser) together with the transform/group context it was read
under and, for models, its transformed world position. Once the replay is
exhausted the records are sorted by squared distance to the camera (terrain shapes
first so the ground appears before the props on it), then built a budgeted slice
per frame through the normal node path with the captured transform and group
restored -- so placement, grouping and the per-cell instance buckets come out
identical to an in-order load.

Two supporting fixes make out-of-order/late insertion correct:
- a cell/section whose geometry was already baked (the renderer finalised it
  before a deferred node arrived) now appends the new shape/lines straight into
  its live geometry bank instead of merging into vertex-freed geometry, which
  would silently drop it; create_geometry() remembers the bank for every cell.
- events that bind to visual model instances (lights/animation/texture/visible)
  are deferred from InitEvents() to a new InitInstanceEvents() run after the
  visual nodes are built, so their target models exist when they initialise.

Verified on td.scn: playable ~2s, 540 deferred nodes enumerated and built
nearest-first ~0.5s later, no duplicate instances.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:11:39 +02:00
maj00r
fd8bfdbcf3 Add two-pass progressive scenery load (sequential; infra then visuals)
When replaying a binary twin, load in two passes over the same data: the first
pass loads infrastructure (tracks/traction/events/memcells/sounds + directives),
the second pass loads the visual nodes (3d models, terrain shapes/lines) that the
reader skipped via the v6 node-class markers.

- cParser: setReplayPass() selects the served node class (propagated to include
  children); restartReplay() rewinds the twin for the second pass.
- state_serializer: first pass uses the infrastructure pass; on completion it
  restarts the twin for the visual pass. Stateful directives (trainset, event,
  camera, light, sky, time, ...) are skipped on the visual pass so their side
  effects do not duplicate; transform/group directives re-run so deferred visual
  nodes get correct placement. A text/compile load (no twin) stays single-pass.

Verified: td.scn replays through both passes with no duplicate vehicles/events
and reaches the normal load endpoint. This is the sequential foundation; moving
the visual pass into the driver (so play starts after the infrastructure pass) is
the next step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 23:10:06 +02:00
maj00r
c337866c58 Add v6 node-class markers (Stage 2 foundation for progressive loading)
Each top-level node is wrapped in a marker carrying its class (infrastructure vs
visual) and byte span, so the reader can serve or skip a whole node per load pass
without knowing its terminator token. The bake recognizes nodes by the "node"
keyword + type token (map type->terminator confirmed against the deserializers)
and classifies them; the writer buffers a node's entries and emits the marker.
The reader gains a pass selector (all / infrastructure / visual) and skips nodes
not in the pass by advancing past their byte span.

This is the foundation only: the default pass is "all", so loading is identical
to v5 (markers are transparent) -- verified by loading td.scn to the same point.
The eager/visual split is wired up by the upcoming loader/driver integration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 20:09:09 +02:00
maj00r
8a91559895 Fix infinite loop loading non-scenery files (e.g. .fiz) via cParser
The buffer-based tokenizer reads the source into memory once at construction, so
the underlying stream fail bit never flips at end-of-input. cParser::ok() was
not stream.fail(), so while( parser.ok() ) loops (TMoverParameters::LoadFIZ and
similar) never terminated -- they spun on empty tokens past EOF, hanging the
vehicle/.fiz load. Redefine ok() as "input still remains" (buffer not exhausted,
or for replay not exhausted), which terminates those loops while staying true
right after opening a non-empty file (the open checks if(!ok())).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:13:38 +02:00
maj00r
eec5986056 Improve binary scenery format (v5): streaming reader, smaller files, headless parallel bake
- Reader is buffer-based and streaming: the twin is read once and parsed by
  pointer, entries decoded on demand (no per-byte stream, no vector-of-entry
  materialisation), string tokens served as views into the buffer.
- Format v5 is markedly smaller without compression: string interning with
  varint indices, entry type packed into the head varint, integers as zig-zag
  varint and fractional numbers as f32 (f64 only when needed). Tokens are stored
  in original case with a quoted flag and lower-cased per consumer at replay, so
  capture is grammar-independent.
- Writer encodes entries incrementally into a compact buffer instead of holding
  a struct per token, keeping memory bounded when baking huge files in parallel.
- New headless bake mode (-bake) precompiles a scenario and all its includes into
  twins on a thread pool, with no window/renderer/scene; each file is tokenised in
  isolation and baked exactly once.
- Fix stack overflow on files with long runs of consecutive includes by handling
  include directives iteratively instead of recursively (also hardens the normal
  text load).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 18:49:09 +02:00
maj00r
65fd1c10f3 Add binary scenery format (eu7 v3) with async baking, replacing SBT
Text scenery component files (.scn/.inc/.scm/.ctr) are compiled into
per-file binary twins (.scnb/.incb/.scmb/.ctrb), handled transparently
at the cParser layer: a fresh twin (mtime-checked, version-matched) is
replayed instead of re-tokenizing text; otherwise the text is parsed and
a twin is compiled alongside it for next time.

Format details:
- per-file string interning: keywords/paths stored once, referenced by
  varint index (so node/endmodel/... are not repeated as text)
- numeric tokens stored as 8-byte IEEE doubles, not ASCII
- includes kept as references with parameters; random sets stored verbatim
  and re-evaluated on every load (choice not frozen at compile time)
- twin writing offloaded to a bounded thread pool so baking overlaps
  scene construction instead of blocking the load

The legacy terrain-only .sbt path is removed; terrain now loads as
ordinary scenery content.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 21:37:36 +02:00
1aac9b4e76 Merge pull request #111 from MaSzyna-EU07/opengl-instancing
Some rendering improvements
2026-06-16 23:09:12 +02:00
306ed64f38 Merge pull request #110 from xwizard/macos-arm64-build
Add macOS ARM64 build support
2026-06-16 11:15:46 +02:00
1be56de0fa Add sleepermodel optional parameter for tracks 2026-05-19 22:10:47 +02:00
Mateusz Włodarczyk
dd412894bb Add macOS ARM64 build support
Three small source tweaks that let the OpenGL renderer build and run
on Apple Silicon without affecting the Windows/Linux paths:

- stdafx.h: use GLM_FORCE_NEON on ARM64 instead of GLM_FORCE_AVX2.
  AVX2 pulls <immintrin.h>, which clang on ARM64 rejects because
  the x86 intrinsics are unimplemented.
- utilities/utilities.cpp: provide an Apple libc++ fallback for
  Now() using localtime_r + strftime, since
  std::chrono::current_zone() is not yet implemented there.
- audio/audio.h: prefer Khronos-style <AL/al.h> headers when they
  are on the include path (brew openal-soft) and fall back to the
  deprecated Apple OpenAL.framework's <OpenAL/al.h> only otherwise.
2026-05-19 10:45:18 +02:00
Christopher Kwiatkowski
3707a7845b Merge pull request #1 from docentYT/fix-clamp-exception
Merge with fix-clamp-exception
2026-05-12 17:01:47 +02:00
docentYT
a787bdd77c fix min max in Random 2026-05-12 16:56:48 +02:00
docentYT
be91c4da70 Fix C4566 2026-05-12 16:36:32 +02:00
docentYT
6542021253 Optimize substr_path implementation and change return type to sd::string 2026-05-07 12:59:57 +02:00
f4f4897798 Revert substr_path function 2026-05-06 22:20:32 +02:00
cde62d42e3 Merge pull request #99 from docentYT/utilities-simplification
Utilities simplification
2026-05-06 18:35:29 +02:00
docentYT
5367cb9940 Merge remote-tracking branch 'upstream/master' into utilities-simplification 2026-05-05 23:08:43 +02:00
Christopher Kwiatkowski
112c714f79 Resolve conflict 2026-05-04 11:47:38 +02:00
docentYT
d75301f3c7 Add once again parser optimizations 2026-05-04 11:45:44 +02:00
Christopher Kwiatkowski
d64dfe29bf Merge branch 'master' into faster-parser 2026-05-04 11:39:19 +02:00
docentYT
e8335a316b Fix not all cars are loaded 2026-05-04 11:31:42 +02:00
3a42a8c158 Revert "Reduce string creations and deletions" 2026-05-04 01:24:11 +02:00
e7edf601c2 Merge pull request #100 from docentYT/faster-parser
Reduce string creations and deletions
2026-05-04 00:03:41 +02:00
docentYT
13c7317f6e Do not allow implicit cast for inline std::string to_string(bool Value) and change to_string to std::string for non boolean values 2026-05-03 15:49:29 +02:00
docentYT
cef6b258a1 Reduce string creations and deletions 2026-05-02 22:17:20 +02:00
docentYT
7081dab3c2 Replace RadToDeg and DegToRad with glm::degrees and glm::radians 2026-05-02 14:51:13 +02:00
docentYT
ca699d117f Remove not needed includes 2026-05-02 14:40:31 +02:00
docentYT
b734cbcd51 Refactor smoothInterpolate 2026-05-02 01:37:22 +02:00
docentYT
3628eb0fc3 Replace interpolate with std::lerp and glm::mix 2026-05-02 01:31:38 +02:00
docentYT
362338ee5e Refactor min_speed 2026-05-02 00:43:02 +02:00
docentYT
28baad8c5b Refactor clamp_power_of_two 2026-05-02 00:35:50 +02:00
docentYT
76844e33e7 Refactor clamp_circular 2026-05-02 00:07:06 +02:00
bae20a69e8 Bring back old simulator console as ini option showsystemconsole 2026-05-01 23:51:57 +02:00
docentYT
d1f16411a1 Replace clamp with std::clamp 2026-05-01 22:14:29 +02:00
docentYT
1bdd000443 is_equal more precise type and code cleanup 2026-05-01 18:56:24 +02:00
docentYT
2d1616af60 SafeDelete template type set to pointer 2026-05-01 18:47:52 +02:00
docentYT
0c19bd4507 return empty string_view in substr_path if no '/' was found 2026-05-01 18:38:28 +02:00
docentYT
0e6950a386 Replace count_trailing_zeros with std::countr_zero 2026-05-01 17:43:43 +02:00
docentYT
ec1dfae953 Replace starts_with and ends_with with string class methods 2026-05-01 17:31:11 +02:00
docentYT
9c27b54960 Simplify len_common_prefix 2026-05-01 13:57:33 +02:00
docentYT
664c340d84 substr_path refactor 2026-05-01 13:45:57 +02:00
docentYT
5f117832fb slashes functions refactor 2026-05-01 13:44:23 +02:00
docentYT
bd21b8f49c Replace string_starts_with and string_ends_with with string::starts_with and string::ends_with 2026-05-01 13:33:32 +02:00
docentYT
614a7f51a5 Refactor LocalRandom() 2026-05-01 13:17:05 +02:00