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}