ragc_core/ffi/
agc_index.rs

1// Rust FFI bindings to C++ AGC index writing (Archive + Collection_V3)
2
3use std::ffi::{CStr, CString};
4use std::os::raw::c_char;
5use std::ptr;
6
7// C++ bridge functions (from agc_index.cpp)
8extern "C" {
9    fn agc_archive_create_writer() -> *mut std::ffi::c_void;
10    fn agc_archive_open(archive_ptr: *mut std::ffi::c_void, path: *const c_char) -> bool;
11    fn agc_archive_register_stream(archive_ptr: *mut std::ffi::c_void, name: *const c_char) -> i32;
12    fn agc_archive_add_part(
13        archive_ptr: *mut std::ffi::c_void,
14        stream_id: i32,
15        data: *const u8,
16        data_len: usize,
17        metadata: u64,
18    ) -> bool;
19    fn agc_archive_close(archive_ptr: *mut std::ffi::c_void) -> bool;
20    fn agc_archive_destroy(archive_ptr: *mut std::ffi::c_void);
21
22    fn agc_collection_create() -> *mut std::ffi::c_void;
23    fn agc_collection_set_archives(
24        coll_ptr: *mut std::ffi::c_void,
25        out_archive_ptr: *mut std::ffi::c_void,
26        num_threads: u32,
27        batch_size: usize,
28        segment_size: u32,
29        kmer_length: u32,
30    ) -> bool;
31    fn agc_collection_complete_serialization(coll_ptr: *mut std::ffi::c_void);
32    fn agc_collection_store_contig_batch(coll_ptr: *mut std::ffi::c_void, id_from: u32, id_to: u32);
33    fn agc_collection_get_no_samples(coll_ptr: *mut std::ffi::c_void) -> usize;
34    fn agc_collection_destroy(coll_ptr: *mut std::ffi::c_void);
35
36    fn agc_collection_register_sample_contig(
37        coll_ptr: *mut std::ffi::c_void,
38        sample_name: *const c_char,
39        contig_name: *const c_char,
40    ) -> bool;
41
42    fn agc_collection_add_segments_placed(
43        coll_ptr: *mut std::ffi::c_void,
44        placements: *const CSegmentPlacement,
45        count: usize,
46    );
47}
48
49#[repr(C)]
50struct CSegmentPlacement {
51    sample_name: *const c_char,
52    contig_name: *const c_char,
53    seg_part_no: u32,
54    group_id: i32,
55    in_group_id: i32,
56    is_rev_comp: bool,
57    data_size: u32,
58}
59
60/// Rust-friendly segment placement structure
61pub struct SegmentPlacement {
62    pub sample_name: String,
63    pub contig_name: String,
64    pub seg_part_no: u32,
65    pub group_id: u32,
66    pub in_group_id: u32,
67    pub is_rev_comp: bool,
68    pub data_size: u32,
69}
70
71/// Safe wrapper around C++ AGC Archive
72pub struct CppArchive {
73    ptr: *mut std::ffi::c_void,
74}
75
76impl CppArchive {
77    pub fn new_writer() -> Self {
78        unsafe {
79            let ptr = agc_archive_create_writer();
80            assert!(!ptr.is_null(), "Failed to create C++ archive");
81            Self { ptr }
82        }
83    }
84
85    pub fn open(&mut self, path: &str) -> anyhow::Result<()> {
86        let c_path = CString::new(path)?;
87        unsafe {
88            if agc_archive_open(self.ptr, c_path.as_ptr()) {
89                Ok(())
90            } else {
91                Err(anyhow::anyhow!("Failed to open archive: {}", path))
92            }
93        }
94    }
95
96    pub fn register_stream(&mut self, name: &str) -> anyhow::Result<i32> {
97        let c_name = CString::new(name)?;
98        unsafe {
99            let stream_id = agc_archive_register_stream(self.ptr, c_name.as_ptr());
100            if stream_id >= 0 {
101                Ok(stream_id)
102            } else {
103                Err(anyhow::anyhow!("Failed to register stream: {}", name))
104            }
105        }
106    }
107
108    pub fn add_part(&mut self, stream_id: i32, data: &[u8], metadata: u64) -> anyhow::Result<()> {
109        unsafe {
110            if agc_archive_add_part(self.ptr, stream_id, data.as_ptr(), data.len(), metadata) {
111                Ok(())
112            } else {
113                Err(anyhow::anyhow!(
114                    "Failed to add part to stream {}",
115                    stream_id
116                ))
117            }
118        }
119    }
120
121    pub fn close(&mut self) -> anyhow::Result<()> {
122        unsafe {
123            if agc_archive_close(self.ptr) {
124                Ok(())
125            } else {
126                Err(anyhow::anyhow!("Failed to close archive"))
127            }
128        }
129    }
130
131    pub fn as_ptr(&mut self) -> *mut std::ffi::c_void {
132        self.ptr
133    }
134}
135
136impl Drop for CppArchive {
137    fn drop(&mut self) {
138        unsafe {
139            agc_archive_destroy(self.ptr);
140        }
141    }
142}
143
144// Archive must be Send + Sync for multi-threading
145unsafe impl Send for CppArchive {}
146unsafe impl Sync for CppArchive {}
147
148/// Safe wrapper around C++ AGC Collection_V3
149pub struct CppCollection {
150    ptr: *mut std::ffi::c_void,
151}
152
153impl CppCollection {
154    pub fn new() -> Self {
155        unsafe {
156            let ptr = agc_collection_create();
157            assert!(!ptr.is_null(), "Failed to create C++ collection");
158            Self { ptr }
159        }
160    }
161
162    pub fn set_archives(
163        &mut self,
164        archive: &mut CppArchive,
165        num_threads: u32,
166        batch_size: usize,
167        segment_size: u32,
168        kmer_length: u32,
169    ) -> anyhow::Result<()> {
170        unsafe {
171            if agc_collection_set_archives(
172                self.ptr,
173                archive.as_ptr(),
174                num_threads,
175                batch_size,
176                segment_size,
177                kmer_length,
178            ) {
179                Ok(())
180            } else {
181                Err(anyhow::anyhow!("Failed to set archives"))
182            }
183        }
184    }
185
186    pub fn register_sample_contig(
187        &mut self,
188        sample_name: &str,
189        contig_name: &str,
190    ) -> anyhow::Result<()> {
191        let c_sample_name = CString::new(sample_name)?;
192        let c_contig_name = CString::new(contig_name)?;
193
194        unsafe {
195            if agc_collection_register_sample_contig(
196                self.ptr,
197                c_sample_name.as_ptr(),
198                c_contig_name.as_ptr(),
199            ) {
200                Ok(())
201            } else {
202                Err(anyhow::anyhow!(
203                    "Failed to register sample '{}' contig '{}'",
204                    sample_name,
205                    contig_name
206                ))
207            }
208        }
209    }
210
211    pub fn add_segments_placed(&mut self, placements: &[SegmentPlacement]) -> anyhow::Result<()> {
212        if placements.is_empty() {
213            return Ok(());
214        }
215
216        // Convert Rust strings to C strings (must keep them alive during FFI call)
217        let c_sample_names: Vec<CString> = placements
218            .iter()
219            .map(|p| CString::new(p.sample_name.as_str()).unwrap())
220            .collect();
221
222        let c_contig_names: Vec<CString> = placements
223            .iter()
224            .map(|p| CString::new(p.contig_name.as_str()).unwrap())
225            .collect();
226
227        // Build C-compatible array
228        let c_placements: Vec<CSegmentPlacement> = placements
229            .iter()
230            .enumerate()
231            .map(|(i, p)| CSegmentPlacement {
232                sample_name: c_sample_names[i].as_ptr(),
233                contig_name: c_contig_names[i].as_ptr(),
234                seg_part_no: p.seg_part_no,
235                group_id: p.group_id as i32,
236                in_group_id: p.in_group_id as i32,
237                is_rev_comp: p.is_rev_comp,
238                data_size: p.data_size,
239            })
240            .collect();
241
242        unsafe {
243            agc_collection_add_segments_placed(self.ptr, c_placements.as_ptr(), c_placements.len());
244        }
245
246        Ok(())
247    }
248
249    pub fn complete_serialization(&mut self) {
250        unsafe {
251            agc_collection_complete_serialization(self.ptr);
252        }
253    }
254
255    pub fn store_contig_batch(&mut self, id_from: u32, id_to: u32) {
256        unsafe {
257            agc_collection_store_contig_batch(self.ptr, id_from, id_to);
258        }
259    }
260
261    pub fn get_no_samples(&self) -> usize {
262        unsafe { agc_collection_get_no_samples(self.ptr) }
263    }
264}
265
266impl Drop for CppCollection {
267    fn drop(&mut self) {
268        unsafe {
269            agc_collection_destroy(self.ptr);
270        }
271    }
272}
273
274// Collection must be Send + Sync for multi-threading
275unsafe impl Send for CppCollection {}
276unsafe impl Sync for CppCollection {}