Skip to content

Commit

Permalink
Merge pull request #5 from MrElectrify/fixed-map
Browse files Browse the repository at this point in the history
Implement FixedMap, fixes to FixedList, Equality trait changes
  • Loading branch information
neutr0nst4r authored Apr 13, 2024
2 parents a46303d + 4bce483 commit 968ac7a
Show file tree
Hide file tree
Showing 23 changed files with 308 additions and 118 deletions.
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "eastl-rs"
authors = ["Andrew Buck"]
description = "EASTL binary-compatible Rust implementations"
documentation = "https://docs.rs/crate/eastl-rs"
version = "0.13.2"
version = "0.14.0"
edition = "2021"
license-file = "LICENSE"
readme = "README.md"
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
# EASTL-rs

A binary-compatible EASTL implementation written in Rust

[Crate](https://crates.io/crates/eastl-rs) | [Documentation](https://docs.rs/crate/eastl-rs)

## Fully implemented containers*

- `HashMap`
- `HashSet`
- `String`
- `Vector`
- `Deque`
- `Queue`
- `VectorMap`
- `FixedVector`
- `FixedVector` (only with overflow enabled)
- `List`

* there might be some things missing :)

## Partially implemented containers

- `Map` (traversal only)
- `Set` (traversal only)
- `FixedList` (not fully conformant on stable, only with overflow enabled)
- `FixedMap` (only non-conformant version implemented)

## Planned containers

- `HashMultimap`
- `HashMultiset`
- `Map`
Expand Down
2 changes: 1 addition & 1 deletion src/equals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub struct EqualTo<T> {
_marker: PhantomData<T>,
}

impl<T: Eq> Equals<T> for EqualTo<T> {
impl<T: PartialEq> Equals<T> for EqualTo<T> {
fn equals(lhs: &T, rhs: &T) -> bool {
lhs == rhs
}
Expand Down
7 changes: 5 additions & 2 deletions src/fixed_list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
pub mod conformant;

use crate::allocator::{Allocator, DefaultAllocator};
use crate::fixed_pool::with_overflow::FixedPoolWithOverflow;
use crate::fixed_pool::{with_overflow::FixedPoolWithOverflow, PoolAllocator};
use crate::list::node::{ListNode, ListNodeBase};
use crate::list::List;
use moveit::{new, New};
Expand All @@ -27,6 +27,8 @@ pub struct FixedList<T, const NODE_COUNT: usize, OverflowAllocator: Allocator> {
// this should `technically` be conformant - `buffer` should be aligned to the alignment of
// `ListNode<T>`...
buffer: [MaybeUninit<ListNode<T>>; NODE_COUNT],
// ... and then we add an extra node for the padding induced as shown in the conformant version
_pad: MaybeUninit<ListNode<T>>,
}

#[allow(private_bounds)]
Expand All @@ -50,6 +52,7 @@ impl<T, const NODE_COUNT: usize, OverflowAllocator: Allocator>
},
// we actually don't care what the buffer contains
buffer: MaybeUninit::uninit().assume_init(),
_pad: MaybeUninit::uninit().assume_init(),
})
.with(|this| {
let this = this.get_unchecked_mut();
Expand Down Expand Up @@ -204,7 +207,7 @@ mod test {
mem::size_of::<ListNodeBase>()
+ mem::size_of::<usize>()
+ mem::size_of::<FixedPoolWithOverflow<ListNode<Test>, DefaultAllocator>>()
+ mem::size_of::<ListNode<Test>>()
+ mem::size_of::<ListNode<Test>>() * 2
);
}

Expand Down
154 changes: 154 additions & 0 deletions src/fixed_map/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//!
//! Copyright (C) Warsaw Revamped. Any unauthorized use, modification, or distribution of any portion of this file is prohibited. All rights reserved.
//!
use crate::allocator::{Allocator, DefaultAllocator};
use crate::compare::{Compare, Less};
use crate::fixed_pool::with_overflow::FixedPoolWithOverflow;
use crate::fixed_pool::{FixedPool, PoolAllocator};
use crate::internal::rb_tree::{
iter::{Iter, IterMut},
node::Node,
};
use crate::map::Map;
use duplicate::duplicate_item;
use moveit::{new, New};
use std::mem::MaybeUninit;
use std::{mem, slice};

/// A fixed map with overflow which uses the default allocator as an overflow.
pub type DefaultFixedMapWithOverflow<K, V, const NODE_COUNT: usize, C> =
FixedMapWithOverflow<K, V, NODE_COUNT, DefaultAllocator, C>;

/// A fixed map without overflow.
pub type FixedMap<K, V, const NODE_COUNT: usize, C = Less<K>> =
FixedMapImpl<K, V, NODE_COUNT, FixedPool<Node<K, V>>, C>;

/// A fixed map with overflow using the given overflow allocator.
pub type FixedMapWithOverflow<K, V, const NODE_COUNT: usize, OverflowAllocator, C = Less<K>> =
FixedMapImpl<K, V, NODE_COUNT, FixedPoolWithOverflow<Node<K, V>, OverflowAllocator>, C>;

#[repr(C)]
pub struct FixedMapImpl<
K: PartialEq,
V,
const NODE_COUNT: usize,
A: Allocator,
C: Compare<K> = Less<K>,
> {
// real EASTL uses a fixed_node_pool here, which is just fixed_pool_with_overflow templated
// by node size instead of type, so it does not matter and we use fixed_pool_with_overflow
// directly
base_map: Map<K, V, A, C>,
// this should `technically` be conformant - `buffer` should be aligned to the alignment of
// `ListNode<T>`...
buffer: [MaybeUninit<Node<K, V>>; NODE_COUNT],
// ... and then we add an extra node for the padding induced as shown in the conformant version (of FixedList)
_pad: MaybeUninit<Node<K, V>>,
}

impl<
K: PartialEq,
V,
const NODE_COUNT: usize,
A: PoolAllocator + Default,
C: Compare<K> + Default,
> FixedMapImpl<K, V, NODE_COUNT, A, C>
{
/// Create a new, empty fixed map.
///
/// # Arguments
/// `allocator`: The allocator to use
///
/// # Safety
/// The resulting map must not be moved.
pub unsafe fn new() -> impl New<Output = Self> {
new::of(Self {
base_map: Map::with_allocator(A::default()),
// we actually don't care what the buffer contains
buffer: MaybeUninit::uninit().assume_init(),
_pad: MaybeUninit::uninit().assume_init(),
})
.with(|this| {
let this = this.get_unchecked_mut();
this.base_map
.inner
.allocator
.init(slice::from_raw_parts_mut(
this.buffer.as_mut_ptr().cast(),
this.buffer.len() * mem::size_of::<Node<K, V>>(),
));
})
}
}

impl<K: PartialEq, V, const NODE_COUNT: usize, A: Allocator, C: Compare<K>>
FixedMapImpl<K, V, NODE_COUNT, A, C>
{
/// Clears the map, removing all key-value pairs
pub fn clear(&mut self) {
self.base_map.clear()
}

/// Returns true if the map contains a pair indexed
/// by the given key
///
/// # Arguments
///
/// `key`: The key to index the pair
pub fn contains_key(&self, key: &K) -> bool {
self.base_map.contains_key(key)
}

/// Fetches the value indexed by the key in the map
///
/// # Arguments
///
/// `key`: The key to index the pair
pub fn get(&self, key: &K) -> Option<&V> {
self.base_map.get(key)
}

/// Fetches the value indexed by the key in the map
///
/// # Arguments
///
/// `key`: The key to index the pair
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
self.base_map.get_mut(key)
}

/// Returns true if the map contains no elements
pub fn is_empty(&self) -> bool {
self.base_map.is_empty()
}

/// Returns an iterator over the elements in the map.
///
/// # Safety
/// This iterator is not tested as trees are only partially implemented.
#[duplicate_item(
iter Self Iter;
[iter] [&Self] [Iter];
[iter_mut] [&mut Self] [IterMut];
)]
#[allow(clippy::needless_arbitrary_self_type)]
pub unsafe fn iter(self: Self) -> Iter<K, V> {
self.base_map.iter()
}

/// Returns the number of elements in the map
pub fn len(&self) -> usize {
self.base_map.len()
}

/// Removes a key-value pair from the map,
/// returning the pair if it was found
///
/// # Arguments
///
/// `key`: The key to index the pair
pub fn remove_entry(&mut self, key: &K) -> Option<(K, V)> {
self.base_map.remove_entry(key)
}
}
28 changes: 17 additions & 11 deletions src/fixed_pool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ use crate::allocator::Allocator;
use std::marker::PhantomData;
use std::{mem, ptr};

pub trait PoolAllocator: Allocator {
/// Initializes the fixed pool allocator with the given memory.
///
/// # Safety
/// `memory` must be a valid chunk of memory, solely owned and managed by the pool allocator.
unsafe fn init(&mut self, memory: &mut [u8]);
}

