Something went wrong. Try again.
MIRROR: javascript for ๐'s, a tiny runtime with big ambitions ant.themackabu.dev
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873#include <compat.h> // IWYU pragma: keep
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <time.h>#include <uv.h>
#include "errors.h"#include "runtime.h"#include "internal.h"
#include "silver/engine.h"#include "gc/roots.h"#include "gc/modules.h"#include "modules/abort.h"#include "modules/timer.h"#include "modules/symbol.h"
typedef struct timer_entry { uv_timer_t handle; ant_value_t callback; ant_value_t *args; int nargs; int timer_id; int active; int closed; int is_interval; struct timer_entry *next; struct timer_entry *prev;} timer_entry_t;
typedef struct microtask_entry { ant_value_t callback; ant_value_t promise; struct microtask_entry *next; uint8_t argc; ant_value_t argv[];} microtask_entry_t;
typedef struct immediate_entry { ant_value_t callback; int immediate_id; int active; struct immediate_entry *next;} immediate_entry_t;
static struct { ant_t *js; timer_entry_t *timers; microtask_entry_t *next_ticks; microtask_entry_t *next_ticks_tail; microtask_entry_t *next_ticks_processing; microtask_entry_t *microtasks; microtask_entry_t *microtasks_tail; microtask_entry_t *microtasks_processing; immediate_entry_t *immediates; immediate_entry_t *immediates_tail; int next_timer_id; int next_immediate_id; int active_timer_count;} timer_state = { .js = NULL, .timers = NULL, .next_ticks = NULL, .next_ticks_tail = NULL, .next_ticks_processing = NULL, .microtasks = NULL, .microtasks_tail = NULL, .microtasks_processing = NULL, .immediates = NULL, .immediates_tail = NULL, .next_timer_id = 1, .next_immediate_id = 1, .active_timer_count = 0,};
static ant_value_t g_timeout_proto = 0;static ant_value_t g_interval_proto = 0;
static void add_timer_entry(timer_entry_t *entry) { entry->next = timer_state.timers; entry->prev = NULL; if (timer_state.timers) timer_state.timers->prev = entry; timer_state.timers = entry;}
static void remove_timer_entry(timer_entry_t *entry) { if (entry->prev) entry->prev->next = entry->next; else timer_state.timers = entry->next; if (entry->next) entry->next->prev = entry->prev;}
static int timer_entry_is_registered(timer_entry_t *entry) { for (timer_entry_t *it = timer_state.timers; it != NULL; it = it->next) if (it == entry) return 1; return 0;}
static timer_entry_t *find_timer_entry_by_id(int timer_id) { for (timer_entry_t *entry = timer_state.timers; entry != NULL; entry = entry->next) if (entry->timer_id == timer_id) return entry; return NULL;}
static int timer_copy_args(timer_entry_t *entry, ant_value_t *args, int nargs) { entry->nargs = nargs > 2 ? nargs - 2 : 0; if (entry->nargs > 0) { entry->args = ant_calloc(sizeof(ant_value_t) * entry->nargs); if (!entry->args) return -1; memcpy(entry->args, args + 2, sizeof(ant_value_t) * entry->nargs); } else entry->args = NULL; return 0;}
static ant_value_t timer_to_primitive(ant_t *js, ant_value_t *args, int nargs) { return js_get_slot(js_getthis(js), SLOT_DATA);}
static ant_value_t timer_inspect(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); ant_value_t id_val = js_get_slot(this_obj, SLOT_DATA); int timer_id = vtype(id_val) == T_NUM ? (int)js_getnum(id_val) : 0;
ant_value_t tag_val = js_get_sym(js, this_obj, get_toStringTag_sym()); const char *tag = vtype(tag_val) == T_STR ? js_getstr(js, tag_val, NULL) : "Timeout";
js_inspect_builder_t builder; if (!js_inspect_builder_init_dynamic(&builder, js, 128)) { return js_mkerr(js, "out of memory"); }
bool ok = js_inspect_header(&builder, "%s (%d)", tag, timer_id); if (ok) ok = js_inspect_object_body(&builder, this_obj); if (ok) ok = js_inspect_close(&builder); if (!ok) { js_inspect_builder_dispose(&builder); return js_mkerr(js, "out of memory"); }
return js_inspect_builder_result(&builder);}
static ant_value_t js_timer_ref(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); timer_entry_t *entry = find_timer_entry_by_id((int)js_getnum(js_get_slot(this_obj, SLOT_DATA))); if (entry && !entry->closed && !uv_is_closing((uv_handle_t *)&entry->handle)) uv_ref((uv_handle_t *)&entry->handle); return this_obj;}
static ant_value_t js_timer_unref(ant_t *js, ant_value_t *args, int nargs) { ant_value_t this_obj = js_getthis(js); timer_entry_t *entry = find_timer_entry_by_id((int)js_getnum(js_get_slot(this_obj, SLOT_DATA))); if (entry && !entry->closed && !uv_is_closing((uv_handle_t *)&entry->handle)) uv_unref((uv_handle_t *)&entry->handle); return this_obj;}
static ant_value_t js_timer_has_ref(ant_t *js, ant_value_t *args, int nargs) { timer_entry_t *entry = find_timer_entry_by_id((int)js_getnum(js_get_slot(js_getthis(js), SLOT_DATA))); if (!entry || entry->closed || uv_is_closing((uv_handle_t *)&entry->handle)) return js_false; return js_bool(uv_has_ref((const uv_handle_t *)&entry->handle) != 0);}
static ant_value_t timer_make_object(ant_t *js, int id, double delay_ms, int is_interval, ant_value_t callback) { ant_value_t obj = js_mkobj(js); ant_value_t proto = is_interval ? g_interval_proto : g_timeout_proto;
if (is_object_type(proto)) js_set_proto_init(obj, proto);
js_set(js, obj, "delay", js_mknum(delay_ms)); js_set(js, obj, "repeat", is_interval ? js_mknum(delay_ms) : js_mknull()); js_set(js, obj, "callback", callback); js_set_descriptor(js, obj, "callback", 8, JS_DESC_W | JS_DESC_C); js_set_slot(obj, SLOT_DATA, js_mknum((double)id)); js_set_sym(js, obj, get_toPrimitive_sym(), js_mkfun(timer_to_primitive));
return obj;}
static int timer_id_from_arg(ant_t *js, ant_value_t arg) { if (vtype(arg) == T_NUM) return (int)js_getnum(arg); return (int)js_getnum(js_get_slot(arg, SLOT_DATA));}
static void timer_close_cb(uv_handle_t *h) { timer_entry_t *entry = (timer_entry_t *)h->data; if (!entry) return; if (entry->closed) return; if (timer_entry_is_registered(entry)) remove_timer_entry(entry); entry->closed = 1; entry->active = 0; entry->callback = 0; entry->next = NULL; entry->prev = NULL; if (entry->args) { free(entry->args); entry->args = NULL; } entry->nargs = 0;}
static void timer_callback(uv_timer_t *handle) { timer_entry_t *entry = (timer_entry_t *)handle->data; if (!entry || entry->closed || !timer_entry_is_registered(entry) || !entry->active) return; ant_t *js = timer_state.js; if (!entry->is_interval) { entry->active = 0; timer_state.active_timer_count--; }
sv_vm_call(js->vm, js, entry->callback, js_mkundef(), entry->args, entry->nargs, NULL, false); process_microtasks(js); if (!entry->is_interval) if (!uv_is_closing((uv_handle_t *)&entry->handle)) uv_close( (uv_handle_t *)&entry->handle, timer_close_cb );}
// setTimeout(callback, delay, ...args)static ant_value_t js_set_timeout(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) { return js_mkerr(js, "setTimeout requires at least 1 argument (callback)"); } ant_value_t callback = args[0]; double delay_ms = nargs > 1 ? js_getnum(args[1]) : 0; uint64_t ms = delay_ms >= 1 ? (uint64_t)delay_ms : 0; timer_entry_t *entry = ant_calloc(sizeof(timer_entry_t)); if (entry == NULL) return js_mkerr(js, "failed to allocate timer"); if (timer_copy_args(entry, args, nargs) < 0) { free(entry); return js_mkerr(js, "failed to allocate timer args"); } uv_timer_init(uv_default_loop(), &entry->handle); entry->handle.data = entry; entry->callback = callback; entry->timer_id = timer_state.next_timer_id++; entry->active = 1; entry->closed = 0; entry->is_interval = 0; add_timer_entry(entry); timer_state.active_timer_count++; uv_timer_start(&entry->handle, timer_callback, ms, 0);
return timer_make_object(js, entry->timer_id, delay_ms, 0, callback);}
// setInterval(callback, delay, ...args)static ant_value_t js_set_interval(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) { return js_mkerr(js, "setInterval requires at least 1 argument (callback)"); } ant_value_t callback = args[0]; double delay_ms = nargs > 1 ? js_getnum(args[1]) : 0; uint64_t ms = delay_ms >= 1 ? (uint64_t)delay_ms : 1; timer_entry_t *entry = ant_calloc(sizeof(timer_entry_t)); if (entry == NULL) return js_mkerr(js, "failed to allocate timer"); if (timer_copy_args(entry, args, nargs) < 0) { free(entry); return js_mkerr(js, "failed to allocate timer args"); } uv_timer_init(uv_default_loop(), &entry->handle); entry->handle.data = entry; entry->callback = callback; entry->timer_id = timer_state.next_timer_id++; entry->active = 1; entry->closed = 0; entry->is_interval = 1; add_timer_entry(entry); timer_state.active_timer_count++; uv_timer_start(&entry->handle, timer_callback, ms, ms);
return timer_make_object(js, entry->timer_id, delay_ms, 1, callback);}
// clearTimeout(timerId | timerObject)static ant_value_t js_clear_timeout(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkundef(); int timer_id = timer_id_from_arg(js, args[0]); for (timer_entry_t *entry = timer_state.timers; entry != NULL; entry = entry->next) { if (entry->timer_id == timer_id && entry->active) { entry->active = 0; timer_state.active_timer_count--; if (!uv_is_closing((uv_handle_t *)&entry->handle)) uv_close((uv_handle_t *)&entry->handle, timer_close_cb); break; }} return js_mkundef();}
// setImmediate(callback)static ant_value_t js_set_immediate(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) { return js_mkerr(js, "setImmediate requires 1 argument (callback)"); } ant_value_t callback = args[0]; immediate_entry_t *entry = ant_calloc(sizeof(immediate_entry_t)); if (entry == NULL) { return js_mkerr(js, "failed to allocate immediate"); } entry->callback = callback; entry->immediate_id = timer_state.next_immediate_id++; entry->active = 1; entry->next = NULL; if (timer_state.immediates_tail == NULL) { timer_state.immediates = entry; timer_state.immediates_tail = entry; } else { timer_state.immediates_tail->next = entry; timer_state.immediates_tail = entry; }
ant_value_t obj = js_mkobj(js); js_set(js, obj, "id", js_mknum((double)entry->immediate_id)); js_set(js, obj, "callback", callback); return obj;}
// clearImmediate(immediateId | immediateObject)static ant_value_t js_clear_immediate(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_mkundef(); int immediate_id = timer_id_from_arg(js, args[0]); for (immediate_entry_t *entry = timer_state.immediates; entry != NULL; entry = entry->next) { if (entry->immediate_id == immediate_id) { entry->active = 0; break; } } return js_mkundef();}
// queueMicrotask(callback)static ant_value_t js_queue_microtask(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) { return js_mkerr(js, "queueMicrotask requires 1 argument (callback)"); } queue_microtask(js, args[0]); return js_mkundef();}
static ant_value_t timers_promises_get_state(ant_t *js) { return js_get_slot(js->current_func, SLOT_DATA);}
static ant_value_t timers_promises_abort_reason(ant_t *js, ant_value_t signal) { ant_value_t reason = abort_signal_get_reason(signal); if (vtype(reason) != T_UNDEF && vtype(reason) != T_NULL) return reason; return js_mkerr_typed(js, JS_ERR_TYPE, "The operation was aborted");}
static void timers_promises_remove_abort_listener(ant_t *js, ant_value_t state) { ant_value_t signal = 0; ant_value_t listener = 0;
if (!is_object_type(state)) return;
signal = js_get(js, state, "signal"); listener = js_get(js, state, "abortListener");
if (abort_signal_is_signal(signal) && is_callable(listener)) abort_signal_remove_listener(js, signal, listener);
js_set(js, state, "abortListener", js_mkundef());}
static void timers_promises_settle(ant_t *js, ant_value_t state, bool reject, ant_value_t value) { ant_value_t settled = 0; ant_value_t promise = 0;
if (!is_object_type(state)) return;
settled = js_get(js, state, "settled"); if (js_truthy(js, settled)) return;
js_set(js, state, "settled", js_true); timers_promises_remove_abort_listener(js, state); js_set(js, state, "handle", js_mkundef());
promise = js_get(js, state, "promise"); if (reject) js_reject_promise(js, promise, value); else js_resolve_promise(js, promise, value);}
static ant_value_t timers_promises_resolve(ant_t *js, ant_value_t *args, int nargs) { ant_value_t state = timers_promises_get_state(js); ant_value_t value = js_mkundef(); if (!is_object_type(state)) return js_mkundef(); value = js_get(js, state, "value"); timers_promises_settle(js, state, false, value); return js_mkundef();}
static ant_value_t timers_promises_on_abort(ant_t *js, ant_value_t *args, int nargs) { ant_value_t state = timers_promises_get_state(js); ant_value_t signal = 0; ant_value_t handle = 0; ant_value_t is_immediate = 0; ant_value_t reason = js_mkundef(); ant_value_t clear_args[1];
if (!is_object_type(state)) return js_mkundef();
signal = js_get(js, state, "signal"); handle = js_get(js, state, "handle"); is_immediate = js_get(js, state, "isImmediate");
if (vtype(handle) != T_UNDEF && vtype(handle) != T_NULL) { clear_args[0] = handle; if (js_truthy(js, is_immediate)) js_clear_immediate(js, clear_args, 1); else js_clear_timeout(js, clear_args, 1); }
if (abort_signal_is_signal(signal)) reason = timers_promises_abort_reason(js, signal); else reason = js_mkerr_typed(js, JS_ERR_TYPE, "The operation was aborted");
timers_promises_settle(js, state, true, reason); return js_mkundef();}
static bool timers_promises_parse_options( ant_t *js, ant_value_t value, ant_value_t *signal_out, ant_value_t *error_out) { ant_value_t signal = js_mkundef();
if (signal_out) *signal_out = js_mkundef(); if (error_out) *error_out = js_mkundef();
if (vtype(value) == T_UNDEF || vtype(value) == T_NULL) return true; if (vtype(value) != T_OBJ) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Timer options must be an object"); return false; }
signal = js_get(js, value, "signal"); if (vtype(signal) != T_UNDEF && vtype(signal) != T_NULL && !abort_signal_is_signal(signal)) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "options.signal must be an AbortSignal"); return false; }
if (signal_out) *signal_out = signal; return true;}
static ant_value_t timers_promises_schedule( ant_t *js, double delay_ms, ant_value_t value, ant_value_t signal, bool is_immediate) { ant_value_t promise = js_mkpromise(js); ant_value_t state = js_mkobj(js); ant_value_t callback = 0; ant_value_t handle = 0; ant_value_t args[2];
if (abort_signal_is_signal(signal) && abort_signal_is_aborted(signal)) { js_reject_promise(js, promise, timers_promises_abort_reason(js, signal)); return promise; }
js_set(js, state, "promise", promise); js_set(js, state, "value", value); js_set(js, state, "signal", signal); js_set(js, state, "abortListener", js_mkundef()); js_set(js, state, "handle", js_mkundef()); js_set(js, state, "settled", js_false); js_set(js, state, "isImmediate", js_bool(is_immediate));
callback = js_heavy_mkfun(js, timers_promises_resolve, state); if (is_immediate) handle = js_set_immediate(js, &callback, 1); else { args[0] = callback; args[1] = js_mknum(delay_ms); handle = js_set_timeout(js, args, 2); }
if (is_err(handle)) { js_reject_promise(js, promise, handle); return promise; }
js_set(js, state, "handle", handle);
if (abort_signal_is_signal(signal)) { ant_value_t listener = js_heavy_mkfun(js, timers_promises_on_abort, state); js_set(js, state, "abortListener", listener); abort_signal_add_listener(js, signal, listener); }
return promise;}
static ant_value_t js_timers_promises_setTimeout(ant_t *js, ant_value_t *args, int nargs) { double delay_ms = nargs > 0 ? js_getnum(args[0]) : 0; ant_value_t value = nargs > 1 ? args[1] : js_mkundef(); ant_value_t options = nargs > 2 ? args[2] : js_mkundef(); ant_value_t signal = js_mkundef(); ant_value_t error = js_mkundef();
if (!timers_promises_parse_options(js, options, &signal, &error)) return error; return timers_promises_schedule(js, delay_ms, value, signal, false);}
static ant_value_t js_timers_promises_setImmediate(ant_t *js, ant_value_t *args, int nargs) { ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); ant_value_t options = nargs > 1 ? args[1] : js_mkundef(); ant_value_t signal = js_mkundef(); ant_value_t error = js_mkundef();
if (!timers_promises_parse_options(js, options, &signal, &error)) return error; return timers_promises_schedule(js, 0, value, signal, true);}
static ant_value_t js_timers_promises_setInterval(ant_t *js, ant_value_t *args, int nargs) { return js_mkerr_typed(js, JS_ERR_TYPE, "node:timers/promises setInterval() is not implemented yet");}
static ant_value_t js_timers_promises_scheduler_wait(ant_t *js, ant_value_t *args, int nargs) { return js_timers_promises_setTimeout(js, args, nargs);}
static ant_value_t js_timers_promises_scheduler_yield(ant_t *js, ant_value_t *args, int nargs) { return js_timers_promises_setImmediate(js, args, nargs);}
static void queue_microtask_entry( microtask_entry_t **head, microtask_entry_t **tail, microtask_entry_t *entry) { if (*tail == NULL) goto empty;
(*tail)->next = entry; *tail = entry; return;
empty: *head = entry; *tail = entry;}
void queue_microtask(ant_t *js, ant_value_t callback) { microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t)); if (entry == NULL) return; entry->callback = callback; entry->promise = js_mkundef(); entry->next = NULL; entry->argc = 0; queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry);}
void queue_microtask_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs) { if (nargs <= 0) { queue_microtask(js, callback); return; } microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t) + (size_t)nargs * sizeof(ant_value_t)); if (entry == NULL) return; entry->callback = callback; entry->promise = js_mkundef(); entry->next = NULL; entry->argc = (uint8_t)nargs; for (int i = 0; i < nargs; i++) entry->argv[i] = args[i]; queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry);}
void queue_next_tick(ant_t *js, ant_value_t callback) { microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t)); if (entry == NULL) return;
entry->callback = callback; entry->promise = js_mkundef(); entry->next = NULL; entry->argc = 0;
queue_microtask_entry(&timer_state.next_ticks, &timer_state.next_ticks_tail, entry);}
void queue_next_tick_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs) { if (nargs <= 0) { queue_next_tick(js, callback); return; }
microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t) + (size_t)nargs * sizeof(ant_value_t)); if (entry == NULL) return;
entry->callback = callback; entry->promise = js_mkundef(); entry->next = NULL; entry->argc = (uint8_t)nargs;
for (int i = 0; i < nargs; i++) entry->argv[i] = args[i]; queue_microtask_entry(&timer_state.next_ticks, &timer_state.next_ticks_tail, entry);}
void queue_promise_trigger(ant_t *js, ant_value_t promise) { if (!js_mark_promise_trigger_queued(js, promise)) return;
microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t)); if (entry == NULL) { js_mark_promise_trigger_dequeued(js, promise); return; } entry->callback = js_mkundef(); entry->promise = promise; entry->next = NULL; queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry);}
static inline void process_microtask_entry(ant_t *js, microtask_entry_t *entry) { if (!entry) return;
if (vtype(entry->promise) == T_PROMISE) { GC_ROOT_SAVE(root_mark, js); ant_value_t promise = entry->promise; GC_ROOT_PIN(js, promise); js_mark_promise_trigger_dequeued(js, promise); js_process_promise_handlers(js, promise); GC_ROOT_RESTORE(js, root_mark); return; }
GC_ROOT_SAVE(root_mark, js); ant_value_t callback = entry->callback; GC_ROOT_PIN(js, callback); for (uint8_t i = 0; i < entry->argc; i++) GC_ROOT_PIN(js, entry->argv[i]); sv_vm_call(js->vm, js, callback, js_mkundef(), entry->argv, entry->argc, NULL, false); GC_ROOT_RESTORE(js, root_mark);}
static inline microtask_entry_t *take_microtask_batch(void) { microtask_entry_t *batch = timer_state.microtasks;
timer_state.microtasks = NULL; timer_state.microtasks_tail = NULL; timer_state.microtasks_processing = batch; return batch;}
static inline microtask_entry_t *take_next_tick_batch(void) { microtask_entry_t *batch = timer_state.next_ticks;
timer_state.next_ticks = NULL; timer_state.next_ticks_tail = NULL; timer_state.next_ticks_processing = batch; return batch;}
static inline void process_microtask_batch(ant_t *js, microtask_entry_t *batch) {while (batch != NULL) { microtask_entry_t *entry = batch; batch = entry->next; timer_state.microtasks_processing = batch; process_microtask_entry(js, entry); free(entry);}}
static inline void process_next_tick_batch(ant_t *js, microtask_entry_t *batch) {while (batch != NULL) { microtask_entry_t *entry = batch; batch = entry->next; timer_state.next_ticks_processing = batch; process_microtask_entry(js, entry); free(entry);}}
static void process_microtasks_internal(ant_t *js, bool check_unhandled_rejections) { microtask_entry_t *batch = NULL;
if (!js || js->microtasks_draining) return; js->microtasks_draining = true;
while (timer_state.next_ticks != NULL || timer_state.microtasks != NULL) { while ((batch = timer_state.next_ticks) != NULL) { batch = take_next_tick_batch(); process_next_tick_batch(js, batch); } while ((batch = timer_state.microtasks) != NULL) { batch = take_microtask_batch(); process_microtask_batch(js, batch); }}
timer_state.next_ticks_processing = NULL; timer_state.microtasks_processing = NULL; if (check_unhandled_rejections) js_check_unhandled_rejections(js); js->microtasks_draining = false; reap_retired_coroutines();}
void process_microtasks(ant_t *js) { process_microtasks_internal(js, true);}
bool js_maybe_drain_microtasks(ant_t *js) { if (!js) return false; if (js->microtasks_draining) return false; if (js->vm_exec_depth != 0) return false; if (!has_pending_microtasks()) return false; process_microtasks_internal(js, true); return true;}
bool js_maybe_drain_microtasks_after_async_settle(ant_t *js) { if (!js) return false;
if (js->microtasks_draining) return false; if (!has_pending_microtasks()) return false;
process_microtasks_internal(js, false); return true;}
void process_immediates(ant_t *js) {while (timer_state.immediates != NULL) { immediate_entry_t *entry = timer_state.immediates; timer_state.immediates = entry->next; if (timer_state.immediates == NULL) { timer_state.immediates_tail = NULL; } if (entry->active) { ant_value_t args[0]; sv_vm_call(js->vm, js, entry->callback, js_mkundef(), args, 0, NULL, false); process_microtasks(js); } free(entry);}}
int has_pending_immediates(void) { for ( immediate_entry_t *entry = timer_state.immediates; entry != NULL; entry = entry->next ) if (entry->active) return 1; return 0;}
int has_pending_timers(void) { return timer_state.active_timer_count > 0;}
int has_pending_microtasks(void) { return (timer_state.next_ticks != NULL || timer_state.microtasks != NULL) ? 1 : 0;}
static void timers_define_common(ant_t *js, ant_value_t obj) { js_set(js, obj, "setTimeout", js_mkfun(js_set_timeout)); js_set(js, obj, "clearTimeout", js_mkfun(js_clear_timeout)); js_set(js, obj, "setInterval", js_mkfun(js_set_interval)); js_set(js, obj, "clearInterval", js_mkfun(js_clear_timeout)); js_set(js, obj, "setImmediate", js_mkfun(js_set_immediate)); js_set(js, obj, "clearImmediate", js_mkfun(js_clear_immediate)); js_set(js, obj, "queueMicrotask", js_mkfun(js_queue_microtask));}
void init_timer_module() { ant_t *js = rt->js; timer_state.js = js;
g_timeout_proto = js_mkobj(js); g_interval_proto = js_mkobj(js); gc_register_root(&g_timeout_proto); gc_register_root(&g_interval_proto);
js_set_proto_init(g_timeout_proto, js->sym.object_proto); js_set(js, g_timeout_proto, "ref", js_mkfun(js_timer_ref)); js_set(js, g_timeout_proto, "unref", js_mkfun(js_timer_unref)); js_set(js, g_timeout_proto, "hasRef", js_mkfun(js_timer_has_ref)); js_set_sym(js, g_timeout_proto, get_toStringTag_sym(), js_mkstr(js, "Timeout", 7)); js_set_sym(js, g_timeout_proto, get_inspect_sym(), js_mkfun(timer_inspect));
js_set_proto_init(g_interval_proto, js->sym.object_proto); js_set(js, g_interval_proto, "ref", js_mkfun(js_timer_ref)); js_set(js, g_interval_proto, "unref", js_mkfun(js_timer_unref)); js_set(js, g_interval_proto, "hasRef", js_mkfun(js_timer_has_ref)); js_set_sym(js, g_interval_proto, get_toStringTag_sym(), js_mkstr(js, "Interval", 8)); js_set_sym(js, g_interval_proto, get_inspect_sym(), js_mkfun(timer_inspect));
timers_define_common(js, js_glob(js));}
ant_value_t timers_library(ant_t *js) { ant_value_t lib = js_mkobj(js);
timers_define_common(js, lib); js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "timers", 6));
return lib;}
// TODO: mostly stubbedant_value_t timers_promises_library(ant_t *js) { ant_value_t lib = js_mkobj(js); ant_value_t scheduler = js_mkobj(js);
js_set(js, lib, "scheduler", scheduler); js_set(js, lib, "setTimeout", js_mkfun(js_timers_promises_setTimeout)); js_set(js, lib, "setImmediate", js_mkfun(js_timers_promises_setImmediate)); js_set(js, lib, "setInterval", js_mkfun(js_timers_promises_setInterval)); js_set(js, scheduler, "wait", js_mkfun(js_timers_promises_scheduler_wait)); js_set(js, scheduler, "yield", js_mkfun(js_timers_promises_scheduler_yield)); js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "timers/promises", 16));
return lib;}
void gc_mark_timers(ant_t *js, gc_mark_fn mark) { if (is_object_type(g_timeout_proto)) mark(js, g_timeout_proto); if (is_object_type(g_interval_proto)) mark(js, g_interval_proto); for (timer_entry_t *t = timer_state.timers; t; t = t->next) { mark(js, t->callback); for (int i = 0; i < t->nargs; i++) mark(js, t->args[i]); } for (microtask_entry_t *m = timer_state.microtasks; m; m = m->next) { mark(js, m->callback); mark(js, m->promise); for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); } for (microtask_entry_t *m = timer_state.microtasks_processing; m; m = m->next) { mark(js, m->callback); mark(js, m->promise); for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); } for (microtask_entry_t *m = timer_state.next_ticks; m; m = m->next) { mark(js, m->callback); mark(js, m->promise); for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); } for (microtask_entry_t *m = timer_state.next_ticks_processing; m; m = m->next) { mark(js, m->callback); mark(js, m->promise); for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); } for (immediate_entry_t *i = timer_state.immediates; i; i = i->next) { mark(js, i->callback); }}