Skip to main content

qtbridge_runtime/
qml_register.rs

1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
3
4use std::rc::Rc;
5use std::cell::RefCell;
6
7use crate::QObjectHolder;
8use crate::QMetaInfo;
9use crate::qqmllistproperty::{list_append, list_count, list_at, list_clear};
10use crate::qproxies::QCppProxy;
11use crate::qproxies::ConstructionMode;
12use qtbridge_type_lib::QObject;
13use qtbridge_type_lib::QMetaType;
14use qtbridge_type_lib::QMetaTypeGet;
15use qtbridge_type_lib::QMetaTypeInterface;
16use qtbridge_type_lib::{QVariant, list_property_to_qvariant};
17
18pub trait QmlRegister : QMetaTypeGet + QObjectHolder + Default
19{
20    const URI: &str;
21    const ELEMENT_NAME: &str;
22    const MINOR_VERSION: u8;
23    const MAJOR_VERSION: u8;
24    const IS_SINGLETON: bool;
25
26    fn get_list_qmetatype() -> QMetaType {
27        // TODO: The HashMap can be replaced with OnceCell in a per-type generated implementation
28        // of this function in qtbridge-gen. Might improve performance.
29        use std::collections::HashMap;
30        thread_local!(static LIST_IFACE_MAP: RefCell<HashMap<i32, *const QMetaTypeInterface>>
31            = RefCell::new(HashMap::new()));
32
33        let element = <Self as QMetaTypeGet>::get_qmetatype();
34        let key = element.id();
35
36        let existing = LIST_IFACE_MAP.with_borrow(|m| m.get(&key).copied().unwrap_or_default());
37        let iface = if existing.is_null() {
38            let leaked = std::ptr::from_ref(Box::leak(Box::new(
39                QMetaTypeInterface::qqml_list_property_for(&element)
40            )));
41            LIST_IFACE_MAP.with_borrow_mut(|m| m.insert(key, leaked));
42            leaked
43        } else {
44            existing
45        };
46        QMetaType::new_with_interface(iface)
47    }
48
49    fn list_to_qvariant<Owner, Notify>(owner: &Owner, store: &Vec<Rc<RefCell<Self>>>, _notify: Notify) -> QVariant
50    where
51        Owner: QObjectHolder,
52        Notify: Fn(&mut Owner) + 'static,
53    {
54        debug_assert_eq!(std::mem::size_of::<Notify>(), 0, "Notify must be a zero-sized type");
55        let qobject = owner.get_qobject_ptr();
56        let base = owner as *const Owner as *const u8;
57        let field = store as *const _ as *const u8;
58        // SAFETY: `store` is a field within `owner`, so both pointers are in the same allocation.
59        // The `owner` can be reconstructed safely, even when objects are moved since we store a
60        // shared reference to it. With the known offset we can also reconstruct `store`. The callbacks
61        // in crate::qqmllistproperty expect this exact format.
62        let store_offset = unsafe { field.offset_from(base) } as usize;
63        unsafe { list_property_to_qvariant(
64            &Self::get_list_qmetatype(),
65            qobject,
66            store_offset as *mut u8,
67            (list_append::<Owner, Self, Notify> as *const ()).addr(),
68            (list_count::<Owner, Self> as *const ()).addr(),
69            (list_at::<Owner, Self> as *const ()).addr(),
70            (list_clear::<Owner, Self, Notify> as *const ()).addr(),
71        ) }
72    }
73
74    fn register() {
75        let meta_obj_data = <Self as QMetaInfo>::get_shared_dynamic_meta_object_data();
76        let meta_obj = unsafe {
77            meta_obj_data
78                .get_meta_object()
79                .as_ref()
80                .expect("Failed to get QMetaObject")
81        };
82
83        if Self::IS_SINGLETON {
84            qtbridge_type_lib::qml_register_singleton(
85                <Self as QMetaTypeGet>::get_qmetatype(),
86                monomorphize_singleton_ctor::<Self>(),
87                Self::URI.as_bytes(),
88                Self::MAJOR_VERSION,
89                Self::MINOR_VERSION,
90                Self::ELEMENT_NAME.as_bytes(),
91                meta_obj,
92            )
93        } else {
94            let list_metatype = Self::get_list_qmetatype();
95            list_metatype.register_type();
96
97            qtbridge_type_lib::qml_register_element(
98                <Self as QMetaTypeGet>::get_qmetatype(),
99                list_metatype,
100                <<Self as QMetaInfo>::CppProxy as QCppProxy>::get_size() as u32,
101                <<Self as QMetaInfo>::CppProxy as QCppProxy>::parser_status_cast(),
102                monomorphize_element_ctor::<Self>(),
103                Self::URI.as_bytes(),
104                Self::MAJOR_VERSION,
105                Self::MINOR_VERSION,
106                Self::ELEMENT_NAME.as_bytes(),
107                meta_obj,
108            );
109        }
110    }
111}
112
113fn element_ctor<T: QmlRegister>(addr: *mut u8, _userdata: *mut u8) {
114    let instance = std::rc::Rc::new(std::cell::RefCell::new(T::default()));
115    T::register_instance_in_map(instance.clone(), ConstructionMode::AtAddress(addr));
116}
117
118fn singleton_ctor<T: QmlRegister>() -> *mut QObject {
119    let instance = std::rc::Rc::new(std::cell::RefCell::new(T::default()));
120    T::register_instance_in_map(instance.clone(), ConstructionMode::Strong);
121    instance.borrow().get_qobject_ptr()
122}
123
124fn monomorphize_element_ctor<T: QmlRegister>() -> usize {
125    extern "C" fn default_ctor<T: QmlRegister>(addr: *mut u8, userdata: *mut u8) {
126        element_ctor::<T>(addr, userdata)
127    }
128    default_ctor::<T> as *const () as usize
129}
130
131fn monomorphize_singleton_ctor<T: QmlRegister>() -> usize {
132    extern "C" fn default_ctor<T: QmlRegister>() -> *mut QObject {
133        singleton_ctor::<T>()
134    }
135    default_ctor::<T> as *const () as usize
136}