Skip to content

CSIG-3471: bulk table duration/distance extraction - #24

Draft
inesusvet wants to merge 1 commit into
masterfrom
CSIG-3471/bulk-table-extraction
Draft

inesusvet wants to merge 1 commit into
masterfrom
CSIG-3471/bulk-table-extraction

Conversation

@inesusvet

@inesusvet inesusvet commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Context

Ticket: CSIG-3471. This is PR 5, workstream 3: bulk table extraction.

street-routing reads /api/table results one cell at a time, using Response::get_duration and get_distance. Each call goes through libosrmc's osrmc_table_response_duration or osrmc_table_response_distance. For every cell, that means:

  • two unordered_map<std::string, Value> lookups (find followed by operator[]),
  • a copy of the json::Value variant,
  • for unreachable cells, a heap-allocated osrmc_error that Rust then frees.

So an N×M table costs 2·N·M FFI calls. This PR adds one call per matrix.

Changes

  • osrmc-sys/src/osrmc_bulk.{h,cc} (new). A small C++ shim, compiled next to osrmc.cc in build.rs (same cc::Build, flags and includes). It is exposed to bindgen through wrapper.h.
    • size_t osrmc_table_response_durations(osrmc_table_response_t, float* out, size_t len, osrmc_error_t*)
    • size_t osrmc_table_response_distances(...)
    • Each function looks up "durations"/"distances" once, walks the outer and inner arrays by const&, and writes a row-major buffer. JSON null becomes INFINITY, which matches today's per-cell "unreachable" semantics. It returns the number of cells written.
    • It returns an error in these cases:
      • NoTable: the key is missing.
      • BufferTooSmall: len is too small. It checks before writing anything.
      • Exception: the response has an unexpected shape, or some other C++ exception was thrown. Every exception is caught at the boundary.
    • Errors are allocated as osrmc_error using a definition identical to the one in osrmc.cc. The ODR allows identical definitions, so libosrmc's osrmc_error_destruct, called from the existing Rust Error::from path, frees them correctly.
  • src/table.rs.
    • Response now records num_sources and num_destinations, set in Osrm::table.
    • New durations()/distances() -> Result<Vec<f32>>: row-major ([from * num_destinations + to]), with f32::INFINITY for unreachable cells.
    • distances() returns an error when the response was built without distances, like get_distance.
    • New num_sources()/num_destinations() accessors.
  • osrmc-sys/build.rs. Adds one .file(...) for the shim. It also drops a trailing blank line that made cargo fmt --check fail on master.

What's NOT changing

  • The libosrmc submodule is untouched. It stays at 38e6af2, and the shim lives outside it.
  • The per-cell API is kept as is. get_duration/get_distance have the same signatures and behaviour, including ErrorKind::NoRoute for unreachable cells.
  • No flatbuffers output format. That path maps unreachable cells to 0, which is indistinguishable from a real zero-length route.
  • route, Config/Osrm construction, error types and the crate version fields are unchanged.

Testing

I ran this in the local sr-local-ci image (Debian bullseye, OSRM v5.26.0 built from source, cargo 1.80). The test data came from ./prepare-test-data.sh: gcc-states-latest with the foot profile, extracted with the same OSRM v5.26.0.

  • cargo build: pass
  • cargo fmt --all -- --check: pass
  • cargo clippy --all-targets -- -D warnings: pass
  • cargo test --all: 8 passed, 0 failed. That is the 6 existing tests plus 2 new ones:
    • test_table_bulk_matches_per_cell: a 3×4 non-square table that includes the known unroutable BROKEN_A → BROKEN_B pair. For every cell, the bulk duration and distance equal the per-cell value exactly. Wherever the per-cell call returns NoRoute, the bulk value is INFINITY. The test also asserts that the fixture really contains at least one unroutable cell.
    • test_table_bulk_distances_without_distance: distances() returns an error when include_distance is false, like get_distance.

Note: Cargo.lock is gitignored in this repo. With cargo 1.80, home 0.5.12 (edition 2024, pulled in transitively by bindgen) does not resolve, so I pinned it locally with cargo update -p home --precise 0.5.9. Nothing about this is committed. CI on a newer toolchain is unaffected.

Release

After merge, tag v0.0.3 on master. street-routing then bumps Cargo.toml (osrm = { tag = "v0.0.3", ... }) and Cargo.lock in a separate street-routing PR, which replaces the per-cell loops in calculate_table with durations()/distances().

🤖 Generated with Claude Code

Per-cell get_duration/get_distance go through libosrmc's
osrmc_table_response_duration/distance, which per cell do two
unordered_map lookups, copy a json::Value, and heap-allocate an
osrmc_error for every unreachable cell. For an NxM table that is
N*M FFI calls.

Add a small C++ shim (osrmc-sys/src/osrmc_bulk.{h,cc}), compiled
alongside osrmc.cc, that looks up "durations"/"distances" once and
walks the nested arrays by const reference, writing a row-major float
buffer with INFINITY for null (same unreachable semantics as the
per-cell API). All C++ exceptions are caught at the boundary and
reported via osrmc_error_t, freed by the existing Rust error path.

Rust: table::Response now records num_sources/num_destinations and
exposes durations()/distances() -> Result<Vec<f32>> plus
num_sources()/num_destinations(). The per-cell API is unchanged and
the libosrmc submodule is untouched.

Also drops a trailing blank line in build.rs that failed cargo fmt.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant