Skip to main content

mobench_sdk/
uniffi_types.rs

1//! UniFFI integration helpers for generating mobile bindings.
2//!
3//! This module provides utilities for integrating mobench-sdk types with UniFFI
4//! for generating Kotlin/Swift bindings. Since UniFFI requires scaffolding to be
5//! set up in the consuming crate, this module provides conversion traits and
6//! ready-to-use type definitions that can be easily adapted.
7//!
8//! ## Quick Start
9//!
10//! To use mobench-sdk with UniFFI in your crate:
11//!
12//! 1. Add uniffi to your dependencies:
13//!
14//! ```toml
15//! [dependencies]
16//! mobench-sdk = "0.1"
17//! uniffi = { version = "0.28", features = ["cli"] }
18//!
19//! [build-dependencies]
20//! uniffi = { version = "0.28", features = ["build"] }
21//! ```
22//!
23//! 2. Define your FFI types with UniFFI annotations:
24//!
25//! ```ignore
26//! use uniffi;
27//!
28//! // Set up UniFFI scaffolding
29//! uniffi::setup_scaffolding!();
30//!
31//! #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, uniffi::Record)]
32//! pub struct BenchSpec {
33//!     pub name: String,
34//!     pub iterations: u32,
35//!     pub warmup: u32,
36//! }
37//!
38//! #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, uniffi::Record)]
39//! pub struct BenchSample {
40//!     pub duration_ns: u64,
41//!     pub cpu_time_ms: Option<u64>,
42//!     pub peak_memory_kb: Option<u64>,
43//! }
44//!
45//! #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, uniffi::Record)]
46//! pub struct BenchReport {
47//!     pub spec: BenchSpec,
48//!     pub samples: Vec<BenchSample>,
49//! }
50//!
51//! #[derive(Debug, thiserror::Error, uniffi::Error)]
52//! #[uniffi(flat_error)]
53//! pub enum BenchError {
54//!     #[error("iterations must be greater than zero")]
55//!     InvalidIterations,
56//!     #[error("unknown benchmark function: {name}")]
57//!     UnknownFunction { name: String },
58//!     #[error("benchmark execution failed: {reason}")]
59//!     ExecutionFailed { reason: String },
60//! }
61//! ```
62//!
63//! 3. Implement conversions using the traits from this module:
64//!
65//! ```ignore
66//! use mobench_sdk::uniffi_types::{FromSdkSpec, FromSdkSample, FromSdkReport, FromSdkError};
67//!
68//! impl FromSdkSpec for BenchSpec {
69//!     fn from_sdk(spec: mobench_sdk::BenchSpec) -> Self {
70//!         Self {
71//!             name: spec.name,
72//!             iterations: spec.iterations,
73//!             warmup: spec.warmup,
74//!         }
75//!     }
76//!
77//!     fn to_sdk(&self) -> mobench_sdk::BenchSpec {
78//!         mobench_sdk::BenchSpec {
79//!             name: self.name.clone(),
80//!             iterations: self.iterations,
81//!             warmup: self.warmup,
82//!         }
83//!     }
84//! }
85//!
86//! // ... implement other traits similarly
87//! ```
88//!
89//! 4. Export your benchmark function:
90//!
91//! ```ignore
92//! #[uniffi::export]
93//! pub fn run_benchmark(spec: BenchSpec) -> Result<BenchReport, BenchError> {
94//!     let sdk_spec = spec.to_sdk();
95//!     let sdk_report = mobench_sdk::run_benchmark(sdk_spec)?;
96//!     Ok(BenchReport::from_sdk_report(sdk_report))
97//! }
98//! ```
99//!
100//! ## Complete Example
101//!
102//! See the `examples/ffi-benchmark` directory for a complete working example.
103
104use serde::{Deserialize, Serialize};
105
106/// Trait for converting from SDK's BenchSpec type.
107///
108/// Implement this trait on your UniFFI-annotated BenchSpec type.
109pub trait FromSdkSpec: Sized {
110    /// Convert from the SDK's BenchSpec type.
111    fn from_sdk(spec: crate::BenchSpec) -> Self;
112
113    /// Convert to the SDK's BenchSpec type.
114    fn to_sdk(&self) -> crate::BenchSpec;
115}
116
117/// Trait for converting from SDK's BenchSample type.
118///
119/// Implement this trait on your UniFFI-annotated BenchSample type.
120pub trait FromSdkSample: Sized {
121    /// Convert from the SDK's BenchSample type.
122    fn from_sdk(sample: crate::BenchSample) -> Self;
123
124    /// Convert to the SDK's BenchSample type.
125    fn to_sdk(&self) -> crate::BenchSample;
126}
127
128/// Trait for converting from SDK's RunnerReport type.
129///
130/// Implement this trait on your UniFFI-annotated BenchReport type.
131pub trait FromSdkReport<Spec: FromSdkSpec, Sample: FromSdkSample>: Sized {
132    /// Convert from the SDK's RunnerReport type.
133    fn from_sdk_report(report: crate::RunnerReport) -> Self;
134}
135
136/// Trait for converting from SDK's BenchError type.
137///
138/// Implement this trait on your UniFFI-annotated error type.
139pub trait FromSdkError: Sized {
140    /// Convert from the SDK's BenchError type.
141    fn from_sdk(err: crate::types::BenchError) -> Self;
142}
143
144/// Pre-defined BenchSpec structure matching SDK's BenchSpec.
145///
146/// This struct can be used as a template for your own UniFFI-annotated type.
147/// Copy this definition and add the `#[derive(uniffi::Record)]` attribute.
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct BenchSpecTemplate {
150    /// Name of the benchmark function to run.
151    pub name: String,
152    /// Number of measurement iterations.
153    pub iterations: u32,
154    /// Number of warmup iterations before measurement.
155    pub warmup: u32,
156}
157
158impl From<crate::BenchSpec> for BenchSpecTemplate {
159    fn from(spec: crate::BenchSpec) -> Self {
160        Self {
161            name: spec.name,
162            iterations: spec.iterations,
163            warmup: spec.warmup,
164        }
165    }
166}
167
168impl From<BenchSpecTemplate> for crate::BenchSpec {
169    fn from(spec: BenchSpecTemplate) -> Self {
170        Self {
171            name: spec.name,
172            iterations: spec.iterations,
173            warmup: spec.warmup,
174        }
175    }
176}
177
178/// Pre-defined BenchSample structure matching SDK's BenchSample.
179///
180/// This struct can be used as a template for your own UniFFI-annotated type.
181/// Copy this definition and add the `#[derive(uniffi::Record)]` attribute.
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct BenchSampleTemplate {
184    /// Duration of the iteration in nanoseconds.
185    pub duration_ns: u64,
186    /// CPU time consumed by the measured iteration in milliseconds.
187    pub cpu_time_ms: Option<u64>,
188    /// Peak memory growth during the measured iteration in kilobytes.
189    pub peak_memory_kb: Option<u64>,
190}
191
192impl From<crate::BenchSample> for BenchSampleTemplate {
193    fn from(sample: crate::BenchSample) -> Self {
194        Self {
195            duration_ns: sample.duration_ns,
196            cpu_time_ms: sample.cpu_time_ms,
197            peak_memory_kb: sample.peak_memory_kb,
198        }
199    }
200}
201
202impl From<BenchSampleTemplate> for crate::BenchSample {
203    fn from(sample: BenchSampleTemplate) -> Self {
204        Self {
205            duration_ns: sample.duration_ns,
206            cpu_time_ms: sample.cpu_time_ms,
207            peak_memory_kb: sample.peak_memory_kb,
208        }
209    }
210}
211
212/// Pre-defined BenchReport structure matching SDK's RunnerReport.
213///
214/// This struct can be used as a template for your own UniFFI-annotated type.
215/// Copy this definition and add the `#[derive(uniffi::Record)]` attribute.
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct BenchReportTemplate {
218    /// The specification used for this benchmark run.
219    pub spec: BenchSpecTemplate,
220    /// All collected timing samples.
221    pub samples: Vec<BenchSampleTemplate>,
222    /// Optional semantic phase timings captured during measured iterations.
223    pub phases: Vec<crate::SemanticPhase>,
224    /// Exact harness timeline spans in execution order.
225    pub timeline: Vec<crate::HarnessTimelineSpan>,
226}
227
228impl From<crate::RunnerReport> for BenchReportTemplate {
229    fn from(report: crate::RunnerReport) -> Self {
230        Self {
231            spec: report.spec.into(),
232            samples: report.samples.into_iter().map(Into::into).collect(),
233            phases: report.phases,
234            timeline: report.timeline,
235        }
236    }
237}
238
239/// Error variant enum for UniFFI integration.
240///
241/// This enum provides the standard error variants. Copy this and add
242/// `#[derive(uniffi::Error)]` and `#[uniffi(flat_error)]` attributes.
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub enum BenchErrorVariant {
245    /// The iteration count was zero.
246    InvalidIterations,
247    /// The requested benchmark function was not found.
248    UnknownFunction { name: String },
249    /// An error occurred during benchmark execution.
250    ExecutionFailed { reason: String },
251    /// Configuration error.
252    ConfigError { message: String },
253    /// I/O error.
254    IoError { message: String },
255}
256
257impl From<crate::types::BenchError> for BenchErrorVariant {
258    fn from(err: crate::types::BenchError) -> Self {
259        match err {
260            crate::types::BenchError::Runner(runner_err) => match runner_err {
261                crate::timing::TimingError::NoIterations { .. } => {
262                    BenchErrorVariant::InvalidIterations
263                }
264                crate::timing::TimingError::Execution(msg) => {
265                    BenchErrorVariant::ExecutionFailed { reason: msg }
266                }
267            },
268            crate::types::BenchError::UnknownFunction(name, _available) => {
269                BenchErrorVariant::UnknownFunction { name }
270            }
271            crate::types::BenchError::Execution(msg) => {
272                BenchErrorVariant::ExecutionFailed { reason: msg }
273            }
274            crate::types::BenchError::Io(e) => BenchErrorVariant::IoError {
275                message: e.to_string(),
276            },
277            crate::types::BenchError::Serialization(e) => BenchErrorVariant::ConfigError {
278                message: e.to_string(),
279            },
280            crate::types::BenchError::Config(msg) => {
281                BenchErrorVariant::ConfigError { message: msg }
282            }
283            crate::types::BenchError::Build(msg) => BenchErrorVariant::ExecutionFailed {
284                reason: format!("build error: {}", msg),
285            },
286        }
287    }
288}
289
290impl From<crate::timing::TimingError> for BenchErrorVariant {
291    fn from(err: crate::timing::TimingError) -> Self {
292        match err {
293            crate::timing::TimingError::NoIterations { .. } => BenchErrorVariant::InvalidIterations,
294            crate::timing::TimingError::Execution(msg) => {
295                BenchErrorVariant::ExecutionFailed { reason: msg }
296            }
297        }
298    }
299}
300
301/// Helper function to run a benchmark and convert result to template types.
302///
303/// This is useful for implementing your own `run_benchmark` FFI function:
304///
305/// ```ignore
306/// #[uniffi::export]
307/// pub fn run_benchmark(spec: BenchSpec) -> Result<BenchReport, BenchError> {
308///     let sdk_spec: mobench_sdk::BenchSpec = spec.into();
309///     let template_result = mobench_sdk::uniffi_types::run_benchmark_template(sdk_spec);
310///     match template_result {
311///         Ok(report) => Ok(BenchReport::from(report)),
312///         Err(err) => Err(BenchError::from(err)),
313///     }
314/// }
315/// ```
316#[cfg(feature = "full")]
317pub fn run_benchmark_template(
318    spec: crate::BenchSpec,
319) -> Result<BenchReportTemplate, BenchErrorVariant> {
320    crate::run_benchmark(spec)
321        .map(Into::into)
322        .map_err(Into::into)
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    #[test]
330    fn test_bench_spec_template_conversion() {
331        let sdk_spec = crate::BenchSpec {
332            name: "test".to_string(),
333            iterations: 100,
334            warmup: 10,
335        };
336
337        let template: BenchSpecTemplate = sdk_spec.clone().into();
338        assert_eq!(template.name, "test");
339        assert_eq!(template.iterations, 100);
340        assert_eq!(template.warmup, 10);
341
342        let back: crate::BenchSpec = template.into();
343        assert_eq!(back.name, sdk_spec.name);
344        assert_eq!(back.iterations, sdk_spec.iterations);
345        assert_eq!(back.warmup, sdk_spec.warmup);
346    }
347
348    #[test]
349    fn test_bench_sample_template_conversion() {
350        let sdk_sample = crate::BenchSample {
351            duration_ns: 12345,
352            cpu_time_ms: Some(12),
353            peak_memory_kb: Some(48),
354        };
355        let template: BenchSampleTemplate = sdk_sample.into();
356        assert_eq!(template.duration_ns, 12345);
357        assert_eq!(template.cpu_time_ms, Some(12));
358        assert_eq!(template.peak_memory_kb, Some(48));
359    }
360
361    #[test]
362    fn test_bench_error_variant_conversion() {
363        let err = crate::types::BenchError::UnknownFunction(
364            "test_func".to_string(),
365            vec!["available_func".to_string()],
366        );
367        let variant: BenchErrorVariant = err.into();
368        match variant {
369            BenchErrorVariant::UnknownFunction { name } => assert_eq!(name, "test_func"),
370            _ => panic!("Expected UnknownFunction variant"),
371        }
372    }
373}