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