diff --git a/Cargo.toml b/Cargo.toml index 6ad90f8..a108cd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,10 @@ edition = "2021" [dependencies] once_cell = "1" parking_lot = "0.12" -read-write-api = "0.17" serde = { version = "1", optional = true, features = ["derive"] } [dev-dependencies] +serde_json = "1" static_assertions = "1" [features] diff --git a/README.md b/README.md index 0ca21d3..2c5b276 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,4 @@ # blazemap -_Provides a wrapper for replacing a small number of clumsy objects with identifiers, and also implements a vector-based -slab-like map with an interface similar to that of `HashMap`._ - -Let's imagine that at runtime you create a small number of clumsy objects that are used as keys in hashmaps. - -This crate allows you to seamlessly replace them with lightweight identifiers in a slab-like manner -using the `register_blazemap_id_wrapper` macro as well as using them as keys in -the `BlazeMap` -— a vector-based slab-like map with an interface similar to that of `HashMap`. - -You can also use the `register_blazemap_id` macro if you want to create a new type based on `usize` -that is generated incrementally to use as such a key. - -# No-brain vs `blazemap` approach - -The logic behind the `register_blazemap_id_wrapper` macro is shown below. - -## Standard no-brain approach - -![OldApproach.svg](./docs/drawio/README_-_OldApproach.svg) - -```rust -let clumsy = MassiveStruct::new(); -let mut map = HashMap::new(); -map.insert(clumsy, "clumsy") // Too inefficient -``` - -## `blazemap` approach - -```rust -use blazemap::prelude::{BlazeMap, register_blazemap_id_wrapper}; - -register_blazemap_id_wrapper! { - struct Id(MassiveStruct) -} - -let clumsy = MassiveStruct::new(); -let clumsy_id = Id::new(clumsy); - -let mut map = BlazeMap::new(); -map.insert(clumsy_id, "clumsy") // Very efficient -``` - -![NewApproach.svg](./docs/drawio/README_-_NewApproach.svg) - -# Type-generating macros - -## `register_blazemap_id_wrapper` - -Creates a new type that acts as an `usize`-based replacement for the old type -that can be used as a key for `blazemap` collections. - -This macro supports optional inference of standard traits using the following syntax: - -* `Derive(as for Original Type)` — derives traits as for the original type - for which `blazemap` ID is being registered. Each call to methods on these traits - requires an additional `.read` call on the internal synchronization primitive, - so — all other things being equal — their calls may be less optimal - than the corresponding calls on instances of the original key's type. - This method supports inference of the following traits: - * `Default` - * `PartialOrd` (mutually exclusive with `Ord`) - * `Ord` (also derives `PartialOrd`, so mutually exclusive with `PartialOrd`) - * `Debug` - * `Display` - * `Serialize` (with `serde` feature only) - * `Deserialize` (with `serde` feature only) -* `Derive(as for Serial Number)` — derives traits in the same way as for - the serial number assigned when registering an instance of the original type - the first time [`IdWrapper::new`](crate::prelude::KeyWrapper::new) was called. - Because methods inferred by this option do not require additional - locking on synchronization primitives, - they do not incur any additional overhead compared to methods inferred for plain `usize`. - This method supports inference of the following traits: - * `PartialOrd` (mutually exclusive with `Ord`) - * `Ord` (also derives `PartialOrd`, so mutually exclusive with `PartialOrd`) - -### Example - -```rust -use blazemap::prelude::{BlazeMap, register_blazemap_id_wrapper}; - -register_blazemap_id_wrapper! { - pub struct Key(String); - Derive(as for Original Type): { // Optional section - Debug, - Display, - }; - Derive(as for Serial Number): { // Optional section - Ord, - } -} - -let key_1 = Key::new("first".to_string()); -let key_2 = Key::new("second".to_string()); -let key_3 = Key::new("third".to_string()); - -let mut map = BlazeMap::new(); -map.insert(key_2, "2"); -map.insert(key_1, "1"); -map.insert(key_3, "3"); - -assert_eq!(format!("{map:?}"), r#"{"first": "1", "second": "2", "third": "3"}"#) -``` - -## `register_blazemap_id` - -Creates a new type based on incrementally generated `usize` instances -that can be used as a key for `blazemap` collections. - -This macro supports optional inference of standard traits using the following syntax: - -* `Derive` — derives traits in the same way as for - the serial number assigned when creating a new instance of the type. - Because methods inferred by this option do not require additional - locking on synchronization primitives, - they do not incur any additional overhead compared to methods inferred for plain `usize`. - This method supports inference of the following traits: - * `PartialOrd` (mutually exclusive with `Ord`) - * `Ord` (also derives `PartialOrd`, so mutually exclusive with `PartialOrd`) - * `Serialize` (with `serde` feature only) - -### Example - -```rust -use blazemap::prelude::{BlazeMap, register_blazemap_id}; - -register_blazemap_id! { - pub struct Id(start from: 1); // "(start from: number)" is optional - Derive: { // Derive section is also optional - Ord - }; -} - -let key_1 = Id::new(); -let key_2 = Id::new(); -let key_3 = Id::new(); - -let mut map = BlazeMap::new(); -map.insert(key_2, "2"); -map.insert(key_1, "1"); -map.insert(key_3, "3"); - -assert_eq!(format!("{map:?}"), r#"{1: "1", 2: "2", 3: "3"}"#) -``` \ No newline at end of file +_Implements a vector-based slab-like map with an interface similar to that of `HashMap`, +and also provides tools for generating lightweight identifiers that can be type-safely used as keys for this map._ \ No newline at end of file diff --git a/src/blazemap_id.rs b/src/blazemap_id.rs index 58f1052..a138d14 100644 --- a/src/blazemap_id.rs +++ b/src/blazemap_id.rs @@ -1,29 +1,20 @@ +use std::borrow::Borrow; use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; -use std::ops::Range; - -use read_write_api::ReadApi; - -use crate::orig_type_id_map::StaticInfoApi; +use std::ops::{Deref, Range}; /// Provides interface for `blazemap` id types -/// defined by the [`register_blazemap_id_wrapper`](crate::register_blazemap_id_wrapper) -/// and [`register_blazemap_id`](crate::register_blazemap_id) macros. +/// defined by the [`define_key_wrapper`](crate::define_key_wrapper) +/// and [`define_plain_id`](crate::define_plain_id) macros. pub trait BlazeMapId: Copy { /// Original key type. type OrigType: 'static + Clone + Eq + Hash; - #[doc(hidden)] - type StaticInfoApi: 'static + StaticInfoApi; - - #[doc(hidden)] - type StaticInfoApiLock: ReadApi; - /// Creates an iterator over all identifiers registered at the time this method was called. #[inline] fn all_instances_iter() -> AllInstancesIter { - let num_elems = Self::static_info().read().num_elems(); + let num_elems = Self::capacity_info_provider().number_of_register_keys(); AllInstancesIter { range: 0..num_elems, phantom: Default::default(), @@ -37,16 +28,33 @@ pub trait BlazeMapId: Copy { unsafe fn from_index_unchecked(index: usize) -> Self; #[doc(hidden)] - fn static_info() -> Self::StaticInfoApiLock; + fn capacity_info_provider() -> impl Deref; + + #[doc(hidden)] + fn key_by_index_provider() -> impl Deref>; } /// Provides interface for constructable `blazemap` key-wrapper types -/// defined by the [`register_blazemap_id_wrapper`](crate::register_blazemap_id_wrapper) macro. +/// defined by the [`define_key_wrapper`](crate::define_key_wrapper) macro. pub trait BlazeMapIdWrapper: BlazeMapId { /// Creates a new instance of [`Self`] based on the [`Self::OrigType`](BlazeMapId::OrigType) instance. fn new(key: Self::OrigType) -> Self; } +#[doc(hidden)] +pub trait CapacityInfoProvider { + fn number_of_register_keys(&self) -> usize; +} + +#[doc(hidden)] +pub trait KeyByIndexProvider { + type KeyUnchecked<'a>: Borrow + where + Self: 'a; + + unsafe fn key_by_index_unchecked(&self, index: usize) -> Self::KeyUnchecked<'_>; +} + /// Iterator over consecutive `blazemap` identifiers. pub struct AllInstancesIter { range: Range, diff --git a/src/collections/blazemap.rs b/src/collections/blazemap.rs index 005fe1e..4f25971 100644 --- a/src/collections/blazemap.rs +++ b/src/collections/blazemap.rs @@ -2,8 +2,6 @@ use std::borrow::Borrow; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; -use read_write_api::ReadApi; - #[cfg(feature = "serde")] use { crate::prelude::BlazeMapIdWrapper, @@ -15,12 +13,13 @@ use { }; use crate::blazemap_id::BlazeMapId; +use crate::blazemap_id::CapacityInfoProvider; +use crate::blazemap_id::KeyByIndexProvider; use crate::collections::blazemap::entry::VacantEntryInner; pub use crate::collections::blazemap::{ entry::{Entry, OccupiedEntry, VacantEntry}, iter::{Drain, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys, Values, ValuesMut}, }; -use crate::orig_type_id_map::StaticInfoApi; mod entry; mod iter; @@ -143,7 +142,7 @@ where /// with capacity equal to the current total number of unique `K` instances. #[inline] pub fn with_current_key_wrapper_capacity() -> Self { - let current_capacity = K::static_info().read().num_elems(); + let current_capacity = K::capacity_info_provider().number_of_register_keys(); Self { inner: Vec::with_capacity(current_capacity), len: 0, @@ -312,15 +311,14 @@ impl Default for BlazeMap { macro_rules! blaze_map_orig_key_blocking_iter { ($self:ident, $iter:ident, $guard:ident) => { - let $guard = K::static_info(); - let $guard = $guard.read(); + let $guard = K::key_by_index_provider(); let $iter = $self .inner .iter() .enumerate() .filter_map(|(idx, value)| Some((idx, value.as_ref()?))) .map(|(idx, value)| { - let key = unsafe { $guard.get_key_unchecked(idx) }; + let key = unsafe { $guard.key_by_index_unchecked(idx) }; (key, value) }); }; diff --git a/src/collections/blazemap/iter.rs b/src/collections/blazemap/iter.rs index 682bada..393c43d 100644 --- a/src/collections/blazemap/iter.rs +++ b/src/collections/blazemap/iter.rs @@ -4,10 +4,8 @@ use std::marker::PhantomData; use std::mem::needs_drop; use std::panic::{RefUnwindSafe, UnwindSafe}; -use read_write_api::ReadApi; - +use crate::blazemap_id::KeyByIndexProvider; use crate::collections::blazemap::BlazeMap; -use crate::orig_type_id_map::StaticInfoApi; use crate::prelude::BlazeMapId; /// An iterator over the entries of a [`BlazeMap`]. @@ -514,11 +512,10 @@ where { #[inline] fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - let static_info = K::static_info(); - let static_info = static_info.read(); + let key_provider = K::key_by_index_provider(); let mut debug_map = f.debug_map(); for (key, value) in self.into_iter() { - let key = unsafe { static_info.get_key_unchecked(key.get_index()) }; + let key = unsafe { key_provider.key_by_index_unchecked(key.get_index()) }; debug_map.entry(key.borrow(), value); } debug_map.finish() @@ -574,11 +571,10 @@ where { #[inline] fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - let static_info = K::static_info(); - let static_info = static_info.read(); + let key_provider = K::key_by_index_provider(); let mut debug_list = f.debug_list(); for key in self.into_iter() { - let key = unsafe { static_info.get_key_unchecked(key.get_index()) }; + let key = unsafe { key_provider.key_by_index_unchecked(key.get_index()) }; debug_list.entry(key.borrow()); } debug_list.finish() diff --git a/src/lib.rs b/src/lib.rs index 425e800..e3427d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,15 @@ -//! Provides a [wrapper](register_blazemap_id_wrapper) for replacing a small number +//! Provides a [wrapper](define_key_wrapper) for replacing a small number //! of clumsy objects with identifiers, //! and also implements a [vector-based slab-like map](prelude::BlazeMap) //! with an interface similar to that of [`HashMap`](std::collections::HashMap). -mod blazemap_id; +#[doc(hidden)] +pub mod blazemap_id; /// Collection types. pub mod collections; -mod macros; #[doc(hidden)] -pub mod orig_type_id_map; +pub mod static_containers; +mod type_gen; #[doc(hidden)] pub mod utils; @@ -17,7 +18,7 @@ pub mod prelude { pub use crate::{ blazemap_id::{AllInstancesIter, BlazeMapId, BlazeMapIdWrapper}, collections::blazemap::BlazeMap, - register_blazemap_id, register_blazemap_id_wrapper, + define_key_wrapper, define_plain_id, }; } @@ -26,18 +27,20 @@ pub mod external { #[cfg(feature = "serde")] pub use serde; - pub use {once_cell, parking_lot, read_write_api}; + pub use {once_cell, parking_lot}; } #[cfg(test)] mod tests { - use crate::{register_blazemap_id, register_blazemap_id_wrapper}; + use crate::prelude::BlazeMapId; + use crate::{define_key_wrapper, define_plain_id}; #[cfg(feature = "serde")] mod serde_compatible { - use crate::{register_blazemap_id, register_blazemap_id_wrapper}; + use crate::blazemap_id::BlazeMapId; + use crate::{define_key_wrapper, define_plain_id}; - register_blazemap_id_wrapper! { + define_key_wrapper! { pub struct BlazeMapKeyExample(usize); Derive(as for Original Type): { Default, @@ -49,16 +52,26 @@ mod tests { } } - register_blazemap_id! { + define_plain_id! { pub struct BlazeMapIdExample; Derive: { Ord, Serialize } } + + #[test] + fn plain_id() { + let first = BlazeMapIdExample::new(); + let second = BlazeMapIdExample::new(); + assert_eq!(first.get_index(), 0); + assert_eq!(second.get_index(), 1); + assert_eq!(serde_json::ser::to_string(&first).unwrap(), "0"); + assert_eq!(serde_json::ser::to_string(&second).unwrap(), "1") + } } - register_blazemap_id_wrapper! { + define_key_wrapper! { pub struct BlazeMapKeyExample(usize); Derive(as for Original Type): { Default, @@ -68,10 +81,18 @@ mod tests { } } - register_blazemap_id! { + define_plain_id! { pub struct BlazeMapIdExample; Derive: { Ord } } + + #[test] + fn plain_id() { + let first = BlazeMapIdExample::new(); + let second = BlazeMapIdExample::new(); + assert_eq!(first.get_index(), 0); + assert_eq!(second.get_index(), 1) + } } diff --git a/src/orig_type_id_map.rs b/src/orig_type_id_map.rs deleted file mode 100644 index 0b4cb23..0000000 --- a/src/orig_type_id_map.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::borrow::Borrow; - -#[doc(hidden)] -pub trait StaticInfoApi { - type KeyUnchecked<'a>: Borrow - where - Self: 'a; - - fn num_elems(&self) -> usize; - - unsafe fn get_key_unchecked(&self, index: usize) -> Self::KeyUnchecked<'_>; -} diff --git a/src/static_containers.rs b/src/static_containers.rs new file mode 100644 index 0000000..d6f8af8 --- /dev/null +++ b/src/static_containers.rs @@ -0,0 +1,4 @@ +pub use {key_wrapper::KeyWrapperStaticContainer, plain_id::PlainIdStaticContainer}; + +mod key_wrapper; +mod plain_id; diff --git a/src/static_containers/key_wrapper.rs b/src/static_containers/key_wrapper.rs new file mode 100644 index 0000000..20797c3 --- /dev/null +++ b/src/static_containers/key_wrapper.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; +use std::hash::Hash; + +use once_cell::sync::Lazy; + +use crate::blazemap_id::{CapacityInfoProvider, KeyByIndexProvider}; + +/// Global, statically initialized container with correspondence mapping +/// between blazemap index wrappers and original keys. +#[doc(hidden)] +#[derive(Debug)] +pub struct KeyWrapperStaticContainer { + index_to_orig: Vec, + orig_to_index: Lazy>, +} + +impl KeyWrapperStaticContainer { + /// Creates a new instance of [`KeyWrapperStaticContainer`]. + #[inline] + pub const fn new() -> Self { + Self { + index_to_orig: vec![], + orig_to_index: Lazy::new(Default::default), + } + } +} + +impl KeyWrapperStaticContainer +where + K: Clone + Eq + Hash, +{ + #[inline] + pub fn get_index(&self, key: &K) -> Option { + self.orig_to_index.get(key).copied() + } + + #[inline] + pub unsafe fn insert_new_key_unchecked(&mut self, key: K) -> usize { + let Self { + index_to_orig, + orig_to_index, + } = self; + let next_id = index_to_orig.len(); + index_to_orig.push(key.clone()); + orig_to_index.insert(key, next_id); + next_id + } +} + +impl CapacityInfoProvider for KeyWrapperStaticContainer { + #[inline] + fn number_of_register_keys(&self) -> usize { + self.index_to_orig.len() + } +} + +impl KeyByIndexProvider for KeyWrapperStaticContainer { + type KeyUnchecked<'a> = &'a K + where Self: 'a; + + #[inline] + unsafe fn key_by_index_unchecked(&self, index: usize) -> Self::KeyUnchecked<'_> { + self.index_to_orig.get_unchecked(index) + } +} diff --git a/src/static_containers/plain_id.rs b/src/static_containers/plain_id.rs new file mode 100644 index 0000000..358f721 --- /dev/null +++ b/src/static_containers/plain_id.rs @@ -0,0 +1,46 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +use crate::blazemap_id::{CapacityInfoProvider, KeyByIndexProvider}; + +#[doc(hidden)] +#[derive(Debug)] +pub struct PlainIdStaticContainer { + next_id: AtomicUsize, +} + +impl PlainIdStaticContainer { + /// Creates a new instance of [`PlainIdStaticContainer`]. + #[inline] + pub const fn new(first_id: usize) -> Self { + Self { + next_id: AtomicUsize::new(first_id), + } + } + + /// Returns the next identifier. + #[inline] + pub fn next_id(&self) -> usize { + self.next_id + .fetch_update(Ordering::Release, Ordering::Acquire, |next_id| { + next_id.checked_add(1) + }) + .expect("usize overflow") + } +} + +impl CapacityInfoProvider for PlainIdStaticContainer { + #[inline] + fn number_of_register_keys(&self) -> usize { + self.next_id.load(Ordering::Acquire) + } +} + +impl KeyByIndexProvider for PlainIdStaticContainer { + type KeyUnchecked<'a> = usize + where Self: 'a; + + #[inline] + unsafe fn key_by_index_unchecked(&self, index: usize) -> usize { + index + } +} diff --git a/src/type_gen.rs b/src/type_gen.rs new file mode 100644 index 0000000..4d67b10 --- /dev/null +++ b/src/type_gen.rs @@ -0,0 +1,2 @@ +mod key_wrapper; +mod plain_id; diff --git a/src/macros.rs b/src/type_gen/key_wrapper.rs similarity index 53% rename from src/macros.rs rename to src/type_gen/key_wrapper.rs index 60f6b8a..eaafff1 100644 --- a/src/macros.rs +++ b/src/type_gen/key_wrapper.rs @@ -29,9 +29,9 @@ /// # Example /// /// ```rust -/// use blazemap::prelude::{BlazeMap, register_blazemap_id_wrapper}; +/// use blazemap::prelude::{BlazeMap, define_key_wrapper}; /// -/// register_blazemap_id_wrapper! { +/// define_key_wrapper! { /// pub struct Key(&'static str); /// Derive(as for Original Type): { // Optional section /// Debug, @@ -54,7 +54,7 @@ /// assert_eq!(format!("{map:?}"), r#"{"first": "1", "second": "2", "third": "3"}"#) /// ``` #[macro_export] -macro_rules! register_blazemap_id_wrapper { +macro_rules! define_key_wrapper { ( $(#[$attrs:meta])* $vis:vis @@ -63,13 +63,13 @@ macro_rules! register_blazemap_id_wrapper { $(; Derive(as for Serial Number): {$( $to_derive_sn:ident),+ $(,)?} )? $(;)? ) => { - $crate::blazemap_id_wrapper_inner! { + $crate::key_wrapper_inner! { $(#[$attrs])* $vis struct $new_type($orig_type) } - $($($crate::blazemap_derive_key_inner! {@DERIVE $to_derive_orig $new_type})*)? - $($($crate::blazemap_derive_assigned_sn! {@DERIVE $to_derive_sn $new_type})*)? + $($($crate::key_wrapper_derive! {@DERIVE $to_derive_orig $new_type})*)? + $($($crate::assigned_sn_derive! {@DERIVE $to_derive_sn $new_type})*)? }; ( $(#[$attrs:meta])* @@ -79,89 +79,19 @@ macro_rules! register_blazemap_id_wrapper { $(; Derive(as for Original Type): {$($to_derive_orig:ident),+ $(,)?} )? $(;)? ) => { - $crate::blazemap_id_wrapper_inner! { + $crate::key_wrapper_inner! { $(#[$attrs])* $vis struct $new_type($orig_type) } - $($($crate::blazemap_derive_key_inner! {@DERIVE $to_derive_orig $new_type})*)? - $($($crate::blazemap_derive_assigned_sn! {@DERIVE $to_derive_sn $new_type})*)? + $($($crate::key_wrapper_derive! {@DERIVE $to_derive_orig $new_type})*)? + $($($crate::assigned_sn_derive! {@DERIVE $to_derive_sn $new_type})*)? } } -/// Creates a new type based on incrementally generated `usize` instances -/// that can be used as a key for `blazemap` collections. -/// -/// This macro supports optional inference of standard traits using the following syntax: -/// -/// * `Derive` — derives traits in the same way as for -/// the serial number assigned when creating a new instance of the type. -/// Because methods inferred by this option do not require additional -/// locking on synchronization primitives, -/// they do not incur any additional overhead compared to methods inferred for plain `usize`. -/// This method supports inference of the following traits: -/// * `PartialOrd` (mutually exclusive with `Ord`) -/// * `Ord` (also derives `PartialOrd`, so mutually exclusive with `PartialOrd`) -/// * `Serialize` (with `serde` feature only) -/// -/// # Example -/// -/// ```rust -/// use blazemap::prelude::{BlazeMap, register_blazemap_id}; -/// -/// register_blazemap_id! { -/// pub struct Id(start from: 1); // "(start from: number)" is optional -/// Derive: { // Derive section is also optional -/// Ord -/// }; -/// } -/// -/// let key_1 = Id::new(); -/// let key_2 = Id::new(); -/// let key_3 = Id::new(); -/// -/// let mut map = BlazeMap::new(); -/// map.insert(key_2, "2"); -/// map.insert(key_1, "1"); -/// map.insert(key_3, "3"); -/// -/// assert_eq!(format!("{map:?}"), r#"{1: "1", 2: "2", 3: "3"}"#) -/// ``` -#[macro_export] -macro_rules! register_blazemap_id { - ( - $(#[$attrs:meta])* - $vis:vis - struct $new_type:ident(start from: $first_id:literal) - $(; Derive: {$($to_derive_sn:ident),+ $(,)?} )? - $(;)? - ) => { - $crate::blazemap_id_inner! { - $(#[$attrs])* - $vis - struct $new_type($first_id) - } - $($($crate::blazemap_id_inner_derive! {@DERIVE $to_derive_sn $new_type})*)? - }; - ( - $(#[$attrs:meta])* - $vis:vis - struct $new_type:ident - $(; Derive: {$($to_derive_sn:ident),+ $(,)?} )? - $(;)? - ) => { - $crate::blazemap_id_inner! { - $(#[$attrs])* - $vis - struct $new_type(0) - } - $($($crate::blazemap_id_inner_derive! {@DERIVE $to_derive_sn $new_type})*)? - }; -} - #[doc(hidden)] #[macro_export] -macro_rules! blazemap_id_wrapper_inner { +macro_rules! key_wrapper_inner { ( $(#[$attrs:meta])* $vis:vis @@ -178,13 +108,22 @@ macro_rules! blazemap_id_wrapper_inner { pub fn new(value: $orig_type) -> Self { ::new(value) } + + #[inline] + #[doc(hidden)] + fn static_info() -> &'static $crate::external::parking_lot::RwLock<$crate::static_containers::KeyWrapperStaticContainer<$orig_type>> + { + use $crate::external::parking_lot::RwLock; + use $crate::static_containers::KeyWrapperStaticContainer; + + static MAP: RwLock> = RwLock::new(KeyWrapperStaticContainer::new()); + &MAP + } } impl $crate::prelude::BlazeMapId for $new_type { type OrigType = $orig_type; - type StaticInfoApi = $crate::utils::IdWrapperStaticInfo<$orig_type>; - type StaticInfoApiLock = &'static $crate::external::parking_lot::RwLock<$crate::utils::IdWrapperStaticInfo<$orig_type>>; #[inline] fn get_index(self) -> usize { @@ -192,19 +131,19 @@ macro_rules! blazemap_id_wrapper_inner { index.into_offset() } - #[inline(always)] + #[inline] unsafe fn from_index_unchecked(index: usize) -> Self { Self($crate::utils::OffsetProvider::::new(index)) } #[inline] - fn static_info() -> &'static $crate::external::parking_lot::RwLock<$crate::utils::IdWrapperStaticInfo<$orig_type>> - { - use $crate::external::parking_lot::RwLock; - use $crate::utils::IdWrapperStaticInfo; + fn capacity_info_provider() -> impl ::std::ops::Deref { + Self::static_info().read() + } - static MAP: RwLock> = RwLock::new(IdWrapperStaticInfo::new()); - &MAP + #[inline] + fn key_by_index_provider() -> impl ::std::ops::Deref> { + Self::static_info().read() } } @@ -236,84 +175,7 @@ macro_rules! blazemap_id_wrapper_inner { #[doc(hidden)] #[macro_export] -macro_rules! blazemap_id_inner { - ( - $(#[$attrs:meta])* - $vis:vis - struct $new_type:ident($first_id:literal) - ) => { - $(#[$attrs])* - #[derive(Clone, Copy, Eq, PartialEq, Hash)] - #[repr(transparent)] - $vis struct $new_type($crate::utils::OffsetProvider); - - impl $new_type - { - #[inline] - #[allow(dead_code)] - pub fn new() -> Self { - use $crate::prelude::BlazeMapId; - - let next_id = Self::static_info().0.next_id(); - Self(unsafe { $crate::utils::OffsetProvider::::new(next_id) }) - } - } - - impl $crate::prelude::BlazeMapId for $new_type - { - type OrigType = usize; - type StaticInfoApi = $crate::utils::TrivialIdStaticInfo; - type StaticInfoApiLock = &'static $crate::external::read_write_api::RwApiWrapperOwned<$crate::utils::TrivialIdStaticInfo>; - - #[inline] - fn get_index(self) -> usize { - let Self(index) = self; - index.into_offset() - } - - #[inline(always)] - unsafe fn from_index_unchecked(index: usize) -> Self { - Self($crate::utils::OffsetProvider::::new(index)) - } - - #[inline] - fn static_info() -> &'static $crate::external::read_write_api::RwApiWrapperOwned<$crate::utils::TrivialIdStaticInfo> - { - use $crate::utils::TrivialIdStaticInfo; - use $crate::external::read_write_api::RwApiWrapperOwned; - - static INFO: RwApiWrapperOwned = RwApiWrapperOwned(TrivialIdStaticInfo::new($first_id)); - &INFO - } - } - - impl ::std::fmt::Debug for $new_type - { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result - { - let Self(index) = self; - f.debug_tuple(::std::stringify!($new_type)) - .field(&index.into_offset()) - .finish() - } - } - - impl ::std::fmt::Display for $new_type - { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result - { - let Self(index) = self; - write!(f, "{}", index.into_offset()) - } - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! blazemap_derive_key_inner { +macro_rules! key_wrapper_derive { (@DERIVE Default $new_type:ident) => { impl Default for $new_type { #[inline] @@ -326,7 +188,7 @@ macro_rules! blazemap_derive_key_inner { impl PartialOrd for $new_type { #[inline] fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> { - use $crate::orig_type_id_map::StaticInfoApi; + use $crate::blazemap_id::StaticInfoApi; let Self(lhs) = self; let Self(rhs) = other; @@ -352,18 +214,19 @@ macro_rules! blazemap_derive_key_inner { impl Ord for $new_type { #[inline] fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { - use $crate::orig_type_id_map::StaticInfoApi; + use ::std::borrow::Borrow; + use $crate::blazemap_id::KeyByIndexProvider; let Self(lhs) = self; let Self(rhs) = other; - let guard = ::static_info().read(); + let guard = ::key_by_index_provider(); let (lhs, rhs) = unsafe { ( - guard.get_key_unchecked(lhs.into_offset()), - guard.get_key_unchecked(rhs.into_offset()), + guard.key_by_index_unchecked(lhs.into_offset()), + guard.key_by_index_unchecked(rhs.into_offset()), ) }; - lhs.cmp(rhs) + lhs.borrow().cmp(rhs.borrow()) } } }; @@ -371,13 +234,15 @@ macro_rules! blazemap_derive_key_inner { impl ::std::fmt::Debug for $new_type { #[inline] fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - use $crate::orig_type_id_map::StaticInfoApi; + use ::std::borrow::Borrow; + use $crate::blazemap_id::KeyByIndexProvider; let Self(index) = self; let mut f = f.debug_struct(::std::stringify!($new_type)); - let guard = ::static_info().read(); - let original_key = unsafe { guard.get_key_unchecked(index.into_offset()) }; - f.field("original_key", original_key); + let guard = ::key_by_index_provider(); + let original_key = unsafe { guard.key_by_index_unchecked(index.into_offset()) }; + f.field("original_key", original_key.borrow()); + drop(original_key); drop(guard); f.field("index", &index.into_offset()).finish() } @@ -387,12 +252,13 @@ macro_rules! blazemap_derive_key_inner { impl ::std::fmt::Display for $new_type { #[inline] fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - use $crate::orig_type_id_map::StaticInfoApi; + use ::std::borrow::Borrow; + use $crate::blazemap_id::KeyByIndexProvider; let Self(index) = self; - let guard = ::static_info().read(); - let original_key = unsafe { guard.get_key_unchecked(index.into_offset()) }; - write!(f, "{original_key}") + let guard = ::key_by_index_provider(); + let original_key = unsafe { guard.key_by_index_unchecked(index.into_offset()) }; + write!(f, "{}", original_key.borrow()) } } }; @@ -418,13 +284,14 @@ macro_rules! blazemap_derive_key_inner { where S: $crate::external::serde::Serializer, { - use $crate::orig_type_id_map::StaticInfoApi; + use ::std::borrow::Borrow; + use $crate::blazemap_id::KeyByIndexProvider; let Self(index) = self; unsafe { - ::static_info() - .read() - .get_key_unchecked(index.into_offset()) + ::key_by_index_provider() + .key_by_index_unchecked(index.into_offset()) + .borrow() .serialize(serializer) } } @@ -434,39 +301,7 @@ macro_rules! blazemap_derive_key_inner { #[doc(hidden)] #[macro_export] -macro_rules! blazemap_derive_assigned_sn { - (@DERIVE PartialOrd $new_type:ident) => { - impl PartialOrd for $new_type { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> { - let Self(lhs) = self; - let Self(rhs) = other; - lhs.into_offset().partial_cmp(&rhs.into_offset()) - } - } - }; - (@DERIVE Ord $new_type:ident) => { - impl PartialOrd for $new_type { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> { - Some(self.cmp(other)) - } - } - - impl Ord for $new_type { - #[inline] - fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { - let Self(lhs) = self; - let Self(rhs) = other; - lhs.into_offset().cmp(&rhs.into_offset()) - } - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! blazemap_id_inner_derive { +macro_rules! assigned_sn_derive { (@DERIVE PartialOrd $new_type:ident) => { impl PartialOrd for $new_type { #[inline] @@ -494,16 +329,4 @@ macro_rules! blazemap_id_inner_derive { } } }; - (@DERIVE Serialize $new_type:ident) => { - impl $crate::external::serde::Serialize for $new_type { - #[inline] - fn serialize(&self, serializer: S) -> Result - where - S: $crate::external::serde::Serializer, - { - let Self(index) = self; - index.into_offset().serialize(serializer) - } - } - }; } diff --git a/src/type_gen/plain_id.rs b/src/type_gen/plain_id.rs new file mode 100644 index 0000000..3162f50 --- /dev/null +++ b/src/type_gen/plain_id.rs @@ -0,0 +1,193 @@ +/// Creates a new type based on incrementally generated `usize` instances +/// that can be used as a key for `blazemap` collections. +/// +/// This macro supports optional inference of standard traits using the following syntax: +/// +/// * `Derive` — derives traits in the same way as for +/// the serial number assigned when creating a new instance of the type. +/// Because methods inferred by this option do not require additional +/// locking on synchronization primitives, +/// they do not incur any additional overhead compared to methods inferred for plain `usize`. +/// This method supports inference of the following traits: +/// * `PartialOrd` (mutually exclusive with `Ord`) +/// * `Ord` (also derives `PartialOrd`, so mutually exclusive with `PartialOrd`) +/// * `Serialize` (with `serde` feature only) +/// +/// # Example +/// +/// ```rust +/// use blazemap::prelude::{BlazeMap, define_plain_id}; +/// +/// define_plain_id! { +/// pub struct Id(start from: 1); // "(start from: number)" is optional +/// Derive: { // Derive section is also optional +/// Ord +/// }; +/// } +/// +/// let key_1 = Id::new(); +/// let key_2 = Id::new(); +/// let key_3 = Id::new(); +/// +/// let mut map = BlazeMap::new(); +/// map.insert(key_2, "2"); +/// map.insert(key_1, "1"); +/// map.insert(key_3, "3"); +/// +/// assert_eq!(format!("{map:?}"), r#"{1: "1", 2: "2", 3: "3"}"#) +/// ``` +#[macro_export] +macro_rules! define_plain_id { + ( + $(#[$attrs:meta])* + $vis:vis + struct $new_type:ident(start from: $first_id:literal) + $(; Derive: {$($to_derive_sn:ident),+ $(,)?} )? + $(;)? + ) => { + $crate::plain_id_inner! { + $(#[$attrs])* + $vis + struct $new_type($first_id) + } + $($($crate::plain_id_derive! {@DERIVE $to_derive_sn $new_type})*)? + }; + ( + $(#[$attrs:meta])* + $vis:vis + struct $new_type:ident + $(; Derive: {$($to_derive_sn:ident),+ $(,)?} )? + $(;)? + ) => { + $crate::plain_id_inner! { + $(#[$attrs])* + $vis + struct $new_type(0) + } + $($($crate::plain_id_derive! {@DERIVE $to_derive_sn $new_type})*)? + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! plain_id_inner { + ( + $(#[$attrs:meta])* + $vis:vis + struct $new_type:ident($first_id:literal) + ) => { + $(#[$attrs])* + #[derive(Clone, Copy, Eq, PartialEq, Hash)] + #[repr(transparent)] + $vis struct $new_type($crate::utils::OffsetProvider); + + impl $new_type + { + #[inline] + pub fn new() -> Self { + let next_id = Self::static_info().next_id(); + Self(unsafe { $crate::utils::OffsetProvider::::new(next_id) }) + } + + #[inline] + #[doc(hidden)] + fn static_info() -> &'static $crate::static_containers::PlainIdStaticContainer { + use $crate::static_containers::PlainIdStaticContainer; + static INFO: PlainIdStaticContainer = PlainIdStaticContainer::new($first_id); + &INFO + } + } + + impl $crate::prelude::BlazeMapId for $new_type + { + type OrigType = usize; + + #[inline] + fn get_index(self) -> usize { + let Self(index) = self; + index.into_offset() + } + + #[inline] + unsafe fn from_index_unchecked(index: usize) -> Self { + Self($crate::utils::OffsetProvider::::new(index)) + } + + #[inline] + fn capacity_info_provider() -> impl ::std::ops::Deref { + Self::static_info() + } + + #[inline] + fn key_by_index_provider() -> impl ::std::ops::Deref> { + Self::static_info() + } + } + + impl ::std::fmt::Debug for $new_type + { + #[inline] + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result + { + let Self(index) = self; + f.debug_tuple(::std::stringify!($new_type)) + .field(&index.into_offset()) + .finish() + } + } + + impl ::std::fmt::Display for $new_type + { + #[inline] + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result + { + let Self(index) = self; + write!(f, "{}", index.into_offset()) + } + } + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! plain_id_derive { + (@DERIVE PartialOrd $new_type:ident) => { + impl PartialOrd for $new_type { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> { + let Self(lhs) = self; + let Self(rhs) = other; + lhs.into_offset().partial_cmp(&rhs.into_offset()) + } + } + }; + (@DERIVE Ord $new_type:ident) => { + impl PartialOrd for $new_type { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> { + Some(self.cmp(other)) + } + } + + impl Ord for $new_type { + #[inline] + fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { + let Self(lhs) = self; + let Self(rhs) = other; + lhs.into_offset().cmp(&rhs.into_offset()) + } + } + }; + (@DERIVE Serialize $new_type:ident) => { + impl $crate::external::serde::Serialize for $new_type { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: $crate::external::serde::Serializer, + { + let Self(index) = self; + index.into_offset().serialize(serializer) + } + } + }; +} diff --git a/src/utils.rs b/src/utils.rs index 05d7121..87899aa 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,4 @@ -pub use { - offset_provider::OffsetProvider, - static_info::{IdWrapperStaticInfo, TrivialIdStaticInfo}, -}; +pub use offset_provider::OffsetProvider; #[doc(hidden)] mod offset_provider; -#[doc(hidden)] -mod static_info; diff --git a/src/utils/offset_provider.rs b/src/utils/offset_provider.rs index 7b8f8f9..c8a1fd6 100644 --- a/src/utils/offset_provider.rs +++ b/src/utils/offset_provider.rs @@ -5,7 +5,7 @@ use std::num::NonZeroUsize; /// /// Necessary to protect the internal `usize`, which, in the absence of this wrapper, /// would be public in the module calling -/// the [`register_blazemap_id_wrapper`](crate::register_blazemap_id_wrapper). +/// the [`define_key_wrapper`](crate::define_key_wrapper). /// /// Publicity of the internal `usize` may lead to: /// * UB if the programmer of the downstream crate would accidentally mutate it. @@ -17,25 +17,25 @@ use std::num::NonZeroUsize; pub struct OffsetProvider(T); impl OffsetProvider { - #[inline(always)] + #[inline] pub unsafe fn new(offset: usize) -> Self { Self(offset) } - #[inline(always)] + #[inline] pub fn into_offset(self) -> usize { self.0 } } impl OffsetProvider { - #[inline(always)] + #[inline] pub unsafe fn new(offset: usize) -> Self { let inner = offset.checked_add(1).expect("usize overflow"); Self(unsafe { NonZeroUsize::new_unchecked(inner) }) } - #[inline(always)] + #[inline] pub fn into_offset(self) -> usize { self.0.get() - 1 } @@ -54,7 +54,7 @@ mod tests { // These assertions are needed in order to prevent standard traits // from being automatically derived for types - // generated by the [`register_blazemap_id_wrapper`](crate::register_blazemap_id) macro. + // generated by the [`define_key_wrapper`](crate::define_plain_id) macro. assert_not_impl_any!(OffsetProvider: Default, Debug, Display, PartialOrd); assert_not_impl_any!(OffsetProvider: Default, Debug, Display, PartialOrd); diff --git a/src/utils/static_info.rs b/src/utils/static_info.rs deleted file mode 100644 index e0b5118..0000000 --- a/src/utils/static_info.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::collections::HashMap; -use std::hash::Hash; -use std::sync::atomic::{AtomicUsize, Ordering}; - -use once_cell::sync::Lazy; - -use crate::orig_type_id_map::StaticInfoApi; - -/// Global, statically initialized structure that contains correspondence mapping -/// between blazemap index wrappers and original keys. -#[doc(hidden)] -#[derive(Debug)] -pub struct IdWrapperStaticInfo { - index_to_orig: Vec, - orig_to_index: Lazy>, -} - -#[doc(hidden)] -#[derive(Debug)] -pub struct TrivialIdStaticInfo { - next_id: AtomicUsize, -} - -impl IdWrapperStaticInfo { - /// Creates a new instance of [`IdWrapperStaticInfo`]. - #[inline] - pub const fn new() -> Self { - Self { - index_to_orig: vec![], - orig_to_index: Lazy::new(Default::default), - } - } -} - -impl IdWrapperStaticInfo -where - K: Clone + Eq + Hash, -{ - #[inline] - pub fn get_index(&self, key: &K) -> Option { - self.orig_to_index.get(key).copied() - } - - #[inline] - pub unsafe fn insert_new_key_unchecked(&mut self, key: K) -> usize { - let next_id = self.num_elems(); - let Self { - index_to_orig, - orig_to_index, - } = self; - index_to_orig.push(key.clone()); - orig_to_index.insert(key, next_id); - next_id - } -} - -impl TrivialIdStaticInfo { - /// Creates a new instance of [`TrivialIdStaticInfo`]. - #[inline] - pub const fn new(first_id: usize) -> Self { - Self { - next_id: AtomicUsize::new(first_id), - } - } - - /// Returns the next identifier. - #[inline] - pub fn next_id(&self) -> usize { - self.next_id - .fetch_update(Ordering::Release, Ordering::Acquire, |next_id| { - next_id.checked_add(1) - }) - .expect("usize overflow") - } -} - -impl StaticInfoApi for IdWrapperStaticInfo -where - K: Clone + Eq + Hash, -{ - type KeyUnchecked<'a> = &'a K - where Self: 'a; - - #[inline(always)] - fn num_elems(&self) -> usize { - self.index_to_orig.len() - } - - #[inline] - unsafe fn get_key_unchecked(&self, index: usize) -> &K { - self.index_to_orig.get_unchecked(index) - } -} - -impl StaticInfoApi for TrivialIdStaticInfo { - type KeyUnchecked<'a> = usize - where Self: 'a; - - #[inline(always)] - fn num_elems(&self) -> usize { - self.next_id.load(Ordering::Acquire) - } - - #[inline(always)] - unsafe fn get_key_unchecked(&self, index: usize) -> usize { - index - } -}