Skip to main content

kunquant_rs/
buffer.rs

1use crate::error::{KunQuantError, Result};
2use crate::ffi;
3use std::collections::HashMap;
4use std::ffi::CString;
5
6/// A mapping from buffer names to memory buffers for KunQuant computation.
7///
8/// `BufferNameMap` provides the interface between Rust memory and KunQuant's
9/// computation engine. It manages named buffers that serve as inputs and outputs
10/// for factor computations, ensuring memory safety and proper lifetime management.
11///
12/// # Buffer Management
13///
14/// - Input buffers contain market data (prices, volumes, etc.)
15/// - Output buffers store computed factor values
16/// - Buffers are referenced by name as defined in the factor module
17/// - Memory layout must match KunQuant's expectations (row-major, f32 values)
18///
19/// # Memory Safety
20///
21/// The buffer map maintains references to ensure that:
22/// - Buffer memory remains valid during computation
23/// - C strings for buffer names are not deallocated prematurely
24/// - No use-after-free errors occur when accessing buffers
25///
26/// # Thread Safety
27///
28/// This struct is not thread-safe. Each thread should create its own
29/// `BufferNameMap` instance for concurrent computations.
30pub struct BufferNameMap {
31    handle: ffi::KunBufferNameMapHandle,
32    // Keep track of buffer names to prevent use-after-free
33    _buffer_names: HashMap<String, CString>,
34}
35
36impl BufferNameMap {
37    /// Creates a new empty buffer name map.
38    ///
39    /// This initializes the internal data structures needed to manage
40    /// named buffer mappings for KunQuant computations.
41    ///
42    /// # Returns
43    ///
44    /// Returns `Ok(BufferNameMap)` on success, or
45    /// `Err(KunQuantError::BufferNameMapCreationFailed)` if the underlying
46    /// C library fails to create the buffer map.
47    ///
48    /// # Examples
49    ///
50    /// ```rust,no_run
51    /// use kunquant_rs::BufferNameMap;
52    ///
53    /// # fn main() -> kunquant_rs::Result<()> {
54    /// let mut buffers = BufferNameMap::new()?;
55    ///
56    /// // Now ready to add buffer mappings
57    /// // buffers.set_buffer_slice("close", &mut price_data)?;
58    /// # Ok(())
59    /// # }
60    /// ```
61    ///
62    /// # Memory Usage
63    ///
64    /// The initial buffer map has minimal memory overhead. Memory usage
65    /// grows as buffers are added, but the map itself doesn't copy buffer data.
66    pub fn new() -> Result<Self> {
67        let handle = unsafe { ffi::kunCreateBufferNameMap() };
68        if handle.is_null() {
69            return Err(KunQuantError::BufferNameMapCreationFailed);
70        }
71
72        Ok(BufferNameMap {
73            handle,
74            _buffer_names: HashMap::new(),
75        })
76    }
77
78    /// Sets a buffer mapping using a raw pointer (unsafe).
79    ///
80    /// This method directly maps a buffer name to a raw memory pointer.
81    /// It's primarily used internally and by advanced users who need
82    /// direct memory control.
83    ///
84    /// # Arguments
85    ///
86    /// * `name` - The buffer name as defined in the factor module
87    /// * `buffer` - Raw pointer to the buffer memory
88    ///
89    /// # Safety
90    ///
91    /// This function is unsafe because:
92    /// - The buffer must remain valid for the lifetime of this `BufferNameMap`
93    /// - The buffer must be large enough to hold the expected data
94    /// - The pointer must be properly aligned for f32 values
95    /// - The caller must ensure no data races occur during computation
96    ///
97    /// # Examples
98    ///
99    /// ```rust,no_run
100    /// use kunquant_rs::BufferNameMap;
101    ///
102    /// # fn main() -> kunquant_rs::Result<()> {
103    /// let mut buffers = BufferNameMap::new()?;
104    /// let mut data = vec![1.0f32; 1000];
105    ///
106    /// unsafe {
107    ///     buffers.set_buffer("close", data.as_mut_ptr())?;
108    /// }
109    ///
110    /// // data must remain valid until buffers is dropped
111    /// # Ok(())
112    /// # }
113    /// ```
114    ///
115    /// # Preferred Alternative
116    ///
117    /// Consider using `set_buffer_slice()` instead, which provides the same
118    /// functionality with compile-time safety guarantees.
119    pub unsafe fn set_buffer<N: AsRef<str>>(&mut self, name: N, buffer: *mut f32) -> Result<()> {
120        let name_str = name.as_ref();
121        let c_name = CString::new(name_str)?;
122
123        unsafe {
124            ffi::kunSetBufferNameMap(self.handle, c_name.as_ptr(), buffer);
125        }
126        self._buffer_names.insert(name_str.to_string(), c_name);
127
128        Ok(())
129    }
130
131    /// Sets a buffer mapping using a mutable slice (safe).
132    ///
133    /// This is the recommended way to map buffers as it provides compile-time
134    /// safety guarantees. The slice must remain valid for the lifetime of
135    /// the `BufferNameMap`.
136    ///
137    /// # Arguments
138    ///
139    /// * `name` - The buffer name as defined in the factor module. Can be any
140    ///           type that implements `AsRef<str>` (e.g., `&str`, `String`, etc.)
141    /// * `buffer` - Mutable slice containing the buffer data
142    ///
143    /// # Returns
144    ///
145    /// Returns `Ok(())` on success, or an error if:
146    /// - The buffer name contains null bytes
147    /// - The underlying C library call fails
148    ///
149    /// # Examples
150    ///
151    /// ```rust,no_run
152    /// use kunquant_rs::BufferNameMap;
153    ///
154    /// # fn main() -> kunquant_rs::Result<()> {
155    /// let mut buffers = BufferNameMap::new()?;
156    ///
157    /// // Set up input data (16 stocks × 100 time points)
158    /// let mut close_prices = vec![100.0f32; 1600];
159    /// let mut volumes = vec![1000.0f32; 1600];
160    ///
161    /// // Map input buffers
162    /// buffers.set_buffer_slice("close", &mut close_prices)?;
163    /// buffers.set_buffer_slice("volume", &mut volumes)?;
164    ///
165    /// // Set up output buffer
166    /// let mut factor_output = vec![0.0f32; 1600];
167    /// buffers.set_buffer_slice("my_factor", &mut factor_output)?;
168    ///
169    /// // Buffers are now ready for computation
170    /// # Ok(())
171    /// # }
172    /// ```
173    ///
174    /// # Data Layout Requirements
175    ///
176    /// - Data must be in row-major order: `[t0_s0, t0_s1, ..., t0_sN, t1_s0, ...]`
177    /// - Buffer size must match `num_stocks * total_time`
178    /// - All values should be finite floating-point numbers
179    /// - Input buffers should be populated before computation
180    /// - Output buffers will be overwritten during computation
181    ///
182    /// # Memory Management
183    ///
184    /// - The slice must remain valid until the `BufferNameMap` is dropped
185    /// - No copying occurs - the buffer map holds references to your data
186    /// - Ensure the slice is not moved or reallocated during computation
187    pub fn set_buffer_slice<N: AsRef<str>>(&mut self, name: N, buffer: &mut [f32]) -> Result<()> {
188        unsafe { self.set_buffer(name, buffer.as_mut_ptr()) }
189    }
190
191    /// Remove a buffer mapping
192    pub fn erase_buffer<N: AsRef<str>>(&mut self, name: N) -> Result<()> {
193        let name_str = name.as_ref();
194        if let Some(c_name) = self._buffer_names.get(name_str) {
195            unsafe {
196                ffi::kunEraseBufferNameMap(self.handle, c_name.as_ptr());
197            }
198            self._buffer_names.remove(name_str);
199        }
200        Ok(())
201    }
202
203    /// Get the raw handle (for internal use)
204    pub(crate) fn handle(&self) -> ffi::KunBufferNameMapHandle {
205        self.handle
206    }
207}
208
209impl Drop for BufferNameMap {
210    fn drop(&mut self) {
211        if !self.handle.is_null() {
212            unsafe {
213                ffi::kunDestoryBufferNameMap(self.handle);
214            }
215        }
216    }
217}
218
219impl Default for BufferNameMap {
220    fn default() -> Self {
221        Self::new().expect("Failed to create BufferNameMap")
222    }
223}