Rust ECS游戏引擎中Controller组件更新的可变借用冲突问题及优化方案问询
Rust ECS游戏引擎中Controller组件更新的可变借用冲突问题及优化方案问询
我正在用Rust开发一款ECS架构的游戏引擎,目前遇到了一个可变借用的冲突问题,想和大家探讨下有没有更优雅的解决思路。
项目背景
我的每个场景(Scene)结构如下:
pub struct Scene { pub entities_index: usize, pub component_map: HashMap<TypeId, Box<dyn ComponentVec>>, }
组件分为两种形式:一种是基础结构体,比如Transform组件:
pub struct TransformComponent { pub transform: Mat4, children: Vec<usize>, }
另一种是 trait 对象,比如Controller组件,它需要在帧更新时能拿到自身索引和可变的场景引用,以便修改其他组件:
pub trait Controller{ fn update(&mut self, index: usize, scene: &mut Scene); }
目前我用泛型方法来获取动态组件的可变引用,这个逻辑是正常工作的:
pub fn borrow_component_vec_mut<ComponentType: 'static>( &self, ) -> Option<RefMut<Vec<Option<ComponentType>>>> { if let Some(component_vec) = self.component_map.get(&TypeId::of::<ComponentType>()) { if let Some(component_vec) = component_vec .as_any() .downcast_ref::<RefCell<Vec<Option<ComponentType>>>>() { return Some(component_vec.borrow_mut()); } } None }
遇到的问题
当我尝试获取Controller组件的可变引用,并调用它的update方法传入&mut scene时,触发了Rust的借用检查错误。相关代码如下:
let mut scene = self.scenes[0].write().unwrap(); let mut controllers: std::cell::RefMut<'_, Vec<Option<Box<dyn Controller>>>> = scene.borrow_component_vec_mut::<Box<dyn Controller>>().unwrap(); for (index, mut controller) in controllers.iter_mut().enumerate() { if let Some(controller) = controller.as_mut(){ controller.update(index, &mut scene); } } drop(controllers); drop(scene);
我理解问题所在:borrow_component_vec_mut方法需要接收&self(不可变引用),但此时我已经持有了scene的可变引用,Rust的借用规则不允许同一对象同时存在可变和不可变引用,哪怕我之后会立刻drop掉这些引用也不行。而且我不能用不可变方式借用Controller,因为它内部状态需要修改;尝试用Arc<RwLock>包装Scene又会在调用写锁时阻塞,也不是可行方案。
当前临时解决方案
在社区朋友的建议下,我把Controller包装成Arc<Mutex<Box<dyn Controller>>>,并且先把需要处理的Controller克隆出来,释放scene的借用后再执行更新逻辑,这样确实解决了问题:
let mut scene = self.scenes[0].write().unwrap(); let mut test = vec![]; let mut controllers = scene.borrow_component_vec_mut::<Arc<Mutex<Box<dyn Controller>>>>().unwrap(); for (index, controller) in controllers.iter().enumerate() { if let Some(controller) = controller { test.push((index, controller.clone())); } } drop(controllers); drop(scene); for (index, mut controller) in test { let mut controller = controller.lock().unwrap(); controller.update(index, self.scenes[0].clone()); }
但这种方式感觉有点“重量级”,总觉得应该有更简洁、更符合ECS设计思路的方案,想请教各位有没有更好的解决办法?
备注:内容来源于stack exchange,提问作者C. Dow
相关产品推荐
相关产品推荐

