use alloc::alloc::Layout;
use alloc::rc::Rc;
use core::cell::Cell;
use core::marker::PhantomData;
use thiserror::Error;
use crate::utils;
utils::rust_cc_thread_local! {
static STATE: State = const { State::new() };
}
#[inline]
pub(crate) fn state<R>(f: impl FnOnce(&State) -> R) -> R {
try_state(f).unwrap_or_else(|_| panic!("Couldn't access the state"))
}
#[inline]
pub(crate) fn try_state<R>(f: impl FnOnce(&State) -> R) -> Result<R, StateAccessError> {
STATE.try_with(|state| Ok(f(state))).unwrap_or(Err(StateAccessError::AccessError))
}
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum StateAccessError {
#[error("couldn't access the state")]
AccessError,
}
#[cfg(all(test, feature = "std"))] pub(crate) fn reset_state() {
state(|state| {
state.collecting.set(false);
#[cfg(feature = "finalization")]
state.finalizing.set(false);
state.dropping.set(false);
state.allocated_bytes.set(0);
state.executions_counter.set(0);
});
}
pub(crate) struct State {
collecting: Cell<bool>,
#[cfg(feature = "finalization")]
finalizing: Cell<bool>,
dropping: Cell<bool>,
allocated_bytes: Cell<usize>,
executions_counter: Cell<usize>,
_phantom: PhantomData<Rc<()>>, }
impl State {
#[inline]
const fn new() -> Self {
Self {
collecting: Cell::new(false),
#[cfg(feature = "finalization")]
finalizing: Cell::new(false),
dropping: Cell::new(false),
allocated_bytes: Cell::new(0),
executions_counter: Cell::new(0),
_phantom: PhantomData,
}
}
#[inline]
pub(crate) fn allocated_bytes(&self) -> usize {
self.allocated_bytes.get()
}
#[inline]
pub(crate) fn record_allocation(&self, layout: Layout) {
self.allocated_bytes.set(self.allocated_bytes.get() + layout.size());
}
#[inline]
pub(crate) fn record_deallocation(&self, layout: Layout) {
self.allocated_bytes.set(self.allocated_bytes.get() - layout.size());
}
#[inline]
pub(crate) fn executions_count(&self) -> usize {
self.executions_counter.get()
}
#[inline]
pub(super) fn increment_executions_count(&self) {
self.executions_counter.set(self.executions_counter.get() + 1);
}
#[inline]
pub(crate) fn is_collecting(&self) -> bool {
self.collecting.get()
}
#[inline]
pub(crate) fn set_collecting(&self, value: bool) {
self.collecting.set(value);
}
#[cfg(feature = "finalization")]
#[inline]
pub(crate) fn is_finalizing(&self) -> bool {
self.finalizing.get()
}
#[cfg(feature = "finalization")]
#[inline]
pub(crate) fn set_finalizing(&self, value: bool) {
self.finalizing.set(value);
}
#[inline]
pub(crate) fn is_dropping(&self) -> bool {
self.dropping.get()
}
#[inline]
pub(crate) fn set_dropping(&self, value: bool) {
self.dropping.set(value);
}
#[inline]
#[allow(dead_code)] pub(crate) fn is_tracing(&self) -> bool {
#[cfg(feature = "finalization")]
{
self.collecting.get() && !self.finalizing.get() && !self.dropping.get()
}
#[cfg(not(feature = "finalization"))]
{
self.collecting.get() && !self.dropping.get()
}
}
}
impl Default for State {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[inline]
pub fn buffered_objects_count() -> Result<usize, StateAccessError> {
crate::POSSIBLE_CYCLES.try_with(|pc| Ok(pc.size())).unwrap_or(Err(StateAccessError::AccessError))
}
#[inline]
pub fn allocated_bytes() -> Result<usize, StateAccessError> {
try_state(|state| Ok(state.allocated_bytes()))?
}
#[inline]
pub fn executions_count() -> Result<usize, StateAccessError> {
try_state(|state| Ok(state.executions_count()))?
}
#[inline]
pub fn is_tracing() -> Result<bool, StateAccessError> {
try_state(|state| Ok(state.is_tracing()))?
}
macro_rules! replace_state_field {
(dropping, $value:expr, $state:ident) => {
$crate::state::replace_state_field!(__internal is_dropping, set_dropping, bool, $value, $state)
};
(finalizing, $value:expr, $state:ident) => {
$crate::state::replace_state_field!(__internal is_finalizing, set_finalizing, bool, $value, $state)
};
(__internal $is_name:ident, $set_name:ident, $field_type:ty, $value:expr, $state:ident) => {
{
let old_value: $field_type = $crate::state::State::$is_name($state);
$crate::state::State::$set_name($state, $value);
#[must_use = "the drop guard shouldn't be dropped instantly"]
struct DropGuard<'a> {
state: &'a $crate::state::State,
old_value: $field_type,
}
impl<'a> ::core::ops::Drop for DropGuard<'a> {
#[inline]
fn drop(&mut self) {
$crate::state::State::$set_name(self.state, self.old_value);
}
}
#[allow(clippy::redundant_field_names)]
DropGuard { state: $state, old_value }
}
};
}
pub(crate) use replace_state_field;
#[cfg(test)]
mod tests {
use crate::state::{state};
#[test]
fn test_replace_state_field() {
state(|state| {
state.set_dropping(true);
{
let _finalizing_guard = replace_state_field!(dropping, false, state);
assert!(!state.is_dropping());
}
assert!(state.is_dropping());
state.set_dropping(false);
{
let _dropping_guard = replace_state_field!(dropping, true, state);
assert!(state.is_dropping());
}
assert!(!state.is_dropping());
#[cfg(feature = "finalization")]
{
state.set_finalizing(true);
{
let _finalizing_guard = replace_state_field!(finalizing, false, state);
assert!(!state.is_finalizing());
}
assert!(state.is_finalizing());
state.set_finalizing(false);
{
let _finalizing_guard = replace_state_field!(finalizing, true, state);
assert!(state.is_finalizing());
}
assert!(!state.is_finalizing());
}
});
}
}