我正在构造一个“scenegraph”,它是Shape
节点的分层数据结构(例如球体、立方体、网格等,示例代码中未显示)。Shape
可以拥有零个或多个子Shape
示例。这形成了一棵树,树被完全构建,然后不可变地传递给一组工作线程,这些线程基于这个场景图来渲染图像的各个部分。场景图一旦构建好就不需要更改,因此线程只需要读取权限。
x1c 0d1x的数据
我的问题涉及到scenegrape的构造。最初,我有一个自己的子节点。即。一个形状拥有所有的孩子。因此,我没有使用指针或引用。这工作正常,直到我需要添加父指针。Shape
的每个子节点现在必须维护一个指向其父节点的指针。(因为最终场景图是在线程之间共享的),这让我们使用Weak
ptr到父节点以避免循环问题。但这意味着主拥有关系,其中Weak
拥有子节点,现在也必须是Arc
,这个指针比父指针被遍历了很多很多次。
据我所知,Arc
的特性仅在构建此数据结构并移交给线程池期间是必需的。在渲染这些Arc
指针时是不幸的开销。此时数据结构被认为是不可变的,因此不再需要Arc的特性。它们仅在构建数据结构期间需要,支持创建Weak
父指针。
有趣的是,我无法在 safe 代码中找到一种方法来将子对象设置为Shape * 所拥有,并在子对象中设置父对象指针。这是一种双向链表,我知道这在Rust中很难。最后我不得不使用一行unsafe
来设置下面Shape::set_parent()
中的指针:
use std::sync::{Arc, Weak};
pub trait LocalIntersect {
fn local_intersect(&self);
}
#[derive(Debug, Default)]
pub struct Shape {
id: usize,
members: Vec<Arc<Shape>>,
parent: Option<Weak<Shape>>,
}
impl Shape {
fn new(id: usize) -> Self {
Self { id, members: vec![], parent: None }
}
fn add(&mut self, shape: Arc<Shape>) {
self.members.push(shape);
}
fn set_parent(&self, parent: Weak<Shape>) {
let this = self as *const Shape as *mut Shape;
unsafe {
(*this).parent = Some(parent);
}
}
fn set_parent_to_all(&self, parent_weak: Weak<Shape>) {
for member in &self.members {
member.set_parent(parent_weak.clone());
}
}
}
impl LocalIntersect for Shape {
fn local_intersect(&self) {
// For now, check if there is a parent and if so, print its ID
// (Later, we'll combine parent transforms, but not now)
if let Some(parent_weak) = &self.parent {
if let Some(parent) = parent_weak.upgrade() {
println!("local_intersect {}, parent {}", self.id, parent.id);
}
}
}
}
fn main() {
let mut parent_shape = Shape::new(0);
let child_shape = Shape::new(1);
let child_arc = Arc::new(child_shape);
// Add child to the parent's group
parent_shape.add(Arc::clone(&child_arc));
// Wrap the parent shape in Arc
let parent_arc = Arc::new(parent_shape);
// Set the parent pointer of each child in the group
parent_arc.set_parent_to_all(Arc::downgrade(&parent_arc));
// Share across threads (example with a thread)
let shared_node = Arc::clone(&child_arc);
let thread = std::thread::spawn(move || {
shared_node.local_intersect();
});
thread.join().unwrap();
// Output is:
// local_intersect 1, parent 0
}
字符串
Rust Playground
考虑到性能是这个应用程序的重中之重,在Rust中有没有一种惯用的方法来处理这个问题?这是unsafe
代码的好候选者吗?我应该删除Arc
和Weak
,只使用普通的子所有权和父指针的原始指针吗?如果是这样,我以后如何处理删除这个数据结构?
我可以使用像id_tree
或petgraph
这样的东西,但是在查找子对象或父对象时,它们也会带来开销。我真的只是想要一个指针,渲染器可以直接跟踪父对象,而不会影响非常常见的“get child”查找的性能。
我不是Rust或C/C++的新手,但我是编写unsafe
Rust代码的新手。
编辑:例如,在一个C程序中,我只需要在渲染之前给所有的子对象添加一个原始的父对象指针,然后在释放任何对象之前将它们全部置空,这不会影响所有权或导致悬空指针问题。我正在寻找一个类似的过程在Rust中-只是一个漂亮的,简单的,快速的父对象指针在一个最终不可变的数据结构中,但没有鸡和蛋的问题。
1条答案
按热度按时间9w11ddsr1#
如果你真的认为树一旦建立就不可变,也许你可以把所有的节点打包成一个向量,用索引代替引用。(也许这就是所谓的竞技场,不确定是否有什么微妙之处)。(我们从来不需要自己分配id)。在多线程上下文中,我们只需要将节点的向量作为一个整体共享/传递。
下面是根据这个想法稍微修改过的例子(我不理解
local_intersect()
函数,也许它没有做你想要的)。字符串