screencapturekit/utils/
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//! ```no_run
9//! use screencapturekit::utils::completion::SyncCompletion;
10//!
11//! // Create completion for a String result
12//! let (completion, _context) = SyncCompletion::<String>::new();
13//!
14//! // In real use, context would be passed to FFI callback
15//! // The callback would signal completion with a result
16//!
17//! // Block until callback completes (would hang without callback)
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::create()`.
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        // Drop the Arc here - the refcount was incremented in create() via Arc::clone(),
228        // so the data stays alive via the AsyncCompletionFuture's Arc until it's dropped.
229        // Dropping here decrements the refcount from the into_raw() call.
230    }
231}
232
233impl<T> Future for AsyncCompletionFuture<T> {
234    type Output = Result<T, String>;
235
236    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
237        let mut state = self.inner.lock().unwrap();
238
239        state.result.take().map_or_else(
240            || {
241                state.waker = Some(cx.waker().clone());
242                Poll::Pending
243            },
244            Poll::Ready,
245        )
246    }
247}
248
249// ============================================================================
250// Shared Utilities
251// ============================================================================
252
253/// Helper to extract error message from a C string pointer
254///
255/// # Safety
256///
257/// The `msg` pointer must be either null or point to a valid null-terminated C string.
258#[must_use]
259pub unsafe fn error_from_cstr(msg: *const i8) -> String {
260    if msg.is_null() {
261        "Unknown error".to_string()
262    } else {
263        CStr::from_ptr(msg)
264            .to_str()
265            .map_or_else(|_| "Unknown error".to_string(), String::from)
266    }
267}
268
269/// Unit completion - for operations that return success/error without a value
270pub type UnitCompletion = SyncCompletion<()>;
271
272impl UnitCompletion {
273    /// C callback for operations that return (context, success, `error_msg`)
274    ///
275    /// This can be used directly as an FFI callback function.
276    #[allow(clippy::not_unsafe_ptr_arg_deref)]
277    pub extern "C" fn callback(context: *mut c_void, success: bool, msg: *const i8) {
278        if success {
279            unsafe { Self::complete_ok(context, ()) };
280        } else {
281            let error = unsafe { error_from_cstr(msg) };
282            unsafe { Self::complete_err(context, error) };
283        }
284    }
285}