use std::cell::{Cell, Ref, RefCell};
use crate::StackIdx;
use crate::value::LuaValue;
#[derive(Debug, Clone)]
pub enum UpValState {
Open {
thread_id: usize,
idx: StackIdx,
},
Closed(LuaValue),
}
#[derive(Debug)]
pub struct UpVal {
open_thread_id: Cell<i64>,
open_idx: Cell<u32>,
closed_value: Cell<LuaValue>,
pub state: RefCell<UpValState>,
}
const CLOSED_TAG: i64 = -1;
impl UpVal {
pub fn open(thread_id: usize, idx: StackIdx) -> Self {
UpVal {
open_thread_id: Cell::new(thread_id as i64),
open_idx: Cell::new(idx.0),
closed_value: Cell::new(LuaValue::Nil),
state: RefCell::new(UpValState::Open { thread_id, idx }),
}
}
pub fn closed(v: LuaValue) -> Self {
UpVal {
open_thread_id: Cell::new(CLOSED_TAG),
open_idx: Cell::new(0),
closed_value: Cell::new(v),
state: RefCell::new(UpValState::Closed(v)),
}
}
pub fn slot(&self) -> Ref<'_, UpValState> { self.state.borrow() }
pub fn is_open(&self) -> bool { self.open_thread_id.get() >= 0 }
pub fn is_closed(&self) -> bool { self.open_thread_id.get() < 0 }
#[inline(always)]
pub fn try_open_payload(&self) -> Option<(usize, StackIdx)> {
let tid = self.open_thread_id.get();
if tid < 0 {
None
} else {
Some((tid as usize, StackIdx(self.open_idx.get())))
}
}
#[inline(always)]
pub fn closed_value(&self) -> LuaValue { self.closed_value.get() }
pub fn close_with(&self, v: LuaValue) {
self.open_thread_id.set(CLOSED_TAG);
self.open_idx.set(0);
self.closed_value.set(v);
*self.state.borrow_mut() = UpValState::Closed(v);
}
pub fn set_closed_value(&self, v: LuaValue) {
self.open_thread_id.set(CLOSED_TAG);
self.open_idx.set(0);
let old_collectable = self.closed_value.get().is_collectable();
self.closed_value.set(v);
if old_collectable || v.is_collectable() {
*self.state.borrow_mut() = UpValState::Closed(v);
}
}
pub fn try_closed_value(&self) -> Option<LuaValue> {
if self.is_closed() {
Some(self.closed_value.get())
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn closed_scalar_write_updates_canonical_value() {
let uv = UpVal::closed(LuaValue::Int(1));
uv.set_closed_value(LuaValue::Int(2));
assert_eq!(uv.closed_value(), LuaValue::Int(2));
assert_eq!(uv.try_closed_value(), Some(LuaValue::Int(2)));
match &*uv.slot() {
UpValState::Closed(v) => assert_eq!(*v, LuaValue::Int(1)),
UpValState::Open { .. } => panic!("closed upvalue mirror became open"),
};
}
#[test]
fn close_with_refreshes_legacy_mirror() {
let uv = UpVal::open(7, StackIdx(3));
uv.close_with(LuaValue::Bool(true));
assert_eq!(uv.closed_value(), LuaValue::Bool(true));
match &*uv.slot() {
UpValState::Closed(v) => assert_eq!(*v, LuaValue::Bool(true)),
UpValState::Open { .. } => panic!("closed upvalue mirror stayed open"),
};
}
}