diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f92a6c08..2f873f9a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -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 diff --git a/.vscode/agb.code-workspace b/.vscode/agb.code-workspace index 1ef7a26a..3cd3604a 100644 --- a/.vscode/agb.code-workspace +++ b/.vscode/agb.code-workspace @@ -41,6 +41,9 @@ }, { "path": "../examples/combo" + }, + { + "path": "../agb-hashmap" } ] -} +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b3a04cf..f65ee3aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,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 - Moved the existing object API to be the OamManaged API. The old names persist with deprecated notices on them. ## [0.14.0] - 2023/04/11 diff --git a/README.md b/README.md index ed82fb7b..4cf9e327 100644 --- a/README.md +++ b/README.md @@ -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 ` diff --git a/agb-hashmap/Cargo.toml b/agb-hashmap/Cargo.toml new file mode 100644 index 00000000..3a1a7875 --- /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 87% rename from agb/src/hash_map.rs rename to agb-hashmap/src/lib.rs index 59b71ad8..147af353 100644 --- a/agb/src/hash_map.rs +++ b/agb-hashmap/src/lib.rs @@ -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 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 { nodes: NodeStorage, @@ -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(&self, k: &Q) -> bool + where + K: Borrow, + 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(&self, key: &Q) -> Option<(&K, &V)> + where + K: Borrow, + 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(&self, key: &Q) -> Option<&V> + where + K: Borrow, + 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(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + 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 { + /// + /// # 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(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { let hash = self.hash(key); self.nodes @@ -330,7 +433,11 @@ impl HashMap where K: Hash, { - fn hash(&self, key: &K) -> HashType { + fn hash(&self, key: &Q) -> HashType + where + K: Borrow, + Q: Hash + ?Sized, + { let mut hasher = self.hasher.build_hasher(); key.hash(&mut hasher); hasher.finish() as HashType @@ -657,28 +764,18 @@ where } } -impl Index<&K> for HashMap +impl Index<&Q> for HashMap where - K: Eq + Hash, + K: Eq + Hash + Borrow, + 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 Index for HashMap -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 NodeStorage { } } - fn location(&self, key: &K, hash: HashType) -> Option + fn location(&self, key: &Q, hash: HashType) -> Option where - K: Eq, + K: Borrow, + 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 NodeStorage { 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 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; 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); - } - } } diff --git a/agb/Cargo.toml b/agb/Cargo.toml index 583033a5..1f572e9a 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 5b2b1ba4..f6a9e71b 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -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; diff --git a/book/games/pong/Cargo.lock b/book/games/pong/Cargo.lock index 684bc4cd..36dbf3d2 100644 --- a/book/games/pong/Cargo.lock +++ b/book/games/pong/Cargo.lock @@ -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" diff --git a/examples/combo/Cargo.lock b/examples/combo/Cargo.lock index b5f8afee..e623a679 100644 --- a/examples/combo/Cargo.lock +++ b/examples/combo/Cargo.lock @@ -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" diff --git a/examples/hyperspace-roll/Cargo.lock b/examples/hyperspace-roll/Cargo.lock index fb281e7a..aebc74e3 100644 --- a/examples/hyperspace-roll/Cargo.lock +++ b/examples/hyperspace-roll/Cargo.lock @@ -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" diff --git a/examples/the-hat-chooses-the-wizard/Cargo.lock b/examples/the-hat-chooses-the-wizard/Cargo.lock index f04d1e14..725bbc69 100644 --- a/examples/the-hat-chooses-the-wizard/Cargo.lock +++ b/examples/the-hat-chooses-the-wizard/Cargo.lock @@ -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" diff --git a/examples/the-purple-night/Cargo.lock b/examples/the-purple-night/Cargo.lock index 41727379..b0ade7b4 100644 --- a/examples/the-purple-night/Cargo.lock +++ b/examples/the-purple-night/Cargo.lock @@ -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" diff --git a/justfile b/justfile index 4a0b9e7d..0c3326fd 100644 --- a/justfile +++ b/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}} diff --git a/tools/src/publish.rs b/tools/src/publish.rs index be68209f..48d2d616 100644 --- a/tools/src/publish.rs +++ b/tools/src/publish.rs @@ -170,7 +170,8 @@ mod test { "agb-image-converter", "agb-sound-converter", "agb-macros", - "agb-fixnum" + "agb-fixnum", + "agb-hashmap", ] ); Ok(())