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}