use core::cell::UnsafeCell;
use core::ffi::{c_char, c_uint};
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
static OVERRIDES_ACTIVE: AtomicBool = AtomicBool::new(false);
#[inline]
pub fn overrides_active() -> bool {
OVERRIDES_ACTIVE.load(Ordering::Relaxed)
}
pub trait FValueOverridable: Copy {
fn with_overrides<R>(
f: impl FnOnce(&mut std::collections::HashMap<usize, Vec<Self>>) -> R,
) -> R;
fn override_top(addr: usize) -> Option<Self> {
Self::with_overrides(|m| m.get(&addr).and_then(|s| s.last().copied()))
}
fn override_push(addr: usize, value: Self) {
OVERRIDES_ACTIVE.store(true, Ordering::Relaxed);
Self::with_overrides(|m| m.entry(addr).or_default().push(value));
}
fn override_pop(addr: usize) {
Self::with_overrides(|m| {
if let Some(stack) = m.get_mut(&addr) {
stack.pop();
if stack.is_empty() {
m.remove(&addr);
}
}
});
}
}
thread_local! {
static BOOL_OVERRIDES: core::cell::RefCell<std::collections::HashMap<usize, Vec<bool>>> =
core::cell::RefCell::new(std::collections::HashMap::new());
static INT_OVERRIDES: core::cell::RefCell<std::collections::HashMap<usize, Vec<i32>>> =
core::cell::RefCell::new(std::collections::HashMap::new());
}
impl FValueOverridable for bool {
fn with_overrides<R>(
f: impl FnOnce(&mut std::collections::HashMap<usize, Vec<Self>>) -> R,
) -> R {
BOOL_OVERRIDES.with(|c| f(&mut c.borrow_mut()))
}
}
impl FValueOverridable for i32 {
fn with_overrides<R>(
f: impl FnOnce(&mut std::collections::HashMap<usize, Vec<Self>>) -> R,
) -> R {
INT_OVERRIDES.with(|c| f(&mut c.borrow_mut()))
}
}
impl<T: FValueOverridable> FValue<T> {
pub fn push_test_override(&self, value: T) {
T::override_push(self as *const FValue<T> as usize, value);
}
pub fn pop_test_override(&self) {
T::override_pop(self as *const FValue<T> as usize);
}
}
pub struct FValue<T> {
pub(crate) value: UnsafeCell<T>,
pub(crate) dynamic: bool,
pub(crate) name: *const c_char,
pub(crate) next: UnsafeCell<*const FValue<T>>,
pub(crate) version: UnsafeCell<c_uint>,
}
unsafe impl<T: Sync> Sync for FValue<T> {}
pub fn set_luau_bool_flags(value: bool) {
unsafe {
let mut cur = <bool as FValueList>::head().load(Ordering::Relaxed) as *const FValue<bool>;
while !cur.is_null() {
let name = core::ffi::CStr::from_ptr((*cur).name);
if !name.to_bytes().starts_with(b"Debug") {
*(*cur).value.get() = value;
}
cur = *(*cur).next.get();
}
}
}
pub trait FValueList: Sized {
fn head() -> &'static AtomicPtr<FValue<Self>>;
}
static FVALUE_LIST_BOOL: AtomicPtr<FValue<bool>> = AtomicPtr::new(core::ptr::null_mut());
impl FValueList for bool {
fn head() -> &'static AtomicPtr<FValue<bool>> {
&FVALUE_LIST_BOOL
}
}
static FVALUE_LIST_INT: AtomicPtr<FValue<i32>> = AtomicPtr::new(core::ptr::null_mut());
impl FValueList for i32 {
fn head() -> &'static AtomicPtr<FValue<i32>> {
&FVALUE_LIST_INT
}
}
impl<T: Copy> FValue<T> {
pub fn set(&self, value: T) {
unsafe { *self.value.get() = value };
}
pub fn version(&self) -> c_uint {
unsafe { *self.version.get() }
}
pub(crate) fn set_version(&self, version: c_uint) {
unsafe { *self.version.get() = version };
}
}
impl<T: FValueList> FValue<T> {
pub unsafe fn register(&'static self) {
let head = T::head();
let old = head.load(Ordering::Relaxed);
*self.next.get() = old as *const FValue<T>;
head.store(
self as *const FValue<T> as *mut FValue<T>,
Ordering::Relaxed,
);
}
}
impl<T: FValueList + Copy + 'static> FValue<T> {
pub fn set_value_by_name(name: &str, value: T) {
unsafe {
let mut cur = T::head().load(Ordering::Relaxed) as *const FValue<T>;
while !cur.is_null() {
let fvalue = &*cur;
if core::ffi::CStr::from_ptr(fvalue.name).to_bytes() == name.as_bytes() {
*fvalue.value.get() = value;
}
cur = *fvalue.next.get();
}
}
}
pub fn set_all_unless(value: T, skip: impl Fn(&str) -> bool) {
unsafe {
let mut cur = T::head().load(Ordering::Relaxed) as *const FValue<T>;
while !cur.is_null() {
let fvalue = &*cur;
let name = core::ffi::CStr::from_ptr(fvalue.name)
.to_str()
.unwrap_or("");
if !skip(name) {
*fvalue.value.get() = value;
}
cur = *fvalue.next.get();
}
}
}
}