luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use core::cell::Cell;
use core::ptr;

use luau_vm::state::LuaCallbacks;

use crate::hooks::debug::{self, DebugHandler};
use crate::hooks::interrupt::{self, InterruptHandle, InterruptHandler, InterruptMode};
use crate::lua::Lua;

pub(crate) struct CallbackManager {
    interrupt_handler: Option<Box<dyn InterruptHandler>>,
    interrupt_mode: InterruptMode,
    interrupt_handle: InterruptHandle,
    interrupt_active: Cell<bool>,
    debug_handler: Option<Box<dyn DebugHandler>>,
}

impl Default for CallbackManager {
    fn default() -> Self {
        Self {
            interrupt_handler: None,
            interrupt_mode: InterruptMode::Continuous,
            interrupt_handle: InterruptHandle::new(),
            interrupt_active: Cell::new(false),
            debug_handler: None,
        }
    }
}

impl Lua {
    pub(in crate::hooks) fn install_callbacks(&mut self) {
        let (thread, manager) = self.callback_parts();
        unsafe {
            manager.install(&mut *thread.callbacks());
        }
    }
}

impl CallbackManager {
    pub(in crate::hooks) fn set_interrupt_handler(&mut self, handler: Box<dyn InterruptHandler>) {
        self.interrupt_handle.clear();
        self.interrupt_mode = handler.mode();
        self.interrupt_handler = Some(handler);
    }

    pub(in crate::hooks) fn remove_interrupt_handler(&mut self) {
        self.interrupt_handle.clear();
        self.interrupt_mode = InterruptMode::Continuous;
        self.interrupt_handler = None;
    }

    pub(in crate::hooks) fn set_debug_handler(&mut self, handler: Box<dyn DebugHandler>) {
        self.debug_handler = Some(handler);
    }

    pub(in crate::hooks) fn remove_debug_handler(&mut self) {
        self.debug_handler = None;
    }

    pub(crate) fn install(&self, callbacks: &mut LuaCallbacks) {
        callbacks.execution_interrupt = self
            .has_interrupt_handler()
            .then_some(interrupt::execution_interrupt);
        callbacks.pattern_interrupt = self
            .has_interrupt_handler()
            .then_some(interrupt::pattern_interrupt);
        callbacks.gc_interrupt = self
            .has_interrupt_handler()
            .then_some(interrupt::gc_interrupt);
        callbacks.interrupt_request =
            if self.has_interrupt_handler() && self.interrupt_mode == InterruptMode::Requested {
                self.interrupt_handle.as_ptr()
            } else {
                ptr::null()
            };
        callbacks.debug_step = self.has_debug_handler().then_some(debug::debug_step);
        callbacks.debug_break = self.has_debug_handler().then_some(debug::debug_break);
        callbacks.debug_interrupt = self.has_debug_handler().then_some(debug::debug_interrupt);
        callbacks.debug_protected_error = self
            .has_debug_handler()
            .then_some(debug::debug_protected_error);
    }

    pub(in crate::hooks) fn invoke_interrupt<R>(
        &self,
        invoke: impl FnOnce(&dyn InterruptHandler, &Cell<bool>) -> R,
    ) -> Option<R> {
        let handler = self.interrupt_handler.as_deref()?;
        if self.interrupt_active.replace(true) {
            if self.interrupt_mode == InterruptMode::Requested {
                self.interrupt_handle.request();
            }
            return None;
        }

        struct ActiveGuard<'a>(&'a Cell<bool>);

        impl Drop for ActiveGuard<'_> {
            fn drop(&mut self) {
                self.0.set(false);
            }
        }

        let _active = ActiveGuard(&self.interrupt_active);
        let deferred = Cell::new(false);
        let result = invoke(handler, &deferred);
        if self.interrupt_mode == InterruptMode::Requested && deferred.get() {
            self.interrupt_handle.request();
        }
        Some(result)
    }

    pub(in crate::hooks) fn interrupt_handle(&self) -> InterruptHandle {
        self.interrupt_handle.clone()
    }

    pub(in crate::hooks) fn debug_handler(&self) -> Option<&dyn DebugHandler> {
        self.debug_handler.as_deref()
    }

    fn has_interrupt_handler(&self) -> bool {
        self.interrupt_handler.is_some()
    }

    fn has_debug_handler(&self) -> bool {
        self.debug_handler.is_some()
    }
}