mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 16:21:33 +11:00
Extract agb hashmap (#418)
Pulls out the hashmap to its own crate. Allows us to use this in interop code between agb and desktop code and also gives us the ability to run miri on it to make sure we're not running into any crazy unsoundness bugs. - [x] Changelog updated / no changelog update needed
This commit is contained in:
commit
6355ac0df7
5
.github/workflows/build-and-test.yml
vendored
5
.github/workflows/build-and-test.yml
vendored
|
@ -18,6 +18,11 @@ jobs:
|
|||
steps:
|
||||
- name: Install build tools
|
||||
run: sudo apt-get update && sudo apt-get install build-essential binutils-arm-none-eabi libelf-dev zip -y
|
||||
- name: Install Miri
|
||||
run: |
|
||||
rustup toolchain install nightly --component miri
|
||||
rustup override set nightly
|
||||
cargo miri setup
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
|
|
3
.vscode/agb.code-workspace
vendored
3
.vscode/agb.code-workspace
vendored
|
@ -38,6 +38,9 @@
|
|||
},
|
||||
{
|
||||
"path": "../tools"
|
||||
},
|
||||
{
|
||||
"path": "../agb-hashmap"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Changed
|
||||
- Importing background tiles has been improved. You no longer need to use `include_gfx!` with the toml file. Instead, use `include_background_gfx`. See the documentation for usage.
|
||||
- The hashmap implementation is now it its own crate, `agb-hashmap`. There is no change in API, but you can now use this for interop between non-agb code and agb code
|
||||
|
||||
## [0.14.0] - 2023/04/11
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ to just write games for the Game Boy Advance using this library:
|
|||
* Install with `cargo install just`
|
||||
* [mdbook](https://rust-lang.github.io/mdBook/index.html)
|
||||
* Install with `cargo install mdbook`
|
||||
* [miri](https://github.com/rust-lang/miri)
|
||||
* Some of the unsafe code is tested using miri, install with `rustup component add miri`
|
||||
|
||||
With all of this installed, you should be able to run a full build of agb using by running
|
||||
```sh
|
||||
|
@ -85,6 +87,8 @@ for performant decimals.
|
|||
|
||||
`agb-sound-converter` - a crate which converts wav files into a format supported by the game boy advance
|
||||
|
||||
`agb-hashmap` - an no_std hashmap implementation tuned for use on the game boy advance
|
||||
|
||||
`agb` - the main library code
|
||||
|
||||
`agb/examples` - basic examples often targeting 1 feature, you can run these using `just run-example <example-name>`
|
||||
|
|
13
agb-hashmap/Cargo.toml
Normal file
13
agb-hashmap/Cargo.toml
Normal file
|
@ -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"] }
|
|
@ -1,9 +1,26 @@
|
|||
#![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(clippy::must_use_candidate)]
|
||||
#![deny(missing_docs)]
|
||||
#![deny(clippy::trivially_copy_pass_by_ref)]
|
||||
#![deny(clippy::semicolon_if_nothing_returned)]
|
||||
#![deny(clippy::map_unwrap_or)]
|
||||
#![deny(clippy::needless_pass_by_value)]
|
||||
#![deny(clippy::redundant_closure_for_method_calls)]
|
||||
#![deny(clippy::cloned_instead_of_copied)]
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
#![deny(rustdoc::private_intra_doc_links)]
|
||||
#![deny(rustdoc::invalid_html_tags)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::{alloc::Global, vec::Vec};
|
||||
use core::{
|
||||
alloc::Allocator,
|
||||
borrow::Borrow,
|
||||
hash::{BuildHasher, BuildHasherDefault, Hash, Hasher},
|
||||
iter::FromIterator,
|
||||
mem::{self, MaybeUninit},
|
||||
|
@ -83,6 +100,38 @@ type HashType = u32;
|
|||
///
|
||||
/// [`Eq`]: https://doc.rust-lang.org/core/cmp/trait.Eq.html
|
||||
/// [`Hash`]: https://doc.rust-lang.org/core/hash/trait.Hash.html
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use agb_hashmap::HashMap;
|
||||
///
|
||||
/// // Type inference lets you omit the type signature (which would be HashMap<String, String> in this example)
|
||||
/// let mut game_reviews = HashMap::new();
|
||||
///
|
||||
/// // Review some games
|
||||
/// game_reviews.insert(
|
||||
/// "Pokemon Emerald".to_string(),
|
||||
/// "Best post-game battle experience of any generation.".to_string(),
|
||||
/// );
|
||||
/// game_reviews.insert(
|
||||
/// "Golden Sun".to_string(),
|
||||
/// "Some of the best music on the console".to_string(),
|
||||
/// );
|
||||
/// game_reviews.insert(
|
||||
/// "Super Dodge Ball Advance".to_string(),
|
||||
/// "Really great launch title".to_string(),
|
||||
/// );
|
||||
///
|
||||
/// // Check for a specific entry
|
||||
/// if !game_reviews.contains_key("Legend of Zelda: The Minish Cap") {
|
||||
/// println!("We've got {} reviews, but The Minish Cap ain't one", game_reviews.len());
|
||||
/// }
|
||||
///
|
||||
/// // Iterate over everything
|
||||
/// for (game, review) in &game_reviews {
|
||||
/// println!("{game}: \"{review}\"");
|
||||
/// }
|
||||
/// ```
|
||||
pub struct HashMap<K, V, ALLOCATOR: Allocator = Global> {
|
||||
nodes: NodeStorage<K, V, ALLOCATOR>,
|
||||
|
||||
|
@ -283,13 +332,21 @@ where
|
|||
}
|
||||
|
||||
/// Returns `true` if the map contains a value for the specified key.
|
||||
pub fn contains_key(&self, k: &K) -> bool {
|
||||
pub fn contains_key<Q>(&self, k: &Q) -> bool
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
{
|
||||
let hash = self.hash(k);
|
||||
self.nodes.location(k, hash).is_some()
|
||||
}
|
||||
|
||||
/// Returns the key-value pair corresponding to the supplied key
|
||||
pub fn get_key_value(&self, key: &K) -> Option<(&K, &V)> {
|
||||
pub fn get_key_value<Q>(&self, key: &Q) -> Option<(&K, &V)>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
{
|
||||
let hash = self.hash(key);
|
||||
|
||||
self.nodes
|
||||
|
@ -299,13 +356,45 @@ where
|
|||
|
||||
/// Returns a reference to the value corresponding to the key. Returns [`None`] if there is
|
||||
/// no element in the map with the given key.
|
||||
pub fn get(&self, key: &K) -> Option<&V> {
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use agb_hashmap::HashMap;
|
||||
///
|
||||
/// let mut map = HashMap::new();
|
||||
/// map.insert("a".to_string(), "A");
|
||||
/// assert_eq!(map.get("a"), Some(&"A"));
|
||||
/// assert_eq!(map.get("b"), None);
|
||||
/// ```
|
||||
pub fn get<Q>(&self, key: &Q) -> Option<&V>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
{
|
||||
self.get_key_value(key).map(|(_, v)| v)
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the value corresponding to the key. Return [`None`] if
|
||||
/// there is no element in the map with the given key.
|
||||
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use agb_hashmap::HashMap;
|
||||
///
|
||||
/// let mut map = HashMap::new();
|
||||
/// map.insert("a".to_string(), "A");
|
||||
///
|
||||
/// if let Some(x) = map.get_mut("a") {
|
||||
/// *x = "b";
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(map["a"], "b");
|
||||
/// ```
|
||||
pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
{
|
||||
let hash = self.hash(key);
|
||||
|
||||
if let Some(location) = self.nodes.location(key, hash) {
|
||||
|
@ -317,7 +406,21 @@ where
|
|||
|
||||
/// Removes the given key from the map. Returns the current value if it existed, or [`None`]
|
||||
/// if it did not.
|
||||
pub fn remove(&mut self, key: &K) -> Option<V> {
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use agb_hashmap::HashMap;
|
||||
///
|
||||
/// let mut map = HashMap::new();
|
||||
/// map.insert(1, "a");
|
||||
/// assert_eq!(map.remove(&1), Some("a"));
|
||||
/// assert_eq!(map.remove(&1), None);
|
||||
/// ```
|
||||
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized,
|
||||
{
|
||||
let hash = self.hash(key);
|
||||
|
||||
self.nodes
|
||||
|
@ -330,7 +433,11 @@ impl<K, V, ALLOCATOR: ClonableAllocator> HashMap<K, V, ALLOCATOR>
|
|||
where
|
||||
K: Hash,
|
||||
{
|
||||
fn hash(&self, key: &K) -> HashType {
|
||||
fn hash<Q>(&self, key: &Q) -> HashType
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + ?Sized,
|
||||
{
|
||||
let mut hasher = self.hasher.build_hasher();
|
||||
key.hash(&mut hasher);
|
||||
hasher.finish() as HashType
|
||||
|
@ -657,28 +764,18 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<K, V, ALLOCATOR: ClonableAllocator> Index<&K> for HashMap<K, V, ALLOCATOR>
|
||||
impl<K, V, Q, ALLOCATOR: ClonableAllocator> Index<&Q> for HashMap<K, V, ALLOCATOR>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
K: Eq + Hash + Borrow<Q>,
|
||||
Q: Eq + Hash + ?Sized,
|
||||
{
|
||||
type Output = V;
|
||||
|
||||
fn index(&self, key: &K) -> &V {
|
||||
fn index(&self, key: &Q) -> &V {
|
||||
self.get(key).expect("no entry found for key")
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, ALLOCATOR: ClonableAllocator> Index<K> for HashMap<K, V, ALLOCATOR>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
type Output = V;
|
||||
|
||||
fn index(&self, key: K) -> &V {
|
||||
self.get(&key).expect("no entry found for key")
|
||||
}
|
||||
}
|
||||
|
||||
const fn number_before_resize(capacity: usize) -> usize {
|
||||
capacity * 85 / 100
|
||||
}
|
||||
|
@ -812,9 +909,10 @@ impl<K, V, ALLOCATOR: ClonableAllocator> NodeStorage<K, V, ALLOCATOR> {
|
|||
}
|
||||
}
|
||||
|
||||
fn location(&self, key: &K, hash: HashType) -> Option<usize>
|
||||
fn location<Q>(&self, key: &Q, hash: HashType) -> Option<usize>
|
||||
where
|
||||
K: Eq,
|
||||
K: Borrow<Q>,
|
||||
Q: Eq + ?Sized,
|
||||
{
|
||||
for distance_to_initial_bucket in 0..(self.max_distance_to_initial_bucket + 1) {
|
||||
let location = fast_mod(
|
||||
|
@ -824,7 +922,7 @@ impl<K, V, ALLOCATOR: ClonableAllocator> NodeStorage<K, V, ALLOCATOR> {
|
|||
|
||||
let node = &self.nodes[location];
|
||||
if let Some(node_key_ref) = node.key_ref() {
|
||||
if node_key_ref == key {
|
||||
if node_key_ref.borrow() == key {
|
||||
return Some(location);
|
||||
}
|
||||
} else {
|
||||
|
@ -993,10 +1091,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 +1105,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 +1116,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 +1127,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 +1144,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 +1164,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 {
|
||||
|
@ -1086,6 +1183,7 @@ mod test {
|
|||
}
|
||||
|
||||
impl NoisyDrop {
|
||||
#[cfg(not(miri))]
|
||||
fn new(i: i32) -> Self {
|
||||
Self { i, dropped: false }
|
||||
}
|
||||
|
@ -1115,17 +1213,33 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
#[test_case]
|
||||
fn extreme_case(_gba: &mut Gba) {
|
||||
trait RngNextI32 {
|
||||
fn next_i32(&mut self) -> i32;
|
||||
}
|
||||
|
||||
impl<T> RngNextI32 for T
|
||||
where
|
||||
T: rand::RngCore,
|
||||
{
|
||||
fn next_i32(&mut self) -> i32 {
|
||||
self.next_u32() as i32
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(miri))] // takes way too long to run under miri
|
||||
#[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<i32>; 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 +1326,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 +1353,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 +1377,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 +1399,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 +1415,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 +1431,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 +1450,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 +1502,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 +1520,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 +1538,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 +1549,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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
|
|
|
@ -151,7 +151,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;
|
||||
|
|
8
book/games/pong/Cargo.lock
generated
8
book/games/pong/Cargo.lock
generated
|
@ -19,6 +19,7 @@ name = "agb"
|
|||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
|
@ -35,6 +36,13 @@ dependencies = [
|
|||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
|
|
8
examples/combo/Cargo.lock
generated
8
examples/combo/Cargo.lock
generated
|
@ -19,6 +19,7 @@ name = "agb"
|
|||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
|
@ -35,6 +36,13 @@ dependencies = [
|
|||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
|
|
8
examples/hyperspace-roll/Cargo.lock
generated
8
examples/hyperspace-roll/Cargo.lock
generated
|
@ -19,6 +19,7 @@ name = "agb"
|
|||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
|
@ -35,6 +36,13 @@ dependencies = [
|
|||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
|
|
8
examples/the-hat-chooses-the-wizard/Cargo.lock
generated
8
examples/the-hat-chooses-the-wizard/Cargo.lock
generated
|
@ -19,6 +19,7 @@ name = "agb"
|
|||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
|
@ -35,6 +36,13 @@ dependencies = [
|
|||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
|
|
8
examples/the-purple-night/Cargo.lock
generated
8
examples/the-purple-night/Cargo.lock
generated
|
@ -19,6 +19,7 @@ name = "agb"
|
|||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"agb_fixnum",
|
||||
"agb_hashmap",
|
||||
"agb_image_converter",
|
||||
"agb_macros",
|
||||
"agb_sound_converter",
|
||||
|
@ -35,6 +36,13 @@ dependencies = [
|
|||
"agb_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_hashmap"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agb_image_converter"
|
||||
version = "0.14.0"
|
||||
|
|
7
justfile
7
justfile
|
@ -14,6 +14,7 @@ clippy:
|
|||
test:
|
||||
just _test-debug agb
|
||||
just _test-debug agb-fixnum
|
||||
just _test-debug agb-hashmap
|
||||
just _test-debug-arm agb
|
||||
just _test-debug tools
|
||||
|
||||
|
@ -27,6 +28,7 @@ doctest-agb:
|
|||
check-docs:
|
||||
(cd agb && cargo doc --target=thumbv6m-none-eabi --no-deps)
|
||||
just _build_docs agb-fixnum
|
||||
just _build_docs agb-hashmap
|
||||
|
||||
_build_docs crate:
|
||||
(cd "{{crate}}" && cargo doc --no-deps)
|
||||
|
@ -59,7 +61,7 @@ check-linker-script-consistency:
|
|||
find -type f -name gba.ld -print0 | xargs -0 -n1 cmp -- agb/gba.ld
|
||||
find -type f -name gba_mb.ld -print0 | xargs -0 -n1 cmp -- agb/gba_mb.ld
|
||||
|
||||
ci: check-linker-script-consistency build-debug clippy fmt-check test build-release test-release doctest-agb build-roms build-book check-docs
|
||||
ci: check-linker-script-consistency build-debug clippy fmt-check test miri build-release test-release doctest-agb build-roms build-book check-docs
|
||||
|
||||
build-roms:
|
||||
just _build-rom "examples/the-purple-night" "PURPLENIGHT"
|
||||
|
@ -85,6 +87,9 @@ publish *args: (_run-tool "publish" args)
|
|||
|
||||
release +args: (_run-tool "release" args)
|
||||
|
||||
miri:
|
||||
(cd agb-hashmap && cargo miri test)
|
||||
|
||||
_run-tool +tool:
|
||||
(cd tools && cargo build)
|
||||
"$CARGO_TARGET_DIR/debug/tools" {{tool}}
|
||||
|
|
|
@ -170,7 +170,8 @@ mod test {
|
|||
"agb-image-converter",
|
||||
"agb-sound-converter",
|
||||
"agb-macros",
|
||||
"agb-fixnum"
|
||||
"agb-fixnum",
|
||||
"agb-hashmap",
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
|
|
Loading…
Reference in a new issue