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}