Add AppendOnlyGraph, to allow safe appends without invalidating pointers.

This commit is contained in:
Stu Black 2019-07-17 19:21:27 -04:00
parent bbb9be1ae6
commit 2f63b80e36
3 changed files with 106 additions and 16 deletions

View File

@ -3,11 +3,13 @@ pub mod mutators;
pub mod nav; pub mod nav;
pub mod search; pub mod search;
use std::cell::UnsafeCell;
use std::hash::Hash; use std::hash::Hash;
use std::ops::Deref;
use base::{EdgeId, RawEdge, RawVertex, VertexId}; use base::{EdgeId, RawEdge, RawVertex, VertexId};
use mutators::{MutEdge, MutNode}; use mutators::{MutEdge, MutNode};
use nav::Node; use nav::{Edge, Node};
use symbol_map::indexing::{Indexing, Insertion}; use symbol_map::indexing::{Indexing, Insertion};
use symbol_map::SymbolId; use symbol_map::SymbolId;
@ -26,7 +28,7 @@ use symbol_map::SymbolId;
/// ///
/// Vertices are addressed by content. To examine graph contents, obtain a node /// Vertices are addressed by content. To examine graph contents, obtain a node
/// handle with `get_node`. To modify graph contents, add new root vertices with /// handle with `get_node`. To modify graph contents, add new root vertices with
/// `add_root` and retrieve extant vertices with `get_node_mut`. /// `add_node` and retrieve extant vertices with `get_node_mut`.
pub struct Graph<T, S, A> pub struct Graph<T, S, A>
where where
T: Hash + Eq + Clone, T: Hash + Eq + Clone,
@ -123,14 +125,14 @@ where
} }
} }
/// Adds a root vertex (one with no parents) for the given game state and /// Adds a vertex (with no parents or children) for the given game state and
/// data and returns a mutable handle for it. /// data and returns a mutable handle for it.
/// ///
/// If `state` is already known, returns a mutable handle to that state, /// If `state` is already known, returns a mutable handle to that state,
/// ignoring the `data` parameter. As a result, this method is guaranteed to /// ignoring the `data` parameter. As a result, this method is guaranteed to
/// return a handle for a root vertex only when `state` is a novel game /// return a handle for a root vertex only when `state` is a novel game
/// state. /// state.
pub fn add_root<'s>(&'s mut self, state: T, data: S) -> MutNode<'s, T, S, A> { pub fn add_node<'s>(&'s mut self, state: T, data: S) -> MutNode<'s, T, S, A> {
let node_id = match self.state_ids.get_or_insert(state).map(|s| s.id().clone()) { let node_id = match self.state_ids.get_or_insert(state).map(|s| s.id().clone()) {
Insertion::Present(id) => id, Insertion::Present(id) => id,
Insertion::New(id) => { Insertion::New(id) => {
@ -214,6 +216,94 @@ where
} }
} }
/// Allows both read-only references into a `Graph` and operations that modify
/// the graph but do not invalidate family of types defined in the `nav`
/// module.
///
/// All methods on `Graph` that take a `&self` parameter may also be called on
/// an `AppendOnlyGraph`. The `add_node` and `add_edge` methods may also be
/// called, because they do not invalidate `Node`, `Edge`, or other such smart
/// pointers into the underlying graph. Unlike the analogous methods on `Graph`,
/// these methods return a read-only view of the graph topology, instead of
/// granting read-write access.
///
/// For example:
///
/// ```rust
/// # use search_graph::{AppendOnlyGraph, Graph};
/// # use search_graph::nav::{Node, Edge};
/// # fn main() {
/// let appendable: AppendOnlyGraph<u32, String, ()> = Graph::new().into();
/// let root1 = appendable.add_node(0, "data1".to_string());
/// // If appendable were a Graph, we could not call add_node while root1 is alive.
/// let root2 = appendable.add_node(1, "data2".to_string());
/// assert_eq!("data1", appendable.get_node(&0).unwrap().get_data());
/// # }
/// ```
pub struct AppendOnlyGraph<T, S, A>
where
T: Hash + Eq + Clone,
{
graph: UnsafeCell<Graph<T, S, A>>,
}
impl<T, S, A> AppendOnlyGraph<T, S, A>
where
T: Hash + Eq + Clone,
{
pub fn add_node<'s>(&'s self, state: T, data: S) -> Node<'s, T, S, A> {
unsafe { (*self.graph.get()).add_node(state, data).to_node() }
}
pub fn add_edge<'s, F, G>(
&'s mut self,
source: T,
source_data: F,
dest: T,
dest_data: G,
edge_data: A,
) -> Edge<'s, T, S, A>
where
F: for<'b> FnOnce(Node<'b, T, S, A>) -> S,
G: for<'b> FnOnce(Node<'b, T, S, A>) -> S,
{
unsafe {
(*self.graph.get())
.add_edge(source, source_data, dest, dest_data, edge_data)
.to_edge()
}
}
}
impl<T, S, A> Deref for AppendOnlyGraph<T, S, A>
where
T: Hash + Eq + Clone,
{
type Target = Graph<T, S, A>;
fn deref(&self) -> &Graph<T, S, A> {
unsafe { &*self.graph.get() }
}
}
impl<T, S, A> From<AppendOnlyGraph<T, S, A>> for Graph<T, S, A>
where
T: Hash + Eq + Clone,
{
fn from(graph: AppendOnlyGraph<T, S, A>) -> Self {
graph.graph.into_inner()
}
}
impl<T, S, A> From<Graph<T, S, A>> for AppendOnlyGraph<T, S, A>
where
T: Hash + Eq + Clone,
{
fn from(graph: Graph<T, S, A>) -> Self {
AppendOnlyGraph { graph: UnsafeCell::new(graph) }
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crossbeam_utils::thread; use crossbeam_utils::thread;

View File

@ -290,9 +290,9 @@ mod test {
#[test] #[test]
fn mark_roots_ok() { fn mark_roots_ok() {
let mut g = empty_graph(); let mut g = empty_graph();
g.add_root("0", ""); g.add_node("0", "");
g.add_root("1", ""); g.add_node("1", "");
g.add_root("2", ""); g.add_node("2", "");
assert_eq!(3, g.vertex_count()); assert_eq!(3, g.vertex_count());
assert_eq!(0, g.edge_count()); assert_eq!(0, g.edge_count());
let root_ids = [VertexId(0), VertexId(1), VertexId(2)]; let root_ids = [VertexId(0), VertexId(1), VertexId(2)];

View File

@ -345,7 +345,7 @@ mod test {
#[test] #[test]
fn instantiation_ok() { fn instantiation_ok() {
let mut g = Graph::new(); let mut g = Graph::new();
let root = g.add_root("root", "root"); let root = g.add_node("root", "root");
let path = Stack::new(root); let path = Stack::new(root);
assert_eq!(1, path.len()); assert_eq!(1, path.len());
@ -355,7 +355,7 @@ mod test {
#[test] #[test]
fn push_no_children_ok() { fn push_no_children_ok() {
let mut g = Graph::new(); let mut g = Graph::new();
let root = g.add_root("root", "root"); let root = g.add_node("root", "root");
let mut path = Stack::new(root); let mut path = Stack::new(root);
assert_eq!(1, path.len()); assert_eq!(1, path.len());
@ -377,7 +377,7 @@ mod test {
#[test] #[test]
fn push_no_children_err() { fn push_no_children_err() {
let mut g = Graph::new(); let mut g = Graph::new();
let root = g.add_root("root", "root"); let root = g.add_node("root", "root");
let mut path = Stack::new(root); let mut path = Stack::new(root);
assert_eq!(1, path.len()); assert_eq!(1, path.len());
@ -478,7 +478,7 @@ mod test {
#[test] #[test]
fn push_no_parents_ok() { fn push_no_parents_ok() {
let mut g = Graph::new(); let mut g = Graph::new();
let root = g.add_root("root", "root"); let root = g.add_node("root", "root");
let mut path = Stack::new(root); let mut path = Stack::new(root);
assert_eq!(1, path.len()); assert_eq!(1, path.len());
@ -500,7 +500,7 @@ mod test {
#[test] #[test]
fn push_no_parents_err() { fn push_no_parents_err() {
let mut g = Graph::new(); let mut g = Graph::new();
let root = g.add_root("root", "root"); let root = g.add_node("root", "root");
let mut path = Stack::new(root); let mut path = Stack::new(root);
assert_eq!(1, path.len()); assert_eq!(1, path.len());
@ -633,9 +633,9 @@ mod test {
#[test] #[test]
fn search_path_iter_empty_ok() { fn search_path_iter_empty_ok() {
let mut g = Graph::new(); let mut g = Graph::new();
g.add_root("root", "root"); g.add_node("root", "root");
let path = Stack::new(g.add_root("root", "root")); let path = Stack::new(g.add_node("root", "root"));
assert_eq!(1, path.len()); assert_eq!(1, path.len());
assert_eq!("root", *path.head().get_data()); assert_eq!("root", *path.head().get_data());
@ -651,7 +651,7 @@ mod test {
#[test] #[test]
fn search_path_iter_items_ok() { fn search_path_iter_items_ok() {
let mut g = Graph::new(); let mut g = Graph::new();
g.add_root("root", "root"); g.add_node("root", "root");
add_edge(&mut g, "root", "A"); add_edge(&mut g, "root", "A");
add_edge(&mut g, "A", "B"); add_edge(&mut g, "A", "B");
@ -706,7 +706,7 @@ mod test {
fn pop_empty_is_none_ok() { fn pop_empty_is_none_ok() {
let mut g = Graph::new(); let mut g = Graph::new();
let mut path = Stack::new(g.add_root("root", "root")); let mut path = Stack::new(g.add_node("root", "root"));
assert_eq!(1, path.len()); assert_eq!(1, path.len());
assert!(path.pop().is_none()); assert!(path.pop().is_none());
} }