Clean up the documentation for the entity module.

This commit is contained in:
Jakob Stoklund Olesen
2017-08-18 17:14:31 -07:00
parent 9cb0529be4
commit a9238eda7a
5 changed files with 113 additions and 95 deletions

View File

@@ -1,51 +1,4 @@
//! Small lists of entity references. //! Small lists of entity references.
//!
//! This module defines an `EntityList<T>` type which provides similar functionality to `Vec<T>`,
//! but with some important differences in the implementation:
//!
//! 1. Memory is allocated from a `ListPool<T>` instead of the global heap.
//! 2. The footprint of an entity list is 4 bytes, compared with the 24 bytes for `Vec<T>`.
//! 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<T>`, 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<T>` 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 entity::EntityRef;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -53,8 +6,28 @@ use std::mem;
/// A small list of entity references allocated from a pool. /// 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 /// An `EntityList<T>` type provides similar functionality to `Vec<T>`, but with some important
/// time they are called. Otherwise data structures will be corrupted. /// differences in the implementation:
///
/// 1. Memory is allocated from a `ListPool<T>` instead of the global heap.
/// 2. The footprint of an entity list is 4 bytes, compared with the 24 bytes for `Vec<T>`.
/// 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<T>`, 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 /// 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*. /// 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, /// 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 /// they're ever actually called, because it's not possible to compare the contents of the list
/// without the pool reference. /// 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<T>` 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)] #[derive(Clone, Debug)]
pub struct EntityList<T: EntityRef> { pub struct EntityList<T: EntityRef> {
index: u32, index: u32,

View File

@@ -1,14 +1,17 @@
//! Densely numbered entity references as mapping keys. //! 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 entity::{EntityRef, Keys};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
/// A mapping `K -> V` for densely indexed entity references. /// 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)] #[derive(Debug, Clone)]
pub struct EntityMap<K, V> pub struct EntityMap<K, V>
where K: EntityRef, where K: EntityRef,

View File

@@ -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 //! This module defines a number of data structures based on arrays. The arrays are not indexed by
//! a small integer index. //! `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 keys;
mod list; mod list;

View File

@@ -1,18 +1,17 @@
//! Densely numbered entity references as mapping keys. //! 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 entity::{EntityRef, Keys};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Index, IndexMut}; 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)] #[derive(Debug, Clone)]
pub struct PrimaryMap<K, V> pub struct PrimaryMap<K, V>
where K: EntityRef where K: EntityRef

View File

@@ -6,34 +6,6 @@
//! //!
//! > Briggs, Torczon, *An efficient representation for sparse sets*, //! > Briggs, Torczon, *An efficient representation for sparse sets*,
//! ACM Letters on Programming Languages and Systems, Volume 2, Issue 1-4, March-Dec. 1993. //! ACM Letters on Programming Languages and Systems, Volume 2, Issue 1-4, March-Dec. 1993.
//!
//! A `SparseMap<K, V>` map provides:
//!
//! - Memory usage equivalent to `EntityMap<K, u32>` + `Vec<V>`, so much smaller than
//! `EntityMap<K, V>` 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<V>`.
//!
//! # 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<K>` which means that they must
//! contain their own key.
use entity::{EntityRef, EntityMap}; use entity::{EntityRef, EntityMap};
use std::mem; use std::mem;
@@ -51,6 +23,33 @@ pub trait SparseMapValue<K> {
} }
/// A sparse mapping of entity references. /// A sparse mapping of entity references.
///
/// A `SparseMap<K, V>` map provides:
///
/// - Memory usage equivalent to `EntityMap<K, u32>` + `Vec<V>`, so much smaller than
/// `EntityMap<K, V>` 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<V>`.
///
/// # 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<K>` which means that they must
/// contain their own key.
pub struct SparseMap<K, V> pub struct SparseMap<K, V>
where K: EntityRef, where K: EntityRef,
V: SparseMapValue<K> V: SparseMapValue<K>