diff --git a/.vscode/agb.code-workspace b/.vscode/agb.code-workspace index f7b0fd4..7c02966 100644 --- a/.vscode/agb.code-workspace +++ b/.vscode/agb.code-workspace @@ -38,6 +38,9 @@ }, { "path": "../tools" + }, + { + "path": "../agb-hashmap" } ] -} +} \ No newline at end of file diff --git a/agb-hashmap/Cargo.toml b/agb-hashmap/Cargo.toml new file mode 100644 index 0000000..3a1a787 --- /dev/null +++ b/agb-hashmap/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "agb_hashmap" +version = "0.14.0" +edition = "2021" +license = "MPL-2.0" +description = "A simple no_std hashmap implementation intended for use in the `agb` library" +repository = "https://github.com/agbrs/agb" + +[dependencies] +rustc-hash = { version = "1", default-features = false } + +[dev-dependencies] +rand = { version = "0.8", default-features = false, features = ["small_rng"] } \ No newline at end of file diff --git a/agb/src/hash_map.rs b/agb-hashmap/src/lib.rs similarity index 89% rename from agb/src/hash_map.rs rename to agb-hashmap/src/lib.rs index 59b71ad..f8260ef 100644 --- a/agb/src/hash_map.rs +++ b/agb-hashmap/src/lib.rs @@ -1,6 +1,12 @@ -#![deny(missing_docs)] //! A lot of the documentation for this module was copied straight out of the rust //! standard library. The implementation however is not. +#![no_std] +#![feature(allocator_api)] +#![deny(clippy::all)] +#![deny(missing_docs)] + +extern crate alloc; + use alloc::{alloc::Global, vec::Vec}; use core::{ alloc::Allocator, @@ -993,10 +999,9 @@ mod test { use core::cell::RefCell; use super::*; - use crate::{rng::RandomNumberGenerator, Gba}; - #[test_case] - fn can_store_and_retrieve_8_elements(_gba: &mut Gba) { + #[test] + fn can_store_and_retrieve_8_elements() { let mut map = HashMap::new(); for i in 0..8 { @@ -1008,8 +1013,8 @@ mod test { } } - #[test_case] - fn can_get_the_length(_gba: &mut Gba) { + #[test] + fn can_get_the_length() { let mut map = HashMap::new(); for i in 0..8 { @@ -1019,8 +1024,8 @@ mod test { assert_eq!(map.len(), 4); } - #[test_case] - fn returns_none_if_element_does_not_exist(_gba: &mut Gba) { + #[test] + fn returns_none_if_element_does_not_exist() { let mut map = HashMap::new(); for i in 0..8 { @@ -1030,8 +1035,8 @@ mod test { assert_eq!(map.get(&12), None); } - #[test_case] - fn can_delete_entries(_gba: &mut Gba) { + #[test] + fn can_delete_entries() { let mut map = HashMap::new(); for i in 0..8 { @@ -1047,8 +1052,8 @@ mod test { assert_eq!(map.get(&7), Some(&1)); } - #[test_case] - fn can_iterate_through_all_entries(_gba: &mut Gba) { + #[test] + fn can_iterate_through_all_entries() { let mut map = HashMap::new(); for i in 0..8 { @@ -1067,8 +1072,8 @@ mod test { assert_eq!(max_found, 7); } - #[test_case] - fn can_insert_more_than_initial_capacity(_gba: &mut Gba) { + #[test] + fn can_insert_more_than_initial_capacity() { let mut map = HashMap::new(); for i in 0..65 { @@ -1115,17 +1120,32 @@ mod test { } } - #[test_case] - fn extreme_case(_gba: &mut Gba) { + trait RngNextI32 { + fn next_i32(&mut self) -> i32; + } + + impl RngNextI32 for T + where + T: rand::RngCore, + { + fn next_i32(&mut self) -> i32 { + self.next_u32() as i32 + } + } + + #[test] + fn extreme_case() { + use rand::SeedableRng; + let mut map = HashMap::new(); - let mut rng = RandomNumberGenerator::new(); + let mut rng = rand::rngs::SmallRng::seed_from_u64(20); let mut answers: [Option; 128] = [None; 128]; for _ in 0..5_000 { - let command = rng.gen().rem_euclid(2); - let key = rng.gen().rem_euclid(answers.len() as i32); - let value = rng.gen(); + let command = rng.next_i32().rem_euclid(2); + let key = rng.next_i32().rem_euclid(answers.len() as i32); + let value = rng.next_i32(); match command { 0 => { @@ -1212,8 +1232,8 @@ mod test { } } - #[test_case] - fn correctly_drops_on_remove_and_overall_drop(_gba: &mut Gba) { + #[test] + fn correctly_drops_on_remove_and_overall_drop() { let drop_registry = DropRegistry::new(); let droppable1 = drop_registry.new_droppable(); @@ -1239,8 +1259,8 @@ mod test { drop_registry.assert_dropped_once(id2); } - #[test_case] - fn correctly_drop_on_override(_gba: &mut Gba) { + #[test] + fn correctly_drop_on_override() { let drop_registry = DropRegistry::new(); let droppable1 = drop_registry.new_droppable(); @@ -1263,8 +1283,8 @@ mod test { drop_registry.assert_dropped_once(id2); } - #[test_case] - fn correctly_drops_key_on_override(_gba: &mut Gba) { + #[test] + fn correctly_drops_key_on_override() { let drop_registry = DropRegistry::new(); let droppable1 = drop_registry.new_droppable(); @@ -1285,8 +1305,8 @@ mod test { drop_registry.assert_dropped_n_times(id1, 2); } - #[test_case] - fn test_retain(_gba: &mut Gba) { + #[test] + fn test_retain() { let mut map = HashMap::new(); for i in 0..100 { @@ -1301,8 +1321,8 @@ mod test { assert_eq!(map.iter().count(), 50); // force full iteration } - #[test_case] - fn test_size_hint_iter(_gba: &mut Gba) { + #[test] + fn test_size_hint_iter() { let mut map = HashMap::new(); for i in 0..100 { @@ -1317,8 +1337,8 @@ mod test { assert_eq!(iter.size_hint(), (99, Some(99))); } - #[test_case] - fn test_size_hint_into_iter(_gba: &mut Gba) { + #[test] + fn test_size_hint_into_iter() { let mut map = HashMap::new(); for i in 0..100 { @@ -1336,13 +1356,10 @@ mod test { // Following test cases copied from the rust source // https://github.com/rust-lang/rust/blob/master/library/std/src/collections/hash/map/tests.rs mod rust_std_tests { - use crate::{ - hash_map::{Entry::*, HashMap}, - Gba, - }; + use crate::{Entry::*, HashMap}; - #[test_case] - fn test_entry(_gba: &mut Gba) { + #[test] + fn test_entry() { let xs = [(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)]; let mut map: HashMap<_, _> = xs.iter().copied().collect(); @@ -1391,8 +1408,8 @@ mod test { assert_eq!(map.len(), 6); } - #[test_case] - fn test_occupied_entry_key(_gba: &mut Gba) { + #[test] + fn test_occupied_entry_key() { let mut a = HashMap::new(); let key = "hello there"; let value = "value goes here"; @@ -1409,8 +1426,8 @@ mod test { assert_eq!(a[key], value); } - #[test_case] - fn test_vacant_entry_key(_gba: &mut Gba) { + #[test] + fn test_vacant_entry_key() { let mut a = HashMap::new(); let key = "hello there"; let value = "value goes here"; @@ -1427,8 +1444,8 @@ mod test { assert_eq!(a[key], value); } - #[test_case] - fn test_index(_gba: &mut Gba) { + #[test] + fn test_index() { let mut map = HashMap::new(); map.insert(1, 2); @@ -1438,111 +1455,4 @@ mod test { assert_eq!(map[&2], 1); } } - - mod rust_std_tests_custom_allocator { - use crate::{ - hash_map::{Entry::*, HashMap}, - Gba, InternalAllocator, - }; - - #[test_case] - fn test_entry(_gba: &mut Gba) { - let xs = [(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)]; - - let mut map = HashMap::new_in(InternalAllocator); - for (k, v) in xs { - map.insert(k, v); - } - - // Existing key (insert) - match map.entry(1) { - Vacant(_) => unreachable!(), - Occupied(mut view) => { - assert_eq!(view.get(), &10); - assert_eq!(view.insert(100), 10); - } - } - assert_eq!(map.get(&1).unwrap(), &100); - assert_eq!(map.len(), 6); - - // Existing key (update) - match map.entry(2) { - Vacant(_) => unreachable!(), - Occupied(mut view) => { - let v = view.get_mut(); - let new_v = (*v) * 10; - *v = new_v; - } - } - assert_eq!(map.get(&2).unwrap(), &200); - assert_eq!(map.len(), 6); - - // Existing key (take) - match map.entry(3) { - Vacant(_) => unreachable!(), - Occupied(view) => { - assert_eq!(view.remove(), 30); - } - } - assert_eq!(map.get(&3), None); - assert_eq!(map.len(), 5); - - // Inexistent key (insert) - match map.entry(10) { - Occupied(_) => unreachable!(), - Vacant(view) => { - assert_eq!(*view.insert(1000), 1000); - } - } - assert_eq!(map.get(&10).unwrap(), &1000); - assert_eq!(map.len(), 6); - } - - #[test_case] - fn test_occupied_entry_key(_gba: &mut Gba) { - let mut a = HashMap::new_in(InternalAllocator); - let key = "hello there"; - let value = "value goes here"; - assert!(a.is_empty()); - a.insert(key, value); - assert_eq!(a.len(), 1); - assert_eq!(a[key], value); - - match a.entry(key) { - Vacant(_) => panic!(), - Occupied(e) => assert_eq!(key, *e.key()), - } - assert_eq!(a.len(), 1); - assert_eq!(a[key], value); - } - - #[test_case] - fn test_vacant_entry_key(_gba: &mut Gba) { - let mut a = HashMap::new_in(InternalAllocator); - let key = "hello there"; - let value = "value goes here"; - - assert!(a.is_empty()); - match a.entry(key) { - Occupied(_) => panic!(), - Vacant(e) => { - assert_eq!(key, *e.key()); - e.insert(value); - } - } - assert_eq!(a.len(), 1); - assert_eq!(a[key], value); - } - - #[test_case] - fn test_index(_gba: &mut Gba) { - let mut map = HashMap::new_in(InternalAllocator); - - map.insert(1, 2); - map.insert(2, 1); - map.insert(3, 4); - - assert_eq!(map[&2], 1); - } - } } diff --git a/agb/Cargo.toml b/agb/Cargo.toml index 583033a..1f572e9 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -27,6 +27,7 @@ agb_image_converter = { version = "0.14.0", path = "../agb-image-converter" } agb_sound_converter = { version = "0.14.0", path = "../agb-sound-converter" } agb_macros = { version = "0.14.0", path = "../agb-macros" } agb_fixnum = { version = "0.14.0", path = "../agb-fixnum" } +agb_hashmap = { version = "0.14.0", path = "../agb-hashmap" } bare-metal = "1" modular-bitfield = "0.11" rustc-hash = { version = "1", default-features = false } diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 7292637..14aed6e 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -152,7 +152,7 @@ pub mod mgba; #[doc(inline)] pub use agb_fixnum as fixnum; /// Contains an implementation of a hashmap which suits the gameboy advance's hardware. -pub mod hash_map; +pub use agb_hashmap as hash_map; /// Simple random number generator pub mod rng; pub mod save;