use core::cell::Cell;
use core::ffi::c_void;
use core::ptr::NonNull;
use bun_collections::linear_fifo::{DynamicBuffer, LinearFifo};
use bun_core::Output;
use bun_dotenv::{self as dotenv, Loader as DotEnvLoader};
use bun_io::file_poll::Store as FilePollStore;
use bun_sys::{self as sys, Fd, Mode};
use bun_threading::UnboundedQueue;
use bun_uws::Loop as UwsLoop;
use crate::AnyTaskWithExtraContext::{AnyTaskWithExtraContext, New};
use crate::EventLoopHandle;
#[cfg(not(windows))]
pub type PlatformEventLoop = UwsLoop;
#[cfg(windows)]
pub type PlatformEventLoop = bun_sys::windows::libuv::Loop;
unsafe extern "Rust" {
pub safe fn __bun_stdio_blob_store_new(fd: Fd, is_atty: bool, mode: Mode) -> *mut ();
safe fn __bun_js_vm_get() -> *mut ();
}
pub const PIPE_READ_BUFFER_SIZE: usize = 256 * 1024;
pub type PipeReadBuffer = [u8; PIPE_READ_BUFFER_SIZE];
pub type ConcurrentTaskQueue = UnboundedQueue<AnyTaskWithExtraContext>;
unsafe impl bun_threading::Linked for AnyTaskWithExtraContext {
#[inline]
unsafe fn link(item: *mut Self) -> *const bun_threading::Link<Self> {
unsafe { core::ptr::addr_of!((*item).next) }
}
}
type Queue = LinearFifo<*mut AnyTaskWithExtraContext, DynamicBuffer<*mut AnyTaskWithExtraContext>>;
pub type Task = AnyTaskWithExtraContext;
pub struct MiniEventLoop<'a> {
pub tasks: Queue,
pub concurrent_tasks: ConcurrentTaskQueue,
pub loop_: *mut UwsLoop,
pub file_polls_: Option<Box<FilePollStore>>,
pub env: Option<NonNull<DotEnvLoader<'a>>>,
pub top_level_dir: Box<[u8]>,
pub after_event_loop_callback_ctx: Option<NonNull<c_void>>,
pub after_event_loop_callback: Option<unsafe extern "C" fn(*mut c_void)>,
pub pipe_read_buffer: Option<Box<PipeReadBuffer>>,
pub stdout_store: Option<NonNull<()>>,
pub stderr_store: Option<NonNull<()>>,
}
thread_local! {
pub static GLOBAL_INITIALIZED: Cell<bool> = const { Cell::new(false) };
pub static GLOBAL: Cell<*mut MiniEventLoop<'static>> = const { Cell::new(core::ptr::null_mut()) };
}
pub fn init_global(
env: Option<&'static mut DotEnvLoader<'static>>,
cwd: Option<&[u8]>,
) -> *mut MiniEventLoop<'static> {
if GLOBAL_INITIALIZED.with(|g| g.get()) {
return GLOBAL.with(|g| g.get());
}
let loop_ = MiniEventLoop::init();
let global_ptr: *mut MiniEventLoop<'static> = bun_core::heap::into_raw(Box::new(loop_));
let global = unsafe { &mut *global_ptr };
{
let (tag, ptr) = EventLoopHandle::init_mini(global_ptr).into_tag_ptr();
unsafe {
(*global.loop_ptr())
.internal_loop_data
.set_parent_raw(tag, ptr)
};
}
global.env = env.map(NonNull::from).or_else(|| {
NonNull::new(
dotenv::INSTANCE
.load(core::sync::atomic::Ordering::Acquire)
.cast::<DotEnvLoader<'static>>(),
)
});
if global.env.is_none() {
let map: *mut dotenv::Map = bun_core::heap::into_raw(Box::new(dotenv::Map::init()));
let loader =
bun_core::heap::into_raw_nn(Box::new(DotEnvLoader::init(unsafe { &mut *map })));
global.env = Some(loader);
}
if let Some(dir) = cwd {
global.top_level_dir = Box::<[u8]>::from(dir);
} else if global.top_level_dir.is_empty() {
let mut buf = bun_paths::PathBuffer::uninit();
match sys::getcwd(&mut buf[..]) {
Ok(len) => {
global.top_level_dir = Box::<[u8]>::from(&buf[..len]);
}
Err(_) => {
global.top_level_dir = Box::default();
}
}
}
GLOBAL.with(|g| g.set(global_ptr));
GLOBAL_INITIALIZED.with(|g| g.set(true));
global_ptr
}
impl<'a> MiniEventLoop<'a> {
#[inline]
pub fn loop_ptr(&self) -> *mut UwsLoop {
self.loop_
}
#[inline]
pub fn env_ptr(&self) -> Option<NonNull<DotEnvLoader<'a>>> {
self.env
}
#[inline]
pub fn get_vm_impl(&mut self) -> &mut MiniEventLoop<'a> {
self
}
pub fn throw_error(&mut self, err: &sys::Error) {
bun_core::pretty_errorln!("{}", err);
Output::flush();
}
pub fn pipe_read_buffer(&mut self) -> &mut [u8] {
&mut self
.pipe_read_buffer
.get_or_insert_with(bun_core::boxed_zeroed::<PipeReadBuffer>)[..]
}
pub fn on_after_event_loop(&mut self) {
if let Some(cb) = self.after_event_loop_callback {
let ctx = self.after_event_loop_callback_ctx;
self.after_event_loop_callback = None;
self.after_event_loop_callback_ctx = None;
unsafe { cb(ctx.map_or(core::ptr::null_mut(), |p| p.as_ptr())) };
}
}
pub fn file_polls(&mut self) -> &mut FilePollStore {
if self.file_polls_.is_none() {
self.file_polls_ = Some(Box::new(FilePollStore::init()));
}
self.file_polls_.as_mut().unwrap()
}
pub unsafe fn file_polls_raw(this: *mut Self) -> *mut FilePollStore {
unsafe {
let slot = core::ptr::addr_of_mut!((*this).file_polls_);
if (*slot).is_none() {
slot.write(Some(Box::new(FilePollStore::init())));
}
match &mut *slot {
Some(b) => &raw mut **b,
None => core::hint::unreachable_unchecked(),
}
}
}
pub fn init() -> MiniEventLoop<'a> {
MiniEventLoop {
tasks: Queue::init(),
concurrent_tasks: ConcurrentTaskQueue::default(),
loop_: UwsLoop::get(),
file_polls_: None,
env: None,
top_level_dir: Box::default(),
after_event_loop_callback_ctx: None,
after_event_loop_callback: None,
pipe_read_buffer: None,
stdout_store: None,
stderr_store: None,
}
}
pub fn tick_concurrent_with_count(&mut self) -> usize {
let concurrent = self.concurrent_tasks.pop_batch();
let count = concurrent.count;
if count == 0 {
return 0;
}
let mut iter = concurrent.iterator();
let start_count = self.tasks.readable_length();
let mut written: usize = 0;
{
let mut writable = self.tasks.writable_with_size(count).expect("unreachable");
loop {
let task = iter.next();
if task.is_null() {
break;
}
writable[0] = task;
writable = &mut writable[1..];
written += 1;
if writable.is_empty() {
break;
}
}
}
self.tasks.update(written);
self.tasks.readable_length() - start_count
}
#[inline]
pub fn tick_once(&mut self, context: *mut c_void) {
if self.tick_concurrent_with_count() == 0 && self.tasks.readable_length() == 0 {
unsafe {
(*self.loop_ptr()).inc();
(*self.loop_ptr()).tick();
(*self.loop_ptr()).dec();
}
self.on_after_event_loop();
}
while let Some(task) = self.tasks.read_item() {
unsafe { (*task).run(context) };
}
}
pub fn tick_without_idle(&mut self, context: *mut c_void) {
loop {
let _ = self.tick_concurrent_with_count();
while let Some(task) = self.tasks.read_item() {
unsafe { (*task).run(context) };
}
unsafe { (*self.loop_ptr()).tick_without_idle() };
if self.tasks.readable_length() == 0 && self.tick_concurrent_with_count() == 0 {
break;
}
}
self.on_after_event_loop();
}
pub fn tick<F>(&mut self, context: *mut c_void, is_done: F)
where
F: Fn(*mut c_void) -> bool,
{
while !is_done(context) {
self.tick_once(context);
}
}
pub unsafe fn enqueue_task<C>(
&mut self,
ctx: *mut C,
callback: fn(*mut C, *mut ()),
field_offset: usize,
) {
let task = unsafe { ctx.byte_add(field_offset).cast::<AnyTaskWithExtraContext>() };
unsafe { task.write(New::<C, ()>::init(ctx, callback)) };
self.tasks.write_item(task).expect("unreachable");
}
pub fn enqueue_task_concurrent(&mut self, task: NonNull<AnyTaskWithExtraContext>) {
self.concurrent_tasks.push(task);
unsafe { (*self.loop_ptr()).wakeup() };
}
pub unsafe fn enqueue_task_concurrent_with_extra_ctx<C, P>(
&mut self,
ctx: *mut C,
callback: fn(*mut C, *mut P),
field_offset: usize,
) {
let task = unsafe { ctx.byte_add(field_offset).cast::<AnyTaskWithExtraContext>() };
unsafe { task.write(New::<C, P>::init(ctx, callback)) };
self.concurrent_tasks
.push(unsafe { NonNull::new_unchecked(task) });
unsafe { (*self.loop_ptr()).wakeup() };
}
#[inline]
fn lazy_stdio_store(slot: &mut Option<NonNull<()>>, fd: Fd, is_atty: bool) -> *mut () {
if slot.is_none() {
let mut mode: Mode = 0;
if let Ok(stat) = sys::fstat(fd) {
mode = stat.st_mode as Mode;
}
let store = __bun_stdio_blob_store_new(fd, is_atty, mode);
*slot = NonNull::new(store);
}
slot.unwrap().as_ptr()
}
pub fn stderr(&mut self) -> *mut () {
Self::lazy_stdio_store(
&mut self.stderr_store,
Fd::from_uv(2),
Output::stderr_descriptor_type() == Output::OutputStreamDescriptor::Terminal,
)
}
pub fn stdout(&mut self) -> *mut () {
Self::lazy_stdio_store(
&mut self.stdout_store,
Fd::stdout(),
Output::stdout_descriptor_type() == Output::OutputStreamDescriptor::Terminal,
)
}
}
bun_io::link_impl_EventLoopCtx! {
Mini for MiniEventLoop<'static> => |this| {
platform_event_loop_ptr() => (*this).loop_ptr(),
file_polls_ptr() => MiniEventLoop::file_polls_raw(this),
increment_pending_unref_counter() => panic!("FIXME TODO"),
ref_concurrently() => unreachable!("KeepAlive::refConcurrently is JS-VM-only"),
unref_concurrently() => unreachable!("KeepAlive::unrefConcurrently is JS-VM-only"),
after_event_loop_callback() => (*this).after_event_loop_callback,
set_after_event_loop_callback(cb, ctx) => {
(*this).after_event_loop_callback = cb;
(*this).after_event_loop_callback_ctx = ctx;
},
pipe_read_buffer() => core::ptr::from_mut::<[u8]>((*this).pipe_read_buffer()),
}
}
impl<'a> MiniEventLoop<'a> {
#[inline]
pub fn as_event_loop_ctx(this: &mut MiniEventLoop<'a>) -> bun_io::EventLoopCtx {
unsafe { bun_io::EventLoopCtx::new(bun_io::EventLoopCtxKind::Mini, this) }
}
}
impl<'a> Drop for MiniEventLoop<'a> {
fn drop(&mut self) {
debug_assert!(self.concurrent_tasks.is_empty());
}
}
pub struct MiniVM<'a> {
pub mini: &'a mut MiniEventLoop<'a>,
}
impl<'a> MiniVM<'a> {
pub fn init(inner: &'a mut MiniEventLoop<'a>) -> MiniVM<'a> {
MiniVM { mini: inner }
}
#[inline]
pub fn loop_(&self) -> &MiniEventLoop<'a> {
&*self.mini
}
#[inline]
pub fn platform_event_loop(&self) -> *mut PlatformEventLoop {
bun_io::uws_to_native(self.mini.loop_ptr())
}
#[inline]
pub fn increment_pending_unref_counter(&self) {
let _ = self;
panic!("FIXME TODO");
}
#[inline]
pub fn file_polls(&mut self) -> &mut FilePollStore {
self.mini.file_polls()
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum EventLoopKind {
Js,
Mini,
}
pub trait EventLoopKindT {
type Loop;
type Ref;
fn get_vm() -> Self::Ref;
}
pub struct JsKind;
pub struct MiniKind;
impl EventLoopKindT for JsKind {
type Loop = *mut ();
type Ref = *mut ();
fn get_vm() -> Self::Ref {
__bun_js_vm_get()
}
}
impl EventLoopKindT for MiniKind {
type Loop = MiniEventLoop<'static>;
type Ref = *mut MiniEventLoop<'static>;
fn get_vm() -> Self::Ref {
GLOBAL.with(|g| g.get())
}
}
pub trait AbstractVM<'a> {
type Wrapped;
fn abstract_vm(self) -> Self::Wrapped;
}
impl<'a> AbstractVM<'a> for &'a mut MiniEventLoop<'a> {
type Wrapped = MiniVM<'a>;
fn abstract_vm(self) -> MiniVM<'a> {
MiniVM::init(self)
}
}