Initial commit

This commit is contained in:
Aleksandr 2024-10-15 19:03:39 +03:00
commit 85b08fe00b
29 changed files with 750 additions and 0 deletions

8
crates/std/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "shiny-std"
version = "0.1.0"
edition = "2021"
[dependencies]
fxhash.workspace = true
hashbrown.workspace = true

View file

@ -0,0 +1,147 @@
//! # Interning arena.
//!
//! Additionally, if same value was present on the arena, doesn't add new object, returns old.
use std::hash::{BuildHasherDefault, Hash};
use hashbrown::hash_map::{HashMap, RawEntryMut};
use super::{ptr::Ptr, wonly};
// No raw_entry API on hashset.
type Bi<P> = HashMap<Mapping<P>, (), BuildHasherDefault<hasher::NoHash>>;
#[derive(Debug, Default, Clone, Copy)]
struct Mapping<P> {
hash: u64,
key: P,
}
impl<P> Hash for Mapping<P> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
// [`hasher::NoHash`] implementation ensures that
// hasher stores `write_u64` result only once.
state.write_u64(self.hash);
}
}
#[derive(Clone)]
pub struct Arena<T, P> {
objects: wonly::Arena<T, P>,
bi: Bi<P>,
}
impl<T, P> Default for Arena<T, P> {
fn default() -> Self {
Self {
objects: wonly::Arena::new(),
bi: Bi::default(),
}
}
}
impl<T, P> Arena<T, P> {
pub fn modify(&mut self, ptr: P, f: impl FnOnce(&mut T)) -> bool
where
T: Hash + Eq,
P: Ptr,
{
let Some(object) = self.objects.get(ptr.clone()) else {
return false;
};
let hash = fxhash::hash64(object);
let entry = self
.bi
.raw_entry_mut()
.from_hash(hash, |m| self.objects.get(m.key.clone()) == Some(object));
// SAFETY: if we got here, the object is in arena.
let object = unsafe { self.objects.get_mut(ptr.clone()).unwrap_unchecked() };
f(object);
let hash = fxhash::hash64(object);
entry.and_modify(|k, _| *k = Mapping { hash, key: ptr });
true
}
pub fn get(&self, ptr: P) -> Option<&T>
where
P: Ptr,
{
self.objects.get(ptr)
}
pub fn insert(&mut self, object: T) -> P
where
P: Ptr,
T: Hash + Eq,
{
let hash = fxhash::hash64(&object);
match self.bi.raw_entry_mut().from_hash(hash, |m| {
self.objects
.get(m.key.clone())
.map_or(false, |lhs| lhs == &object)
}) {
RawEntryMut::Occupied(occup) => occup.into_key().key.clone(),
RawEntryMut::Vacant(vacant) => {
let key = self.objects.insert(object);
vacant.insert_hashed_nocheck(
hash,
Mapping {
hash,
key: key.clone(),
},
(),
);
key
}
}
}
pub fn new() -> Self {
Self::default()
}
}
mod hasher;
#[cfg(test)]
mod tests {
use super::*;
use crate::arena::ptr::define;
define! {
struct Token;
}
#[test]
fn modification_works() {
// Ensure that when modifying the value,
// pointer is still valid.
let mut arena: Arena<u32, Token> = Arena::new();
let key100to200 = arena.insert(100);
let modified = arena.modify(key100to200, |x| *x = 200);
assert!(modified);
assert_eq!(arena.get(key100to200), Some(&200));
let key100new = arena.insert(100);
assert_ne!(key100to200, key100new);
assert_eq!(arena.get(key100new), Some(&100));
assert_eq!(arena.get(key100to200), Some(&200));
}
#[test]
fn it_interns() {
let mut arena: Arena<u32, Token> = Arena::new();
let key100 = arena.insert(100);
let key100again = arena.insert(100);
assert_eq!(key100, key100again);
}
}

View file

