use rustc_hash::FxHashMap as HashMap;
use crate::entity_table::EntityTable;
use crate::object::{EntityId, NameId, ObjFlags, PsObject};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum DictKey {
Name(NameId),
Int(i64),
Real(u64), Bool(bool),
String(Vec<u8>), Operator(u16), Identity(u32, u32, u32),
}
pub struct DictEntry {
pub max_length: usize,
pub entries: HashMap<DictKey, PsObject>,
pub access: u8,
pub name: Vec<u8>,
}
pub struct DictStore {
dicts: Vec<DictEntry>,
pub entities: EntityTable,
}
impl DictStore {
pub fn new() -> Self {
Self {
dicts: Vec::new(),
entities: EntityTable::new(),
}
}
pub fn dict_slots(&self) -> usize {
self.dicts.len()
}
pub fn truncate_to(&mut self, dicts_len: usize, entity_len: usize) {
self.dicts.truncate(dicts_len);
self.entities.truncate(entity_len);
}
pub fn allocate(&mut self, max_length: usize, name: &[u8]) -> EntityId {
let index = self.dicts.len() as u32;
self.dicts.push(DictEntry {
max_length,
entries: HashMap::with_capacity_and_hasher(max_length.min(64), Default::default()),
access: ObjFlags::ACCESS_UNLIMITED,
name: name.to_vec(),
});
self.entities.allocate(index, 0, 0, false, 0)
}
pub fn allocate_with(
&mut self,
max_length: usize,
name: &[u8],
save_level: u16,
global: bool,
created_after_save: u32,
) -> EntityId {
let index = self.dicts.len() as u32;
self.dicts.push(DictEntry {
max_length,
entries: HashMap::with_capacity_and_hasher(max_length.min(64), Default::default()),
access: ObjFlags::ACCESS_UNLIMITED,
name: name.to_vec(),
});
self.entities
.allocate(index, 0, save_level, global, created_after_save)
}
fn dict_index(&self, entity: EntityId) -> usize {
self.entities.get(entity).offset as usize
}
pub fn iter_entities(&self) -> impl Iterator<Item = (EntityId, &DictEntry)> {
(0..self.entities.len()).map(|i| {
let id = if self.entities.get_by_index(i).is_global() {
EntityId::global(i as u32)
} else {
EntityId::local(i as u32)
};
(id, &self.dicts[self.dict_index(id)])
})
}
#[inline]
pub fn get(&self, entity: EntityId, key: &DictKey) -> Option<PsObject> {
self.dicts[self.dict_index(entity)]
.entries
.get(key)
.copied()
}
pub fn put(&mut self, entity: EntityId, key: DictKey, value: PsObject) {
let idx = self.dict_index(entity);
self.dicts[idx].entries.insert(key, value);
}
pub fn known(&self, entity: EntityId, key: &DictKey) -> bool {
self.dicts[self.dict_index(entity)]
.entries
.contains_key(key)
}
pub fn get_name(&self, entity: EntityId) -> &[u8] {
&self.dicts[self.dict_index(entity)].name
}
pub fn set_name(&mut self, entity: EntityId, name: &[u8]) {
let idx = self.dict_index(entity);
self.dicts[idx].name.clear();
self.dicts[idx].name.extend_from_slice(name);
}
pub fn length(&self, entity: EntityId) -> usize {
self.dicts[self.dict_index(entity)].entries.len()
}
pub fn max_length(&self, entity: EntityId) -> usize {
self.dicts[self.dict_index(entity)].max_length
}
pub fn remove(&mut self, entity: EntityId, key: &DictKey) {
let idx = self.dict_index(entity);
self.dicts[idx].entries.remove(key);
}
pub fn entry(&self, entity: EntityId) -> &DictEntry {
&self.dicts[self.dict_index(entity)]
}
pub fn entry_mut(&mut self, entity: EntityId) -> &mut DictEntry {
let idx = self.dict_index(entity);
&mut self.dicts[idx]
}
pub fn keys(&self, entity: EntityId) -> impl Iterator<Item = &DictKey> {
self.dicts[self.dict_index(entity)].entries.keys()
}
pub fn access(&self, entity: EntityId) -> u8 {
self.dicts[self.dict_index(entity)].access
}
#[inline]
pub fn require_read(&self, entity: EntityId) -> Result<(), crate::error::PsError> {
if self.access(entity) >= ObjFlags::ACCESS_READ_ONLY {
Ok(())
} else {
Err(crate::error::PsError::InvalidAccess)
}
}
#[inline]
pub fn require_write(&self, entity: EntityId) -> Result<(), crate::error::PsError> {
if self.access(entity) >= ObjFlags::ACCESS_UNLIMITED {
Ok(())
} else {
Err(crate::error::PsError::InvalidAccess)
}
}
pub fn set_access(&mut self, entity: EntityId, access: u8) {
let idx = self.dict_index(entity);
self.dicts[idx].access = access;
}
pub fn cow_copy(&mut self, entity: EntityId) -> EntityId {
let idx = self.dict_index(entity);
let meta = self.entities.get(entity);
let save_level = meta.save_level;
let is_global = meta.is_global();
let created_after_save = meta.created_after_save;
let orig = &self.dicts[idx];
let copy = DictEntry {
max_length: orig.max_length,
entries: orig.entries.clone(),
access: orig.access,
name: orig.name.clone(),
};
let new_index = self.dicts.len() as u32;
self.dicts.push(copy);
let backup_id = self.entities.allocate(
idx as u32, 0,
save_level,
is_global,
created_after_save,
);
self.entities.get_mut(backup_id).set_cow_backup();
self.entities.get_mut(entity).offset = new_index;
backup_id
}
pub fn swap_offsets(&mut self, a: EntityId, b: EntityId) {
let off_a = self.entities.get(a).offset;
let off_b = self.entities.get(b).offset;
self.entities.get_mut(a).offset = off_b;
self.entities.get_mut(b).offset = off_a;
}
}
impl Default for DictStore {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dict_basic() {
let mut store = DictStore::new();
let d = store.allocate(10, b"testdict");
let key = DictKey::Name(NameId(0));
assert!(!store.known(d, &key));
store.put(d, key.clone(), PsObject::int(42));
assert!(store.known(d, &key));
assert_eq!(store.get(d, &key).unwrap().as_i32(), Some(42));
assert_eq!(store.length(d), 1);
assert_eq!(store.max_length(d), 10);
store.remove(d, &key);
assert!(!store.known(d, &key));
}
#[test]
fn test_multiple_dicts() {
let mut store = DictStore::new();
let d1 = store.allocate(10, b"dict1");
let d2 = store.allocate(10, b"dict2");
let key = DictKey::Int(1);
store.put(d1, key.clone(), PsObject::int(100));
store.put(d2, key.clone(), PsObject::int(200));
assert_eq!(store.get(d1, &key).unwrap().as_i32(), Some(100));
assert_eq!(store.get(d2, &key).unwrap().as_i32(), Some(200));
}
#[test]
fn test_cow_copy() {
let mut store = DictStore::new();
let d = store.allocate(10, b"test");
let key = DictKey::Name(NameId(0));
store.put(d, key.clone(), PsObject::int(42));
let backup = store.cow_copy(d);
store.put(d, key.clone(), PsObject::int(99));
assert_eq!(store.get(d, &key).unwrap().as_i32(), Some(99));
assert_eq!(store.get(backup, &key).unwrap().as_i32(), Some(42));
}
#[test]
fn test_swap_offsets() {
let mut store = DictStore::new();
let d1 = store.allocate(10, b"a");
let d2 = store.allocate(10, b"b");
let key = DictKey::Int(0);
store.put(d1, key.clone(), PsObject::int(1));
store.put(d2, key.clone(), PsObject::int(2));
store.swap_offsets(d1, d2);
assert_eq!(store.get(d1, &key).unwrap().as_i32(), Some(2));
assert_eq!(store.get(d2, &key).unwrap().as_i32(), Some(1));
}
}