use crate::Level;
use std::cell::{Cell, OnceCell};
use std::fmt::Display;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use super::task::{Task, TaskID};
pub(crate) static CONTEXT_ID: AtomicU64 = AtomicU64::new(0);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ContextID(pub(crate) u64);
#[derive(Debug)]
pub(crate) struct ContextInner {
pub(crate) parent: Option<Context>,
pub(crate) context_id: u64,
pub(crate) define_task: Option<Task>,
pub(crate) is_tracing: AtomicBool,
}
#[derive(Debug, Clone)]
pub struct Context {
pub(crate) inner: Arc<ContextInner>,
}
impl PartialEq for Context {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for Context {}
impl Hash for Context {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.inner).hash(state);
}
}
impl Display for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let nesting = self.nesting_level();
write!(
f,
"{}{} ({})",
" ".repeat(nesting),
self.task_id(),
self.task().label
)
}
}
impl AsRef<Task> for Context {
fn as_ref(&self) -> &Task {
self.task()
}
}
thread_local! {
pub(crate) static CONTEXT: OnceCell<Cell<Context>> = const { OnceCell::new() };
}
fn get_or_init_context(once: &OnceCell<Cell<Context>>) -> &Cell<Context> {
once.get_or_init(|| {
Cell::new(Context::new_task_internal(
None,
String::new(),
Level::DebugInternal,
false,
0,
))
})
}
impl Context {
#[inline]
pub fn current() -> Context {
CONTEXT.with(|once| {
let c = get_or_init_context(once);
unsafe { &*c.as_ptr() }.clone()
})
}
pub fn task(&self) -> &Task {
if let Some(task) = &self.inner.define_task {
task
} else {
self.inner
.parent
.as_ref()
.expect("No parent context")
.task()
}
}
#[inline]
pub fn new_task(
parent: Option<Context>,
label: String,
completion_level: Level,
should_log_completion: bool,
) -> Context {
let context_id = CONTEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Self::new_task_internal(
parent,
label,
completion_level,
should_log_completion,
context_id,
)
}
#[inline]
pub(crate) fn new_task_internal(
parent: Option<Context>,
label: String,
completion_level: Level,
should_log_completion: bool,
context_id: u64,
) -> Context {
Context {
inner: Arc::new(ContextInner {
parent,
context_id,
define_task: Some(Task::new(label, completion_level, should_log_completion)),
is_tracing: AtomicBool::new(false),
}),
}
}
#[inline]
pub fn reset(label: String) {
let new_context = Context::new_task(
None,
label,
Level::DebugInternal,
crate::log_enabled!(
Level::DebugInternal,
crate::__CALL_LOGWISE_DECLARE_LOGGING_DOMAIN
),
);
new_context.set_current();
}
pub fn from_parent(context: Context) -> Context {
let is_tracing = context.inner.is_tracing.load(Ordering::Relaxed);
Context {
inner: Arc::new(ContextInner {
parent: Some(context),
context_id: CONTEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
define_task: None,
is_tracing: AtomicBool::new(is_tracing),
}),
}
}
#[inline]
pub fn task_id(&self) -> TaskID {
self.task().task_id
}
#[inline]
pub fn is_tracing(&self) -> bool {
self.inner.is_tracing.load(Ordering::Relaxed)
}
#[inline]
pub fn currently_tracing() -> bool {
CONTEXT
.try_with(|once| {
let Some(c) = once.get() else {
return false;
};
unsafe { &*c.as_ptr() }
.inner
.is_tracing
.load(Ordering::Relaxed)
})
.unwrap_or(false)
}
pub fn begin_trace() {
Context::current()
.inner
.is_tracing
.store(true, Ordering::Relaxed);
logwise::trace_sync!("Begin trace");
}
pub fn set_current(self) {
CONTEXT.with(|once| {
get_or_init_context(once).replace(self);
});
}
pub fn nesting_level(&self) -> usize {
let mut level = 0;
let mut current = self;
while let Some(parent) = ¤t.inner.parent {
level += 1;
current = parent;
}
level
}
#[inline]
pub fn context_id(&self) -> ContextID {
ContextID(self.inner.context_id)
}
pub fn pop(id: ContextID) {
let mut current = Context::current();
loop {
if current.context_id() == id {
let parent = current.inner.parent.clone().expect("No parent context");
CONTEXT.with(|once| {
get_or_init_context(once).replace(parent);
});
return;
}
match current.inner.parent.as_ref() {
None => {
logwise::warn_sync!(
"Tried to pop context with ID {id}, but it was not found in the current context chain.",
id = id.0
);
return;
}
Some(ctx) => current = ctx.clone(),
}
}
}
#[doc(hidden)]
#[inline]
pub fn _log_prelude(&self, record: &mut crate::log_record::LogRecord) {
let prefix = if self.is_tracing() { "T" } else { " " };
record.log(prefix);
for _ in 0..self.nesting_level() {
record.log(" ");
}
record.log_owned(format!("{} ", self.task_id()));
}
#[doc(hidden)]
#[inline]
pub fn _add_task_interval(&self, key: &'static str, duration: crate::sys::Duration) {
self.task().add_task_interval(key, duration);
}
#[doc(hidden)]
#[inline]
pub fn _add_task_interval_if(
&self,
key: &'static str,
duration: crate::sys::Duration,
threshold: crate::sys::Duration,
) {
self.task().add_task_interval_if(key, duration, threshold);
}
}