use std::num::NonZeroU64;
use std::sync::atomic::{AtomicU64, Ordering};
use slotmap::{Key, KeyData, SlotMap, new_key_type};
use super::ArgCount;
use super::Error;
use super::ErrorKind;
use super::LuaType;
use super::Result;
use super::RetCount;
use super::State;
use super::TypeError;
use super::Val;
use super::object::{GcHeap, Markable, ObjectPtr};
new_key_type! {
pub(crate) struct AnchorKey;
}
static NEXT_STATE_ID: AtomicU64 = AtomicU64::new(1);
pub(crate) fn next_state_id() -> NonZeroU64 {
let id = NEXT_STATE_ID.fetch_add(1, Ordering::Relaxed);
NonZeroU64::new(id).expect("u64 state-id counter cannot wrap in a real process")
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Anchor {
state_id: NonZeroU64,
key: NonZeroU64,
}
impl Anchor {
fn new(state_id: NonZeroU64, key: AnchorKey) -> Self {
let ffi = key.data().as_ffi();
let key = NonZeroU64::new(ffi).expect("real slotmap keys are never zero");
Self { state_id, key }
}
fn slotmap_key(self) -> AnchorKey {
AnchorKey::from(KeyData::from_ffi(self.key.get()))
}
}
pub(crate) struct Registry {
state_id: NonZeroU64,
slots: SlotMap<AnchorKey, Val>,
}
impl Registry {
pub(crate) fn new(state_id: NonZeroU64) -> Self {
Self {
state_id,
slots: SlotMap::with_key(),
}
}
pub(crate) fn insert(&mut self, value: Val) -> Anchor {
let key = self.slots.insert(value);
Anchor::new(self.state_id, key)
}
pub(crate) fn get(&self, a: Anchor) -> Option<Val> {
if a.state_id != self.state_id {
return None;
}
self.slots.get(a.slotmap_key()).copied()
}
pub(crate) fn remove(&mut self, a: Anchor) -> bool {
if a.state_id != self.state_id {
return false;
}
self.slots.remove(a.slotmap_key()).is_some()
}
pub(crate) fn len(&self) -> usize {
self.slots.len()
}
#[cfg(feature = "snapshot")]
pub(crate) fn clear(&mut self) {
self.slots.clear();
}
}
impl Markable for Registry {
fn mark_reachable(&self, heap: &GcHeap, worklist: &mut Vec<ObjectPtr>) {
for val in self.slots.values() {
val.mark_reachable(heap, worklist);
}
}
}
impl State {
pub fn anchor(&mut self) -> Result<Anchor> {
let val = self.at_index(-1)?;
if matches!(val, Val::Nil) {
return Err(Error::without_location(ErrorKind::AnchorNil));
}
let anchor = self.registry.insert(val);
self.pop_val();
Ok(anchor)
}
pub fn anchor_at(&mut self, idx: isize) -> Result<Anchor> {
let val = self.at_index(idx)?;
if matches!(val, Val::Nil) {
return Err(Error::without_location(ErrorKind::AnchorNil));
}
Ok(self.registry.insert(val))
}
pub fn anchor_function(&mut self) -> Result<Anchor> {
let val = self.at_index(-1)?;
let typ = val.typ(&self.heap);
if typ != LuaType::Function {
return Err(self.type_error(TypeError::FunctionCall(typ)));
}
let anchor = self.registry.insert(val);
self.pop_val();
Ok(anchor)
}
pub fn anchor_function_at(&mut self, idx: isize) -> Result<Anchor> {
let val = self.at_index(idx)?;
let typ = val.typ(&self.heap);
if typ != LuaType::Function {
return Err(self.type_error(TypeError::FunctionCall(typ)));
}
Ok(self.registry.insert(val))
}
pub fn push_anchor(&mut self, a: Anchor) -> Result<()> {
match self.registry.get(a) {
Some(val) => self.push_val(val),
None => Err(Error::without_location(ErrorKind::InvalidAnchor)),
}
}
pub fn call_anchor(&mut self, a: Anchor, args: ArgCount, rets: RetCount) -> Result<()> {
let val = match self.registry.get(a) {
Some(val) => val,
None => return Err(Error::without_location(ErrorKind::InvalidAnchor)),
};
let n_args = match args {
ArgCount::Fixed(n) => n as usize,
ArgCount::Dynamic => {
return Err(Error::without_location(ErrorKind::InternalError(
"call_anchor does not support ArgCount::Dynamic; use ArgCount::Fixed".into(),
)));
}
};
let insert_at = self.stack.len().checked_sub(n_args).ok_or_else(|| {
Error::without_location(ErrorKind::InvalidStackIndex {
index: -(n_args as isize) - 1,
})
})?;
self.check_stack_space(1)?;
self.stack.insert(insert_at, val);
self.call(args, rets)
}
pub fn release_anchor(&mut self, a: Anchor) -> bool {
self.registry.remove(a)
}
pub fn anchor_type(&self, a: Anchor) -> Option<LuaType> {
self.registry.get(a).map(|val| val.typ(&self.heap))
}
pub fn anchor_count(&self) -> usize {
self.registry.len()
}
}