--- title: "Downstream C API" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Downstream C API} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` Rminibwa alignment batches are native external pointers with borrowed column buffers. R users can inspect them through ALTREP vectors, but downstream packages should consume them through the installed C header: ```c #include ``` The header resolves functions with `R_GetCCallable()`. A downstream package therefore needs `LinkingTo: Rminibwa` for the header and an ordinary R package runtime dependency to ensure Rminibwa is loaded before the first call. This vignette uses `Rtinycc` to compile the downstream consumer in-process. The C source is displayed with Rtinycc's C rendering helper and compiled from the same `rminibwa_capi_code` object. ## Build a tiny alignment batch ```{r tiny-batch} library(Rminibwa) td <- tempfile("rminibwa-capi-") dir.create(td) ref <- paste(rep("ACGT", 1000), collapse = "") fa <- file.path(td, "ref.fa") writeLines(c(">chr1", ref), fa, useBytes = TRUE) prefix <- file.path(td, "idx") mb_index_build(fa, prefix, threads = 1L) idx <- mb_index_load(prefix) aln <- charToRaw(substr(ref, 1L, 100L)) |> mb_map(idx, opt = mb_opts("sr", out_n = 0L), name = charToRaw("read1")) mb_align_n(aln) mb_align_col(aln, "tid")[[1]] ``` ## The downstream C consumer ```{r c-consumer-source, echo = FALSE, results = "asis"} capi_candidates <- c( file.path("inst", "capi", "rminibwa_tinycc_consumer.c"), file.path("..", "inst", "capi", "rminibwa_tinycc_consumer.c"), system.file("capi", "rminibwa_tinycc_consumer.c", package = "Rminibwa") ) capi_path <- capi_candidates[file.exists(capi_candidates)][[1L]] rminibwa_capi_code <- paste(readLines(capi_path, warn = FALSE), collapse = "\n") Rtinycc:::rtinycc_c_block(rminibwa_capi_code) ``` ## Compile and call it with Rtinycc ```{r compile-c-consumer} ffi <- tryCatch( Rtinycc::tcc_ffi() |> Rtinycc::tcc_include(system.file("include", package = "Rminibwa")) |> Rtinycc::tcc_source(rminibwa_capi_code) |> Rtinycc::tcc_bind( rminibwa_capi_summary = list(args = list("sexp"), returns = "sexp") ) |> Rtinycc::tcc_compile(), error = identity ) if (inherits(ffi, "error")) { cat("Rtinycc compilation is unavailable on this platform during vignette build:\n") cat(conditionMessage(ffi), "\n") } else { ffi$rminibwa_capi_summary(aln) } ``` The C function never asks R to materialize a data frame. It receives the batch SEXP, obtains the opaque `RmbAlignBatch *`, and reads borrowed `int32_t`, `int64_t`, and packed CIGAR buffers directly. ## Lossless query-group stream For BAM preparation, use `mb_query_stream()` rather than repeatedly converting alignment batches. It requires an explicit `mode` and total `threads` budget, and returns exactly one normalized QNAME group per native `next` call. In paired mode it rejects mismatched names, mate ordering/count errors, and malformed or truncated FASTQ before an ambiguous group is emitted. A supplied `@RG` record is retained exactly; no sample or library metadata is invented. The installed API is deliberately a versioned POD/view interface rather than a `bam1_t` handoff, so a consumer compiled against its own htslib can encode each record once. Its header view reports `SO:unsorted` and `GO:query`: contiguous groups preserve input order but are not query-name sorted: ```c if (Rminibwa_stream_abi_version() != RMINIBWA_STREAM_ABI_VERSION) Rf_error("Rminibwa stream ABI mismatch"); RmbQueryStream *stream = Rminibwa_stream_from_sexp(stream_x); const RmbQueryGroup *group = NULL; Rminibwa_header_view header; Rminibwa_query_group_view_t view; Rminibwa_stream_header(stream, &header); /* @SQ, exact @RG, @PG facts */ while (Rminibwa_stream_next(stream, &group) == RMINIBWA_STREAM_OK) { Rminibwa_query_group_view(group, &view); /* Consume view.reads and view.records before requesting the next group. */ /* record.cigar and typed AS/NM/MQ/MC/RG tags are native, not SAM text. */ } ``` Every pointer in `Rminibwa_query_group_view_t` is borrowed and is valid only until `Rminibwa_stream_next()`, `Rminibwa_stream_cancel()`, or stream destruction. This creates one-group back-pressure: a consumer pauses simply by not requesting another group. `Rminibwa_stream_cancel()` releases the current alignment buffers and leaves a reason code/message available through the C API (or `mb_query_stream_error()` for diagnostics). The producer has no extra worker pool; `threads` is the complete minibwa mapping budget. ```{r cleanup, include = FALSE} unlink(td, recursive = TRUE) ```