diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/thunderdome | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/thunderdome')
-rw-r--r-- | third_party/rust/thunderdome/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/thunderdome/CHANGELOG.md | 32 | ||||
-rw-r--r-- | third_party/rust/thunderdome/Cargo.toml | 24 | ||||
-rw-r--r-- | third_party/rust/thunderdome/LICENSE-APACHE | 201 | ||||
-rw-r--r-- | third_party/rust/thunderdome/LICENSE-MIT | 19 | ||||
-rw-r--r-- | third_party/rust/thunderdome/README.md | 81 | ||||
-rw-r--r-- | third_party/rust/thunderdome/README.tpl | 15 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/arena.rs | 478 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/drain.rs | 96 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/free_pointer.rs | 46 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/generation.rs | 61 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/into_iter.rs | 79 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/iter.rs | 82 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/iter_mut.rs | 82 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/lib.rs | 90 |
15 files changed, 1387 insertions, 0 deletions
diff --git a/third_party/rust/thunderdome/.cargo-checksum.json b/third_party/rust/thunderdome/.cargo-checksum.json new file mode 100644 index 0000000000..86e7f01107 --- /dev/null +++ b/third_party/rust/thunderdome/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"4507f99af9dfe4c8d5ec4a636534b0205801568433460bdd504d96d845f45ded","Cargo.toml":"f730731cddf6f0ce6c098623ed33c380749b6a0d7b7c8d6efc0f03d21102b6e9","LICENSE-APACHE":"6de8a720dfd320383f839d3c569a934aead98b3154e24751827dbca644f3349a","LICENSE-MIT":"732a0060f33bbb7f6df1bfc0ebad6e4a856d20ccf66cc57b2516231cf0742508","README.md":"42e5fa6ba8400389a3125a5700f162370aefb8d175467c6a9356f19ca9ce30ac","README.tpl":"1d9b4c54663143a1d50a7fe0aa1f0f083dd4505c3267dbaaaaaace87e212cdfa","src/arena.rs":"86d282c86a3a8b341196926364bd4aa275a95e5dae5efe5d96c30281e0c5799a","src/drain.rs":"29d8956800a719e6cf7991a50817a5870fd9b680cb939f1d4f36ac8969a74992","src/free_pointer.rs":"bfec9241b986386974269740deaf9beaeb3f24707203da3043affe3a1fa4ec12","src/generation.rs":"f890fd2c22f03593a99a7a8cc37af5ecafdbe5e5814908332e320711a43e11ee","src/into_iter.rs":"7ceafc178f86cdeb2e6ae43c926d5e2ab6583da88fac8390b564a7bb32e5d2f8","src/iter.rs":"515285e2daaec1205c745df661ced1ba9cee804bc0745476633afd31d102ed0c","src/iter_mut.rs":"b14351dca05ef1a75caaa11d06d3d26bc645aec25aae0e19ad2e64d7038bf518","src/lib.rs":"7bf87d72cae5c390ffae2bbaa744dbd4d02bb3d8c75ee1018e768a93dd75a66f"},"package":"7572415bd688d401c52f6e36f4c8e805b9ae1622619303b9fa835d531db0acae"}
\ No newline at end of file diff --git a/third_party/rust/thunderdome/CHANGELOG.md b/third_party/rust/thunderdome/CHANGELOG.md new file mode 100644 index 0000000000..fcc6c724d4 --- /dev/null +++ b/third_party/rust/thunderdome/CHANGELOG.md @@ -0,0 +1,32 @@ +# Thunderdome Changelog + +## Unreleased Changes + +## 0.3.0 (2020-10-16) +* Added `Arena::invalidate` for invalidating indices on-demand, as a faster remove-followed-by-reinsert. +* Added `Index::to_bits` and `Index::from_bits` for converting indices to a form convenient for passing outside of Rust. +* Added `Arena::clear` for conveniently clearing the whole arena. +* Change the semantics of `Arena::drain` to drop any remaining uniterated items when the `Drain` iterator is dropped, clearing the `Arena`. + +## 0.2.1 (2020-10-01) +* Added `Default` implementation for `Arena`. +* Added `IntoIterator` implementation for `Arena` ([#1](https://github.com/LPGhatguy/thunderdome/issues/1)) +* Added `Arena::iter` and `Arena::iter_mut` ([#2](https://github.com/LPGhatguy/thunderdome/issues/2)) + +## 0.2.0 (2020-09-03) +* Bumped MSRV to 1.34.1. +* Reduced size of `Index` by limiting `Arena` to 2^32 elements and 2^32 generations per slot. + * These limits should not be hit in practice, but will consistently trigger panics. +* Changed generation counter to wrap instead of panic on overflow. + * Collisions where an index using the same slot and a colliding generation on [1, 2^32] should be incredibly unlikely. + +## 0.1.1 (2020-09-02) +* Added `Arena::with_capacity` for preallocating space. +* Added `Arena::len`, `Arena::capacity`, and `Arena::is_empty`. +* Improved panic-on-wrap guarantees, especially around unsafe code. +* Simplified and documented implementation. + +## 0.1.0 (2020-09-02) +* Initial release +* Pretty much completely untested +* You probably shouldn't use this version
\ No newline at end of file diff --git a/third_party/rust/thunderdome/Cargo.toml b/third_party/rust/thunderdome/Cargo.toml new file mode 100644 index 0000000000..37715486b6 --- /dev/null +++ b/third_party/rust/thunderdome/Cargo.toml @@ -0,0 +1,24 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "thunderdome" +version = "0.3.0" +authors = ["Lucien Greathouse <me@lpghatguy.com>"] +description = "Fast arena allocator with compact generational indices" +homepage = "https://github.com/LPGhatguy/thunderdome" +documentation = "https://docs.rs/thunderdome" +readme = "README.md" +keywords = ["arena", "slab", "generational"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/LPGhatguy/thunderdome" diff --git a/third_party/rust/thunderdome/LICENSE-APACHE b/third_party/rust/thunderdome/LICENSE-APACHE new file mode 100644 index 0000000000..92ac56e0ac --- /dev/null +++ b/third_party/rust/thunderdome/LICENSE-APACHE @@ -0,0 +1,201 @@ +i Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/third_party/rust/thunderdome/LICENSE-MIT b/third_party/rust/thunderdome/LICENSE-MIT new file mode 100644 index 0000000000..54d0334da1 --- /dev/null +++ b/third_party/rust/thunderdome/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2020 Lucien Greathouse
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file diff --git a/third_party/rust/thunderdome/README.md b/third_party/rust/thunderdome/README.md new file mode 100644 index 0000000000..2d9d45b25e --- /dev/null +++ b/third_party/rust/thunderdome/README.md @@ -0,0 +1,81 @@ +# Thunderdome
+
+[![GitHub CI Status](https://github.com/LPGhatguy/thunderdome/workflows/CI/badge.svg)](https://github.com/LPGhatguy/thunderdome/actions) +[![thunderdome on crates.io](https://img.shields.io/crates/v/thunderdome.svg)](https://crates.io/crates/thunderdome) +[![thunderdome docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/thunderdome) + +Thunderdome is a ~gladitorial~ generational arena inspired by +[generational-arena](https://crates.io/crates/generational-arena), +[slotmap](https://crates.io/crates/slotmap), and +[slab](https://crates.io/crates/slab). It provides constant time insertion, +lookup, and removal via small (8 byte) keys returned from `Arena`. + +Thunderdome's key type, `Index`, is still 8 bytes when put inside of an +`Option<T>` thanks to Rust's `NonZero*` types. + +### Basic Examples + +```rust +let mut arena = Arena::new(); + +let foo = arena.insert("Foo"); +let bar = arena.insert("Bar"); + +assert_eq!(arena[foo], "Foo"); +assert_eq!(arena[bar], "Bar"); + +arena[bar] = "Replaced"; +assert_eq!(arena[bar], "Replaced"); + +let foo_value = arena.remove(foo); +assert_eq!(foo_value, Some("Foo")); + +// The slot previously used by foo will be reused for baz +let baz = arena.insert("Baz"); +assert_eq!(arena[baz], "Baz"); + +// foo is no longer a valid key +assert_eq!(arena.get(foo), None); +``` + +### Comparison With Similar Crates + +| Feature | Thunderdome | generational-arena | slotmap | slab | +|------------------------------|-------------|--------------------|---------|------| +| Generational Indices¹ | Yes | Yes | Yes | No | +| `size_of::<Index>()` | 8 | 16 | 8 | 8 | +| `size_of::<Option<Index>>()` | 8 | 24 | 8 | 16 | +| Max Elements | 2³² | 2⁶⁴ | 2³² | 2⁶⁴ | +| Non-`Copy` Values | Yes | Yes | Sorta² | Yes | +| `no_std` Support | No | Yes | No | No | +| Serde Support | No | Yes | Yes | No | + +* Sizes calculated on rustc `1.44.0-x86_64-pc-windows-msvc` +* See [the Thunderdome comparison + Cargo.toml](https://github.com/LPGhatguy/thunderdome/blob/main/comparison/Cargo.toml) + for versions of each library tested. + +1. Generational indices help solve the [ABA + Problem](https://en.wikipedia.org/wiki/ABA_problem), which can cause dangling + keys to mistakenly access newly-inserted data. +2. slotmap's `SlotMap` and `HopSlotMap` require values to be `Copy` on stable + Rust versions. slotmap's `DenseSlotMap` type supports non-`Copy` types on + stable, but has different performance trade-offs. + +### Minimum Supported Rust Version (MSRV) + +Thunderdome supports Rust 1.34.1 and newer. Until Thunderdome reaches 1.0, +changes to the MSRV will require major version bumps. After 1.0, MSRV changes +will only require minor version bumps, but will need significant justification.
+
+## License
+
+Licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/third_party/rust/thunderdome/README.tpl b/third_party/rust/thunderdome/README.tpl new file mode 100644 index 0000000000..9504c7f28e --- /dev/null +++ b/third_party/rust/thunderdome/README.tpl @@ -0,0 +1,15 @@ +# Thunderdome
+
+{{readme}}
+
+## License
+
+Licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
\ No newline at end of file diff --git a/third_party/rust/thunderdome/src/arena.rs b/third_party/rust/thunderdome/src/arena.rs new file mode 100644 index 0000000000..7125266071 --- /dev/null +++ b/third_party/rust/thunderdome/src/arena.rs @@ -0,0 +1,478 @@ +use std::convert::TryInto; +use std::mem::replace; +use std::ops; + +use crate::drain::Drain; +use crate::free_pointer::FreePointer; +use crate::generation::Generation; +use crate::into_iter::IntoIter; +use crate::iter::Iter; +use crate::iter_mut::IterMut; + +/// Container that can have elements inserted into it and removed from it. +/// +/// Indices use the [`Index`][Index] type, created by inserting values with +/// [`Arena::insert`][Arena::insert]. +#[derive(Debug, Clone)] +pub struct Arena<T> { + storage: Vec<Entry<T>>, + len: u32, + first_free: Option<FreePointer>, +} + +/// Index type for [`Arena`][Arena] that has a generation attached to it. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Index { + pub(crate) slot: u32, + pub(crate) generation: Generation, +} + +impl Index { + /// Convert this `Index` to an equivalent `u64` representation. Mostly + /// useful for passing to code outside of Rust. + #[allow(clippy::integer_arithmetic)] + pub fn to_bits(self) -> u64 { + // This is safe because a `u32` bit-shifted by 32 will still fit in a `u64`. + ((self.generation.to_u32() as u64) << 32) | (self.slot as u64) + } + + /// Convert back from a value generated with `Index::to_bits`. Don't call + /// this with arbitrary inputs; you'll almost certainly just get invalid + /// and/or malformed indices. + /// + /// If fed an index which was not generated by thunderdome or even just run + /// `Index::from_bits(0)`, this function may panic! + #[allow(clippy::integer_arithmetic)] + pub fn from_bits(bits: u64) -> Self { + // By bit-shifting right by 32, we're undoing the left-shift in `to_bits` + // thus this is okay by the same rationale. + let generation = Generation::from_u32((bits >> 32) as u32); + let slot = bits as u32; + + Self { generation, slot } + } +} + +#[derive(Debug, Clone)] +pub(crate) enum Entry<T> { + Occupied(OccupiedEntry<T>), + Empty(EmptyEntry), +} + +impl<T> Entry<T> { + /// Consume the entry, and if it's occupied, return the value. + fn into_value(self) -> Option<T> { + match self { + Entry::Occupied(occupied) => Some(occupied.value), + Entry::Empty(_) => None, + } + } + + /// If the entry is empty, return a copy of the emptiness data. + fn get_empty(&self) -> Option<EmptyEntry> { + match self { + Entry::Empty(empty) => Some(*empty), + Entry::Occupied(_) => None, + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct OccupiedEntry<T> { + pub(crate) generation: Generation, + pub(crate) value: T, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct EmptyEntry { + pub(crate) generation: Generation, + pub(crate) next_free: Option<FreePointer>, +} + +impl<T> Arena<T> { + /// Construct an empty arena. + pub fn new() -> Self { + Self { + storage: Vec::new(), + len: 0, + first_free: None, + } + } + + /// Construct an empty arena with space to hold exactly `capacity` elements + /// without reallocating. + pub fn with_capacity(capacity: usize) -> Self { + Self { + storage: Vec::with_capacity(capacity), + len: 0, + first_free: None, + } + } + + /// Return the number of elements contained in the arena. + pub fn len(&self) -> usize { + self.len as usize + } + + /// Return the number of elements the arena can hold without allocating, + /// including the elements currently in the arena. + pub fn capacity(&self) -> usize { + self.storage.capacity() + } + + /// Returns whether the arena is empty. + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Insert a new value into the arena, returning an index that can be used + /// to later retrieve the value. + pub fn insert(&mut self, value: T) -> Index { + // This value will definitely be inserted, so we can update length now. + self.len = self + .len + .checked_add(1) + .unwrap_or_else(|| panic!("Cannot insert more than u32::MAX elements into Arena")); + + // If there was a previously free entry, we can re-use its slot as long + // as we increment its generation. + if let Some(free_pointer) = self.first_free { + let slot = free_pointer.slot(); + let entry = self.storage.get_mut(slot as usize).unwrap_or_else(|| { + unreachable!("first_free pointed past the end of the arena's storage") + }); + + let empty = entry + .get_empty() + .unwrap_or_else(|| unreachable!("first_free pointed to an occupied entry")); + + // If there is another empty entry after this one, we'll update the + // arena to point to it to use it on the next insertion. + self.first_free = empty.next_free; + + // Overwrite the entry directly using our mutable reference instead + // of indexing into our storage again. This should avoid an + // additional bounds check. + let generation = empty.generation.next(); + *entry = Entry::Occupied(OccupiedEntry { generation, value }); + + Index { slot, generation } + } else { + // There were no more empty entries left in our free list, so we'll + // create a new first-generation entry and push it into storage. + + let generation = Generation::first(); + let slot: u32 = self.storage.len().try_into().unwrap_or_else(|_| { + unreachable!("Arena storage exceeded what can be represented by a u32") + }); + + self.storage + .push(Entry::Occupied(OccupiedEntry { generation, value })); + + Index { slot, generation } + } + } + + /// Get an immutable reference to a value inside the arena by + /// [`Index`][Index], returning `None` if the index is not contained in the + /// arena. + pub fn get(&self, index: Index) -> Option<&T> { + match self.storage.get(index.slot as usize) { + Some(Entry::Occupied(occupied)) if occupied.generation == index.generation => { + Some(&occupied.value) + } + _ => None, + } + } + + /// Get a mutable reference to a value inside the arena by [`Index`][Index], + /// returning `None` if the index is not contained in the arena. + pub fn get_mut(&mut self, index: Index) -> Option<&mut T> { + match self.storage.get_mut(index.slot as usize) { + Some(Entry::Occupied(occupied)) if occupied.generation == index.generation => { + Some(&mut occupied.value) + } + _ => None, + } + } + + /// Remove the value contained at the given index from the arena, returning + /// it if it was present. + pub fn remove(&mut self, index: Index) -> Option<T> { + let entry = self.storage.get_mut(index.slot as usize)?; + + match entry { + Entry::Occupied(occupied) if occupied.generation == index.generation => { + // We can replace an occupied entry with an empty entry with the + // same generation. On next insertion, this generation will + // increment. + let new_entry = Entry::Empty(EmptyEntry { + generation: occupied.generation, + next_free: self.first_free, + }); + + // Swap our new entry into our storage and take ownership of the + // old entry. We'll consume it for its value so we can give that + // back to our caller. + let old_entry = replace(entry, new_entry); + let value = old_entry.into_value().unwrap_or_else(|| unreachable!()); + + // The next time we insert, we can re-use the empty entry we + // just created. If another removal happens before then, that + // entry will be used before this one (FILO). + self.first_free = Some(FreePointer::from_slot(index.slot)); + + self.len = self.len.checked_sub(1).unwrap_or_else(|| unreachable!()); + + Some(value) + } + _ => None, + } + } + + /// Invalidate the given index and return a new index to the same value. This + /// is roughly equivalent to `remove` followed by `insert`, but much faster. + /// If the old index is already invalid, this method returns `None`. + pub fn invalidate(&mut self, index: Index) -> Option<Index> { + let entry = self.storage.get_mut(index.slot as usize)?; + + match entry { + Entry::Occupied(occupied) if occupied.generation == index.generation => { + occupied.generation = occupied.generation.next(); + + Some(Index { + generation: occupied.generation, + ..index + }) + } + _ => None, + } + } + + /// Clear the arena and drop all elements. + pub fn clear(&mut self) { + self.drain().for_each(drop); + } + + /// Iterate over all of the indexes and values contained in the arena. + /// + /// Iteration order is not defined. + pub fn iter(&self) -> Iter<'_, T> { + Iter { + inner: self.storage.iter().enumerate(), + len: self.len, + } + } + + /// Iterate over all of the indexes and values contained in the arena, with + /// mutable access to each value. + /// + /// Iteration order is not defined. + pub fn iter_mut(&mut self) -> IterMut<'_, T> { + IterMut { + inner: self.storage.iter_mut().enumerate(), + len: self.len, + } + } + + /// Returns an iterator that removes each element from the arena. + /// + /// Iteration order is not defined. + /// + /// If the iterator is dropped before it is fully consumed, any uniterated + /// items will be dropped from the arena, and the arena will be empty. + /// The arena's capacity will not be changed. + pub fn drain(&mut self) -> Drain<'_, T> { + Drain { + arena: self, + slot: 0, + } + } +} + +/// Methods exposed only within the crate. +impl<T> Arena<T> { + /// This method is a lot like `remove`, but takes no generation. It's used + /// as part of `drain` and can likely be exposed as a public API eventually. + pub(crate) fn remove_entry_by_slot(&mut self, slot: u32) -> Option<(Index, T)> { + let entry = self.storage.get_mut(slot as usize)?; + + match entry { + Entry::Occupied(occupied) => { + // Construct the index that would be used to access this entry. + let index = Index { + generation: occupied.generation, + slot, + }; + + // This occupied entry will be replaced with an empty one of the + // same generation. Generation will be incremented on the next + // insert. + let next_entry = Entry::Empty(EmptyEntry { + generation: occupied.generation, + next_free: self.first_free, + }); + + // Swap new entry into place and consume the old one. + let old_entry = replace(entry, next_entry); + let value = old_entry.into_value().unwrap_or_else(|| unreachable!()); + + // Set this entry as the next one that should be inserted into, + // should an insertion happen. + self.first_free = Some(FreePointer::from_slot(slot)); + + self.len = self.len.checked_sub(1).unwrap_or_else(|| unreachable!()); + + Some((index, value)) + } + _ => None, + } + } +} + +impl<T> Default for Arena<T> { + fn default() -> Self { + Arena::new() + } +} + +impl<T> IntoIterator for Arena<T> { + type Item = (Index, T); + type IntoIter = IntoIter<T>; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + arena: self, + slot: 0, + } + } +} + +impl<T> ops::Index<Index> for Arena<T> { + type Output = T; + + fn index(&self, index: Index) -> &Self::Output { + self.get(index) + .unwrap_or_else(|| panic!("No entry at index {:?}", index)) + } +} + +impl<T> ops::IndexMut<Index> for Arena<T> { + fn index_mut(&mut self, index: Index) -> &mut Self::Output { + self.get_mut(index) + .unwrap_or_else(|| panic!("No entry at index {:?}", index)) + } +} + +#[cfg(test)] +mod test { + use super::{Arena, Index}; + + use std::mem::size_of; + + #[test] + fn size_of_index() { + assert_eq!(size_of::<Index>(), 8); + assert_eq!(size_of::<Option<Index>>(), 8); + } + + #[test] + fn new() { + let arena: Arena<u32> = Arena::new(); + assert_eq!(arena.len(), 0); + assert_eq!(arena.capacity(), 0); + } + + #[test] + fn with_capacity() { + let arena: Arena<u32> = Arena::with_capacity(8); + assert_eq!(arena.len(), 0); + assert_eq!(arena.capacity(), 8); + } + + #[test] + fn insert_and_get() { + let mut arena = Arena::new(); + + let one = arena.insert(1); + assert_eq!(arena.len(), 1); + assert_eq!(arena.get(one), Some(&1)); + + let two = arena.insert(2); + assert_eq!(arena.len(), 2); + assert_eq!(arena.get(one), Some(&1)); + assert_eq!(arena.get(two), Some(&2)); + } + + #[test] + fn insert_remove_get() { + let mut arena = Arena::new(); + let one = arena.insert(1); + + let two = arena.insert(2); + assert_eq!(arena.len(), 2); + assert_eq!(arena.remove(two), Some(2)); + + let three = arena.insert(3); + assert_eq!(arena.len(), 2); + assert_eq!(arena.get(one), Some(&1)); + assert_eq!(arena.get(three), Some(&3)); + assert_eq!(arena.get(two), None); + } + + #[test] + fn get_mut() { + let mut arena = Arena::new(); + let foo = arena.insert(5); + + let handle = arena.get_mut(foo).unwrap(); + *handle = 6; + + assert_eq!(arena.get(foo), Some(&6)); + } + + #[test] + fn insert_remove_insert_capacity() { + let mut arena = Arena::with_capacity(2); + assert_eq!(arena.capacity(), 2); + + let a = arena.insert("a"); + let b = arena.insert("b"); + assert_eq!(arena.len(), 2); + assert_eq!(arena.capacity(), 2); + + arena.remove(a); + arena.remove(b); + assert_eq!(arena.len(), 0); + assert_eq!(arena.capacity(), 2); + + let _a2 = arena.insert("a2"); + let _b2 = arena.insert("b2"); + assert_eq!(arena.len(), 2); + assert_eq!(arena.capacity(), 2); + } + + #[test] + fn invalidate() { + let mut arena = Arena::new(); + + let a = arena.insert("a"); + assert_eq!(arena.get(a), Some(&"a")); + + let new_a = arena.invalidate(a).unwrap(); + assert_eq!(arena.get(a), None); + assert_eq!(arena.get(new_a), Some(&"a")); + } + + #[test] + fn index_bits_roundtrip() { + let index = Index::from_bits(0x1BADCAFE_DEADBEEF); + assert_eq!(index.to_bits(), 0x1BADCAFE_DEADBEEF); + } + + #[test] + #[should_panic] + fn index_bits_panic_on_zero_generation() { + Index::from_bits(0x00000000_DEADBEEF); + } +} diff --git a/third_party/rust/thunderdome/src/drain.rs b/third_party/rust/thunderdome/src/drain.rs new file mode 100644 index 0000000000..930c0962fa --- /dev/null +++ b/third_party/rust/thunderdome/src/drain.rs @@ -0,0 +1,96 @@ +use std::iter::{ExactSizeIterator, FusedIterator}; + +use crate::arena::{Arena, Index}; + +/// See [`Arena::drain`][Arena::drain]. +pub struct Drain<'a, T> { + pub(crate) arena: &'a mut Arena<T>, + pub(crate) slot: u32, +} + +impl<'a, T> Iterator for Drain<'a, T> { + type Item = (Index, T); + + fn next(&mut self) -> Option<Self::Item> { + loop { + // If there are no entries remaining in the arena, we should always + // return None. Using this check instead of comparing with the + // arena's size allows us to skip any trailing empty entries. + if self.arena.is_empty() { + return None; + } + + // slot may overflow if the arena's underlying storage contains more + // than 2^32 elements, but its internal length value was not + // changed, as it overflowing would panic before reaching this code. + let slot = self.slot; + self.slot = self + .slot + .checked_add(1) + .unwrap_or_else(|| panic!("Overflowed u32 trying to drain Arena")); + + // If this entry is occupied, this method will mark it as an empty. + // Otherwise, we'll continue looping until we've drained all + // occupied entries from the arena. + if let Some((index, value)) = self.arena.remove_entry_by_slot(slot) { + return Some((index, value)); + } + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.arena.len(), Some(self.arena.len())) + } +} + +impl<'a, T> FusedIterator for Drain<'a, T> {} +impl<'a, T> ExactSizeIterator for Drain<'a, T> {} + +impl<'a, T> Drop for Drain<'a, T> { + // Continue iterating/dropping if there are any elements left. + fn drop(&mut self) { + self.for_each(drop); + } +} + +#[cfg(test)] +mod test { + use crate::Arena; + + use std::collections::HashSet; + + #[test] + fn drain() { + let mut arena = Arena::with_capacity(2); + let one = arena.insert(1); + let two = arena.insert(2); + + let mut drained_pairs = HashSet::new(); + { + let mut drain = arena.drain(); + assert_eq!(drain.size_hint(), (2, Some(2))); + + drained_pairs.insert(drain.next().unwrap()); + assert_eq!(drain.size_hint(), (1, Some(1))); + + // Do not fully drain so we can ensure everything is dropped when the + // `Drain` is dropped. + assert_eq!(drain.size_hint(), (1, Some(1))); + } + + assert_eq!(arena.len(), 0); + assert_eq!(arena.capacity(), 2); + assert_eq!(drained_pairs.len(), 1); + + // We should still be able to use the arena after this. + let one_prime = arena.insert(1); + let two_prime = arena.insert(2); + + assert_eq!(arena.len(), 2); + assert_eq!(arena.capacity(), 2); + assert_eq!(arena.get(one_prime), Some(&1)); + assert_eq!(arena.get(two_prime), Some(&2)); + assert_eq!(arena.get(one), None); + assert_eq!(arena.get(two), None); + } +} diff --git a/third_party/rust/thunderdome/src/free_pointer.rs b/third_party/rust/thunderdome/src/free_pointer.rs new file mode 100644 index 0000000000..e0d5f4ae8a --- /dev/null +++ b/third_party/rust/thunderdome/src/free_pointer.rs @@ -0,0 +1,46 @@ +use std::num::NonZeroU32;
+
+/// Contains a reference to a free slot in an arena, encapsulating NonZeroU32
+/// to prevent off-by-one errors and leaking unsafety.
+///
+/// Uses NonZeroU32 to stay small when put inside an `Option`.
+#[derive(Debug, Clone, Copy)]
+#[repr(transparent)]
+pub(crate) struct FreePointer(NonZeroU32);
+
+impl FreePointer {
+ #[must_use]
+ pub(crate) fn from_slot(slot: u32) -> Self {
+ let value = slot
+ .checked_add(1)
+ .expect("u32 overflowed calculating free pointer from u32");
+
+ // This is safe because any u32 + 1 that didn't overflow must not be
+ // zero.
+ FreePointer(unsafe { NonZeroU32::new_unchecked(value) })
+ }
+
+ #[must_use]
+ #[allow(clippy::integer_arithmetic)]
+ pub(crate) fn slot(self) -> u32 {
+ // This will never underflow due to the field being guaranteed non-zero.
+ self.0.get() - 1
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::FreePointer;
+
+ #[test]
+ fn from_slot() {
+ let ptr = FreePointer::from_slot(0);
+ assert_eq!(ptr.slot(), 0);
+ }
+
+ #[test]
+ #[should_panic(expected = "u32 overflowed calculating free pointer from u32")]
+ fn panic_on_overflow() {
+ let _ = FreePointer::from_slot(std::u32::MAX);
+ }
+}
diff --git a/third_party/rust/thunderdome/src/generation.rs b/third_party/rust/thunderdome/src/generation.rs new file mode 100644 index 0000000000..e6982c383c --- /dev/null +++ b/third_party/rust/thunderdome/src/generation.rs @@ -0,0 +1,61 @@ +use std::num::NonZeroU32; + +/// Tracks the generation of an entry in an arena. Encapsulates NonZeroU32 to +/// reduce the number of redundant checks needed, as well as enforcing checked +/// arithmetic on advancing a generation. +/// +/// Uses NonZeroU32 to help `Index` stay the same size when put inside an +/// `Option`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(transparent)] +pub(crate) struct Generation(NonZeroU32); + +impl Generation { + #[must_use] + pub(crate) fn first() -> Self { + // This is safe because 1 is not zero. + Generation(unsafe { NonZeroU32::new_unchecked(1) }) + } + + #[must_use] + pub(crate) fn next(self) -> Self { + let last_generation = self.0.get(); + let next_generation = last_generation.checked_add(1).unwrap_or(1); + + // This is safe because value that would overflow is instead made 1. + Generation(unsafe { NonZeroU32::new_unchecked(next_generation) }) + } + + pub(crate) fn to_u32(self) -> u32 { + self.0.get() + } + + pub(crate) fn from_u32(gen: u32) -> Self { + Generation(NonZeroU32::new(gen).expect("generation IDs must be nonzero!")) + } +} + +#[cfg(test)] +mod test { + use super::Generation; + + use std::num::NonZeroU32; + + #[test] + fn first_and_next() { + let first = Generation::first(); + assert_eq!(first.0.get(), 1); + + let second = first.next(); + assert_eq!(second.0.get(), 2); + } + + #[test] + fn wrap_on_overflow() { + let max = Generation(NonZeroU32::new(std::u32::MAX).unwrap()); + assert_eq!(max.0.get(), std::u32::MAX); + + let next = max.next(); + assert_eq!(next.0.get(), 1); + } +} diff --git a/third_party/rust/thunderdome/src/into_iter.rs b/third_party/rust/thunderdome/src/into_iter.rs new file mode 100644 index 0000000000..e7dd0d9403 --- /dev/null +++ b/third_party/rust/thunderdome/src/into_iter.rs @@ -0,0 +1,79 @@ +use std::iter::{ExactSizeIterator, FusedIterator};
+
+use crate::arena::{Arena, Index};
+
+/// Iterator typed used when an Arena is turned [`IntoIterator`][IntoIterator].
+pub struct IntoIter<T> {
+ pub(crate) arena: Arena<T>,
+ pub(crate) slot: u32,
+}
+
+impl<T> Iterator for IntoIter<T> {
+ type Item = (Index, T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ // If there are no entries remaining in the arena, we should always
+ // return None. Using this check instead of comparing with the
+ // arena's size allows us to skip any trailing empty entries.
+ if self.arena.is_empty() {
+ return None;
+ }
+
+ // slot may overflow if the arena's underlying storage contains more
+ // than 2^32 elements, but its internal length value was not
+ // changed, as it overflowing would panic before reaching this code.
+ let slot = self.slot;
+ self.slot = self
+ .slot
+ .checked_add(1)
+ .unwrap_or_else(|| panic!("Overflowed u32 trying to into_iter Arena"));
+
+ // If this entry is occupied, this method will mark it as an empty.
+ // Otherwise, we'll continue looping until we've removed all
+ // occupied entries from the arena.
+ if let Some((index, value)) = self.arena.remove_entry_by_slot(slot) {
+ return Some((index, value));
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.arena.len(), Some(self.arena.len()))
+ }
+}
+
+impl<T> FusedIterator for IntoIter<T> {}
+impl<T> ExactSizeIterator for IntoIter<T> {}
+
+#[cfg(test)]
+mod test {
+ use crate::Arena;
+
+ use std::collections::HashSet;
+
+ #[test]
+ fn into_iter() {
+ let mut arena = Arena::with_capacity(2);
+ let one = arena.insert(1);
+ let two = arena.insert(2);
+
+ let mut pairs = HashSet::new();
+ let mut into_iter = arena.into_iter();
+ assert_eq!(into_iter.size_hint(), (2, Some(2)));
+
+ pairs.insert(into_iter.next().unwrap());
+ assert_eq!(into_iter.size_hint(), (1, Some(1)));
+
+ pairs.insert(into_iter.next().unwrap());
+ assert_eq!(into_iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(into_iter.next(), None);
+ assert_eq!(into_iter.next(), None);
+ assert_eq!(into_iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(pairs.len(), 2);
+ assert!(pairs.contains(&(one, 1)));
+ assert!(pairs.contains(&(two, 2)));
+ }
+}
diff --git a/third_party/rust/thunderdome/src/iter.rs b/third_party/rust/thunderdome/src/iter.rs new file mode 100644 index 0000000000..0a2404cae7 --- /dev/null +++ b/third_party/rust/thunderdome/src/iter.rs @@ -0,0 +1,82 @@ +use std::convert::TryInto;
+use std::iter::{Enumerate, ExactSizeIterator, FusedIterator};
+use std::slice;
+
+use crate::arena::{Entry, Index};
+
+/// See [`Arena::iter`][Arena::iter].
+pub struct Iter<'a, T> {
+ pub(crate) len: u32,
+ pub(crate) inner: Enumerate<slice::Iter<'a, Entry<T>>>,
+}
+
+impl<'a, T> Iterator for Iter<'a, T> {
+ type Item = (Index, &'a T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ if self.len == 0 {
+ return None;
+ }
+
+ match self.inner.next()? {
+ (_, Entry::Empty(_)) => continue,
+ (slot, Entry::Occupied(occupied)) => {
+ self.len = self
+ .len
+ .checked_sub(1)
+ .unwrap_or_else(|| unreachable!("Underflowed u32 trying to iterate Arena"));
+
+ let slot: u32 = slot
+ .try_into()
+ .unwrap_or_else(|_| unreachable!("Overflowed u32 trying to iterate Arena"));
+
+ let index = Index {
+ slot,
+ generation: occupied.generation,
+ };
+
+ return Some((index, &occupied.value));
+ }
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.len as usize, Some(self.len as usize))
+ }
+}
+
+impl<'a, T> FusedIterator for Iter<'a, T> {}
+impl<'a, T> ExactSizeIterator for Iter<'a, T> {}
+
+#[cfg(test)]
+mod test {
+ use crate::Arena;
+
+ use std::collections::HashSet;
+
+ #[test]
+ fn iter() {
+ let mut arena = Arena::with_capacity(2);
+ let one = arena.insert(1);
+ let two = arena.insert(2);
+
+ let mut pairs = HashSet::new();
+ let mut iter = arena.iter();
+ assert_eq!(iter.size_hint(), (2, Some(2)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (1, Some(1)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert!(pairs.contains(&(one, &1)));
+ assert!(pairs.contains(&(two, &2)));
+ }
+}
diff --git a/third_party/rust/thunderdome/src/iter_mut.rs b/third_party/rust/thunderdome/src/iter_mut.rs new file mode 100644 index 0000000000..7ff71c12e3 --- /dev/null +++ b/third_party/rust/thunderdome/src/iter_mut.rs @@ -0,0 +1,82 @@ +use std::convert::TryInto;
+use std::iter::{Enumerate, ExactSizeIterator, FusedIterator};
+use std::slice;
+
+use crate::arena::{Entry, Index};
+
+/// See [`Arena::iter_mut`][Arena::iter_mut].
+pub struct IterMut<'a, T> {
+ pub(crate) len: u32,
+ pub(crate) inner: Enumerate<slice::IterMut<'a, Entry<T>>>,
+}
+
+impl<'a, T> Iterator for IterMut<'a, T> {
+ type Item = (Index, &'a T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ if self.len == 0 {
+ return None;
+ }
+
+ match self.inner.next()? {
+ (_, Entry::Empty(_)) => continue,
+ (slot, Entry::Occupied(occupied)) => {
+ self.len = self
+ .len
+ .checked_sub(1)
+ .unwrap_or_else(|| unreachable!("Underflowed u32 trying to iterate Arena"));
+
+ let slot: u32 = slot
+ .try_into()
+ .unwrap_or_else(|_| unreachable!("Overflowed u32 trying to iterate Arena"));
+
+ let index = Index {
+ slot,
+ generation: occupied.generation,
+ };
+
+ return Some((index, &mut occupied.value));
+ }
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.len as usize, Some(self.len as usize))
+ }
+}
+
+impl<'a, T> FusedIterator for IterMut<'a, T> {}
+impl<'a, T> ExactSizeIterator for IterMut<'a, T> {}
+
+#[cfg(test)]
+mod test {
+ use crate::Arena;
+
+ use std::collections::HashSet;
+
+ #[test]
+ fn iter_mut() {
+ let mut arena = Arena::with_capacity(2);
+ let one = arena.insert(1);
+ let two = arena.insert(2);
+
+ let mut pairs = HashSet::new();
+ let mut iter = arena.iter_mut();
+ assert_eq!(iter.size_hint(), (2, Some(2)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (1, Some(1)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert!(pairs.contains(&(one, &1)));
+ assert!(pairs.contains(&(two, &2)));
+ }
+}
diff --git a/third_party/rust/thunderdome/src/lib.rs b/third_party/rust/thunderdome/src/lib.rs new file mode 100644 index 0000000000..08bd29e07e --- /dev/null +++ b/third_party/rust/thunderdome/src/lib.rs @@ -0,0 +1,90 @@ +/*! +[![GitHub CI Status](https://github.com/LPGhatguy/thunderdome/workflows/CI/badge.svg)](https://github.com/LPGhatguy/thunderdome/actions) +[![thunderdome on crates.io](https://img.shields.io/crates/v/thunderdome.svg)](https://crates.io/crates/thunderdome) +[![thunderdome docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/thunderdome) + +Thunderdome is a ~gladitorial~ generational arena inspired by +[generational-arena](https://crates.io/crates/generational-arena), +[slotmap](https://crates.io/crates/slotmap), and +[slab](https://crates.io/crates/slab). It provides constant time insertion, +lookup, and removal via small (8 byte) keys returned from `Arena`. + +Thunderdome's key type, `Index`, is still 8 bytes when put inside of an +`Option<T>` thanks to Rust's `NonZero*` types. + +## Basic Examples + +```rust +# use thunderdome::{Arena, Index}; +let mut arena = Arena::new(); + +let foo = arena.insert("Foo"); +let bar = arena.insert("Bar"); + +assert_eq!(arena[foo], "Foo"); +assert_eq!(arena[bar], "Bar"); + +arena[bar] = "Replaced"; +assert_eq!(arena[bar], "Replaced"); + +let foo_value = arena.remove(foo); +assert_eq!(foo_value, Some("Foo")); + +// The slot previously used by foo will be reused for baz +let baz = arena.insert("Baz"); +assert_eq!(arena[baz], "Baz"); + +// foo is no longer a valid key +assert_eq!(arena.get(foo), None); +``` + +## Comparison With Similar Crates + +| Feature | Thunderdome | generational-arena | slotmap | slab | +|------------------------------|-------------|--------------------|---------|------| +| Generational Indices¹ | Yes | Yes | Yes | No | +| `size_of::<Index>()` | 8 | 16 | 8 | 8 | +| `size_of::<Option<Index>>()` | 8 | 24 | 8 | 16 | +| Max Elements | 2³² | 2⁶⁴ | 2³² | 2⁶⁴ | +| Non-`Copy` Values | Yes | Yes | Sorta² | Yes | +| `no_std` Support | No | Yes | No | No | +| Serde Support | No | Yes | Yes | No | + +* Sizes calculated on rustc `1.44.0-x86_64-pc-windows-msvc` +* See [the Thunderdome comparison + Cargo.toml](https://github.com/LPGhatguy/thunderdome/blob/main/comparison/Cargo.toml) + for versions of each library tested. + +1. Generational indices help solve the [ABA + Problem](https://en.wikipedia.org/wiki/ABA_problem), which can cause dangling + keys to mistakenly access newly-inserted data. +2. slotmap's `SlotMap` and `HopSlotMap` require values to be `Copy` on stable + Rust versions. slotmap's `DenseSlotMap` type supports non-`Copy` types on + stable, but has different performance trade-offs. + +## Minimum Supported Rust Version (MSRV) + +Thunderdome supports Rust 1.34.1 and newer. Until Thunderdome reaches 1.0, +changes to the MSRV will require major version bumps. After 1.0, MSRV changes +will only require minor version bumps, but will need significant justification. +*/ + +#![forbid(missing_docs)] +// This crate is sensitive to integer overflow and wrapping behavior. As such, +// we should usually use methods like `checked_add` and `checked_sub` instead +// of the `Add` or `Sub` operators. +#![deny(clippy::integer_arithmetic)] + +mod arena; +mod drain; +mod free_pointer; +mod generation; +mod into_iter; +mod iter; +mod iter_mut; + +pub use crate::arena::{Arena, Index}; +pub use crate::drain::Drain; +pub use crate::into_iter::IntoIter; +pub use crate::iter::Iter; +pub use crate::iter_mut::IterMut; |