use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Weak;
use qtbridge_type_lib::QObject;
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("qtbridge-type-lib/src/generated/core/qobject/cpp/qobject.h");
type QObject = qtbridge_type_lib::QObject;
include!("cpp/registry.h");
}
unsafe extern "C++" {
include!("qtbridge-type-lib/src/generated/qml/qqmlapplicationengine/cpp/qqmlapplicationengine.h");
type QQmlApplicationEngine = qtbridge_type_lib::QQmlApplicationEngine;
}
#[namespace = "rust::bridge::registry"]
unsafe extern "C++" {
#[rust_name = has_live_js_wrapper]
unsafe fn hasLiveJsWrapper(obj: *const QObject) -> bool;
#[rust_name = set_cpp_ownership]
unsafe fn setCppOwnership(obj: *mut QObject);
#[rust_name = set_javascript_ownership]
unsafe fn setJavaScriptOwnership(obj: *mut QObject);
#[rust_name = is_javascript_ownership]
unsafe fn isJavaScriptOwnership(obj: *mut QObject) -> bool;
#[rust_name = install_gc_sentinel_impl]
fn installGcSentinel(engine: Pin<&mut QQmlApplicationEngine>);
}
#[namespace = "rust::bridge::registry"]
extern "Rust" {
fn collect_garbage();
}
}
pub fn install_gc_sentinel(engine: core::pin::Pin<&mut qtbridge_type_lib::QQmlApplicationEngine>) {
ffi::install_gc_sentinel_impl(engine);
}
#[derive(Clone, PartialEq)]
pub enum Owner {
RustRegistry,
Engine,
}
struct Entry {
proxy: *const u8,
qobject: *mut QObject,
value: Weak<dyn Any>,
owner: Owner,
}
struct Entries {
map: HashMap<*const u8, Entry>,
owned: usize,
}
impl Drop for Entries {
fn drop(&mut self) {
for (_, entry) in self.map.drain() {
if entry.owner == Owner::RustRegistry {
QObject::delete(entry.qobject); }
}
}
}
thread_local! {
static REGISTRY: RefCell<Entries> = RefCell::new(Entries { map: HashMap::new(), owned: 0 });
}
thread_local! {
static COLLECT_THRESHOLD: Cell<usize> = const { Cell::new(64) };
}
fn owned_count() -> usize {
REGISTRY.with_borrow(|entries| entries.owned)
}
pub(crate) fn register(
key: *const u8, proxy: *const u8, qobject: *mut QObject,
value: Weak<dyn Any>, owner: Owner
) {
let registry_owned = owner == Owner::RustRegistry;
if registry_owned {
unsafe { ffi::set_cpp_ownership(qobject) };
}
REGISTRY.with_borrow_mut(|entries| {
let old = entries.map.insert(key, Entry { proxy, qobject, value, owner });
debug_assert!(old.is_none(), "Object is already registered");
entries.owned += registry_owned as usize;
});
if registry_owned && owned_count() >= COLLECT_THRESHOLD.get() {
collect_garbage();
}
}
pub(crate) fn repin(key: *const u8) {
REGISTRY.with_borrow(|entries| {
if let Some(entry) = entries.map.get(&key) {
if entry.owner == Owner::RustRegistry {
unsafe { ffi::set_cpp_ownership(entry.qobject) };
}
}
});
}
pub(crate) fn unregister(key: *const u8) {
let _ = REGISTRY.try_with(|entries: &RefCell<Entries>| {
let mut entries = entries.borrow_mut();
if let Some(entry) = entries.map.remove(&key) {
entries.owned -= (entry.owner == Owner::RustRegistry) as usize;
}
});
}
pub(crate) fn proxy_ptr(key: *const u8) -> *const u8 {
REGISTRY.with_borrow(|entries| {
entries.map.get(&key).map_or(std::ptr::null(), |entry| entry.proxy)
})
}
pub fn live_count() -> usize {
owned_count()
}
pub fn live_proxy_count() -> usize {
REGISTRY.with_borrow(|entries| entries.map.len())
}
pub fn collect_garbage() {
loop {
let doomed: Vec<(Weak<dyn Any>, *mut QObject)> = REGISTRY.with_borrow_mut(|entries| {
let extracted: Vec<_> = entries.map.extract_if(|_key, entry| {
if entry.owner == Owner::Engine {
return false;
}
debug_assert!(entry.value.strong_count() >= 1);
if entry.value.strong_count() > 1 {
return false;
}
if unsafe { ffi::is_javascript_ownership(entry.qobject) } {
return false;
}
if unsafe { ffi::has_live_js_wrapper(entry.qobject) } {
unsafe { ffi::set_javascript_ownership(entry.qobject) };
return false;
}
true
})
.map(|(_key, entry)| {
(entry.value, entry.qobject)
})
.collect();
entries.owned -= extracted.len();
extracted
});
if doomed.is_empty() {
break;
}
for (value, qobject) in doomed {
let keep_alive = value.upgrade();
assert!(!keep_alive.is_none());
QObject::delete(qobject);
drop(keep_alive);
}
}
COLLECT_THRESHOLD.set((owned_count() * 2).max(64));
}