standalone: VoxelForgeCore library -- the fast generator behind a clean C++20 API, output unchanged
- Standalone/Core: public header VoxelForgeCore.h (no Unreal types), static library target, ResolvedWorldSnapshot (from the schema-v1 JSON), World (compile, generateTile, density/sign, point/segment solid, classifyBox), host-supplied JobScheduler (the core starts no threads), sequenced, idempotent EditTransaction returning affected tiles per LOD. - The viewer, benchmarks, edits and audits run on Core; StreamingWorld stays host code. - Same as round 11: 11 hash rows at W1 and W8, routes 5642/5642, look/passage/far audits pass, bad-shell control fails, six fixed captures byte-identical; cold W8 1.025 s vs 1.041 s reference. - SHARED_CPP_LIBRARY_PLAN.md rewritten for a UBT module compiling the same sources via shims. Open issue before Unreal: Core's implementation still compiles the Compat stand-ins named like Unreal types (TArray, FVector...); inside a UE binary that is an ODR clash. Must be namespaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
631dd57eb4
commit
557f6aac30
22 files changed
+6185
-5063
No files matched your search
@@ -1,74 +1,134 @@
|
||||
# Shared C++ library plan
|
||||
# VoxelForgeCore shared-library plan
|
||||
|
||||
This round keeps the standalone copy in place. The next extraction should make one implementation
|
||||
serve both hosts; it should not turn the compatibility layer into a second public API.
|
||||
This plan describes the extracted shape-world pipeline and the next Unreal integration. The
|
||||
standalone implementation is under `Standalone/Core`; no file in the plugin's `Source` tree was
|
||||
changed.
|
||||
|
||||
## Library contents
|
||||
## Current layout and build
|
||||
|
||||
Move the deterministic CPU pipeline into `VoxelForgeCore`:
|
||||
- `Core/include/VoxelForgeCore/VoxelForgeCore.h` is the public C++20 API. It includes only standard
|
||||
C++ headers and value types.
|
||||
- `Core/src/VoxelForgeCore.cpp` adapts the copied density evaluator and marching-cubes mesher to the
|
||||
public API. `ShapeWorldInternal.h` owns shape compilation, density/detail evaluation, edits and
|
||||
audits. `FarShellsInternal.h` owns far-parent repairs and strata/connector shells.
|
||||
- `Core/core_sources.txt` is the translation-unit manifest. `Build.ps1` reads it for both
|
||||
`-Target Core` and `-Target All`; the latter links the viewer against
|
||||
`Build/VoxelForgeCore/VoxelForgeCore.lib`. Use `-OutputName` to keep another executable open.
|
||||
Builds use `/std:c++20 /O2 /arch:AVX2 /fp:precise /MP8`, BelowNormal priority and affinity
|
||||
`0xFF00`.
|
||||
- `Source/StandaloneShapeWorld.h` and `Source/StandaloneResearchTunnels.h` are forwarding headers
|
||||
for old standalone include sites. `StreamingWorld.h` remains host code: it owns worker threads,
|
||||
queued jobs, caches, cancellation, D3D upload state and per-frame upload budgets. When a core
|
||||
world is supplied, it gets desired tiles and their priority from the library and submits each
|
||||
tile to `World::generateTile`. Shape-world `--stream-benchmark`, `--stream-pipeline`, edit,
|
||||
audit, and `--benchmark-level` runs use the Core world. Explicit evaluator and owner-comparison
|
||||
modes retain the compatibility generator only as the comparison side of those diagnostics.
|
||||
- The build manifest compiles the copied CPU generator and mesher with the private compatibility
|
||||
headers under `Compat`. Those headers and their Unreal-shaped stand-ins are implementation
|
||||
details. They are absent from the public include tree and public API.
|
||||
|
||||
- Plain voxel, tile, vector, bounds, and resolved-world input types.
|
||||
- Deterministic math, noise, density operations, cave and passage fields, strate composition, and
|
||||
the room/SDF caches they use.
|
||||
- Marching-cubes tables and meshing, including the density-lattice gradient normals.
|
||||
- Clipmap desired-set calculation and the shared density evaluation entry points.
|
||||
## API and ownership
|
||||
|
||||
Keep JSON and Unreal asset loading, material/actor creation, D3D11 and Unreal rendering, collision,
|
||||
worker scheduling, cancellation policy, and per-frame mesh upload budgets in the host applications.
|
||||
The cache-region traversal found in this round belongs with the density/meshing implementation.
|
||||
The worker-local room graph cache is keyed by exact XY bounds and stratum. Grouping samples by the
|
||||
legacy cache regions reduced a profiled LOD3 tile from 1,084 graph builds to 229. At coarser sizes,
|
||||
plane-first traversal still thrashes the same XY windows across a tall Z run, so tiles wider than 16
|
||||
chunks keep each XY column's Z samples contiguous. The profiled 64-tile LOD6 case fell from 36,480
|
||||
graph builds (13.189 s in graph construction) to 12,160 (5.210 s), with the same density hash.
|
||||
`ResolvedWorldSnapshot::FromJson` takes the normalized UTF-8 schema-v1 JSON produced by the
|
||||
standalone owner export. It resolves the input into owned read-only data. `World` owns the compiled
|
||||
shape field and edit state for that snapshot. See [Core/API.md](Core/API.md) for signatures, units,
|
||||
and examples.
|
||||
|
||||
Cache capacity is also scale-dependent: a 32-chunk LOD5 tile benefits from two cached stratum
|
||||
windows (26,926 to 9,700 builds across 64 tiles), while a 64-chunk LOD6 tile regresses with two
|
||||
slots (12,160 to 23,104), so the current generator selects two only for 32-chunk tile contexts and
|
||||
one otherwise. Replacing the room-graph `std::function` recursion then reduced profiled LOD5
|
||||
RoomGraphBuild from 4.531 to 4.417 s and LOD6 from 5.210 to 5.038 s across the same 64 tiles,
|
||||
without changing graph build counts or density hashes. These timings are profiling evidence, not a
|
||||
cross-host benchmark. The shared API
|
||||
should own the traversal and cache policy, with parity tests covering both traversal paths while
|
||||
writing the same original density-grid indices.
|
||||
The host supplies a `JobScheduler`. The core calls its synchronous `parallel_for` during shape
|
||||
compilation; the host chooses the pool, fibers, task graph or serial execution. Core code starts no
|
||||
threads. After compilation, hosts may run independent tile generation and query calls on their own
|
||||
workers. Serialize compilation and edit application against reads/generation of the same `World`;
|
||||
there is no internal lock or cancellation policy.
|
||||
|
||||
The compatibility-map and allocator change did not move the original LOD0 run; those changes remove
|
||||
known linear-lookup and heap-growth gaps, but stage profiling put the cost in density evaluation and
|
||||
room-graph cache churn. The hot room-graph walks also used an owning `std::function` compatibility
|
||||
alias for recursion; replacing them with generic self-recursive lambdas kept the same depth-first
|
||||
order and removed repeated type-erasure/allocation overhead. Keep that code in the core rather than
|
||||
carrying the UE-style callback alias into it. Viewer lighting and its palette remain host renderer
|
||||
choices and stay outside the shared generator.
|
||||
Tile meshes own positions, normals, UVs, colors and indices. Positions are centimetres; query and
|
||||
tile coordinates are voxels. Group counts expose the ground index run, ceiling index run, and skirt
|
||||
triangle subset. Density/sign, point-solid, segment-solid, and tile-classification queries support
|
||||
host gameplay and collision checks without returning engine objects.
|
||||
|
||||
## Compatibility boundary
|
||||
Edit transactions contain an authority-supplied increasing sequence and an ordered brush list. The
|
||||
core hashes those exact values, accepts an identical replay idempotently, rejects sequence reuse
|
||||
with different data and out-of-order transactions, and returns affected desired tiles grouped by
|
||||
LOD. The host schedules `generateTile` for those keys and drops results made stale by its own
|
||||
cancellation/version policy. Every client must apply the same sequence and brush order.
|
||||
|
||||
The core should expose standard C++ value types, explicit immutable resolved inputs, and owned mesh
|
||||
results. No Unreal headers, reflection macros, `UObject`, Unreal containers, renderer types, or host
|
||||
allocator should cross the boundary. The plugin adapter converts settings and loaded assets into
|
||||
the resolved input snapshot, then converts the returned vertices, indices, and normals into Unreal
|
||||
mesh types. The standalone JSON loader maps its exported snapshot into the same input snapshot.
|
||||
## Unreal integration: UBT module
|
||||
|
||||
Do not move scheduling into the library. Each host should pass an immutable generation context to a
|
||||
worker and receive an owned result; this lets the plugin retain its task admission and game-thread
|
||||
rules while the viewer retains its worker pool and bounded D3D11 upload pump.
|
||||
Add a `VoxelForgeCore` runtime module to the plugin. Its `Build.cs` sets
|
||||
`CppStandardVersion.Cpp20`, exports only `Standalone/Core/include`, and keeps these private include
|
||||
roots available to the core implementation:
|
||||
|
||||
## Plugin integration
|
||||
- `Standalone/Core/src`
|
||||
- `Standalone/Compat`
|
||||
- `Standalone/Source`
|
||||
- `Standalone/Source/VoxelForge/Public`
|
||||
- `Standalone/Source/VoxelForge/Private`
|
||||
|
||||
Add a `VoxelForgeCore` C++20 target and make both builds compile or link that target from the same
|
||||
source files. For Unreal, first prototype the Unreal Build Tool integration: either a small core
|
||||
module that compiles the library sources or a platform-built static library with explicit build
|
||||
rules. Prefer source integration if packaging a binary per Unreal platform would add maintenance.
|
||||
The plugin then replaces its copied evaluator and mesher calls with the core API behind a thin
|
||||
adapter. Preserve plugin-owned UObject resolution, task cancellation, collision, game-thread mesh
|
||||
application, and upload budgets. The standalone keeps its JSON loader and D3D11 renderer.
|
||||
UBT discovers translation units under a module's `Source` tree. Add one thin `.cpp` include shim per
|
||||
row of `Core/core_sources.txt`, with each shim including exactly one canonical source file by a
|
||||
relative path into `Standalone`. Keeping one included source per shim preserves the translation
|
||||
unit boundaries used by `Build.ps1`. A manifest check in the UBT module should fail when a manifest
|
||||
row lacks a shim or a shim names a different file. Do not copy implementation files into the
|
||||
module. The standalone and UBT builds then compile the same source bytes.
|
||||
|
||||
## Preventing drift before extraction
|
||||
The existing `VoxelForge` module owns the adapter and calls the new module. Its adapter runs on the
|
||||
game thread, resolves soft references and asset defaults, preserves array order, and serializes
|
||||
settings, biome and terrain-op records, strata layouts, passages, seed, voxel size, clip limits and
|
||||
viewer start into the exact schema-v1 document accepted by `FromJson`. It must not serialize
|
||||
UObject pointers or depend on unordered map iteration. Snapshot creation returns a descriptive
|
||||
error for unresolved or unsupported assets. Once created, workers only see the immutable core
|
||||
snapshot and standard C++ results.
|
||||
|
||||
Until the common target exists, keep a manifest of mirrored files and a sync/check script that names
|
||||
the canonical source for each file, compares hashes, and refuses when both copies have changed.
|
||||
Avoid hand-editing only one copy without recording that the edit is pending synchronization. After
|
||||
each density or meshing change, run the 3,436-sample bitwise corpus and the selected-tile density
|
||||
hash check. Keep `/fp:precise`; do not accept evaluator changes that alter density bits as a speed
|
||||
shortcut. A standalone-only optimization must be marked as pending synchronization until it is
|
||||
ported or explicitly rejected for the canonical core. Delete the duplicate standalone source only
|
||||
after both host builds use the common target and the parity corpus passes through each adapter.
|
||||
The plugin supplies a `JobScheduler` backed by its chosen task system. The adapter submits shape
|
||||
build work and tile jobs to that scheduler, observes generation IDs for cancellation, and discards
|
||||
stale results. It copies completed `TileMesh` data into `FVoxelMeshData` or the plugin's current
|
||||
mesh payload on the host side, preserving centimetre positions, normals, UVs, colors, indices and
|
||||
ground/ceiling/skirt group counts. UObject creation, collision cooking, render-resource changes,
|
||||
game-thread application, residency and upload budgets remain in the plugin.
|
||||
|
||||
## What the plugin pipeline replaces
|
||||
|
||||
The current plugin's `AVoxelWorld::GenerateTileResult` worker path performs proofs and
|
||||
`UVoxelGenerator::ClassifyTile`, calls `UVoxelMarchingCubesMesher::GenerateMesh`, builds
|
||||
`RealtimeMesh` streams with `BuildTileStreamSet`, then publishes an epoch-tagged `FChunkResult` for
|
||||
game-thread upload/collision handling. `AVoxelWorld::CarveAtPosition`, `CarveBox`, and
|
||||
`CarveCapsule` route soft `FVoxelModification` brushes through `UVoxelDiffLayer::ApplyModification`
|
||||
and remesh the affected chunks. The adapter replaces those CPU-field and edit operations as
|
||||
follows:
|
||||
|
||||
| Current call or state | Core API replacement | Keep in the plugin |
|
||||
| --- | --- | --- |
|
||||
| Shape interval/detail-band build and any active shape-world dispatch | `World::compile` with explicit bounds/detail options | Asset resolution, build admission and cancellation |
|
||||
| `GetDensityAt`, lattice sign tests, and point/segment checks in meshing/gameplay | `densityAt`, `densityAtLattice`, `isSolidAt`, `isSolidAlongSegment` | Gameplay policy and physics objects |
|
||||
| `ClassifyTile` / tile proof | `classifyBox` | Clipmap residency and tile lifetime |
|
||||
| `GenerateMesh` plus shape-world far-parent repair and shell stitching | `generateTile` | Convert owned result to `FVoxelMeshData`, call `BuildTileStreamSet`, and upload it |
|
||||
| `VoxelClipmapDesiredTiles::Build` tile-set calculation | `desiredTiles` and `prioritizeTiles` | Decide when to request, cancel, retain or evict jobs |
|
||||
| `UVoxelDiffLayer::ApplyModification` and affected-chunk scan | `applyEditTransaction` with `densityOffset` brushes carrying signed strength and falloff; regenerate returned LOD keys | Brush budgets, authority sequence, replication, and result acceptance |
|
||||
|
||||
Delete plugin-side active-world dispatch when routing these calls; the `World` context is an
|
||||
explicit object passed to each operation. The plugin's epoch/cancellation checks still reject stale
|
||||
results, but cannot interrupt an executing core call. Because edit application replaces field edit
|
||||
state, the host must drain in-flight core calls before applying a transaction and then submit the
|
||||
affected tiles under a new epoch. Do not move worker admission, collision cooking, actor or material
|
||||
creation, or rendering into the library.
|
||||
|
||||
## Acceptance record
|
||||
|
||||
The retained Round 11 executable is the reference at checkout `631dd57`. The final Core build
|
||||
matched its 11 hash rows (full detail, whole view and LOD0–8) at both W1 and W8. It retained the
|
||||
5,642/5,642 route audit, passed the shape/look and far-survival audits, and the damaged-shell control
|
||||
still reports 512 removed triangles, 16 missing boundary surfaces, 16 holes, and exit 14. Six
|
||||
fixed 1280×800 captures compare byte-for-byte with the Round 11 references. The final single-tile
|
||||
`--shape-world --benchmark-level=0` path reports `generator=VoxelForgeCore`.
|
||||
|
||||
The final no-hash, cold W8 direct full-payload benchmark was interleaved
|
||||
reference/core/core/reference with `--stream-benchmark --shape-world --force-generate --workers=8
|
||||
--clip-radius=3`. Process wall means were 1.041 s for the reference (1.035, 1.047) and 1.0245 s
|
||||
for Core (1.037, 1.012), 16.5 ms faster. Each run generated 2,226 tiles with the full 110.90 MiB
|
||||
payload and zero cache hits.
|
||||
|
||||
The separate neighborhood-first `--stream-pipeline` benchmark was repeated in eight interleaved
|
||||
processes per build. Whole-view-ready means were 0.887 s for the reference (0.878–0.899 s) and
|
||||
0.894 s for Core (0.880–0.932 s). The 7 ms mean difference is smaller than the 54 ms combined
|
||||
run-to-run range; Core reached the 27-tile neighborhood and 960-tile full-detail milestones at
|
||||
comparable times. Direct benchmark, pipeline, audit, and capture logs are retained in
|
||||
`Core/verification`.
|
||||
Reference in new issue
Block a user