use crate::{JsData, JsValue, object::JsObject};
use boa_gc::{Finalize, Trace, custom_trace};
use indexmap::{Equivalent, IndexMap};
use std::{
fmt::Debug,
hash::{Hash, Hasher},
};
#[derive(PartialEq, Eq, Clone, Debug)]
pub(crate) enum MapKey {
Key(JsValue),
Empty(usize), }
impl Hash for MapKey {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Key(v) => v.hash(state),
Self::Empty(e) => e.hash(state),
}
}
}
impl Equivalent<MapKey> for JsValue {
fn equivalent(&self, key: &MapKey) -> bool {
match key {
MapKey::Key(v) => v == self,
MapKey::Empty(_) => false,
}
}
}
#[derive(Clone, Finalize, JsData)]
pub struct OrderedMap<V> {
map: IndexMap<MapKey, Option<V>>,
lock: u32,
empty_count: usize,
}
unsafe impl<V: Trace> Trace for OrderedMap<V> {
custom_trace!(this, mark, {
for (k, v) in &this.map {
if let MapKey::Key(key) = k {
mark(key);
}
mark(v);
}
});
}
impl<V: Debug> Debug for OrderedMap<V> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
self.map.fmt(formatter)
}
}
impl<V> Default for OrderedMap<V> {
fn default() -> Self {
Self::new()
}
}
impl<V> OrderedMap<V> {
#[must_use]
pub fn new() -> Self {
Self {
map: IndexMap::new(),
lock: 0,
empty_count: 0,
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
map: IndexMap::with_capacity(capacity),
lock: 0,
empty_count: 0,
}
}
#[must_use]
pub fn full_len(&self) -> usize {
self.map.len()
}
#[must_use]
pub fn len(&self) -> usize {
self.map.len() - self.empty_count
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn insert(&mut self, key: JsValue, value: V) -> Option<V> {
self.map.insert(MapKey::Key(key), Some(value)).flatten()
}
pub fn remove(&mut self, key: &JsValue) -> Option<V> {
if self.lock == 0 {
self.map.shift_remove(key).flatten()
} else if self.map.contains_key(key) {
self.map.insert(MapKey::Empty(self.empty_count), None);
self.empty_count += 1;
self.map.swap_remove(key).flatten()
} else {
None
}
}
pub fn clear(&mut self) {
self.map.clear();
self.map.shrink_to_fit();
self.empty_count = 0;
}
pub fn get(&self, key: &JsValue) -> Option<&V> {
self.map.get(key).and_then(Option::as_ref)
}
#[must_use]
pub fn get_index(&self, index: usize) -> Option<(&JsValue, &V)> {
if let (MapKey::Key(key), Some(value)) = self.map.get_index(index)? {
Some((key, value))
} else {
None
}
}
pub fn iter(&self) -> impl Iterator<Item = (&JsValue, &V)> {
self.map.iter().filter_map(|o| {
if let (MapKey::Key(key), Some(value)) = o {
Some((key, value))
} else {
None
}
})
}
#[must_use]
pub fn contains_key(&self, key: &JsValue) -> bool {
self.map.contains_key(key)
}
pub(crate) fn lock(&mut self, map: JsObject) -> MapLock {
self.lock += 1;
MapLock(map)
}
fn unlock(&mut self) {
self.lock -= 1;
if self.lock == 0 {
self.map.retain(|k, _| matches!(k, MapKey::Key(_)));
self.empty_count = 0;
}
}
}
#[derive(Debug, Trace)]
pub(crate) struct MapLock(JsObject);
impl Finalize for MapLock {
fn finalize(&self) {
self.0
.downcast_mut::<OrderedMap<JsValue>>()
.expect("MapLock does not point to a map")
.unlock();
}
}