code-mode/src/runtime/timers.rs
114 lines
use std::thread;use std::time::Duration;use super::RuntimeCommand;use super::RuntimeState;use super::value::value_to_error_text;pub(super) struct ScheduledTimeout { callback: v8::Global<v8::Function>,}pub(super) fn schedule_timeout( scope: &mut v8::PinScope<'_, '_>, args: v8::FunctionCallbackArguments,) -> Result<u64, String> { let callback = args.get(0); if !callback.is_function() { return Err("setTimeout expects a function callback".to_string()); } let callback = v8::Local::<v8::Function>::try_from(callback) .map_err(|_| "setTimeout expects a function callback".to_string())?; let delay_ms = args .get(1) .number_value(scope) .map(normalize_delay_ms) .unwrap_or(0); let callback = v8::Global::new(scope, callback); let state = scope .get_slot_mut::<RuntimeState>() .ok_or_else(|| "runtime state unavailable".to_string())?; let timeout_id = state.next_timeout_id; state.next_timeout_id = state.next_timeout_id.saturating_add(1); let runtime_command_tx = state.runtime_command_tx.clone(); state .pending_timeouts .insert(timeout_id, ScheduledTimeout { callback }); thread::spawn(move || { thread::sleep(Duration::from_millis(delay_ms)); let _ = runtime_command_tx.send(RuntimeCommand::TimeoutFired { id: timeout_id }); }); Ok(timeout_id)}pub(super) fn clear_timeout( scope: &mut v8::PinScope<'_, '_>, args: v8::FunctionCallbackArguments,) -> Result<(), String> { let Some(timeout_id) = timeout_id_from_args(scope, args)? else { return Ok(()); }; let Some(state) = scope.get_slot_mut::<RuntimeState>() else { return Err("runtime state unavailable".to_string()); }; state.pending_timeouts.remove(&timeout_id); Ok(())}pub(super) fn invoke_timeout_callback( scope: &mut v8::PinScope<'_, '_>, timeout_id: u64,) -> Result<(), String> { let callback = { let state = scope .get_slot_mut::<RuntimeState>() .ok_or_else(|| "runtime state unavailable".to_string())?; state.pending_timeouts.remove(&timeout_id) }; let Some(callback) = callback else { return Ok(()); }; let tc = std::pin::pin!(v8::TryCatch::new(scope)); let mut tc = tc.init(); let callback = v8::Local::new(&tc, &callback.callback); let receiver = v8::undefined(&tc).into(); let _ = callback.call(&tc, receiver, &[]); if tc.has_caught() { return Err(tc .exception() .map(|exception| value_to_error_text(&mut tc, exception)) .unwrap_or_else(|| "unknown code mode exception".to_string())); } Ok(())}fn timeout_id_from_args( scope: &mut v8::PinScope<'_, '_>, args: v8::FunctionCallbackArguments,) -> Result<Option<u64>, String> { if args.length() == 0 || args.get(0).is_null_or_undefined() { return Ok(None); } let Some(timeout_id) = args.get(0).number_value(scope) else { return Err("clearTimeout expects a numeric timeout id".to_string()); }; if !timeout_id.is_finite() || timeout_id <= 0.0 { return Ok(None); } Ok(Some(timeout_id.trunc().min(u64::MAX as f64) as u64))}fn normalize_delay_ms(delay_ms: f64) -> u64 { if !delay_ms.is_finite() || delay_ms <= 0.0 { 0 } else { delay_ms.trunc().min(u64::MAX as f64) as u64 }}