use alloc::boxed::Box;
use alloc::string::String;
use core::{
alloc::Layout,
ffi::c_void,
fmt,
sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
};
use azul_css::AzString;
pub type RefAnyDestructorType = extern "C" fn(*mut c_void);
#[derive(Debug)]
#[repr(C)]
#[allow(clippy::pub_underscore_fields)]
pub struct RefCountInner {
pub _internal_ptr: *const c_void,
pub num_copies: AtomicUsize,
pub num_refs: AtomicUsize,
pub num_mutable_refs: AtomicUsize,
pub _internal_len: usize,
pub _internal_layout_size: usize,
pub _internal_layout_align: usize,
pub type_id: u64,
pub type_name: AzString,
pub custom_destructor: extern "C" fn(*mut c_void),
pub serialize_fn: usize,
pub deserialize_fn: usize,
pub update_fn: usize,
}
#[derive(Hash, PartialEq, PartialOrd, Ord, Eq)]
#[repr(C)]
pub struct RefCount {
pub ptr: *const RefCountInner,
pub run_destructor: bool,
}
impl fmt::Debug for RefCount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.downcast().fmt(f)
}
}
impl Clone for RefCount {
fn clone(&self) -> Self {
if !self.ptr.is_null() {
unsafe {
(*self.ptr).num_copies.fetch_add(1, AtomicOrdering::SeqCst);
}
}
Self {
ptr: self.ptr,
run_destructor: true,
}
}
}
impl Drop for RefCount {
#[allow(clippy::used_underscore_binding)] fn drop(&mut self) {
if !self.run_destructor || self.ptr.is_null() {
return;
}
self.run_destructor = false;
let inner = self.ptr;
self.ptr = core::ptr::null();
let current_copies = unsafe {
match (*inner).num_copies.fetch_update(
AtomicOrdering::SeqCst,
AtomicOrdering::SeqCst,
|n| n.checked_sub(1),
) {
Ok(prev) => prev,
Err(_zero) => return,
}
};
if current_copies != 1 {
return;
}
let sharing_info = unsafe { Box::from_raw(inner.cast_mut()) };
let sharing_info = *sharing_info;
let data_ptr = sharing_info._internal_ptr;
if sharing_info._internal_len == 0
|| sharing_info._internal_layout_size == 0
|| data_ptr.is_null()
{
let mut _dummy: [u8; 0] = [];
(sharing_info.custom_destructor)(_dummy.as_mut_ptr().cast::<c_void>());
} else {
let layout = Layout::from_size_align(
sharing_info._internal_layout_size,
sharing_info._internal_layout_align,
)
.expect("RefCount::drop: stored layout was invalid");
(sharing_info.custom_destructor)(data_ptr.cast_mut());
unsafe {
alloc::alloc::dealloc(data_ptr as *mut u8, layout);
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct RefCountInnerDebug {
pub(crate) num_copies: usize,
pub(crate) num_refs: usize,
pub(crate) num_mutable_refs: usize,
pub(crate) _internal_len: usize,
pub(crate) _internal_layout_size: usize,
pub(crate) _internal_layout_align: usize,
pub(crate) type_id: u64,
pub(crate) type_name: AzString,
pub(crate) custom_destructor: usize,
pub(crate) serialize_fn: usize,
pub(crate) deserialize_fn: usize,
}
#[cfg(feature = "std")]
fn report_released_downcast() {
use std::sync::atomic::{AtomicBool, Ordering};
static SAID: AtomicBool = AtomicBool::new(false);
if !SAID.swap(true, Ordering::Relaxed) {
eprintln!(
"[azul][refany] a RELEASED RefAny was downcast: its RefCount is already freed, so \
the borrow returned None and whatever wanted the data did nothing. This is a \
use-after-release in the CALLER, not here. Re-run with RUST_BACKTRACE=1 to name \
it. (said once per process)"
);
if std::env::var("RUST_BACKTRACE").is_ok() {
eprintln!("{}", std::backtrace::Backtrace::force_capture());
}
}
}
#[cfg(not(feature = "std"))]
const fn report_released_downcast() {}
impl RefCount {
fn new(ref_count: RefCountInner) -> Self {
Self {
ptr: Box::into_raw(Box::new(ref_count)),
run_destructor: true,
}
}
pub(crate) const fn is_released(&self) -> bool {
self.ptr.is_null()
}
fn downcast(&self) -> &RefCountInner {
assert!(
!self.ptr.is_null(),
"[RefCount::downcast] FATAL: self.ptr is null!"
);
unsafe { &*self.ptr }
}
#[allow(clippy::used_underscore_binding)] pub(crate) fn debug_get_refcount_copied(&self) -> RefCountInnerDebug {
let dc = self.downcast();
RefCountInnerDebug {
num_copies: dc.num_copies.load(AtomicOrdering::SeqCst),
num_refs: dc.num_refs.load(AtomicOrdering::SeqCst),
num_mutable_refs: dc.num_mutable_refs.load(AtomicOrdering::SeqCst),
_internal_len: dc._internal_len,
_internal_layout_size: dc._internal_layout_size,
_internal_layout_align: dc._internal_layout_align,
type_id: dc.type_id,
type_name: dc.type_name.clone(),
custom_destructor: dc.custom_destructor as usize,
serialize_fn: dc.serialize_fn,
deserialize_fn: dc.deserialize_fn,
}
}
#[must_use]
pub fn can_be_shared(&self) -> bool {
self.downcast()
.num_mutable_refs
.load(AtomicOrdering::SeqCst)
== 0
}
#[must_use]
pub fn can_be_shared_mut(&self) -> bool {
let info = self.downcast();
info.num_mutable_refs.load(AtomicOrdering::SeqCst) == 0
&& info.num_refs.load(AtomicOrdering::SeqCst) == 0
}
pub fn increase_ref(&self) {
self.downcast()
.num_refs
.fetch_add(1, AtomicOrdering::SeqCst);
}
pub fn decrease_ref(&self) {
let _ = self.downcast().num_refs.fetch_update(
AtomicOrdering::SeqCst,
AtomicOrdering::SeqCst,
|n| n.checked_sub(1),
);
}
pub fn increase_refmut(&self) {
self.downcast()
.num_mutable_refs
.fetch_add(1, AtomicOrdering::SeqCst);
}
pub fn decrease_refmut(&self) {
let _ = self.downcast().num_mutable_refs.fetch_update(
AtomicOrdering::SeqCst,
AtomicOrdering::SeqCst,
|n| n.checked_sub(1),
);
}
}
#[derive(Debug)]
#[repr(C)]
pub struct Ref<'a, T> {
ptr: &'a T,
sharing_info: RefCount,
}
impl<T> Drop for Ref<'_, T> {
fn drop(&mut self) {
self.sharing_info.decrease_ref();
}
}
impl<T> core::ops::Deref for Ref<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.ptr
}
}
#[derive(Debug)]
#[repr(C)]
pub struct RefMut<'a, T> {
ptr: &'a mut T,
sharing_info: RefCount,
}
impl<T> Drop for RefMut<'_, T> {
fn drop(&mut self) {
self.sharing_info.decrease_refmut();
}
}
impl<T> core::ops::Deref for RefMut<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&*self.ptr
}
}
impl<T> core::ops::DerefMut for RefMut<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.ptr
}
}
#[derive(Debug)]
#[repr(C)]
pub struct RefAny {
pub sharing_info: RefCount,
pub instance_id: u64,
}
impl PartialEq for RefAny {
fn eq(&self, other: &Self) -> bool {
self.sharing_info == other.sharing_info
}
}
impl Eq for RefAny {}
impl core::hash::Hash for RefAny {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(&self.sharing_info, state);
}
}
impl PartialOrd for RefAny {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for RefAny {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.sharing_info.cmp(&other.sharing_info)
}
}
impl_option!(
RefAny,
OptionRefAny,
copy = false,
[Debug, Hash, Clone, PartialEq, PartialOrd, Ord, Eq]
);
#[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for RefAny {}
unsafe impl Sync for RefAny {}
impl RefAny {
pub fn new<T: 'static>(value: T) -> Self {
extern "C" fn default_custom_destructor<U: 'static>(ptr: *mut c_void) {
use core::{mem, ptr};
let run = || unsafe {
if size_of::<U>() == 0 {
#[allow(clippy::uninit_assumed_init)]
drop(mem::MaybeUninit::<U>::uninit().assume_init());
return;
}
let mut stack_mem = mem::MaybeUninit::<U>::uninit();
ptr::copy_nonoverlapping(
ptr as *const U,
stack_mem.as_mut_ptr(),
1, );
let stack_mem = stack_mem.assume_init();
drop(stack_mem); };
#[cfg(feature = "std")]
{
drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(run)));
}
#[cfg(not(feature = "std"))]
{
run();
}
}
let type_name = ::core::any::type_name::<T>();
let type_id = Self::get_type_id_static::<T>();
let st = AzString::from_const_str(type_name);
let s = Self::new_c(
(&raw const value) as *const c_void,
::core::mem::size_of::<T>(),
::core::mem::align_of::<T>(), type_id,
st,
default_custom_destructor::<T>,
0, 0, );
::core::mem::forget(value); s
}
#[allow(clippy::used_underscore_binding)] pub fn new_c(
ptr: *const c_void,
len: usize,
align: usize,
type_id: u64,
type_name: AzString,
custom_destructor: extern "C" fn(*mut c_void),
serialize_fn: usize,
deserialize_fn: usize,
) -> Self {
use core::ptr;
assert!(
!(len > 0 && ptr.is_null()),
"RefAny::new_c: NULL pointer passed for non-ZST type (size={}). \
This would cause undefined behavior. Type: {:?}",
len,
type_name.as_str()
);
let (_internal_ptr, layout) = if len == 0 {
let _dummy: [u8; 0] = [];
(ptr::null_mut(), Layout::for_value(&_dummy))
} else {
let layout = Layout::from_size_align(len, align).expect("Failed to create layout");
let heap_struct_as_bytes = unsafe { alloc::alloc::alloc(layout) };
if heap_struct_as_bytes.is_null() {
alloc::alloc::handle_alloc_error(layout);
}
unsafe { ptr::copy_nonoverlapping(ptr as *const u8, heap_struct_as_bytes, len) };
(heap_struct_as_bytes, layout)
};
let ref_count_inner = RefCountInner {
_internal_ptr: _internal_ptr as *const c_void,
num_copies: AtomicUsize::new(1), num_refs: AtomicUsize::new(0), num_mutable_refs: AtomicUsize::new(0), _internal_len: len,
_internal_layout_size: layout.size(),
_internal_layout_align: layout.align(),
type_id,
type_name,
custom_destructor,
serialize_fn,
deserialize_fn,
update_fn: 0, };
let sharing_info = RefCount::new(ref_count_inner);
Self {
sharing_info,
instance_id: 0, }
}
#[allow(clippy::used_underscore_binding)]
#[must_use]
pub fn get_data_ptr(&self) -> *const c_void {
self.sharing_info.downcast()._internal_ptr
}
#[allow(clippy::used_underscore_binding)]
#[must_use]
pub fn get_data_len(&self) -> usize {
self.sharing_info.downcast()._internal_len
}
pub(crate) fn has_no_copies(&self) -> bool {
self.sharing_info
.downcast()
.num_copies
.load(AtomicOrdering::SeqCst)
== 1
&& self
.sharing_info
.downcast()
.num_refs
.load(AtomicOrdering::SeqCst)
== 0
&& self
.sharing_info
.downcast()
.num_mutable_refs
.load(AtomicOrdering::SeqCst)
== 0
}
#[allow(clippy::used_underscore_binding)]
#[inline]
pub fn downcast_ref<U: 'static>(&mut self) -> Option<Ref<'_, U>> {
if self.sharing_info.is_released() {
report_released_downcast();
return None;
}
let stored_type_id = self.get_type_id();
let target_type_id = Self::get_type_id_static::<U>();
let is_same_type = stored_type_id == target_type_id;
if !is_same_type {
return None;
}
self.sharing_info.increase_ref();
if !self.sharing_info.can_be_shared() {
self.sharing_info.decrease_ref();
return None;
}
let data_ptr = self.sharing_info.downcast()._internal_ptr;
if data_ptr.is_null() && size_of::<U>() != 0 {
self.sharing_info.decrease_ref();
return None;
}
Some(Ref {
ptr: unsafe {
if data_ptr.is_null() {
&*core::ptr::NonNull::<U>::dangling().as_ptr()
} else {
&*(data_ptr as *const U)
}
},
sharing_info: self.sharing_info.clone(),
})
}
#[allow(clippy::used_underscore_binding)]
#[inline]
pub fn downcast_mut<U: 'static>(&mut self) -> Option<RefMut<'_, U>> {
if self.sharing_info.is_released() {
report_released_downcast();
return None;
}
let is_same_type = self.get_type_id() == Self::get_type_id_static::<U>();
if !is_same_type {
return None;
}
let inner = self.sharing_info.downcast();
if inner
.num_mutable_refs
.compare_exchange(0, 1, AtomicOrdering::SeqCst, AtomicOrdering::SeqCst)
.is_err()
{
return None;
}
if inner.num_refs.load(AtomicOrdering::SeqCst) != 0 {
inner.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
return None;
}
let data_ptr = inner._internal_ptr;
if data_ptr.is_null() {
if size_of::<U>() != 0 {
inner.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
return None;
}
return Some(RefMut {
ptr: unsafe { &mut *core::ptr::NonNull::<U>::dangling().as_ptr() },
sharing_info: self.sharing_info.clone(),
});
}
let update_fn = inner.update_fn;
if update_fn != 0 {
let cb: extern "C" fn(*const c_void, usize) =
unsafe { core::mem::transmute(update_fn as *const ()) };
let len = inner._internal_len;
#[cfg(feature = "std")]
{
drop(std::panic::catch_unwind(std::panic::AssertUnwindSafe(
|| {
cb(data_ptr, len);
},
)));
}
#[cfg(not(feature = "std"))]
{
cb(data_ptr, len);
}
}
Some(RefMut {
ptr: unsafe { &mut *(data_ptr as *mut U) },
sharing_info: self.sharing_info.clone(),
})
}
#[inline]
fn get_type_id_static<T: 'static>() -> u64 {
use core::{any::TypeId, mem};
let t_id = TypeId::of::<T>();
let struct_as_bytes = unsafe {
core::slice::from_raw_parts((&raw const t_id) as *const u8, size_of::<TypeId>())
};
struct_as_bytes.iter().fold(0u64, |hash, &b| {
(hash.rotate_left(5) ^ u64::from(b)).wrapping_mul(0x51_7c_c1_b7_27_22_0a_95)
})
}
#[must_use]
pub fn is_type(&self, type_id: u64) -> bool {
self.sharing_info.downcast().type_id == type_id
}
#[must_use]
pub fn get_type_id(&self) -> u64 {
if self.sharing_info.is_released() {
return 0;
}
self.sharing_info.downcast().type_id
}
#[must_use]
pub fn get_type_name(&self) -> AzString {
if self.sharing_info.is_released() {
return AzString::from_const_str("<released>");
}
self.sharing_info.downcast().type_name.clone()
}
#[must_use]
pub fn get_ref_count(&self) -> usize {
self.sharing_info
.downcast()
.num_copies
.load(AtomicOrdering::SeqCst)
}
#[must_use]
pub fn get_serialize_fn(&self) -> usize {
self.sharing_info.downcast().serialize_fn
}
#[must_use]
pub fn get_deserialize_fn(&self) -> usize {
self.sharing_info.downcast().deserialize_fn
}
pub fn set_serialize_fn(&mut self, serialize_fn: usize) {
let inner = self.sharing_info.ptr.cast_mut();
unsafe {
(*inner).serialize_fn = serialize_fn;
}
}
pub fn set_deserialize_fn(&mut self, deserialize_fn: usize) {
let inner = self.sharing_info.ptr.cast_mut();
unsafe {
(*inner).deserialize_fn = deserialize_fn;
}
}
pub fn set_update_fn(&mut self, update_fn: usize) {
let inner = self.sharing_info.ptr.cast_mut();
unsafe {
(*inner).update_fn = update_fn;
}
}
#[must_use]
pub fn get_update_fn(&self) -> usize {
self.sharing_info.downcast().update_fn
}
#[must_use]
pub fn can_serialize(&self) -> bool {
self.get_serialize_fn() != 0
}
#[must_use]
pub fn can_deserialize(&self) -> bool {
self.get_deserialize_fn() != 0
}
#[allow(clippy::used_underscore_binding)] pub fn replace_contents(&mut self, new_value: Self) -> bool {
use core::ptr;
let inner = self.sharing_info.ptr.cast_mut();
let inner_ref = self.sharing_info.downcast();
let mutable_lock_result = inner_ref.num_mutable_refs.compare_exchange(
0, 1, AtomicOrdering::SeqCst,
AtomicOrdering::SeqCst,
);
if mutable_lock_result.is_err() {
return false;
}
if inner_ref.num_refs.load(AtomicOrdering::SeqCst) != 0 {
inner_ref.num_mutable_refs.store(0, AtomicOrdering::SeqCst);
return false;
}
unsafe {
let old_ptr = (*inner)._internal_ptr;
let old_len = (*inner)._internal_len;
let old_layout_size = (*inner)._internal_layout_size;
let old_layout_align = (*inner)._internal_layout_align;
let old_destructor = (*inner).custom_destructor;
if old_len > 0 && !old_ptr.is_null() {
old_destructor(old_ptr.cast_mut());
}
if old_layout_size > 0 && !old_ptr.is_null() {
let old_layout = Layout::from_size_align(old_layout_size, old_layout_align)
.expect("replace_contents: stored old layout was invalid");
alloc::alloc::dealloc(old_ptr as *mut u8, old_layout);
}
let new_inner = new_value.sharing_info.downcast();
let new_ptr = new_inner._internal_ptr;
let new_len = new_inner._internal_len;
let new_layout_size = new_inner._internal_layout_size;
let new_layout_align = new_inner._internal_layout_align;
let allocated_ptr = if new_len == 0 {
ptr::null_mut()
} else {
let new_layout = Layout::from_size_align(new_len, new_layout_align)
.expect("Failed to create layout");
let heap_ptr = alloc::alloc::alloc(new_layout);
if heap_ptr.is_null() {
alloc::alloc::handle_alloc_error(new_layout);
}
ptr::copy_nonoverlapping(new_ptr as *const u8, heap_ptr, new_len);
heap_ptr
};
(*inner)._internal_ptr = allocated_ptr as *const c_void;
(*inner)._internal_len = new_len;
(*inner)._internal_layout_size = new_layout_size;
(*inner)._internal_layout_align = new_layout_align;
(*inner).type_id = new_inner.type_id;
(*inner).type_name = new_inner.type_name.clone();
(*inner).custom_destructor = new_inner.custom_destructor;
(*inner).serialize_fn = new_inner.serialize_fn;
(*inner).deserialize_fn = new_inner.deserialize_fn;
(*inner).update_fn = new_inner.update_fn;
}
self.sharing_info
.downcast()
.num_mutable_refs
.store(0, AtomicOrdering::SeqCst);
#[allow(clippy::items_after_statements)]
const extern "C" fn noop_destructor(_: *mut c_void) {}
let new_inner = new_value.sharing_info.ptr.cast_mut();
if !new_inner.is_null() {
unsafe {
(*new_inner).custom_destructor = noop_destructor;
}
}
drop(new_value);
true
}
}
impl Clone for RefAny {
fn clone(&self) -> Self {
let inner = self.sharing_info.downcast();
let prev = inner.num_copies.fetch_add(1, AtomicOrdering::SeqCst);
let new_instance_id = (prev + 1) as u64;
Self {
sharing_info: RefCount {
ptr: self.sharing_info.ptr, run_destructor: true, },
instance_id: new_instance_id,
}
}
}
impl Drop for RefAny {
fn drop(&mut self) {
}
}
#[cfg(test)]
#[path = "refany_test.rs"]
mod refany_test;