Clean up mark-and-compact to pass basic tests.
This commit is contained in:
parent
fe1e3cffe6
commit
4bc7b6bfe2
@ -1,3 +1,4 @@
|
||||
use std::cmp::{Eq, PartialEq};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::hash::Hash;
|
||||
@ -113,10 +114,19 @@ pub struct Arc<A> {
|
||||
/// `Target::Unexpanded(())`; otherwise, it is either `Target::Cycle(id)` or
|
||||
/// `Target::Expanded(id)` for target vertex with a `StateId` of `id`.
|
||||
pub target: Target<StateId, ()>,
|
||||
/// Used for mark-and-sweep garbage collection.
|
||||
pub mark: bool,
|
||||
}
|
||||
|
||||
/// This implementation will conflate parallel edges with identical statistics.
|
||||
impl<A> PartialEq for Arc<A> where A: PartialEq {
|
||||
fn eq(&self, other: &Arc<A>) -> bool {
|
||||
self.source == other.source
|
||||
&& self.target == other.target
|
||||
&& self.data == other.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> Eq for Arc<A> where A: Eq { }
|
||||
|
||||
/// Internal type for graph vertices.
|
||||
#[derive(Debug)]
|
||||
pub struct Vertex<S> {
|
||||
@ -126,6 +136,14 @@ pub struct Vertex<S> {
|
||||
pub parents: Vec<ArcId>,
|
||||
/// Child edges pointing out of this vertex.
|
||||
pub children: Vec<ArcId>,
|
||||
/// Used for mark-and-sweep garbage collection.
|
||||
pub mark: bool,
|
||||
}
|
||||
|
||||
impl<S> PartialEq for Vertex<S> where S: PartialEq {
|
||||
fn eq(&self, other: &Vertex<S>) -> bool {
|
||||
self.parents == other.parents
|
||||
&& self.children == other.children
|
||||
&& self.data == other.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Eq for Vertex<S> where S: Eq { }
|
||||
|
446
src/hidden/mutators/mark_compact.rs
Normal file
446
src/hidden/mutators/mark_compact.rs
Normal file
@ -0,0 +1,446 @@
|
||||
//! Mark-and-compact garbage collection for pruning graphs.
|
||||
//!
|
||||
//! This module implements a mark-and-compact garbage collector that can prune a
|
||||
//! graph so that only components reachable from a set of root game states are
|
||||
//! retained. Running time and memory required are linear in graph size,
|
||||
//! although there is a potential for a high cost when rebuilding the hashtable
|
||||
//! that maps from game states to their IDs.
|
||||
|
||||
use ::{Graph, Target};
|
||||
use ::hidden::base::{ArcId, StateId};
|
||||
|
||||
use std::cmp::Eq;
|
||||
use std::collections::VecDeque;
|
||||
use std::hash::Hash;
|
||||
use std::mem;
|
||||
|
||||
/// Permutes `data` so that element `i` of data is reassigned to be at index
|
||||
/// `f(id_map[i])`.
|
||||
///
|
||||
/// Elements `j` of `data` for which `id_map[j]` is `None` are discarded.
|
||||
fn permute_compact<T, F>(data: &mut Vec<T>, f: F) where F: Fn(usize) -> Option<usize> {
|
||||
if data.is_empty() {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: We should benchmark doing this in-place vs. via moving.
|
||||
let mut new_data: Vec<T> = Vec::with_capacity(data.len());
|
||||
let mut retained_count = 0;
|
||||
{
|
||||
let compacted = data.drain(0..).enumerate()
|
||||
.filter_map(|(old_index, t)| f(old_index).map(|new_index| (new_index, t)));
|
||||
for (new_index, mut t) in compacted {
|
||||
mem::swap(unsafe { new_data.get_unchecked_mut(new_index) }, &mut t);
|
||||
mem::forget(t);
|
||||
retained_count += 1;
|
||||
}
|
||||
}
|
||||
unsafe { new_data.set_len(retained_count) }; // Maybe do this after each swap?
|
||||
mem::replace(data, new_data);
|
||||
}
|
||||
|
||||
/// Garbage collector state.
|
||||
pub struct Collector<'a, T, S, A> where T: Hash + Eq + Clone + 'a, S: 'a, A: 'a {
|
||||
graph: &'a mut Graph<T, S, A>,
|
||||
marked_state_count: usize,
|
||||
marked_arc_count: usize,
|
||||
state_id_map: Vec<Option<StateId>>,
|
||||
arc_id_map: Vec<Option<ArcId>>,
|
||||
frontier: VecDeque<StateId>,
|
||||
}
|
||||
|
||||
impl<'a, T, S, A> Collector<'a, T, S, A> where T: Hash + Eq + Clone + 'a, S: 'a, A: 'a {
|
||||
/// Runs mark-and-sweep garbage collection on a graph. Graph components not
|
||||
/// reachable from the vertices corresponding to roots will be dropped.
|
||||
///
|
||||
/// This is intended as the main entrypoint for this module. But this
|
||||
/// function is not exported by the crate, so you probably want the
|
||||
/// `retain_reachable()` method of `MutNode` or the `retain_reachable_from`
|
||||
/// method of `Graph`.
|
||||
pub fn retain_reachable(graph: &'a mut Graph<T, S, A>, roots: &[StateId]) {
|
||||
let mut c = Collector::new(graph);
|
||||
c.mark(roots);
|
||||
c.sweep();
|
||||
}
|
||||
|
||||
/// Creates a new mark-and-sweep garbage collector with empty initial state.
|
||||
fn new(graph: &'a mut Graph<T, S, A>) -> Self {
|
||||
let empty_states = vec!(None; graph.vertices.len());
|
||||
let empty_arcs = vec!(None; graph.arcs.len());
|
||||
Collector {
|
||||
graph: graph,
|
||||
marked_state_count: 0,
|
||||
marked_arc_count: 0,
|
||||
state_id_map: empty_states,
|
||||
arc_id_map: empty_arcs,
|
||||
frontier: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverses graph components reachable from `roots` and marks them as
|
||||
/// reachable. Also builds a new graph component addressing scheme that
|
||||
/// reassigns `StateId` and `ArcId` values.
|
||||
///
|
||||
/// As side effects, arc sources and vertex children are updated to use the
|
||||
/// new addressing scheme.
|
||||
fn mark(&mut self, roots: &[StateId]) {
|
||||
for id in roots.iter() {
|
||||
Self::remap_state_id(&mut self.state_id_map, &mut self.marked_state_count, *id);
|
||||
self.frontier.push_back(*id);
|
||||
}
|
||||
while self.mark_next() { }
|
||||
}
|
||||
|
||||
/// Looks up the mapping between old and new StateIds. May update
|
||||
/// `state_id_map` with a new mapping, given that we have remapped
|
||||
/// `marked_state_count` StateIds so far.
|
||||
fn remap_state_id(state_id_map: &mut [Option<StateId>], marked_state_count: &mut usize,
|
||||
old_state_id: StateId) -> StateId {
|
||||
let index = old_state_id.as_usize();
|
||||
if let Some(new_state_id) = state_id_map[index] {
|
||||
return new_state_id
|
||||
}
|
||||
let new_state_id = StateId(*marked_state_count);
|
||||
state_id_map[index] = Some(new_state_id);
|
||||
*marked_state_count += 1;
|
||||
new_state_id
|
||||
}
|
||||
|
||||
/// Looks up the mapping between old and new ArcIds. May update
|
||||
/// `arc_id_map` with a new mapping, given that we have remapped
|
||||
/// `marked_arc_count` ArcIds so far.
|
||||
fn remap_arc_id(arc_id_map: &mut [Option<ArcId>], marked_arc_count: &mut usize,
|
||||
old_arc_id: ArcId) -> ArcId {
|
||||
let index = old_arc_id.as_usize();
|
||||
if let Some(new_arc_id) = arc_id_map[index] {
|
||||
return new_arc_id
|
||||
}
|
||||
let new_arc_id = ArcId(*marked_arc_count);
|
||||
arc_id_map[index] = Some(new_arc_id);
|
||||
*marked_arc_count += 1;
|
||||
new_arc_id
|
||||
}
|
||||
|
||||
fn mark_next(&mut self) -> bool{
|
||||
match self.frontier.pop_front() {
|
||||
None => false,
|
||||
Some(state_id) => {
|
||||
let (new_state_id, mut child_arc_ids): (StateId, Vec<ArcId>) = {
|
||||
let vertex = self.graph.get_vertex_mut(state_id);
|
||||
(self.state_id_map[state_id.as_usize()].unwrap(),
|
||||
vertex.children.drain(0..).collect())
|
||||
};
|
||||
|
||||
for arc_id in child_arc_ids.iter_mut() {
|
||||
let arc = self.graph.get_arc_mut(*arc_id);
|
||||
// Update arc sources to use new state IDs.
|
||||
arc.source = new_state_id;
|
||||
if let Target::Expanded(child_vertex_id) = arc.target {
|
||||
if self.state_id_map[child_vertex_id.as_usize()].is_none() {
|
||||
Self::remap_state_id(
|
||||
&mut self.state_id_map, &mut self.marked_state_count, child_vertex_id);
|
||||
self.frontier.push_back(child_vertex_id);
|
||||
}
|
||||
}
|
||||
let new_arc_id =
|
||||
Self::remap_arc_id(&mut self.arc_id_map, &mut self.marked_arc_count, *arc_id);
|
||||
self.arc_id_map[arc_id.as_usize()] = Some(new_arc_id);
|
||||
*arc_id = new_arc_id;
|
||||
}
|
||||
|
||||
// Update vertex children to use new ArcIds.
|
||||
self.graph.get_vertex_mut(state_id).children = child_arc_ids;
|
||||
true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops vertices which were not reached in the previous `mark()`. Must be
|
||||
/// run after `mark()`.
|
||||
///
|
||||
/// Also, updates vertex pointers to parent edges to use the new `ArcId`
|
||||
/// addressing scheme built in the previous call to `mark()`.
|
||||
fn sweep(&mut self) {
|
||||
let state_id_map = {
|
||||
let mut state_id_map = Vec::new();
|
||||
mem::swap(&mut state_id_map, &mut self.state_id_map);
|
||||
state_id_map
|
||||
};
|
||||
let arc_id_map = {
|
||||
let mut arc_id_map = Vec::new();
|
||||
mem::swap(&mut arc_id_map, &mut self.arc_id_map);
|
||||
arc_id_map
|
||||
};
|
||||
// Compact marked vertices.
|
||||
permute_compact(&mut self.graph.vertices, |i| state_id_map[i].map(|id| id.as_usize()));
|
||||
// Drop unmarked vertices.
|
||||
self.graph.vertices.truncate(self.marked_state_count);
|
||||
// Reassign and compact vertex parents.
|
||||
for mut vertex in self.graph.vertices.iter_mut() {
|
||||
let mut store_index = 0;
|
||||
for scan_index in 0..vertex.parents.len() {
|
||||
let old_arc_id = vertex.parents[scan_index];
|
||||
if let Some(new_arc_id) = arc_id_map[old_arc_id.as_usize()] {
|
||||
vertex.parents[store_index] = new_arc_id;
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
vertex.parents.truncate(store_index);
|
||||
vertex.parents.shrink_to_fit();
|
||||
}
|
||||
|
||||
// Compact marked arcs.
|
||||
permute_compact(&mut self.graph.arcs, |i| arc_id_map[i].map(|id| id.as_usize()));
|
||||
// Reassign arc targets.
|
||||
for mut arc in self.graph.arcs.iter_mut() {
|
||||
arc.target = match arc.target {
|
||||
Target::Expanded(old_state_id) =>
|
||||
Target::Expanded(state_id_map[old_state_id.as_usize()].unwrap()),
|
||||
x @ _ => x,
|
||||
};
|
||||
}
|
||||
|
||||
// Update state namespace to use new mapping.
|
||||
self.graph.state_ids.remap(|_, old_state_id| state_id_map[old_state_id.as_usize()]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Collector;
|
||||
use ::hidden::base::{ArcId, Arc, StateId, Vertex};
|
||||
use ::Target;
|
||||
|
||||
type Graph = ::Graph<&'static str, &'static str, &'static str>;
|
||||
|
||||
fn empty_graph() -> Graph {
|
||||
let g = Graph::new();
|
||||
assert_eq!(0, g.vertex_count());
|
||||
assert_eq!(0, g.edge_count());
|
||||
g
|
||||
}
|
||||
|
||||
fn make_vertex(data: &'static str, parents: Vec<ArcId>, children: Vec<ArcId>)
|
||||
-> Vertex<&'static str> {
|
||||
Vertex { data: data, parents: parents, children: children, }
|
||||
}
|
||||
|
||||
fn make_arc(data: &'static str, source: StateId, target: Target<StateId, ()>)
|
||||
-> Arc<&'static str> {
|
||||
Arc { data: data, source: source, target: target, }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_graph_ok() {
|
||||
let mut g = empty_graph();
|
||||
Collector::retain_reachable(&mut g, &[]);
|
||||
assert_eq!(0, g.vertex_count());
|
||||
assert_eq!(0, g.edge_count());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mark_roots_ok() {
|
||||
let mut g = empty_graph();
|
||||
g.add_root("0", "");
|
||||
g.add_root("1", "");
|
||||
g.add_root("2", "");
|
||||
assert_eq!(3, g.vertex_count());
|
||||
assert_eq!(0, g.edge_count());
|
||||
let root_ids = [StateId(0), StateId(1), StateId(2)];
|
||||
let mut c = Collector::new(&mut g);
|
||||
c.mark(&root_ids);
|
||||
for (i, new_id) in c.state_id_map.iter().enumerate() {
|
||||
if new_id.is_some() {
|
||||
assert!(root_ids.contains(&StateId(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reachable_loop_ok() {
|
||||
let mut g = empty_graph();
|
||||
// Original StateIds are:
|
||||
// "0": 0
|
||||
// "00": 1
|
||||
// "01": 2
|
||||
// "1": 3
|
||||
// "10": 4
|
||||
// "11": 5
|
||||
// "2": 6
|
||||
// "20": 7
|
||||
// "21": 8
|
||||
// "210": 9
|
||||
// "211": 10
|
||||
// "2100": 11
|
||||
|
||||
// Original ArcIds are:
|
||||
// "0" -> "00": 0
|
||||
// "0" -> "01": 1
|
||||
// "1" -> "10": 2
|
||||
// "1" -> "11": 3
|
||||
// "11" -> "0": 4
|
||||
// "2" -> "20": 5
|
||||
// "2" -> "21": 6
|
||||
// "21" -> "210": 7
|
||||
// "21" -> "211": 8
|
||||
// "210" -> "0": 9
|
||||
// "210" -> "2100": 10
|
||||
// "2100" -> "0": 11
|
||||
|
||||
g.add_edge("0", |_| "0_data",
|
||||
"00", |_| "00_data",
|
||||
"0_00_data");
|
||||
g.add_edge("0", |_| "0_data",
|
||||
"01", |_| "01_data",
|
||||
"0_01_data");
|
||||
g.add_edge("1", |_| "1_data",
|
||||
"10", |_| "10_data",
|
||||
"1_10_data");
|
||||
g.add_edge("1", |_| "1_data",
|
||||
"11", |_| "11_data",
|
||||
"1_11_data");
|
||||
g.add_edge("11", |_| "11_data",
|
||||
"0", |_| "0_data",
|
||||
"11_0_data");
|
||||
g.add_edge("2", |_| "2_data",
|
||||
"20", |_| "20_data",
|
||||
"2_20_data");
|
||||
g.add_edge("2", |_| "2_data",
|
||||
"21", |_| "21_data",
|
||||
"2_21_data");
|
||||
g.add_edge("21", |_| "21_data",
|
||||
"210", |_| "210_data",
|
||||
"21_210_data");
|
||||
g.add_edge("21", |_| "21_data",
|
||||
"211", |_| "211_data",
|
||||
"21_211_data");
|
||||
g.add_edge("210", |_| "210_data",
|
||||
"0", |_| "0_data",
|
||||
"210_0_data");
|
||||
g.add_edge("210", |_| "210_data",
|
||||
"2100", |_| "2100_data",
|
||||
"210_2100_data");
|
||||
g.add_edge("2100", |_| "2100_data",
|
||||
"0", |_| "0_data",
|
||||
"2100_0_data");
|
||||
|
||||
let root_ids = [StateId(6)];
|
||||
let reachable_state_ids = [
|
||||
StateId(6), StateId(7), StateId(8), StateId(9), StateId(10), StateId(11),
|
||||
StateId(0), StateId(1), StateId(2)];
|
||||
let unreachable_state_ids = [StateId(3), StateId(4), StateId(5)];
|
||||
|
||||
// Mark.
|
||||
let mut c = Collector::new(&mut g);
|
||||
c.mark(&root_ids);
|
||||
|
||||
for (i, new_id) in c.state_id_map.iter().enumerate() {
|
||||
if new_id.is_some() {
|
||||
// Reachable IDs are remapped.
|
||||
assert!(reachable_state_ids.contains(&StateId(i)));
|
||||
} else {
|
||||
// Unreachable IDs aren't.
|
||||
assert!(unreachable_state_ids.contains(&StateId(i)));
|
||||
}
|
||||
}
|
||||
|
||||
// New StateIds are:
|
||||
// "2": 0
|
||||
// "20": 1
|
||||
// "21": 2
|
||||
// "210": 3
|
||||
// "211": 4
|
||||
// "0": 5
|
||||
// "2100": 6
|
||||
// "00": 7
|
||||
// "01": 8
|
||||
// This is BFS order, as we eagerly remap StateIds when we first
|
||||
// encounter them, not when they are visited. This should help by
|
||||
// compacting memory so that child vertices are adjacent to one another.
|
||||
assert_eq!(c.state_id_map,
|
||||
vec!(Some(StateId(5)),
|
||||
Some(StateId(7)),
|
||||
Some(StateId(8)),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(StateId(0)),
|
||||
Some(StateId(1)),
|
||||
Some(StateId(2)),
|
||||
Some(StateId(3)),
|
||||
Some(StateId(4)),
|
||||
Some(StateId(6)),));
|
||||
|
||||
// New ArcIds are:
|
||||
// "2" -> "20": 0
|
||||
// "2" -> "21": 1
|
||||
// "21" -> "210": 2
|
||||
// "21" -> "211": 3
|
||||
// "210" -> "0": 4
|
||||
// "210" -> "2100": 5
|
||||
// "0" -> "00": 6
|
||||
// "0" -> "01": 7
|
||||
// "2100" -> "0": 8
|
||||
// Again, this places child arc data in contiguous segments of memory.
|
||||
assert_eq!(c.arc_id_map,
|
||||
vec!(Some(ArcId(6)),
|
||||
Some(ArcId(7)),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(ArcId(0)),
|
||||
Some(ArcId(1)),
|
||||
Some(ArcId(2)),
|
||||
Some(ArcId(3)),
|
||||
Some(ArcId(4)),
|
||||
Some(ArcId(5)),
|
||||
Some(ArcId(8)),));
|
||||
|
||||
c.sweep();
|
||||
assert_eq!(c.graph.vertices,
|
||||
vec!(make_vertex("2_data",
|
||||
vec![],
|
||||
vec![ArcId(0), ArcId(1)],),
|
||||
make_vertex("20_data",
|
||||
vec![ArcId(0)],
|
||||
vec![]),
|
||||
make_vertex("21_data",
|
||||
vec![ArcId(1)],
|
||||
vec![ArcId(2), ArcId(3)]),
|
||||
make_vertex("210_data",
|
||||
vec![ArcId(2)],
|
||||
vec![ArcId(4), ArcId(5)]),
|
||||
make_vertex("211_data",
|
||||
vec![ArcId(3)],
|
||||
vec![]),
|
||||
make_vertex("0_data",
|
||||
vec![ArcId(4), ArcId(8)],
|
||||
vec![ArcId(6), ArcId(7)]),
|
||||
make_vertex("2100_data",
|
||||
vec![ArcId(5)],
|
||||
vec![ArcId(8)]),
|
||||
make_vertex("00_data",
|
||||
vec![ArcId(6)],
|
||||
vec![]),
|
||||
make_vertex("01_data",
|
||||
vec![ArcId(7)],
|
||||
vec![]),));
|
||||
|
||||
assert_eq!(c.graph.arcs,
|
||||
vec!(make_arc("2_20_data", StateId(0), Target::Expanded(StateId(1))),
|
||||
make_arc("2_21_data", StateId(0), Target::Expanded(StateId(2))),
|
||||
make_arc("21_210_data", StateId(2), Target::Expanded(StateId(3))),
|
||||
make_arc("21_211_data", StateId(2), Target::Expanded(StateId(4))),
|
||||
make_arc("210_0_data", StateId(3), Target::Expanded(StateId(5))),
|
||||
make_arc("210_2100_data", StateId(3), Target::Expanded(StateId(6))),
|
||||
make_arc("0_00_data", StateId(5), Target::Expanded(StateId(7))),
|
||||
make_arc("0_01_data", StateId(5), Target::Expanded(StateId(8))),
|
||||
make_arc("2100_0_data", StateId(6), Target::Expanded(StateId(5))),));
|
||||
|
||||
// TODO: Tests of state namespace.
|
||||
}
|
||||
|
||||
// TODO: Test that unexpanded arcs are handled correctly.
|
||||
|
||||
// TODO: Test that parallel edges are handled correctly.
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
//! Mark-and-sweep garbage collection for pruning graphs.
|
||||
//!
|
||||
//! This module implements a mark-and-sweep garbage collector that can prune a
|
||||
//! graph so that only components reachable from a set of root game states are
|
||||
//! retained. Running time and memory required are linear in graph size,
|
||||
//! although there is a potential for a high cost when rebuilding the hashtable
|
||||
//! that maps from game states to their IDs.
|
||||
|
||||
use ::{Graph, Target};
|
||||
use ::hidden::base::{ArcId, StateId, StateNamespace};
|
||||
|
||||
use std::cmp::Eq;
|
||||
use std::collections::VecDeque;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Garbage collector state. Use the `clean()` method to prune a graph.
|
||||
pub struct Collector<'a, T, S, A> where T: Hash + Eq + Clone + 'a, S: 'a, A: 'a {
|
||||
graph: &'a mut Graph<T, S, A>,
|
||||
marked_state_count: usize,
|
||||
marked_arc_count: usize,
|
||||
state_id_map: Vec<Option<StateId>>,
|
||||
arc_id_map: Vec<Option<ArcId>>,
|
||||
}
|
||||
|
||||
impl<'a, T, S, A> Collector<'a, T, S, A> where T: Hash + Eq + Clone + 'a, S: 'a, A: 'a {
|
||||
/// Runs mark-and-sweep garbage collection on a graph. Graph components not
|
||||
/// reachable from the vertices corresponding to roots will be dropped.
|
||||
///
|
||||
/// This is intended as the main entrypoint for this module. But this
|
||||
/// function is not exported by the crate, so you probably want the
|
||||
/// `retain_reachable()` method of `MutNode` or the `retain_reachable_from`
|
||||
/// method of `Graph`.
|
||||
pub fn retain_reachable(graph: &'a mut Graph<T, S, A>, roots: &[StateId]) {
|
||||
let mut c = Collector::new(graph);
|
||||
c.mark(roots);
|
||||
c.sweep_vertices();
|
||||
c.sweep_arcs();
|
||||
c.remap_state_namespace();
|
||||
}
|
||||
|
||||
/// Creates a new mark-and-sweep garbage collector with empty initial state.
|
||||
fn new(graph: &'a mut Graph<T, S, A>) -> Self {
|
||||
let empty_state_ids = vec![None; graph.vertices.len()];
|
||||
let empty_arc_ids = vec![None; graph.arcs.len()];
|
||||
Collector {
|
||||
graph: graph,
|
||||
marked_state_count: 0,
|
||||
marked_arc_count: 0,
|
||||
state_id_map: empty_state_ids,
|
||||
arc_id_map: empty_arc_ids,
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverses graph components reachable from `roots` and marks them as
|
||||
/// reachable. Also builds a new graph component addressing scheme that
|
||||
/// reassigns `StateId` and `ArcId` values.
|
||||
///
|
||||
/// As side effects, arc sources and vertex children are updated to use the
|
||||
/// new addressing scheme.
|
||||
fn mark(&mut self, roots: &[StateId]) {
|
||||
let mut frontier = VecDeque::new();
|
||||
for id in roots.iter() {
|
||||
frontier.push_back(*id);
|
||||
}
|
||||
loop {
|
||||
match frontier.pop_front() {
|
||||
None => break,
|
||||
Some(state_id) => {
|
||||
let (new_state_id, mut child_arc_ids) = {
|
||||
let vertex = self.graph.get_vertex_mut(state_id);
|
||||
if vertex.mark {
|
||||
continue
|
||||
}
|
||||
// Mark all reachable vertices.
|
||||
vertex.mark = true;
|
||||
let new_state_id = {
|
||||
let new_state_id = StateId(self.marked_state_count);
|
||||
self.marked_state_count += 1;
|
||||
new_state_id
|
||||
};
|
||||
self.state_id_map[state_id.as_usize()] = Some(new_state_id);
|
||||
(new_state_id, vertex.children.clone())
|
||||
};
|
||||
|
||||
for arc_id in child_arc_ids.iter_mut() {
|
||||
let arc = self.graph.get_arc_mut(*arc_id);
|
||||
if arc.mark {
|
||||
continue
|
||||
}
|
||||
// Mark all reachable arcs.
|
||||
arc.mark = true;
|
||||
// Update arc sources to use new state IDs.
|
||||
arc.source = new_state_id;
|
||||
if let Target::Expanded(child_vertex_id) = arc.target {
|
||||
frontier.push_front(child_vertex_id);
|
||||
}
|
||||
let new_arc_id = {
|
||||
let new_arc_id = ArcId(self.marked_arc_count);
|
||||
self.marked_arc_count += 1;
|
||||
new_arc_id
|
||||
};
|
||||
self.arc_id_map[arc_id.as_usize()] = Some(new_arc_id);
|
||||
*arc_id = new_arc_id;
|
||||
}
|
||||
// Update vertex children to use new ArcIds.
|
||||
self.graph.get_vertex_mut(state_id).children = child_arc_ids;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops vertices which were not reached in the previous `mark()`. Must be
|
||||
/// run after `mark()`.
|
||||
///
|
||||
/// Also, resets the mark state on all vertices and updates vertex pointers
|
||||
/// to parent edges to use the new `ArcId` addressing scheme built in the
|
||||
/// previous call to `mark()`.
|
||||
fn sweep_vertices(&mut self) {
|
||||
let mut new_vertices = Vec::with_capacity(self.graph.vertices.len());
|
||||
for mut vertex in self.graph.vertices.drain(0..) {
|
||||
if !vertex.mark {
|
||||
continue
|
||||
}
|
||||
// Unmark marked vertices.
|
||||
vertex.mark = false;
|
||||
// Update vertex parents to use new ArcIds.
|
||||
for parent_arc_id in vertex.parents.iter_mut() {
|
||||
*parent_arc_id = self.arc_id_map[parent_arc_id.as_usize()].unwrap();
|
||||
}
|
||||
new_vertices.push(vertex);
|
||||
}
|
||||
new_vertices.shrink_to_fit();
|
||||
// Retain only marked vertices.
|
||||
self.graph.vertices = new_vertices;
|
||||
}
|
||||
|
||||
/// Drops arcs which were not reached in the previous `mark()`. Must be run
|
||||
/// after `mark()`.
|
||||
///
|
||||
/// Also, resets the mark state on all arcs and updates arc targets to use
|
||||
/// the new `StateId` addressing scheme built in the previous call to
|
||||
/// `mark()`.
|
||||
fn sweep_arcs(&mut self) {
|
||||
let mut new_arcs = Vec::with_capacity(self.graph.arcs.len());
|
||||
for mut arc in self.graph.arcs.drain(0..) {
|
||||
if !arc.mark {
|
||||
continue
|
||||
}
|
||||
// Unmark marked arcs.
|
||||
arc.mark = false;
|
||||
// Update arc targets to use new StateIds.
|
||||
arc.target = match arc.target {
|
||||
Target::Expanded(old_arc_id) =>
|
||||
Target::Expanded(self.state_id_map[old_arc_id.as_usize()].unwrap()),
|
||||
Target::Unexpanded(()) =>
|
||||
Target::Unexpanded(()),
|
||||
};
|
||||
new_arcs.push(arc);
|
||||
}
|
||||
new_arcs.shrink_to_fit();
|
||||
// Retain only marked arcs.
|
||||
self.graph.arcs = new_arcs;
|
||||
}
|
||||
|
||||
/// Remaps associations between game states to use post-sweep `StateId`
|
||||
/// associations. Must be run after `mark()`.
|
||||
fn remap_state_namespace(&mut self) {
|
||||
Self::do_remap(&mut self.graph.state_ids, &self.state_id_map);
|
||||
}
|
||||
|
||||
/// Private method for remapping StateIds. Borrow rules prevent us from invoking
|
||||
/// remap directly in `remap_state_namespace`.
|
||||
fn do_remap(state_ids: &mut StateNamespace<T>, state_id_map: &[Option<StateId>]) {
|
||||
state_ids.remap(|_, old_state_id| state_id_map[old_state_id.as_usize()]);
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ use ::hidden::nav::{ChildList, ChildListIter, Edge, Node, ParentList, ParentList
|
||||
use ::hidden::nav::{make_child_list, make_edge, make_node, make_parent_list};
|
||||
|
||||
pub mod path;
|
||||
pub mod mark_sweep;
|
||||
pub mod mark_compact;
|
||||
|
||||
/// Mutable handle to a graph vertex ("node handle").
|
||||
///
|
||||
@ -115,6 +115,7 @@ impl<'a, T, S, A> MutNode<'a, T, S, A> where T: Hash + Eq + Clone + 'a, S: 'a, A
|
||||
/// this node.
|
||||
pub fn retain_reachable(&mut self) {
|
||||
self.graph.retain_reachable_from_ids(&[self.id]);
|
||||
self.id = StateId(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
15
src/lib.rs
15
src/lib.rs
@ -73,7 +73,6 @@ impl<T, S, A> Graph<T, S, A> where T: Hash + Eq + Clone {
|
||||
data: data,
|
||||
parents: Vec::new(),
|
||||
children: Vec::new(),
|
||||
mark: false,
|
||||
});
|
||||
self.vertices.last_mut().unwrap()
|
||||
}
|
||||
@ -86,7 +85,7 @@ impl<T, S, A> Graph<T, S, A> where T: Hash + Eq + Clone {
|
||||
if let Target::Expanded(target_id) = target {
|
||||
self.get_vertex_mut(target_id).parents.push(arc_id);
|
||||
}
|
||||
self.arcs.push(Arc { data: data, source: source, target: target, mark: false, });
|
||||
self.arcs.push(Arc { data: data, source: source, target: target, });
|
||||
arc_id
|
||||
}
|
||||
|
||||
@ -158,6 +157,14 @@ impl<T, S, A> Graph<T, S, A> where T: Hash + Eq + Clone {
|
||||
make_mut_edge(self, arc_id)
|
||||
}
|
||||
|
||||
pub fn vertex_count(&self) -> usize {
|
||||
self.vertices.len()
|
||||
}
|
||||
|
||||
pub fn edge_count(&self) -> usize {
|
||||
self.arcs.len()
|
||||
}
|
||||
|
||||
pub fn retain_reachable_from(&mut self, roots: &[T]) {
|
||||
let mut root_ids = Vec::with_capacity(roots.len());
|
||||
for state in roots.iter() {
|
||||
@ -169,7 +176,7 @@ impl<T, S, A> Graph<T, S, A> where T: Hash + Eq + Clone {
|
||||
}
|
||||
|
||||
fn retain_reachable_from_ids(&mut self, root_ids: &[StateId]) {
|
||||
self::hidden::mutators::mark_sweep::Collector::retain_reachable(self, root_ids);
|
||||
self::hidden::mutators::mark_compact::Collector::retain_reachable(self, root_ids);
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,7 +186,7 @@ impl<T, S, A> Graph<T, S, A> where T: Hash + Eq + Clone {
|
||||
/// with all of their edges in the unexpanded state. Graph-modifying operations
|
||||
/// which are executed while exploring the game state topology will expand these
|
||||
/// edges. Cycle detection is done at edge expansion time.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Target<T, R> {
|
||||
/// Edge has not yet been expanded.
|
||||
Unexpanded(R),
|
||||
|
Loading…
Reference in New Issue
Block a user