use std::{
any::Any,
marker::PhantomData,
sync::{
atomic::{AtomicU32, Ordering},
Arc,
},
};
use crate::{
context::Context,
event::Channel,
handle::root::NapiRef,
sys::{lifecycle, raw::Env, tsfn::ThreadsafeFunction},
types::promise::NodeApiDeferred,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub(crate) struct InstanceId(u32);
impl InstanceId {
fn next() -> Self {
static NEXT_ID: AtomicU32 = AtomicU32::new(0);
let next = NEXT_ID.fetch_add(1, Ordering::SeqCst).checked_add(1);
match next {
Some(id) => Self(id),
None => panic!("u32 overflow ocurred in Lifecycle InstanceId"),
}
}
}
pub(crate) struct InstanceData {
id: InstanceId,
drop_queue: Arc<ThreadsafeFunction<DropData>>,
shared_channel: Channel,
locals: LocalTable,
}
#[derive(Default)]
pub(crate) struct LocalTable {
cells: Vec<LocalCell>,
}
pub(crate) type LocalCellValue = Box<dyn Any + Send + 'static>;
#[derive(Default)]
pub(crate) enum LocalCell {
#[default]
Uninit,
Trying,
Init(LocalCellValue),
}
impl LocalCell {
fn pre_init<F>(&mut self, f: F)
where
F: FnOnce() -> LocalCell,
{
match self {
LocalCell::Uninit => {
*self = f();
}
LocalCell::Trying => panic!("attempt to reinitialize Local during initialization"),
LocalCell::Init(_) => {}
}
}
pub(crate) fn get<'cx, 'a, C>(cx: &'a mut C, id: usize) -> Option<&'a mut LocalCellValue>
where
C: Context<'cx>,
{
let cell = InstanceData::locals(cx).get(id);
match cell {
LocalCell::Init(ref mut b) => Some(b),
_ => None,
}
}
pub(crate) fn get_or_init<'cx, 'a, C, F>(
cx: &'a mut C,
id: usize,
f: F,
) -> &'a mut LocalCellValue
where
C: Context<'cx>,
F: FnOnce() -> LocalCellValue,
{
InstanceData::locals(cx)
.get(id)
.pre_init(|| LocalCell::Init(f()));
LocalCell::get(cx, id).unwrap()
}
pub(crate) fn get_or_try_init<'cx, 'a, C, E, F>(
cx: &'a mut C,
id: usize,
f: F,
) -> Result<&'a mut LocalCellValue, E>
where
C: Context<'cx>,
F: FnOnce(&mut C) -> Result<LocalCellValue, E>,
{
{
let mut tx = TryInitTransaction::new(cx, id);
tx.run(|cx| f(cx))?;
}
Ok(LocalCell::get(cx, id).unwrap())
}
}
impl LocalTable {
pub(crate) fn get(&mut self, index: usize) -> &mut LocalCell {
if index >= self.cells.len() {
self.cells.resize_with(index + 1, Default::default);
}
&mut self.cells[index]
}
}
struct TryInitTransaction<'cx, 'a, C: Context<'cx>> {
cx: &'a mut C,
id: usize,
_lifetime: PhantomData<&'cx ()>,
}
impl<'cx, 'a, C: Context<'cx>> TryInitTransaction<'cx, 'a, C> {
fn new(cx: &'a mut C, id: usize) -> Self {
let mut tx = Self {
cx,
id,
_lifetime: PhantomData,
};
tx.cell().pre_init(|| LocalCell::Trying);
tx
}
fn run<E, F>(&mut self, f: F) -> Result<(), E>
where
F: FnOnce(&mut C) -> Result<LocalCellValue, E>,
{
if self.is_trying() {
let value = f(self.cx)?;
*self.cell() = LocalCell::Init(value);
}
Ok(())
}
fn cell(&mut self) -> &mut LocalCell {
InstanceData::locals(self.cx).get(self.id)
}
#[allow(clippy::wrong_self_convention)]
fn is_trying(&mut self) -> bool {
matches!(self.cell(), LocalCell::Trying)
}
}
impl<'cx, 'a, C: Context<'cx>> Drop for TryInitTransaction<'cx, 'a, C> {
fn drop(&mut self) {
if self.is_trying() {
*self.cell() = LocalCell::Uninit;
}
}
}
pub(crate) enum DropData {
Deferred(NodeApiDeferred),
Ref(NapiRef),
}
impl DropData {
fn drop(env: Option<Env>, data: Self) {
if let Some(env) = env {
unsafe {
match data {
DropData::Deferred(data) => data.leaked(env),
DropData::Ref(data) => data.unref(env),
}
}
}
}
}
impl InstanceData {
pub(crate) fn get<'cx, C: Context<'cx>>(cx: &mut C) -> &mut InstanceData {
let env = cx.env().to_raw();
let data = unsafe { lifecycle::get_instance_data::<InstanceData>(env).as_mut() };
if let Some(data) = data {
return data;
}
let drop_queue = unsafe {
let queue = ThreadsafeFunction::new(env, DropData::drop);
queue.unref(env);
queue
};
let shared_channel = {
let mut channel = Channel::new(cx);
channel.unref(cx);
channel
};
let data = InstanceData {
id: InstanceId::next(),
drop_queue: Arc::new(drop_queue),
shared_channel,
locals: LocalTable::default(),
};
unsafe { &mut *lifecycle::set_instance_data(env, data) }
}
pub(crate) fn drop_queue<'cx, C: Context<'cx>>(
cx: &mut C,
) -> Arc<ThreadsafeFunction<DropData>> {
Arc::clone(&InstanceData::get(cx).drop_queue)
}
pub(crate) fn channel<'cx, C: Context<'cx>>(cx: &mut C) -> Channel {
let mut channel = InstanceData::get(cx).shared_channel.clone();
channel.reference(cx);
channel
}
pub(crate) fn id<'cx, C: Context<'cx>>(cx: &mut C) -> InstanceId {
InstanceData::get(cx).id
}
pub(crate) fn locals<'cx, C: Context<'cx>>(cx: &mut C) -> &mut LocalTable {
&mut InstanceData::get(cx).locals
}
}