@ -0,0 +1,22 @@
use std::hash::Hasher;
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub struct NoHash(Option<u64>);
impl Hasher for NoHash {
fn write_u64(&mut self, i: u64) {
if self.0.is_some() {
panic!("called write_u64 twice")
} else {
self.0 = Some(i);
}
}
fn write(&mut self, _: &[u8]) {
unreachable!()
}
fn finish(&self) -> u64 {
self.0.unwrap()
}
}

View file

@ -0,0 +1,20 @@
/// Combination of pointers.
#[macro_export]
#[doc(hidden)]
macro_rules! _make_comb {
($(
$item:item
)*) => {$(
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
)]
$item
)*};
}

View file

@ -0,0 +1,8 @@
pub mod intern;
pub mod wonly;
pub mod ptr;
pub use crate::_make_comb as make_comb;
mod macros;

View file

@ -0,0 +1,59 @@
use std::{hash::Hash, num::NonZeroU32};
#[macro_export]
#[doc(hidden)]
macro_rules! _ptrs {
($(
$(#[$outer:meta])*
$vis:vis struct $name:ident;
)*) => {$(
$(#[$outer])*
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
)]
$vis struct $name(::core::num::NonZeroU32);
impl $name {
/// Create instance of the pointer.
pub const fn new(v: ::core::num::NonZeroU32) -> Self {
Self(v)
}
pub const fn get(&self) -> ::core::num::NonZeroU32 {
self.0
}
}
impl ::core::convert::From<$name> for ::core::num::NonZeroU32 {
fn from(value: $name) -> Self {
value.get()
}
}
impl ::core::convert::From<::core::num::NonZeroU32> for $name {
fn from(value: ::core::num::NonZeroU32) -> Self {
Self::new(value)
}
}
impl $crate::arena::ptr::Ptr for $name {
fn get(&self) -> ::core::num::NonZeroU32 {
self.0
}
}
)*};
}
/// Pointer in arena.
pub trait Ptr: Copy + Eq + Into<NonZeroU32> + From<NonZeroU32> + Hash {
fn get(&self) -> NonZeroU32;
}
pub use crate::_ptrs as define;

View file

@ -0,0 +1,60 @@
//! # Write-only arena, without deletion
use std::{marker::PhantomData, num::NonZeroU32};
use super::ptr::Ptr;
#[derive(Clone)]
pub struct Arena<T, P> {
mem: Vec<T>,
_ptr_ty: PhantomData<P>,
}
fn calc_idx(v: NonZeroU32) -> usize {
v.get() as usize - 1
}
impl<T, P> Arena<T, P> {
pub fn get_mut(&mut self, ptr: P) -> Option<&mut T>
where
P: Ptr,
{
self.mem.get_mut(calc_idx(ptr.into()))
}
pub fn get(&self, ptr: P) -> Option<&T>
where
P: Ptr,
{
self.mem.get(calc_idx(ptr.into()))
}
pub fn insert(&mut self, obj: T) -> P
where
P: Ptr,
{
let idx = self.mem.len();
self.mem.push(obj);
idx.checked_add(1)
.and_then(|v| v.try_into().ok())
.and_then(NonZeroU32::new)
.expect("shit happens")
.into()
}
}
impl<T, P> Default for Arena<T, P> {
fn default() -> Self {
Self::new()
}
}
impl<T, P> Arena<T, P> {
pub const fn new() -> Self {
Self {
mem: Vec::new(),
_ptr_ty: PhantomData,
}
}
}

View file

@ -0,0 +1,6 @@
use std::hash::BuildHasherDefault;
use fxhash::FxHasher;
pub type HashMap<K, V> = hashbrown::HashMap<K, V, BuildHasherDefault<FxHasher>>;
pub type HashSet<V> = hashbrown::HashSet<V, BuildHasherDefault<FxHasher>>;

3
crates/std/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod collections;
pub mod arena;