qtbridge_runtime/qthreadhandle.rs
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
3
4use std::cell::RefCell;
5use std::marker::PhantomData;
6use std::rc::Rc;
7
8use qtbridge_type_lib::QObject;
9
10use crate::QObjectHolder;
11use crate::qproxies::QRustProxy;
12use crate::rustobjectgetter::get_rust_proxy;
13
14#[cxx::bridge]
15pub mod ffi {
16 #[allow(clippy::missing_safety_doc)]
17 unsafe extern "C++" {
18 include!("qtbridge-type-lib/src/core/qobject/cpp/qobject.h");
19 type QObject = qtbridge_type_lib::QObject;
20
21 include!("cpp/qthreadhandle.h");
22 type QThreadHandleCpp;
23
24 /// Creates the C++ guard holding a `QPointer<QObject>` with `target`.
25 ///
26 /// The returned guard may be moved to, used from and destroyed on
27 /// any thread.
28 ///
29 /// A null `target`, or one not living on the application thread,
30 /// yields a guard whose calls are all discarded.
31 ///
32 /// # Safety
33 ///
34 /// Must be called on `target`'s thread, with `target` either alive
35 /// or null.
36 unsafe fn make_qt_thread_handle(target: *mut QObject) -> UniquePtr<QThreadHandleCpp>;
37
38 /// Posts `closure` to the event loop running on the Qt thread.
39 /// Thread-safe; never touches the target object on the calling thread.
40 fn post_closure(handle: &QThreadHandleCpp, closure: Box<QObjectClosureBox>);
41
42 /// Posts `closure` to the event loop running on the Qt thread, without
43 /// a target. Thread-safe.
44 fn post_void_closure(closure: Box<VoidClosureBox>);
45 }
46
47 extern "Rust" {
48 type VoidClosureBox;
49 fn run(self: &mut VoidClosureBox);
50
51 type QObjectClosureBox;
52 fn run(self: &mut QObjectClosureBox, target: &QObject);
53 }
54}
55
56
57type VoidClosure = dyn FnOnce() + Send;
58#[doc(hidden)]
59pub struct VoidClosureBox {
60 f: Option<Box<VoidClosure>>,
61}
62
63impl VoidClosureBox {
64 fn run(&mut self) {
65 if let Some(f) = self.f.take() {
66 f();
67 }
68 }
69}
70
71type QObjectClosure = dyn FnOnce(&QObject) + Send;
72#[doc(hidden)]
73pub struct QObjectClosureBox {
74 f: Option<Box<QObjectClosure>>,
75}
76
77impl QObjectClosureBox {
78 fn run(&mut self, target: &QObject) {
79 if let Some(f) = self.f.take() {
80 f(target);
81 }
82 }
83}
84
85/// A thread-safe handle that schedules Rust closures to run on the Qt thread.
86///
87/// A `QThreadHandle<T>` is bound to a `#[qobject]` instance of type `T` and
88/// hands the closure access to it: shared via [`invoke`](Self::invoke),
89/// mutable via [`invoke_mut`](Self::invoke_mut). Obtain one via
90/// [`QmlObject::get_thread_handle`](crate::QmlObject::get_thread_handle).
91/// The calls are type-checked at compile time and need no `#[qslot]` annotation.
92/// A `QThreadHandle<T>` has to be created on the Qt thread and can only be bound
93/// to `#[qobject]` instances that live on that thread.
94///
95/// A `QThreadHandle<()>`, created via [`QThreadHandle::default`], is not
96/// bound to any `#[qobject]` instance and just runs closures on the Qt thread.
97/// It can be created in any thread.
98///
99/// Closures are posted to the Qt event loop through the application instance;
100/// On delivery the target's liveness is checked on the Qt thread, so a target
101/// deleted at any point is safe: the closure is then silently discarded.
102/// Without an application, closures are discarded as well, and so are the
103/// closures of a handle whose target was created on another thread.
104///
105/// While a closure runs, it holds a borrow of the `Rc<RefCell<_>>` shared
106/// with the QML engine. If the object is already mutably borrowed at that
107/// point (for example by a slot running a nested event loop), the application
108/// is aborted.
109///
110/// # Example
111///
112/// ```
113/// # use qtbridge::{qobject, QmlObject};
114/// # #[qobject]
115/// # pub mod example {
116/// # #[derive(Default)]
117/// # pub struct Backend { pub value: i32 }
118/// # impl Backend {
119/// # #[qsignal]
120/// # pub fn data_ready(&mut self);
121/// # }
122/// # }
123/// # use example::Backend;
124/// let backend = Backend::default_with_attached_qobject();
125/// let handle = backend.borrow().get_thread_handle();
126/// std::thread::spawn(move || {
127/// handle.invoke_mut(|b| {
128/// b.value = 42;
129/// b.data_ready();
130/// });
131/// }).join().unwrap();
132/// ```
133pub struct QThreadHandle<T> {
134 cpp: cxx::UniquePtr<ffi::QThreadHandleCpp>,
135 _target_type: PhantomData<fn(T) -> T>,
136}
137
138// SAFETY: the C++ guard is immutable after construction. `post_closure`
139// only copies the weak reference `QPointer` and posts to the application
140// instance. Dropping the guard releases the weak reference. No
141// dereferencing of the Qt thread.
142unsafe impl<T> Send for QThreadHandle<T> {}
143unsafe impl<T> Sync for QThreadHandle<T> {}
144
145impl Default for QThreadHandle<()> {
146 /// Creates a handle without a target. May be called on any thread.
147 fn default() -> Self {
148 Self { cpp: cxx::UniquePtr::null(), _target_type: PhantomData }
149 }
150}
151
152impl QThreadHandle<()> {
153 /// Schedules `callback` in the Qt event loop.
154 ///
155 /// If there is no application, the callback is silently discarded, along
156 /// with all captured objects.
157 ///
158 /// # Example
159 ///
160 /// ```
161 /// # use qtbridge::QThreadHandle;
162 /// let handle = QThreadHandle::default();
163 /// std::thread::spawn(move || {
164 /// handle.invoke(|| println!("on the Qt thread"));
165 /// QThreadHandle::default().invoke(|| println!("also on the Qt thread"));
166 /// }).join().unwrap();
167 /// ```
168 pub fn invoke(&self, callback: impl FnOnce() + Send + 'static) {
169 ffi::post_void_closure(Box::new(VoidClosureBox { f: Some(Box::new(callback)) }));
170 }
171}
172
173impl<T: QObjectHolder> QThreadHandle<T> {
174 /// Creates a `QThreadHandle` for `target`.
175 ///
176 /// Prefer
177 /// [`QmlObject::get_thread_handle`](crate::QmlObject::get_thread_handle)
178 /// over calling this directly.
179 pub fn new(target: &T) -> Self {
180 let obj = target.get_qobject_ptr();
181 let cpp = unsafe { ffi::make_qt_thread_handle(obj) };
182 Self { cpp, _target_type: PhantomData }
183 }
184
185 /// Schedules `callback` in the Qt event loop with shared access to the
186 /// bound target.
187 ///
188 /// If the target has been dropped by the time the event loop delivers
189 /// the call the callback is silently discarded, along with all captured
190 /// objects.
191 pub fn invoke(&self, callback: impl FnOnce(&T) + Send + 'static) {
192 self.post(move |rc| callback(&rc.borrow()));
193 }
194
195 /// Schedules `callback` in the Qt event loop with mutable access to the
196 /// target.
197 ///
198 /// If the target has been dropped by the time the event loop delivers
199 /// the call the callback is silently discarded, along with all captured
200 /// objects.
201 pub fn invoke_mut(&self, callback: impl FnOnce(&mut T) + Send + 'static) {
202 self.post(move |rc| callback(&mut rc.borrow_mut()));
203 }
204
205 #[doc(hidden)]
206 fn post(&self, callback: impl FnOnce(Rc<RefCell<T>>) + Send + 'static) {
207 let erased: Box::<dyn FnOnce(&QObject) + Send> = Box::new(move |qobj| callback(recover_rc::<T>(qobj)));
208 ffi::post_closure(&self.cpp, Box::new(QObjectClosureBox { f: Some(erased) }));
209 }
210}
211
212// This is `QObjectHolder::qobject_to_rc_ref_cell` minus two things it does
213// not need: the inherits check (the handle is typed at construction, so the
214// `QObject` guarded by the `QPointer` is statically a `T`), and the
215// ownership `repin`.
216fn recover_rc<T: QObjectHolder>(qobj: &QObject) -> Rc<RefCell<T>> {
217 let proxy_ptr = get_rust_proxy(qobj) as *const T::ProxyRust;
218 debug_assert!(!proxy_ptr.is_null());
219 let proxy = unsafe { &*proxy_ptr };
220 let rc_adapter = proxy.get_rust_object_rc();
221
222 // SAFETY: see `qobject_to_rc_ref_cell`
223 let raw_ref_cell = Rc::into_raw(rc_adapter).cast::<RefCell<T>>();
224 unsafe { Rc::from_raw(raw_ref_cell) }
225}