luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use core::any::{Any, TypeId};
use core::fmt;
use core::ops::{Deref, DerefMut};
use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut};
use std::collections::HashMap;

use super::{Lua, LuaRef};

#[derive(Default)]
pub(crate) struct AppData {
    values: RefCell<HashMap<TypeId, Box<dyn Any>>>,
}

/// An immutable borrow from a Luau state's Rust application data.
pub struct AppDataRef<'lua, T: ?Sized> {
    value: Ref<'lua, T>,
}

/// A mutable borrow from a Luau state's Rust application data.
pub struct AppDataRefMut<'lua, T: ?Sized> {
    value: RefMut<'lua, T>,
}

impl Lua {
    /// Stores one Rust value of type `T` on this Luau state.
    ///
    /// Returns the previous value of the same type, if present.
    #[track_caller]
    pub fn set_app_data<T: 'static>(&self, data: T) -> Option<T> {
        self.lua_ref().set_app_data(data)
    }

    /// Attempts to store one Rust value of type `T` on this Luau state.
    ///
    /// Returns `Err(data)` when application data is currently borrowed.
    pub fn try_set_app_data<T: 'static>(&self, data: T) -> Result<Option<T>, T> {
        self.lua_ref().try_set_app_data(data)
    }

    /// Borrows application data of type `T`, if present.
    #[track_caller]
    pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<'_, T>> {
        self.lua_ref().app_data_ref()
    }

    /// Attempts to borrow application data of type `T`.
    pub fn try_app_data_ref<T: 'static>(&self) -> Result<Option<AppDataRef<'_, T>>, BorrowError> {
        self.lua_ref().try_app_data_ref()
    }

    /// Mutably borrows application data of type `T`, if present.
    #[track_caller]
    pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<'_, T>> {
        self.lua_ref().app_data_mut()
    }

    /// Attempts to mutably borrow application data of type `T`.
    pub fn try_app_data_mut<T: 'static>(
        &self,
    ) -> Result<Option<AppDataRefMut<'_, T>>, BorrowMutError> {
        self.lua_ref().try_app_data_mut()
    }

    /// Removes and returns application data of type `T`, if present.
    #[track_caller]
    pub fn remove_app_data<T: 'static>(&self) -> Option<T> {
        self.lua_ref().remove_app_data()
    }

    /// Attempts to remove application data of type `T`.
    pub fn try_remove_app_data<T: 'static>(&self) -> Result<Option<T>, BorrowMutError> {
        self.lua_ref().try_remove_app_data()
    }
}

impl<'lua> LuaRef<'lua> {
    /// Stores one Rust value of type `T` on this Luau state.
    ///
    /// Returns the previous value of the same type, if present.
    #[track_caller]
    pub fn set_app_data<T: 'static>(&self, data: T) -> Option<T> {
        match self.try_set_app_data(data) {
            Ok(previous) => previous,
            Err(_) => panic!("cannot mutably borrow application data"),
        }
    }

    /// Attempts to store one Rust value of type `T` on this Luau state.
    ///
    /// Returns `Err(data)` when application data is currently borrowed.
    pub fn try_set_app_data<T: 'static>(&self, data: T) -> Result<Option<T>, T> {
        let Ok(mut values) = self.runtime().app_data().values.try_borrow_mut() else {
            return Err(data);
        };
        let previous = values
            .insert(TypeId::of::<T>(), Box::new(data))
            .map(|previous| {
                *previous
                    .downcast::<T>()
                    .expect("application data TypeId must match its stored value")
            });
        self.runtime().invalidate_managed_safe_env();
        Ok(previous)
    }

    /// Borrows application data of type `T`, if present.
    #[track_caller]
    pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<'lua, T>> {
        match self.try_app_data_ref() {
            Ok(value) => value,
            Err(error) => panic!("application data is already mutably borrowed: {error}"),
        }
    }

    /// Attempts to borrow application data of type `T`.
    pub fn try_app_data_ref<T: 'static>(&self) -> Result<Option<AppDataRef<'lua, T>>, BorrowError> {
        let values = self.runtime().app_data().values.try_borrow()?;
        Ok(Ref::filter_map(values, |values| {
            values
                .get(&TypeId::of::<T>())
                .and_then(|value| value.downcast_ref())
        })
        .ok()
        .map(|value| AppDataRef { value }))
    }

    /// Mutably borrows application data of type `T`, if present.
    #[track_caller]
    pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<'lua, T>> {
        match self.try_app_data_mut() {
            Ok(value) => value,
            Err(error) => panic!("application data is already borrowed: {error}"),
        }
    }

    /// Attempts to mutably borrow application data of type `T`.
    pub fn try_app_data_mut<T: 'static>(
        &self,
    ) -> Result<Option<AppDataRefMut<'lua, T>>, BorrowMutError> {
        let values = self.runtime().app_data().values.try_borrow_mut()?;
        let value = RefMut::filter_map(values, |values| {
            values
                .get_mut(&TypeId::of::<T>())
                .and_then(|value| value.downcast_mut())
        })
        .ok()
        .map(|value| AppDataRefMut { value });
        if value.is_some() {
            self.runtime().invalidate_managed_safe_env();
        }
        Ok(value)
    }

    /// Removes and returns application data of type `T`, if present.
    #[track_caller]
    pub fn remove_app_data<T: 'static>(&self) -> Option<T> {
        self.try_remove_app_data()
            .unwrap_or_else(|error| panic!("application data is already borrowed: {error}"))
    }

    /// Attempts to remove application data of type `T`.
    pub fn try_remove_app_data<T: 'static>(&self) -> Result<Option<T>, BorrowMutError> {
        let mut values = self.runtime().app_data().values.try_borrow_mut()?;
        let value = values.remove(&TypeId::of::<T>()).map(|value| {
            *value
                .downcast::<T>()
                .expect("application data TypeId must match its stored value")
        });
        if value.is_some() {
            self.runtime().invalidate_managed_safe_env();
        }
        Ok(value)
    }
}

impl<T: ?Sized> Deref for AppDataRef<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl<T: ?Sized> Deref for AppDataRefMut<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

impl<T: ?Sized> DerefMut for AppDataRefMut<'_, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.value
    }
}

impl<T: fmt::Debug + ?Sized> fmt::Debug for AppDataRef<'_, T> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&**self, formatter)
    }
}

impl<T: fmt::Debug + ?Sized> fmt::Debug for AppDataRefMut<'_, T> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&**self, formatter)
    }
}

impl<T: fmt::Display + ?Sized> fmt::Display for AppDataRef<'_, T> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&**self, formatter)
    }
}

impl<T: fmt::Display + ?Sized> fmt::Display for AppDataRefMut<'_, T> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&**self, formatter)
    }
}