diff --git a/lib/cretonne/src/entity/list.rs b/lib/cretonne/src/entity/list.rs index 3685f00eab..e70909bf60 100644 --- a/lib/cretonne/src/entity/list.rs +++ b/lib/cretonne/src/entity/list.rs @@ -1,51 +1,4 @@ //! Small lists of entity references. -//! -//! This module defines an `EntityList` type which provides similar functionality to `Vec`, -//! but with some important differences in the implementation: -//! -//! 1. Memory is allocated from a `ListPool` instead of the global heap. -//! 2. The footprint of an entity list is 4 bytes, compared with the 24 bytes for `Vec`. -//! 3. An entity list doesn't implement `Drop`, leaving it to the pool to manage memory. -//! -//! The list pool is intended to be used as a LIFO allocator. After building up a larger data -//! structure with many list references, the whole thing can be discarded quickly by clearing the -//! pool. -//! -//! # Safety -//! -//! Entity lists are not as safe to use as `Vec`, but they never jeopardize Rust's memory safety -//! guarantees. These are the problems to be aware of: -//! -//! - If you lose track of an entity list, its memory won't be recycled until the pool is cleared. -//! This can cause the pool to grow very large with leaked lists. -//! - If entity lists are used after their pool is cleared, they may contain garbage data, and -//! modifying them may corrupt other lists in the pool. -//! - If an entity list is used with two different pool instances, both pools are likely to become -//! corrupted. -//! -//! # Implementation -//! -//! The `EntityList` itself is designed to have the smallest possible footprint. This is important -//! because it is used inside very compact data structures like `InstructionData`. The list -//! contains only a 32-bit index into the pool's memory vector, pointing to the first element of -//! the list. -//! -//! The pool is just a single `Vec` containing all of the allocated lists. Each list is -//! represented as three contiguous parts: -//! -//! 1. The number of elements in the list. -//! 2. The list elements. -//! 3. Excess capacity elements. -//! -//! The total size of the three parts is always a power of two, and the excess capacity is always -//! as small as possible. This means that shrinking a list may cause the excess capacity to shrink -//! if a smaller power-of-two size becomes available. -//! -//! Both growing and shrinking a list may cause it to be reallocated in the pool vector. -//! -//! The index stored in an `EntityList` points to part 2, the list elements. The value 0 is -//! reserved for the empty list which isn't allocated in the vector. - use entity::EntityRef; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; @@ -53,8 +6,28 @@ use std::mem; /// A small list of entity references allocated from a pool. /// -/// All of the list methods that take a pool reference must be given the same pool reference every -/// time they are called. Otherwise data structures will be corrupted. +/// An `EntityList` type provides similar functionality to `Vec`, but with some important +/// differences in the implementation: +/// +/// 1. Memory is allocated from a `ListPool` instead of the global heap. +/// 2. The footprint of an entity list is 4 bytes, compared with the 24 bytes for `Vec`. +/// 3. An entity list doesn't implement `Drop`, leaving it to the pool to manage memory. +/// +/// The list pool is intended to be used as a LIFO allocator. After building up a larger data +/// structure with many list references, the whole thing can be discarded quickly by clearing the +/// pool. +/// +/// # Safety +/// +/// Entity lists are not as safe to use as `Vec`, but they never jeopardize Rust's memory safety +/// guarantees. These are the problems to be aware of: +/// +/// - If you lose track of an entity list, its memory won't be recycled until the pool is cleared. +/// This can cause the pool to grow very large with leaked lists. +/// - If entity lists are used after their pool is cleared, they may contain garbage data, and +/// modifying them may corrupt other lists in the pool. +/// - If an entity list is used with two different pool instances, both pools are likely to become +/// corrupted. /// /// Entity lists can be cloned, but that operation should only be used as part of cloning the whole /// function they belong to. *Cloning an entity list does not allocate new memory for the clone*. @@ -63,6 +36,29 @@ use std::mem; /// Entity lists can also be hashed and compared for equality, but those operations just panic if, /// they're ever actually called, because it's not possible to compare the contents of the list /// without the pool reference. +/// +/// # Implementation +/// +/// The `EntityList` itself is designed to have the smallest possible footprint. This is important +/// because it is used inside very compact data structures like `InstructionData`. The list +/// contains only a 32-bit index into the pool's memory vector, pointing to the first element of +/// the list. +/// +/// The pool is just a single `Vec` containing all of the allocated lists. Each list is +/// represented as three contiguous parts: +/// +/// 1. The number of elements in the list. +/// 2. The list elements. +/// 3. Excess capacity elements. +/// +/// The total size of the three parts is always a power of two, and the excess capacity is always +/// as small as possible. This means that shrinking a list may cause the excess capacity to shrink +/// if a smaller power-of-two size becomes available. +/// +/// Both growing and shrinking a list may cause it to be reallocated in the pool vector. +/// +/// The index stored in an `EntityList` points to part 2, the list elements. The value 0 is +/// reserved for the empty list which isn't allocated in the vector. #[derive(Clone, Debug)] pub struct EntityList { index: u32, diff --git a/lib/cretonne/src/entity/map.rs b/lib/cretonne/src/entity/map.rs index ca6f1ff7d9..9afc5ff356 100644 --- a/lib/cretonne/src/entity/map.rs +++ b/lib/cretonne/src/entity/map.rs @@ -1,14 +1,17 @@ //! Densely numbered entity references as mapping keys. -//! -//! The `EntityMap` data structure uses the dense index space to implement a map with a vector. -//! Unlike `PrimaryMap`, and `EntityMap` can't be used to allocate entity references. It is used to -//! associate secondary information with entities. use entity::{EntityRef, Keys}; use std::marker::PhantomData; use std::ops::{Index, IndexMut}; /// A mapping `K -> V` for densely indexed entity references. +/// +/// The `EntityMap` data structure uses the dense index space to implement a map with a vector. +/// Unlike `PrimaryMap`, an `EntityMap` can't be used to allocate entity references. It is used to +/// associate secondary information with entities. +/// +/// The map does not track if an entry for a key has been inserted or not. Instead it behaves as if +/// all keys have a default entry from the beginning. #[derive(Debug, Clone)] pub struct EntityMap where K: EntityRef, diff --git a/lib/cretonne/src/entity/mod.rs b/lib/cretonne/src/entity/mod.rs index 9a9cc5d72c..6702c43dab 100644 --- a/lib/cretonne/src/entity/mod.rs +++ b/lib/cretonne/src/entity/mod.rs @@ -1,9 +1,30 @@ -//! Densely numbered entity references as mapping keys. +//! Array-based data structures using densely numbered entity references as mapping keys. //! -//! This module defines an `EntityRef` trait that should be implemented by reference types wrapping -//! a small integer index. +//! This module defines a number of data structures based on arrays. The arrays are not indexed by +//! `usize` as usual, but by *entity references* which are integers wrapped in new-types. This has +//! a couple advantages: //! -//! Various data structures based on the entity references are defined in sub-modules. +//! - Improved type safety. The various map types accept a specific key type, so there is no +//! confusion about the meaning of an array index, as there is with plain arrays. +//! - Smaller indexes. The normal `usize` index is often 64 bits which is way too large for most +//! purposes. The entity reference types can be smaller, allowing for more compact data +//! structures. +//! +//! The `EntityRef` trait should be implemented by types to be used as indexed. The `entity_impl!` +//! macro provides convenient defaults for types wrapping `u32` which is common. +//! +//! - [`PrimaryMap`](struct.PrimaryMap.html) is used to keep track of a vector of entities, +//! assigning a unique entity reference to each. +//! - [`EntityMap`](struct.EntityMap.html) is used to associate secondary information to an entity. +//! The map is implemented as a simple vector, so it does not keep track of which entities have +//! been inserted. Instead, any unknown entities map to the default value. +//! - [`SparseMap`](struct.SparseMap.html) is used to associate secondary information to a small +//! number of entities. It tracks accurately which entities have been inserted. This is a +//! specialized data structure which can use a lot of memory, so read the documentation before +//! using it. +//! - [`EntityList`](struct.EntityList.html) is a compact representation of lists of entity +//! references allocated from an associated memory pool. It has a much smaller footprint than +//! `Vec`. mod keys; mod list; diff --git a/lib/cretonne/src/entity/primary.rs b/lib/cretonne/src/entity/primary.rs index e98698f117..39af48ff3f 100644 --- a/lib/cretonne/src/entity/primary.rs +++ b/lib/cretonne/src/entity/primary.rs @@ -1,18 +1,17 @@ //! Densely numbered entity references as mapping keys. -//! -//! The `PrimaryMap` data structure uses the dense index space to implement a map with a vector. -//! -//! A primary map contains the main definition of an entity, and it can be used to allocate new -//! entity references with the `push` method. -//! -//! There should only be a single `PrimaryMap` instance for a given `EntityRef` type, otherwise -//! conflicting references will be created. - use entity::{EntityRef, Keys}; use std::marker::PhantomData; use std::ops::{Index, IndexMut}; -/// A mapping `K -> V` for densely indexed entity references. +/// A primary mapping `K -> V` allocating dense entity references. +/// +/// The `PrimaryMap` data structure uses the dense index space to implement a map with a vector. +/// +/// A primary map contains the main definition of an entity, and it can be used to allocate new +/// entity references with the `push` method. +/// +/// There should only be a single `PrimaryMap` instance for a given `EntityRef` type, otherwise +/// conflicting references will be created. Using unknown keys for indexing will cause a panic. #[derive(Debug, Clone)] pub struct PrimaryMap where K: EntityRef diff --git a/lib/cretonne/src/entity/sparse.rs b/lib/cretonne/src/entity/sparse.rs index 4918675d27..e22df2786a 100644 --- a/lib/cretonne/src/entity/sparse.rs +++ b/lib/cretonne/src/entity/sparse.rs @@ -6,34 +6,6 @@ //! //! > Briggs, Torczon, *An efficient representation for sparse sets*, //! ACM Letters on Programming Languages and Systems, Volume 2, Issue 1-4, March-Dec. 1993. -//! -//! A `SparseMap` map provides: -//! -//! - Memory usage equivalent to `EntityMap` + `Vec`, so much smaller than -//! `EntityMap` for sparse mappings of larger `V` types. -//! - Constant time lookup, slightly slower than `EntityMap`. -//! - A very fast, constant time `clear()` operation. -//! - Fast insert and erase operations. -//! - Stable iteration that is as fast as a `Vec`. -//! -//! # Compared to `EntityMap` -//! -//! When should we use a `SparseMap` instead of a secondary `EntityMap`? First of all, `SparseMap` -//! does not provide the functionality of a primary `EntityMap` which can allocate and assign -//! entity references to objects as they are pushed onto the map. It is only the secondary -//! entity maps that can be replaced with a `SparseMap`. -//! -//! - A secondary entity map requires its values to implement `Default`, and it is a bit loose -//! about creating new mappings to the default value. It doesn't distinguish clearly between an -//! unmapped key and one that maps to the default value. `SparseMap` does not require `Default` -//! values, and it tracks accurately if a key has been mapped or not. -//! - Iterating over the contents of an `EntityMap` is linear in the size of the *key space*, while -//! iterating over a `SparseMap` is linear in the number of elements in the mapping. This is an -//! advantage precisely when the mapping is sparse. -//! - `SparseMap::clear()` is constant time and super-fast. `EntityMap::clear()` is linear in the -//! size of the key space. (Or, rather the required `resize()` call following the `clear()` is). -//! - `SparseMap` requires the values to implement `SparseMapValue` which means that they must -//! contain their own key. use entity::{EntityRef, EntityMap}; use std::mem; @@ -51,6 +23,33 @@ pub trait SparseMapValue { } /// A sparse mapping of entity references. +/// +/// A `SparseMap` map provides: +/// +/// - Memory usage equivalent to `EntityMap` + `Vec`, so much smaller than +/// `EntityMap` for sparse mappings of larger `V` types. +/// - Constant time lookup, slightly slower than `EntityMap`. +/// - A very fast, constant time `clear()` operation. +/// - Fast insert and erase operations. +/// - Stable iteration that is as fast as a `Vec`. +/// +/// # Compared to `EntityMap` +/// +/// When should we use a `SparseMap` instead of a secondary `EntityMap`? First of all, `SparseMap` +/// does not provide the functionality of a `PrimaryMap` which can allocate and assign entity +/// references to objects as they are pushed onto the map. It is only the secondary entity maps +/// that can be replaced with a `SparseMap`. +/// +/// - A secondary entity map assigns a default mapping to all keys. It doesn't distinguish between +/// an unmapped key and one that maps to the default value. `SparseMap` does not require +/// `Default` values, and it tracks accurately if a key has been mapped or not. +/// - Iterating over the contents of an `EntityMap` is linear in the size of the *key space*, while +/// iterating over a `SparseMap` is linear in the number of elements in the mapping. This is an +/// advantage precisely when the mapping is sparse. +/// - `SparseMap::clear()` is constant time and super-fast. `EntityMap::clear()` is linear in the +/// size of the key space. (Or, rather the required `resize()` call following the `clear()` is). +/// - `SparseMap` requires the values to implement `SparseMapValue` which means that they must +/// contain their own key. pub struct SparseMap where K: EntityRef, V: SparseMapValue