1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//! This task is run once all parse and resolve tasks have been complete
//! and we have deferred onLoad plugins that we need to resume.
//!
//! It enqueues a task to be run on the JS thread which resolves the promise
//! for every onLoad callback which called `.defer()`.
use crate::BundleV2;
// Task is `(tag: u8, ptr: *mut ())` owned by bun_event_loop;
// runtime owns the match-loop. See PORTING.md §Dispatch.
use bun_event_loop::ConcurrentTask::ConcurrentTask;
use bun_event_loop::{Task, task_tag};
/// Re-export for callers that previously named
/// `crate::DeferredBatchTask::CompletionDispatch` — the struct now lives in
/// `bundle_v2::dispatch` alongside the other §Dispatch vtables.
pub use crate::bundle_v2::dispatch::CompletionDispatch;
#[derive(Default)]
pub struct DeferredBatchTask {
// Zig: `running: if (Environment.isDebug) bool else u0` — zero-sized in release.
#[cfg(debug_assertions)]
running: bool,
}
impl DeferredBatchTask {
pub fn init(&mut self) {
// PORT NOTE: kept as `&mut self` (not `-> Self`) — this struct is embedded
// by value in BundleV2 (recovered via container_of in `get_bundle_v2`), so
// it is reset in place, never separately constructed.
#[cfg(debug_assertions)]
debug_assert!(!self.running);
// No Drop / no owned fields — pure reset.
let _ = core::mem::take(self);
}
pub fn get_bundle_v2(&mut self) -> &mut BundleV2<'static> {
// SAFETY: `self` is always the `drain_defer_task` field of a live `BundleV2`;
// this struct is never instantiated standalone. Lifetime erased to 'static
// (mirrors Zig raw `*BundleV2`); callers must not outlive the owning bundle.
unsafe {
&mut *bun_core::from_field_ptr!(
BundleV2<'static>,
drain_defer_task,
std::ptr::from_mut::<Self>(self)
)
}
}
pub fn schedule(&mut self) {
#[cfg(debug_assertions)]
{
debug_assert!(!self.running);
self.running = false;
}
// PORTING.md §Dispatch: tag+ptr, not TaggedPointer. Tag constant lives in
// `bun_event_loop::task_tag::BundleV2DeferredBatchTask`.
let task = ConcurrentTask::create(Task::new(
task_tag::BundleV2DeferredBatchTask,
std::ptr::from_mut::<Self>(self).cast::<()>(),
));
// Zig: `getBundleV2().jsLoopForPlugins().enqueueTaskConcurrent(task)`.
self.get_bundle_v2().enqueue_on_js_loop_for_plugins(task);
}
pub fn run_on_js_thread(&mut self) {
// PORT NOTE: reshaped for borrowck — Zig's `defer this.deinit()` only resets
// the debug `running` flag; since nothing follows `drainDeferred`, ignoring
// its error and resetting the flag afterwards is equivalent on both paths.
{
let bv2 = self.get_bundle_v2();
// Zig: `if (bv2.completion) |c| c.result == .err else false`
let rejected = bv2.completion.map(|c| c.result_is_err()).unwrap_or(false);
// Zig: `bv2.plugins.?.drainDeferred(rejected) catch return;`
// `catch return` collapses to discarding the void result — see
// `Plugin::drain_deferred` for the exception-scope note.
bv2.plugins_mut().expect("plugins").drain_deferred(rejected);
}
self.deinit();
}
// PORT NOTE: not `impl Drop` — this struct is an intrusive field of `BundleV2`
// and `deinit` is a debug-flag reset, not resource teardown.
fn deinit(&mut self) {
#[cfg(debug_assertions)]
{
self.running = false;
}
}
}
// ported from: src/bundler/DeferredBatchTask.zig