screencapturekit/utils/sync_completion.rs
1//! Synchronous completion utilities for async FFI callbacks
2//!
3//! This module provides a generic mechanism for blocking on async Swift FFI callbacks
4//! and propagating results (success or error) back to Rust synchronously.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use screencapturekit::utils::sync_completion::{SyncCompletion, SyncCompletionPtr};
10//!
11//! // Create completion for a String result
12//! let (completion, context) = SyncCompletion::<String>::new();
13//!
14//! // Pass context to FFI, which will call back with result
15//! unsafe { some_ffi_call(context, callback) };
16//!
17//! // Block until callback completes
18//! let result = completion.wait();
19//! ```
20
21use std::ffi::{c_void, CStr};
22use std::future::Future;
23use std::pin::Pin;
24use std::sync::{Arc, Condvar, Mutex};
25use std::task::{Context, Poll, Waker};
26
27// ============================================================================
28// Synchronous Completion (blocking)
29// ============================================================================
30
31/// Internal state for tracking synchronous completion
32struct SyncCompletionState<T> {
33 completed: bool,
34 result: Option<Result<T, String>>,
35}
36
37/// A synchronous completion handler for async FFI callbacks
38///
39/// This type provides a way to block until an async callback completes
40/// and retrieve the result. It uses `Arc<(Mutex, Condvar)>` internally
41/// for thread-safe signaling between the callback and the waiting thread.
42pub struct SyncCompletion<T> {
43 inner: Arc<(Mutex<SyncCompletionState<T>>, Condvar)>,
44}
45
46/// Raw pointer type for passing to FFI callbacks
47pub type SyncCompletionPtr = *mut c_void;
48
49impl<T> SyncCompletion<T> {
50 /// Create a new completion handler and return the context pointer for FFI
51 ///
52 /// Returns a tuple of (completion, `context_ptr`) where:
53 /// - `completion` is used to wait for and retrieve the result
54 /// - `context_ptr` should be passed to the FFI callback
55 #[must_use]
56 pub fn new() -> (Self, SyncCompletionPtr) {
57 let inner = Arc::new((
58 Mutex::new(SyncCompletionState {
59 completed: false,
60 result: None,
61 }),
62 Condvar::new(),
63 ));
64 let raw = Arc::into_raw(Arc::clone(&inner));
65 (Self { inner }, raw as SyncCompletionPtr)
66 }
67
68 /// Wait for the completion callback and return the result
69 ///
70 /// This method blocks until the callback signals completion.
71 ///
72 /// # Errors
73 ///
74 /// Returns an error string if the callback signaled an error.
75 ///
76 /// # Panics
77 ///
78 /// Panics if the internal mutex is poisoned.
79 pub fn wait(self) -> Result<T, String> {
80 let (lock, cvar) = &*self.inner;
81 let mut state = lock.lock().unwrap();
82 while !state.completed {
83 state = cvar.wait(state).unwrap();
84 }
85 state
86 .result
87 .take()
88 .unwrap_or_else(|| Err("Completion signaled without result".to_string()))
89 }
90
91 /// Signal successful completion with a value
92 ///
93 /// # Safety
94 ///
95 /// The `context` pointer must be a valid pointer obtained from `SyncCompletion::new()`.
96 /// This function consumes the Arc reference, so it must only be called once per context.
97 pub unsafe fn complete_ok(context: SyncCompletionPtr, value: T) {
98 Self::complete_with_result(context, Ok(value));
99 }
100
101 /// Signal completion with an error
102 ///
103 /// # Safety
104 ///
105 /// The `context` pointer must be a valid pointer obtained from `SyncCompletion::new()`.
106 /// This function consumes the Arc reference, so it must only be called once per context.
107 pub unsafe fn complete_err(context: SyncCompletionPtr, error: String) {
108 Self::complete_with_result(context, Err(error));
109 }
110
111 /// Signal completion with a result
112 ///
113 /// # Safety
114 ///
115 /// The `context` pointer must be a valid pointer obtained from `SyncCompletion::new()`.
116 /// This function consumes the Arc reference, so it must only be called once per context.
117 ///
118 /// # Panics
119 ///
120 /// Panics if the internal mutex is poisoned.
121 pub unsafe fn complete_with_result(context: SyncCompletionPtr, result: Result<T, String>) {
122 if context.is_null() {
123 return;
124 }
125 let inner = Arc::from_raw(context.cast::<(Mutex<SyncCompletionState<T>>, Condvar)>());
126 let (lock, cvar) = &*inner;
127 {
128 let mut state = lock.lock().unwrap();
129 state.completed = true;
130 state.result = Some(result);
131 }
132 cvar.notify_one();
133 }
134}
135
136impl<T> Default for SyncCompletion<T> {
137 fn default() -> Self {
138 Self::new().0
139 }
140}
141
142// ============================================================================
143// Asynchronous Completion (Future-based)
144// ============================================================================
145
146/// Internal state for tracking async completion
147struct AsyncCompletionState<T> {
148 result: Option<Result<T, String>>,
149 waker: Option<Waker>,
150}
151
152/// An async completion handler for FFI callbacks
153///
154/// This type provides a `Future` that resolves when an async callback completes.
155/// It uses `Arc<Mutex>` internally for thread-safe signaling and waker management.
156pub struct AsyncCompletion<T> {
157 _marker: std::marker::PhantomData<T>,
158}
159
160/// Future returned by `AsyncCompletion`
161pub struct AsyncCompletionFuture<T> {
162 inner: Arc<Mutex<AsyncCompletionState<T>>>,
163}
164
165impl<T> AsyncCompletion<T> {
166 /// Create a new async completion handler and return the context pointer for FFI
167 ///
168 /// Returns a tuple of (future, `context_ptr`) where:
169 /// - `future` can be awaited to get the result
170 /// - `context_ptr` should be passed to the FFI callback
171 #[must_use]
172 pub fn create() -> (AsyncCompletionFuture<T>, SyncCompletionPtr) {
173 let inner = Arc::new(Mutex::new(AsyncCompletionState {
174 result: None,
175 waker: None,
176 }));
177 let raw = Arc::into_raw(Arc::clone(&inner));
178 (AsyncCompletionFuture { inner }, raw as SyncCompletionPtr)
179 }
180
181 /// Signal successful completion with a value
182 ///
183 /// # Safety
184 ///
185 /// The `context` pointer must be a valid pointer obtained from `AsyncCompletion::new()`.
186 /// This function consumes the Arc reference, so it must only be called once per context.
187 pub unsafe fn complete_ok(context: SyncCompletionPtr, value: T) {
188 Self::complete_with_result(context, Ok(value));
189 }
190
191 /// Signal completion with an error
192 ///
193 /// # Safety
194 ///
195 /// The `context` pointer must be a valid pointer obtained from `AsyncCompletion::new()`.
196 /// This function consumes the Arc reference, so it must only be called once per context.
197 pub unsafe fn complete_err(context: SyncCompletionPtr, error: String) {
198 Self::complete_with_result(context, Err(error));
199 }
200
201 /// Signal completion with a result
202 ///
203 /// # Safety
204 ///
205 /// The `context` pointer must be a valid pointer obtained from `AsyncCompletion::new()`.
206 /// This function consumes the Arc reference, so it must only be called once per context.
207 ///
208 /// # Panics
209 ///
210 /// Panics if the internal mutex is poisoned.
211 pub unsafe fn complete_with_result(context: SyncCompletionPtr, result: Result<T, String>) {
212 if context.is_null() {
213 return;
214 }
215 let inner = Arc::from_raw(context.cast::<Mutex<AsyncCompletionState<T>>>());
216
217 let waker = {
218 let mut state = inner.lock().unwrap();
219 state.result = Some(result);
220 state.waker.take()
221 };
222
223 if let Some(w) = waker {
224 w.wake();
225 }
226
227 // Keep the Arc alive - it will be dropped when the future is dropped
228 std::mem::forget(inner);
229 }
230}
231
232impl<T> Future for AsyncCompletionFuture<T> {
233 type Output = Result<T, String>;
234
235 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
236 let mut state = self.inner.lock().unwrap();
237
238 state.result.take().map_or_else(
239 || {
240 state.waker = Some(cx.waker().clone());
241 Poll::Pending
242 },
243 Poll::Ready,
244 )
245 }
246}
247
248// ============================================================================
249// Shared Utilities
250// ============================================================================
251
252/// Helper to extract error message from a C string pointer
253///
254/// # Safety
255///
256/// The `msg` pointer must be either null or point to a valid null-terminated C string.
257#[must_use]
258pub unsafe fn error_from_cstr(msg: *const i8) -> String {
259 if msg.is_null() {
260 "Unknown error".to_string()
261 } else {
262 CStr::from_ptr(msg)
263 .to_str()
264 .map_or_else(|_| "Unknown error".to_string(), String::from)
265 }
266}
267
268/// Unit completion - for operations that return success/error without a value
269pub type UnitCompletion = SyncCompletion<()>;
270
271impl UnitCompletion {
272 /// C callback for operations that return (context, success, `error_msg`)
273 ///
274 /// This can be used directly as an FFI callback function.
275 #[allow(clippy::not_unsafe_ptr_arg_deref)]
276 pub extern "C" fn callback(context: *mut c_void, success: bool, msg: *const i8) {
277 if success {
278 unsafe { Self::complete_ok(context, ()) };
279 } else {
280 let error = unsafe { error_from_cstr(msg) };
281 unsafe { Self::complete_err(context, error) };
282 }
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_sync_completion_success() {
292 let (completion, context) = SyncCompletion::<i32>::new();
293
294 // Simulate callback being called (normally from FFI)
295 unsafe { SyncCompletion::complete_ok(context, 42) };
296
297 let result = completion.wait();
298 assert_eq!(result, Ok(42));
299 }
300
301 #[test]
302 fn test_sync_completion_error() {
303 let (completion, context) = SyncCompletion::<i32>::new();
304
305 // Simulate callback being called with error
306 unsafe { SyncCompletion::<i32>::complete_err(context, "test error".to_string()) };
307
308 let result = completion.wait();
309 assert_eq!(result, Err("test error".to_string()));
310 }
311
312 #[test]
313 fn test_unit_completion_callback_success() {
314 let (completion, context) = UnitCompletion::new();
315
316 // Simulate successful callback
317 UnitCompletion::callback(context, true, std::ptr::null());
318
319 let result = completion.wait();
320 assert!(result.is_ok());
321 }
322
323 #[test]
324 fn test_unit_completion_callback_error() {
325 let (completion, context) = UnitCompletion::new();
326 let error_msg = std::ffi::CString::new("test error").unwrap();
327
328 // Simulate error callback
329 UnitCompletion::callback(context, false, error_msg.as_ptr());
330
331 let result = completion.wait();
332 assert_eq!(result, Err("test error".to_string()));
333 }
334
335 #[test]
336 fn test_error_from_cstr_null() {
337 let result = unsafe { error_from_cstr(std::ptr::null()) };
338 assert_eq!(result, "Unknown error");
339 }
340
341 #[test]
342 fn test_error_from_cstr_valid() {
343 let msg = std::ffi::CString::new("hello").unwrap();
344 let result = unsafe { error_from_cstr(msg.as_ptr()) };
345 assert_eq!(result, "hello");
346 }
347
348 #[test]
349 fn test_async_completion_immediate() {
350 let (future, context) = AsyncCompletion::<i32>::create();
351
352 // Complete immediately before polling
353 unsafe { AsyncCompletion::complete_ok(context, 42) };
354
355 // Poll should return Ready immediately
356 let waker = std::task::Waker::noop();
357 let mut cx = Context::from_waker(&waker);
358 let mut pinned = std::pin::pin!(future);
359
360 match pinned.as_mut().poll(&mut cx) {
361 Poll::Ready(Ok(v)) => assert_eq!(v, 42),
362 _ => panic!("Expected Ready(Ok(42))"),
363 }
364 }
365}