Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **transform**: new module **`vtx_transform`** (`VTX_BUILD_TRANSFORM`, on by default) -- the home for every tool that modifies an existing replay (repair, cut, entity filter). Cut and filter read the source through `vtx_reader` and re-serialize through `vtx_writer`'s formatter policies, so it is the one module that links both; `vtx_reader`, `vtx_writer` and `vtx_differ` stay independent siblings on `vtx_common` (the writer does **not** link the reader). Skipped automatically when reader or writer is off; `vtx_inspector` requires it. Public headers under `vtx/transform/`, exported as `VTX::vtx_transform`
- **transform**: **`FilterReplayFile`** (`vtx/transform/vtx_replay_filter.h`) -- writes a copy of a replay with entities dropped (blacklist) or exclusively kept (whitelist) by **bucket** (schema bucket name glob or bucket index; a dropped bucket is emptied in every frame and keeps its positional slot), **unique id glob**, **struct name glob** or **scalar property value** (`struct.field` resolved through the file's schema; empty struct = any struct with that field); rules combine as a union. Every frame survives: the header block is copied verbatim, every chunk is re-serialized with the surviving entities (same frame ranges, same per-chunk compression, fresh xxHash64 checksums), and the footer is rebuilt with the same `total_frames`, duration and per-frame time table -- only the seek table changes. Timeline events are carried over and (by default) pruned to entities that still exist. Progress callback with cancel (the partial destination is removed); `ReplayFilterResult` reports kept/dropped counts, per-struct and per-bucket drops, event counts and sizes. `ReplayFilterMatcher` / `GlobMatch` expose the rule engine for previews on in-memory frames. Both backends, output keeps the source format. Covered by `ReplayFilterTest.*` (both backends: bucket / struct / unique id / property / numeric / case-insensitive rules, keep-nothing, events carried + pruned, error paths, cancel) plus `ReplayFilterGlob` / `ReplayFilterMatcher` unit tests
- **transform**: **`CutReplayFile`** / **`PlanCutFrames`** / **`PlanCutChunks`** (`vtx/transform/vtx_replay_cut.h`) -- the inspector's `ReplayCutService` promoted to an SDK entry point: header copied verbatim, whole chunks copied verbatim with rebased seek entries, edge chunks re-serialized with only the kept frames, footer rebuilt (frames renumbered from 0, time table sliced with absolute ticks, gaps/segments rebased, duration recomputed). Now **both backends** (the inspector cut was FlatBuffers-only) and the per-chunk zstd decision is mirrored from the source instead of forced on. `ReplayCutResult` reports frames, chunks written / rewritten and size; the destination is removed on failure. First tests for the cut -- it was previously untested and never compiled by CI (GUI tools are off there): `ReplayCutTest.*` (both backends: whole-chunk verbatim copy, exact-frame edge rewrite, bound clamping, error paths) and `ReplayCutPlan.*`
- **common/framing**: **`vtx/common/vtx_replay_framing.h`** -- the `.vtx` on-disk framing defined once: size prefixes, header block, footer trailer, the zstd-if-beneficial rule (`ShouldCompress` / `Compress` / `CompressIfBeneficial`), xxHash64 chunk checksums, the zstd sniff, and a layout probe (`ProbeHeader` / `ProbeFooterTrailer` / `ProbeLayout`) plus `CopyRange` for rewriters. `ChunkedFileSink`, `ChunkedNetworkSink`, `RepairReplayFile`, `CutReplayFile`, `FilterReplayFile` and the reader's zstd sniff all go through it -- four independent copies of the framing collapsed into one, so their outputs are byte-identical by construction (the crash-recovery byte-identity suite is the regression net). Covered by `ReplayFraming.*` (the byte layout of every helper, probes on synthetic files) and `ReplayFramingOnDisk.*` (a file recorded by the real sink re-derives its entire seek table through the shared helpers, both backends)
- **writer/serialization**: `SessionFooter::events` -- both footer serializers now write timeline events when the pointer is set. The writer facade still writes none; the replay filter uses it to carry a source file's events into the rewritten footer
- **writer/sinks**: `ChunkedNetworkSink` now fills `checksum` in the seek table it streams (was always 0), the same contract as the file sink -- a receiver that concatenates the stream gets a footer whose chunks verify
- **tools/inspector**: **File > Filter Entities** -- rewrite the loaded replay into a new `.vtx` without the selected entities (or with only them). Bucket and struct checklists with per-item counts for the current frame, unique id globs (one per line), property rules (struct / field / value glob), case-insensitive and event-pruning toggles; a live preview shows how many entities of the current frame the rules keep per bucket and reports unresolvable rules before anything runs. The rewrite runs on a worker thread (`VTX::FilterReplayFile`) with a chunk progress bar and Cancel, then reports frames, kept/dropped entities, per-struct drops, event counts and sizes
- **tools/inspector**: **File > Cut Replay** now works on Protobuf replays too (it runs through `VTX::CutReplayFile`)

### Changed

- **transform**: `RepairReplayFile` / `ReplayNeedsRecovery` / `RecoveryJournalPath` move from `vtx_writer` to `vtx_transform` -- include **`vtx/transform/vtx_replay_recovery.h`** (was `vtx/writer/core/vtx_replay_recovery.h`) and link `VTX::vtx_transform`. The rule is now uniform: the writer only produces new recordings, and every tool that modifies an existing `.vtx` (repair, cut, entity filter) lives in the transform module. `RecoveryJournal` / `DurableFile` stay in the writer (they are part of recording). Behaviour and on-disk output are unchanged; the `CrashRecovery*` byte-identity suite moves with it to `tests/transform/`. `vtx_inspector` links `vtx_transform` (not `vtx_writer`) for **File > Repair Replay**; `vtx_benchmarks` now requires `VTX_BUILD_TRANSFORM`

### Fixed

- **build**: Linux shared builds (`VTX_BUILD_SHARED=ON`) exported the IXWebSocket and mbedTLS symbols `libvtx_writer.so` links from static archives. A consumer that also links its own copy of those archives (`vtx_tests` does, for its in-process WebSocket server) ended up with one interposed set of their globals, constructed by both images and destroyed twice -- `free(): double free detected in tcache 2` at exit. Latent on `main` (the test executable happened to bind to the `.so`'s copy); surfaced by the link-order change that adding `vtx_transform` to the test executable caused. `libvtx_writer.so` now hides the symbols of every static archive it links (`--exclude-libs,ALL`), keeping a private copy exactly like the Windows DLL always did
- **docs**: `FILE_FORMAT.md` described the 4 bytes before each chunk as a "marker / padding" and the trailing 4 bytes of the file as a "sentinel"; they are the chunk's size prefix and the repeated format magic
- **reader**: `GetFooter().chunk_index[i].checksum` was always 0 on both backends -- the footer-to-native conversions copied every seek-table field except the xxHash64 checksum (the internal seek table used for chunk loading did carry it). Visible as `checksum: 0` for every chunk in the CLI's `chunks` command, and the inspector's cut copied 0 into the seek-table entries of verbatim chunks. The FlatBuffers footer conversion also dropped each timeline event's `entity_unique_id` and `location`. All three conversions now carry the fields; `ReplayFilterTest.DropByStructName` asserts the footer checksums of a freshly written file match the bytes on disk and `ReplayFilterTest.EventsCarriedAndPruned` reads events back with their entity id and location on both backends

## [0.5.0] - 2026-07-27

### Added
Expand Down
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ set(VTX_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/sdk/include")
option(VTX_BUILD_WRITER "Build vtx_writer module" ON)
option(VTX_BUILD_READER "Build vtx_reader module" ON)
option(VTX_BUILD_DIFFER "Build vtx_differ module" ON)
option(VTX_BUILD_TRANSFORM "Build vtx_transform module (repair / cut / entity-filter of existing replays; needs reader + writer)" ON)
option(BUILD_VTX_TOOL "Build tool suite (tools/)" ON)
option(BUILD_VTX_SAMPLES "Build sample programs (samples/)" ON)
option(VTX_BUILD_TESTS "Build unit test suite (tests/)" ON)
Expand Down Expand Up @@ -118,6 +119,9 @@ endif()
if(VTX_BUILD_DIFFER)
add_compile_definitions(VTX_HAS_DIFFER)
endif()
if(VTX_BUILD_TRANSFORM AND VTX_BUILD_READER AND VTX_BUILD_WRITER)
add_compile_definitions(VTX_HAS_TRANSFORM)
endif()

# --- Tools ---
if(BUILD_VTX_TOOL AND VTX_BUILD_READER AND VTX_BUILD_WRITER)
Expand Down Expand Up @@ -145,7 +149,7 @@ else()
endif()

# --- Benchmarks ---
if(VTX_BUILD_BENCHMARKS AND VTX_BUILD_READER AND VTX_BUILD_WRITER AND VTX_BUILD_DIFFER)
if(VTX_BUILD_BENCHMARKS AND VTX_BUILD_READER AND VTX_BUILD_WRITER AND VTX_BUILD_DIFFER AND VTX_BUILD_TRANSFORM)
message(STATUS "VTX: Building benchmarks...")
add_subdirectory(benchmarks)
else()
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,18 @@ The reader's chunk cache is the single sharpest performance edge in the SDK. A w
## Architecture

```
vtx_common Core types, schemas, serialization adapters, compression (zstd)
vtx_common Core types, schemas, serialization adapters, compression (zstd), on-disk framing
|
+--- vtx_writer Record live frame data into .vtx replay files
+--- vtx_writer Record live frame data into .vtx replay files
|
+--- vtx_reader Stream and random-access .vtx replay files with chunk-based caching
+--- vtx_reader Stream and random-access .vtx replay files with chunk-based caching
|
+--- vtx_differ Compute structural diffs between frames or replay trees
+--- vtx_differ Compute structural diffs between frames or replay trees
|
+--- vtx_transform Modify existing replays: repair a crashed one, cut a frame range, filter entities (uses reader + writer)
```

Four static libraries. Enable or disable each with a CMake option. Full module breakdown and extension model in [SDK Architecture](https://github.com/ZenosInteractive/VTX/wiki/SDK-Architecture).
Five static libraries. Enable or disable each with a CMake option. Full module breakdown and extension model in [SDK Architecture](https://github.com/ZenosInteractive/VTX/wiki/SDK-Architecture).

## Tools

Expand Down
1 change: 1 addition & 0 deletions benchmarks/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ target_link_libraries(vtx_benchmarks PRIVATE
vtx_reader
vtx_writer
vtx_differ
vtx_transform # bench_crash_recovery.cpp: VTX::RepairReplayFile
)

target_include_directories(vtx_benchmarks PRIVATE
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/bench_crash_recovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#include "vtx/common/vtx_logger.h"
#include "vtx/common/vtx_types.h"
#include "vtx/reader/core/vtx_reader_facade.h"
#include "vtx/writer/core/vtx_replay_recovery.h"
#include "vtx/transform/vtx_replay_recovery.h"
#include "vtx/writer/core/vtx_writer_facade.h" // brings SchemaRegistry/SchemaSanitizer for writer.h
#include "vtx/writer/core/writer.h"
#include "vtx/writer/policies/formatters/flatbuffers_vtx_policy.h"
Expand Down
Loading
Loading