cxx_qt_gen/generator/rust/
threading.rs

1// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6use crate::{
7    generator::{
8        naming::{
9            namespace::{namespace_combine_ident, NamespaceName},
10            qobject::QObjectNames,
11        },
12        rust::fragment::GeneratedRustFragment,
13    },
14    naming::TypeNames,
15};
16use quote::quote;
17use syn::Result;
18
19use super::fragment::RustFragmentPair;
20
21pub fn generate(
22    qobject_names: &QObjectNames,
23    namespace_ident: &NamespaceName,
24    type_names: &TypeNames,
25) -> Result<GeneratedRustFragment> {
26    let mut blocks = GeneratedRustFragment::default();
27
28    let module_ident = qobject_names.name.require_module()?;
29
30    let cpp_struct_ident = qobject_names.name.rust_unqualified();
31    let cxx_qt_thread_ident = &qobject_names.cxx_qt_thread_class;
32    let cxx_qt_thread_queued_fn_ident = &qobject_names.cxx_qt_thread_queued_fn_struct;
33
34    let (thread_queue_name, thread_queue_attrs, thread_queue_qualified) = qobject_names
35        .cxx_qt_ffi_method("cxxQtThreadQueue")
36        .into_cxx_parts();
37    let (thread_clone_name, thread_clone_attrs, thread_clone_qualified) = qobject_names
38        .cxx_qt_ffi_method("cxxQtThreadClone")
39        .into_cxx_parts();
40    let (thread_drop_name, thread_drop_attrs, thread_drop_qualified) = qobject_names
41        .cxx_qt_ffi_method("cxxQtThreadDrop")
42        .into_cxx_parts();
43    let (thread_fn_name, thread_fn_attrs, thread_fn_qualified) =
44        qobject_names.cxx_qt_ffi_method("qtThread").into_cxx_parts();
45    let (thread_is_destroyed_name, thread_is_destroyed_attrs, thread_is_destroyed_qualified) =
46        qobject_names
47            .cxx_qt_ffi_method("cxxQtThreadIsDestroyed")
48            .into_cxx_parts();
49
50    let cxx_qt_thread_namespace = &namespace_ident.namespace;
51    let namespace_internals = &namespace_ident.internal;
52    let cxx_qt_thread_ident_type_id_str =
53        namespace_combine_ident(&namespace_ident.namespace, cxx_qt_thread_ident);
54    let qualified_impl = type_names.rust_qualified(cpp_struct_ident)?;
55
56    let fragment = RustFragmentPair {
57        cxx_bridge: vec![
58            quote! {
59                unsafe extern "C++" {
60                    // Specialised version of CxxQtThread, which can be moved into other threads.
61                    //
62                    // CXX doesn't support having generic types in the function yet
63                    // so we cannot have CxxQtThread<T> in cxx-qt-lib and then use that here
64                    // For now we use a type alias in C++ then use it like a normal type here
65                    // <https://github.com/dtolnay/cxx/issues/683>
66                    #[doc(hidden)]
67                    #[namespace = #cxx_qt_thread_namespace]
68                    type #cxx_qt_thread_ident = cxx_qt::CxxQtThread<#cpp_struct_ident>;
69                    include!("cxx-qt/thread.h");
70
71                    #[doc(hidden)]
72                    #(#thread_fn_attrs)*
73                    fn #thread_fn_name(qobject: &#cpp_struct_ident) -> #cxx_qt_thread_ident;
74
75                    // SAFETY:
76                    // - Send + 'static: argument closure can be transferred to QObject thread.
77                    // - FnOnce: QMetaObject::invokeMethod() should call the function at most once.
78                    #[doc(hidden)]
79                    #(#thread_queue_attrs)*
80                    fn #thread_queue_name(
81                        cxx_qt_thread: &#cxx_qt_thread_ident,
82                        func: fn(Pin<&mut #cpp_struct_ident>, Box<#cxx_qt_thread_queued_fn_ident>),
83                        arg: Box<#cxx_qt_thread_queued_fn_ident>,
84                    ) -> u8;
85
86                    #[doc(hidden)]
87                    #(#thread_clone_attrs)*
88                    fn #thread_clone_name(cxx_qt_thread: &#cxx_qt_thread_ident) -> #cxx_qt_thread_ident;
89
90                    #[doc(hidden)]
91                    #(#thread_drop_attrs)*
92                    fn #thread_drop_name(cxx_qt_thread: Pin<&mut #cxx_qt_thread_ident>);
93
94                    #[doc(hidden)]
95                    #(#thread_is_destroyed_attrs)*
96                    fn #thread_is_destroyed_name(cxx_qt_thread: &#cxx_qt_thread_ident) -> bool;
97                }
98            },
99            quote! {
100                extern "Rust" {
101                    #[namespace = #namespace_internals]
102                    type #cxx_qt_thread_queued_fn_ident;
103                }
104            },
105        ],
106        implementation: vec![
107            quote! {
108                impl cxx_qt::Threading for #qualified_impl {
109                    type BoxedQueuedFn = #cxx_qt_thread_queued_fn_ident;
110                    type ThreadingTypeId = cxx::type_id!(#cxx_qt_thread_ident_type_id_str);
111
112                    fn qt_thread(&self) -> #module_ident::#cxx_qt_thread_ident
113                    {
114                        #thread_fn_qualified(self)
115                    }
116
117                    #[doc(hidden)]
118                    fn is_destroyed(cxx_qt_thread: &#module_ident::#cxx_qt_thread_ident) -> bool
119                    {
120                        #thread_is_destroyed_qualified(cxx_qt_thread)
121                    }
122
123                    #[doc(hidden)]
124                    fn queue<F>(cxx_qt_thread: &#module_ident::#cxx_qt_thread_ident, f: F) -> std::result::Result<(), cxx_qt::ThreadingQueueError>
125                    where
126                        F: FnOnce(core::pin::Pin<&mut #qualified_impl>),
127                        F: Send + 'static,
128                    {
129                        // Wrap the given closure and pass in to C++ function as an opaque type
130                        // to work around the cxx limitation.
131                        // https://github.com/dtolnay/cxx/issues/114
132                        #[allow(clippy::boxed_local)]
133                        #[doc(hidden)]
134                        fn func(
135                            obj: core::pin::Pin<&mut #qualified_impl>,
136                            arg: std::boxed::Box<#cxx_qt_thread_queued_fn_ident>,
137                        ) {
138                            (arg.inner)(obj)
139                        }
140                        let arg = #cxx_qt_thread_queued_fn_ident { inner: std::boxed::Box::new(f) };
141                        match #thread_queue_qualified(cxx_qt_thread, func, std::boxed::Box::new(arg)) {
142                            0 => Ok(()),
143                            others => Err(others.into()),
144                        }
145                    }
146
147                    #[doc(hidden)]
148                    fn threading_clone(cxx_qt_thread: &#module_ident::#cxx_qt_thread_ident) -> #module_ident::#cxx_qt_thread_ident
149                    {
150                        #thread_clone_qualified(cxx_qt_thread)
151                    }
152
153                    #[doc(hidden)]
154                    fn threading_drop(cxx_qt_thread: Pin<&mut #module_ident::#cxx_qt_thread_ident>)
155                    {
156                        #thread_drop_qualified(cxx_qt_thread);
157                    }
158                }
159            },
160            quote! {
161                #[doc(hidden)]
162                pub struct #cxx_qt_thread_queued_fn_ident {
163                    // An opaque Rust type is required to be Sized.
164                    // https://github.com/dtolnay/cxx/issues/665
165                    inner: std::boxed::Box<dyn FnOnce(core::pin::Pin<&mut #qualified_impl>) + Send>,
166                }
167            },
168        ],
169    };
170
171    blocks
172        .cxx_mod_contents
173        .append(&mut fragment.cxx_bridge_as_items()?);
174    blocks
175        .cxx_qt_mod_contents
176        .append(&mut fragment.implementation_as_items()?);
177
178    Ok(blocks)
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    use crate::naming::TypeNames;
186    use crate::tests::assert_tokens_eq;
187
188    use crate::parser::qobject::tests::create_parsed_qobject;
189
190    #[test]
191    fn test_generate_rust_threading() {
192        let qobject = create_parsed_qobject();
193        let qobject_names = QObjectNames::from_qobject(&qobject, &TypeNames::mock()).unwrap();
194        let namespace_ident = NamespaceName::from(&qobject);
195
196        let generated = generate(&qobject_names, &namespace_ident, &TypeNames::mock()).unwrap();
197
198        assert_eq!(generated.cxx_mod_contents.len(), 2);
199        assert_eq!(generated.cxx_qt_mod_contents.len(), 2);
200
201        // CXX bridges
202
203        assert_tokens_eq(
204            &generated.cxx_mod_contents[0],
205            quote! {
206                unsafe extern "C++" {
207                    #[doc(hidden)]
208                    #[namespace = ""]
209                    type MyObjectCxxQtThread = cxx_qt::CxxQtThread<MyObject>;
210                    include!("cxx-qt/thread.h");
211
212                    #[doc(hidden)]
213                    #[cxx_name = "qtThread"]
214                    #[namespace = "rust::cxxqt1"]
215                    fn cxx_qt_ffi_MyObject_qtThread(qobject: &MyObject) -> MyObjectCxxQtThread;
216
217                    // SAFETY:
218                    // - Send + 'static: argument closure can be transferred to QObject thread.
219                    // - FnOnce: QMetaObject::invokeMethod() should call the function at most once.
220                    #[doc(hidden)]
221                    #[cxx_name = "cxxQtThreadQueue"]
222                    #[namespace = "rust::cxxqt1"]
223                    fn cxx_qt_ffi_MyObject_cxxQtThreadQueue(
224                        cxx_qt_thread: &MyObjectCxxQtThread,
225                        func: fn(Pin<&mut MyObject>, Box<MyObjectCxxQtThreadQueuedFn>),
226                        arg: Box<MyObjectCxxQtThreadQueuedFn>,
227                    ) -> u8;
228
229                    #[doc(hidden)]
230                    #[cxx_name = "cxxQtThreadClone"]
231                    #[namespace = "rust::cxxqt1"]
232                    fn cxx_qt_ffi_MyObject_cxxQtThreadClone(cxx_qt_thread: &MyObjectCxxQtThread) -> MyObjectCxxQtThread;
233
234                    #[doc(hidden)]
235                    #[cxx_name = "cxxQtThreadDrop"]
236                    #[namespace = "rust::cxxqt1"]
237                    fn cxx_qt_ffi_MyObject_cxxQtThreadDrop(cxx_qt_thread: Pin<&mut MyObjectCxxQtThread>);
238
239                    #[doc(hidden)]
240                    #[cxx_name = "cxxQtThreadIsDestroyed"]
241                    #[namespace = "rust::cxxqt1"]
242                    fn cxx_qt_ffi_MyObject_cxxQtThreadIsDestroyed(cxx_qt_thread: &MyObjectCxxQtThread) -> bool;
243                }
244            },
245        );
246        assert_tokens_eq(
247            &generated.cxx_mod_contents[1],
248            quote! {
249                extern "Rust" {
250                    #[namespace = "cxx_qt_MyObject"]
251                    type MyObjectCxxQtThreadQueuedFn;
252                }
253            },
254        );
255
256        // CXX-Qt generated contents
257        assert_tokens_eq(
258            &generated.cxx_qt_mod_contents[0],
259            quote! {
260                impl cxx_qt::Threading for qobject::MyObject {
261                    type BoxedQueuedFn = MyObjectCxxQtThreadQueuedFn;
262                    type ThreadingTypeId = cxx::type_id!("MyObjectCxxQtThread");
263
264                    fn qt_thread(&self) -> qobject::MyObjectCxxQtThread
265                    {
266                        qobject::cxx_qt_ffi_MyObject_qtThread(self)
267                    }
268
269                    #[doc(hidden)]
270                    fn is_destroyed(cxx_qt_thread: &qobject::MyObjectCxxQtThread) -> bool {
271                        qobject::cxx_qt_ffi_MyObject_cxxQtThreadIsDestroyed(cxx_qt_thread)
272                    }
273
274                    #[doc(hidden)]
275                    fn queue<F>(cxx_qt_thread: &qobject::MyObjectCxxQtThread, f: F) -> std::result::Result<(), cxx_qt::ThreadingQueueError>
276                    where
277                        F: FnOnce(core::pin::Pin<&mut qobject::MyObject>),
278                        F: Send + 'static,
279                    {
280                        // Wrap the given closure and pass in to C++ function as an opaque type
281                        // to work around the cxx limitation.
282                        // https://github.com/dtolnay/cxx/issues/114
283                        #[allow(clippy::boxed_local)]
284                        #[doc(hidden)]
285                        fn func(
286                            obj: core::pin::Pin<&mut qobject::MyObject>,
287                            arg: std::boxed::Box<MyObjectCxxQtThreadQueuedFn>,
288                        ) {
289                            (arg.inner)(obj)
290                        }
291                        let arg = MyObjectCxxQtThreadQueuedFn { inner: std::boxed::Box::new(f) };
292                        match qobject::cxx_qt_ffi_MyObject_cxxQtThreadQueue(cxx_qt_thread, func, std::boxed::Box::new(arg)) {
293                            0 => Ok(()),
294                            others => Err(others.into()),
295                        }
296                    }
297
298                    #[doc(hidden)]
299                    fn threading_clone(cxx_qt_thread: &qobject::MyObjectCxxQtThread) -> qobject::MyObjectCxxQtThread
300                    {
301                        qobject::cxx_qt_ffi_MyObject_cxxQtThreadClone(cxx_qt_thread)
302                    }
303
304                    #[doc(hidden)]
305                    fn threading_drop(cxx_qt_thread: Pin<&mut qobject::MyObjectCxxQtThread>)
306                    {
307                        qobject::cxx_qt_ffi_MyObject_cxxQtThreadDrop(cxx_qt_thread);
308                    }
309                }
310            },
311        );
312        assert_tokens_eq(
313            &generated.cxx_qt_mod_contents[1],
314            quote! {
315                #[doc(hidden)]
316                pub struct MyObjectCxxQtThreadQueuedFn {
317                    // An opaque Rust type is required to be Sized.
318                    // https://github.com/dtolnay/cxx/issues/665
319                    inner: std::boxed::Box<dyn FnOnce(core::pin::Pin<&mut qobject::MyObject>) + Send>,
320                }
321            },
322        );
323    }
324}