summaryrefslogtreecommitdiffstats
path: root/third_party/rust/thunderdome
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/thunderdome
parentInitial commit. (diff)
downloadfirefox-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.json1
-rw-r--r--third_party/rust/thunderdome/CHANGELOG.md32
-rw-r--r--third_party/rust/thunderdome/Cargo.toml24
-rw-r--r--third_party/rust/thunderdome/LICENSE-APACHE201
-rw-r--r--third_party/rust/thunderdome/LICENSE-MIT19
-rw-r--r--third_party/rust/thunderdome/README.md81
-rw-r--r--third_party/rust/thunderdome/README.tpl15
-rw-r--r--third_party/rust/thunderdome/src/arena.rs478
-rw-r--r--third_party/rust/thunderdome/src/drain.rs96
-rw-r--r--third_party/rust/thunderdome/src/free_pointer.rs46
-rw-r--r--third_party/rust/thunderdome/src/generation.rs61
-rw-r--r--third_party/rust/thunderdome/src/into_iter.rs79
-rw-r--r--third_party/rust/thunderdome/src/iter.rs82
-rw-r--r--third_party/rust/thunderdome/src/iter_mut.rs82
-rw-r--r--third_party/rust/thunderdome/src/lib.rs90
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;