/// The struct `eastl::fixed_pool_base::Link`. Singly-linked list for memory allocations.
pub(crate) struct Link {
next: *mut Link,
Expand All @@ -30,11 +38,15 @@ impl<Node: Sized> FixedPool<Node> {
res
}

/// Initializes the fixed pool allocator with the given memory.
///
/// # Safety
/// `memory` must be a valid chunk of memory, solely owned and managed by the pool allocator.
pub unsafe fn init(&mut self, memory: &mut [u8]) {
/// Returns true if the pool can allocate.
#[allow(dead_code)]
pub fn can_allocate(&self) -> bool {
!self.head.is_null() || (self.next != self.capacity)
}
}

impl<Node: Sized> PoolAllocator for FixedPool<Node> {
unsafe fn init(&mut self, memory: &mut [u8]) {
let mut memory_size = memory.len();
let mut node_align = mem::align_of::<Node>();
let node_size = mem::size_of::<Node>();
Expand Down Expand Up @@ -64,12 +76,6 @@ impl<Node: Sized> FixedPool<Node> {
self.next = next as *mut Link;
self.capacity = (next + memory_size) as *mut Link;
}

/// Returns true if the pool can allocate.
#[allow(dead_code)]
pub fn can_allocate(&self) -> bool {
!self.head.is_null() || (self.next != self.capacity)
}
}

unsafe impl<Node: Sized> Allocator for FixedPool<Node> {
Expand Down
12 changes: 6 additions & 6 deletions src/fixed_pool/with_overflow.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::allocator::Allocator;
use crate::fixed_pool::FixedPool;
use crate::fixed_pool::{FixedPool, PoolAllocator};
use std::{mem, ptr};

/// The class `eastl::fixed_pool_with_overflow`. Attempts to allocate from the pool,
Expand Down Expand Up @@ -41,12 +41,12 @@ impl<Node: Sized, OverflowAllocator: Allocator> FixedPoolWithOverflow<Node, Over
pub fn can_allocate(&self) -> bool {
self.pool_allocator.can_allocate()
}
}

/// Initializes the fixed pool allocator with the given memory.
///
/// # Safety
/// `memory` must be a valid chunk of memory, solely owned and managed by the pool allocator.
pub unsafe fn init(&mut self, memory: &mut [u8]) {
impl<Node: Sized, OverflowAllocator: Allocator> PoolAllocator
for FixedPoolWithOverflow<Node, OverflowAllocator>
{
unsafe fn init(&mut self, memory: &mut [u8]) {
self.pool_allocator.init(memory);

// store the pool base
Expand Down
2 changes: 0 additions & 2 deletions src/fixed_vector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ use std::{mem, ptr};

mod allocator;

/// TODO overflow_enabled, more than traversal?
/// Fixed vector with the default allocator.
pub type DefaultFixedVector<T, const NODE_COUNT: usize> =
FixedVector<T, NODE_COUNT, DefaultAllocator>;
Expand Down
6 changes: 3 additions & 3 deletions src/hash_map/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use crate::hash::Hash;
use crate::internal::hash_table;

/// An entry in a hash map.
pub struct Entry<'a, K: Eq, V, A: Allocator, H: Hash<K>, E: Equals<K>>(
pub struct Entry<'a, K: PartialEq, V, A: Allocator, H: Hash<K>, E: Equals<K>>(
hash_table::entry::Entry<'a, K, V, A, H, E>,
);

impl<'a, K: Eq, V, A: Allocator, H: Hash<K>, E: Equals<K>> Entry<'a, K, V, A, H, E> {
impl<'a, K: PartialEq, V, A: Allocator, H: Hash<K>, E: Equals<K>> Entry<'a, K, V, A, H, E> {
/// Provides in-place mutable access to the value.
///
/// # Arguments
Expand Down Expand Up @@ -37,7 +37,7 @@ impl<'a, K: Eq, V, A: Allocator, H: Hash<K>, E: Equals<K>> Entry<'a, K, V, A, H,
}
}

impl<'a, K: Eq, V, A: Allocator, H: Hash<K>, E: Equals<K>>
impl<'a, K: PartialEq, V, A: Allocator, H: Hash<K>, E: Equals<K>>
From<hash_table::entry::Entry<'a, K, V, A, H, E>> for Entry<'a, K, V, A, H, E>
{
fn from(value: hash_table::entry::Entry<'a, K, V, A, H, E>) -> Self {
Expand Down
Loading

0 comments on commit 968ac7a

Please sign in to comment.