use std::mem;
use std::rc::{Rc, Weak};
use std::cell::{Cell, RefCell};
#[macro_export]
macro_rules! call_rust_trait_impl {
(mut $self:expr, $method:ident ( $($arg:expr),* )) => {
$self.rust_obj
.try_call_rust_with_handle_mut(|vtable| {
vtable.$method($($arg),*)
})
.expect(concat!(
"Failed to borrow mutably for ",
stringify!($method)
))
};
($self:expr, $method:ident ( $($arg:expr),* )) => {
$self.rust_obj
.try_call_rust_with_handle(|vtable| {
vtable.$method($($arg),*)
})
.expect(concat!(
"Failed to borrow for ",
stringify!($method)
))
};
}
#[macro_export]
macro_rules! call_cpp_impl {
(mut $self:expr, $mut_reference:expr, $method:ident ( $($arg:expr),* )) => {{
let proxy = unsafe {
$self.cpp_proxy
.as_mut()
.expect("cpp_proxy was null")
};
let proxy_pinned = unsafe { std::pin::Pin::new_unchecked(proxy) };
$self.rust_obj
.try_store_handle_and_call_cpp_mut($mut_reference, || proxy_pinned.$method($($arg),*))
.expect(concat!(
"Failed to borrow mutably for ",
stringify!($method)
))
}};
($self:expr, $reference:expr, $method:ident ( $($arg:expr),* )) => {{
let proxy = unsafe {
$self.cpp_proxy
.as_ref()
.expect("cpp_proxy was null")
};
$self.rust_obj
.try_store_handle_and_call_cpp($reference, || proxy.$method($($arg),*))
.expect(concat!(
"Failed to borrow for ",
stringify!($method)
))
}};
}
pub struct RustObjAccess<T: ?Sized> {
shared_reference: SharedReferenceWithQml<T>,
borrow: Cell<BorrowState<T>>,
}
impl<T: ?Sized> RustObjAccess<T> {
pub fn new_strong(ptr: Rc<RefCell<T>>) -> Self {
Self {
shared_reference: SharedReferenceWithQml::OwnedByQml(ptr),
borrow: Cell::new(BorrowState::None),
}
}
pub fn new_weak(ptr: Weak<RefCell<T>>) -> Self {
Self {
shared_reference: SharedReferenceWithQml::OwnedByRust(ptr),
borrow: Cell::new(BorrowState::None),
}
}
pub fn try_call_rust_with_handle<F, R>(&self, f: F) -> Result<R, RustObjAccessError>
where
F: FnOnce(&T) -> R,
{
let guard = BorrowState::consume(&self.borrow);
match guard.content() {
BorrowState::Immutable(ptr) => Ok(f(unsafe { &**ptr })),
BorrowState::Mutable(ptr) => Ok(f(unsafe { &**ptr })),
BorrowState::None => {
let rc = self.shared_reference.get_rc()
.ok_or(RustObjAccessError::ExpiredWeakPtr)?;
let ref_guarded = rc.try_borrow()
.map_err(|err| RustObjAccessError::BorrowError(err))?;
Ok(f(&*ref_guarded))
}
}
}
pub fn try_call_rust_with_handle_mut<F, R>(&self, f: F) -> Result<R, RustObjAccessError>
where
F: FnOnce(&mut T) -> R,
{
let guard = BorrowState::consume(&self.borrow);
match guard.content() {
BorrowState::Mutable(ptr) => Ok(f(unsafe { &mut **ptr })),
BorrowState::Immutable(_) => Err(RustObjAccessError::BorrowConflict),
BorrowState::None => {
let rc = self.shared_reference.get_rc()
.ok_or(RustObjAccessError::ExpiredWeakPtr)?;
let mut ref_guarded = rc.try_borrow_mut()
.map_err(|err| RustObjAccessError::BorrowMutError(err))?;
Ok(f(&mut *ref_guarded))
}
}
}
pub fn try_store_handle_and_call_cpp<F, R>(&self, rust_obj: &T, f: F) -> Result<R, RustObjAccessError>
where
F: FnOnce() -> R,
{
assert!(
self.shared_reference.contains(rust_obj),
"The rust_obj you want to call a function on does not match the shared reference."
);
let guard = BorrowState::store(&self.borrow, rust_obj);
if matches!(guard.content(), BorrowState::Mutable(_)) {
return Err(RustObjAccessError::BorrowConflict);
}
Ok(f())
}
pub fn try_store_handle_and_call_cpp_mut<F, R>(&self, rust_obj: &mut T, f: F) -> Result<R, RustObjAccessError>
where
F: FnOnce() -> R,
{
assert!(
self.shared_reference.contains(rust_obj),
"The rust_obj you want to call a function on does not match the shared reference."
);
let guard = BorrowState::store_mut(&self.borrow, rust_obj);
if matches!(guard.content(), BorrowState::Mutable(_)) || matches!(guard.content(), BorrowState::Immutable(_)) {
return Err(RustObjAccessError::BorrowConflict);
}
Ok(f())
}
pub fn get_rc(&self) -> Option<Rc<RefCell<T>>> {
self.shared_reference.get_rc()
}
}
enum BorrowState<T: ?Sized> {
None,
Immutable(*const T),
Mutable(*mut T),
}
impl<T: ?Sized> BorrowState<T> {
fn consume(cell: &Cell<Self>) -> BorrowGuard<'_, T> {
let consumed = cell.replace(BorrowState::None);
BorrowGuard { cell, consumed }
}
fn store<'a>(cell: &'a Cell<Self>, rust_obj: &T) -> BorrowGuard<'a, T> {
let old = cell.replace(BorrowState::Immutable(rust_obj as *const T));
BorrowGuard { cell, consumed: old }
}
fn store_mut<'a>(cell: &'a Cell<Self>, rust_obj: &mut T) -> BorrowGuard<'a, T> {
let old = cell.replace(BorrowState::Mutable(rust_obj as *mut T));
BorrowGuard { cell, consumed: old }
}
}
struct BorrowGuard<'a, T: ?Sized> {
cell: &'a Cell<BorrowState<T>>,
consumed: BorrowState<T>,
}
impl<T: ?Sized> BorrowGuard<'_, T> {
fn content(&self) -> &BorrowState<T> {
&self.consumed
}
}
impl<T: ?Sized> Drop for BorrowGuard<'_, T> {
fn drop(&mut self) {
self.cell.set(mem::replace(&mut self.consumed, BorrowState::None));
}
}
#[derive(Debug)]
pub enum RustObjAccessError {
BorrowError(std::cell::BorrowError),
BorrowMutError(std::cell::BorrowMutError),
BorrowConflict,
ExpiredWeakPtr,
}
enum SharedReferenceWithQml<T: ?Sized> {
OwnedByRust(Weak<RefCell<T>>),
OwnedByQml(Rc<RefCell<T>>),
}
impl<T: ?Sized> SharedReferenceWithQml<T> {
fn get_rc(&self) -> Option<Rc<RefCell<T>>> {
match self {
SharedReferenceWithQml::OwnedByRust(weak) => weak.upgrade(),
SharedReferenceWithQml::OwnedByQml(rc) => Some(rc.clone()),
}
}
fn contains(&self, obj: &T) -> bool {
let Some(rc) = self.get_rc() else { return false };
let expected = rc.as_ptr() as *const ();
let actual = obj as *const T as *const ();
expected == actual
}
}