#arena-allocator #tree #arena #ecs #generation

vec-tree

使用竞技场分配器的安全树,通过使用代际索引,允许删除而不受ABA问题的困扰。

3个版本

0.1.2 2018年11月30日
0.1.1 2018年11月29日
0.1.0 2018年11月29日

#360 in 内存管理

Download history 17/week @ 2024-03-11 13/week @ 2024-03-18 19/week @ 2024-03-25 75/week @ 2024-04-01 9/week @ 2024-04-08 20/week @ 2024-04-15 21/week @ 2024-04-22 11/week @ 2024-04-29 7/week @ 2024-05-06 15/week @ 2024-05-13 9/week @ 2024-05-20 20/week @ 2024-05-27 13/week @ 2024-06-03 9/week @ 2024-06-10 14/week @ 2024-06-17 18/week @ 2024-06-24

56 每月下载

MPL-2.0 许可证

39KB
449

vec-tree

使用竞技场分配器的安全树,通过使用代际索引,允许删除而不受ABA问题的困扰。

它内部使用由fitzgen制作的generational-arena,特别感谢他。

generational-arena本身受到Catherine West在2018年RustConf闭幕演讲的启发,这些想法在游戏编程的实体-组件-系统背景下被提出。

什么?为什么?

当你正在处理一个树,并且你想要一次添加和删除单个节点,或者当你编写一个游戏,其世界由许多相互引用的对象组成,这些对象的生存期依赖于用户输入时,这些情况会使匹配Rust的所有权和生命周期规则变得复杂。

对于结构体来说,使用共享所有权(即 Rc<RefCell<T>>Arc<Mutex<T>>)或借用引用(即 &'a T&'a mut T)是没有意义的。循环规则排除了引用计数类型,所需的共享可变性规则排除了借用。此外,生存期是动态的,并不遵循借用数据的生存期长于借用人这一规则。

在这些情况下,我们可能会倾向于将对象存储在一个 Vec<T> 中,并通过它们的索引相互引用。不再需要借用检查或所有权问题!通常,这种解决方案已经足够好了。

然而,现在当我们不再需要它们时,无法从那个 Vec<T> 中删除单个项目,因为我们最终会

  • 弄乱被删除元素之后每个元素的索引,或者

  • 遭受ABA问题。为了进一步阐述,如果我们尝试将 Vec<T> 替换为 Vec<Option<T>>,并通过将其设置为 None 来删除一个元素,那么我们就会产生以下存在缺陷的序列

    • obj1 在索引 i 处引用 obj2

    • 其他人从索引 i 删除了 obj2,将该元素设置为 None

    • 第三件事在索引 i 分配了 obj3,因为该索引处的元素是 None,因此可以用于分配

    • obj1 尝试在索引 i 处获取 obj2,但错误地得到了 obj3,而实际上获取应该失败。

通过在集合中引入单调递增的生成计数器,将每个元素与它被插入时的生成关联起来,并通过索引和元素被插入时的生成时间对获取集合中的元素,然后我们可以解决上述ABA问题。当在集合中索引时,如果索引对的生成与该索引处的元素生成不匹配,则该操作失败。

特性

  • unsafe
  • 有不同类型的迭代器遍历树
  • 经过良好测试

用法

首先,将 vec-tree 添加到您的 Cargo.toml

[dependencies]
vec-tree = "0.1"

然后,导入crate并使用 vec-tree::Tree

extern crate vec_tree;
use vec_tree::VecTree;

let mut tree = VecTree::new();

// Insert some elements into the tree.
let root_node = tree.insert_root(1);
let child_node_1 = tree.insert(10, root_node);
let child_node_2 = tree.insert(11, root_node);
let child_node_3 = tree.insert(12, root_node);
let grandchild = tree.insert(100, child_node_3);

// Inserted elements can be accessed infallibly via indexing (and missing
// entries will panic).
assert_eq!(tree[child_node_1], 10);

// Alternatively, the `get` and `get_mut` methods provide fallible lookup.
if let Some(node_value) = tree.get(child_node_2) {
    println!("The node value is: {}", node_value);
}
if let Some(node_value) = tree.get_mut(grandchild) {
    *node_value = 101;
}

// We can remove elements.
tree.remove(child_node_3);

// Insert a new one.
let child_node_4 = tree.insert(13, root_node);

// The tree does not contain `child_node_3` anymore, but it does contain
// `child_node_4`, even though they are almost certainly at the same index
// within the arena of the tree in practice. Ambiguities are resolved with
// an associated generation tag.
assert!(!tree.contains(child_node_3));
assert!(tree.contains(child_node_4));

// We can also move a node (and its descendants).
tree.append_child(child_node_1, child_node_4);

// Iterate over the children of a node.
for value in tree.children(child_node_1) {
    println!("value: {:?}", value);
}

// Or all the descendants in depth first search order.
let descendants = tree
    .descendants(root_node)
    .map(|node| tree[node])
    .collect::<Vec<i32>>();

assert_eq!(descendants, [1, 10, 13, 11]);

依赖关系

~56KB