英文:
Having trouble with proper design pattern
问题
我正在尝试编写一个简单的游戏引擎,但由于严格的借用规则,我通常用于此目的的常规模式在这里不起作用。
有一个名为World
的结构体,它拥有一组Object
特征对象。我们可以将这些对象视为游戏引擎中的移动物体。World
负责在每个游戏刻调用每个对象的update
和draw
方法。
World
结构体还拥有一组Event
特征对象。每个事件封装了在游戏刻期间修改objects
的任意代码片段。
理想情况下,Event
特征对象有一个名为do_event
的方法,不接受任何参数,可以由World
在每个游戏刻调用。问题在于,特定的Event
需要对它修改的对象有可变引用,但这些对象由World
拥有。我们可以在World
为每个事件调用do_event
时将必要的对象传递给它,但它如何知道要传递给每个事件对象哪些对象呢?
如果这是C++或Python,构造特定的Event
时,我会将事件的目标传递给构造函数,然后在do_event
期间使用它。显然,在Rust中这不起作用,因为目标都由World
拥有,所以我们不能将可变引用存储在其他地方。
我可以添加一些ID系统和查找表,以便World
知道要传递给哪些Event
哪些对象,但这很混乱而且昂贵。我怀疑我解决这类问题的整个方法需要改变,但我束手无策。
我的代码草图:
trait Object {
fn update(&mut self);
fn draw(&self);
}
trait Event {
fn do_event(&mut self);
}
struct World {
objects: Vec<Box<dyn Object + 'a>>,
events: Vec<Box<dyn Event + 'a>>,
}
impl World {
fn update(&mut self) {
for obj in self.objects.iter_mut() {
obj.update();
}
for evt in self.events.iter_mut() {
evt.do_event();
}
}
fn draw(&mut self) { /*...*/ }
}
struct SomeEvent<'a, T: Object> {
target: &'a mut T,
}
impl<'a, T: Object> Event for SomeEvent<'a, T> {
fn update(&self) {
self.target.do_shrink(); // 这里需要可变引用
}
}
我知道RefCell
可以使多个对象获取可变引用。也许这是我应该采取的方向。然而,根据我从Rust书中学到的内容,我感到RefCell
通过引入不安全的代码和循环引用来破坏了Rust的一些主要思想。我只是想知道是否有其他明显的设计模式,更符合惯用的做法。
英文:
I'm trying to write a simple game engine, but the normal patterns I would use to do this don't work here due to the strict borrowing rules.
There is a World
struct which owns a collection of Object
trait objects. We can think of these as moving physical objects in the game engine. World
is responsible for calling update
and draw
on each of these during each game tick.
The World
struct also owns a collection of Event
trait objects. Each of these events encapsulates an arbitrary piece of code that modifies the objects
during the game ticks.
Ideally, the Event
trait object has a single method do_event
that takes no arguments that can be called by World
during each game tick. The problem is that a particular Event
needs to have mutable references to the objects that it modifies but these objects are owned by World
. We could have World
pass mutable references to the necessary objects when it calls do_event
for each event, but how does it know which objects to pass to each event object?
If this were C++ or Python, when constructing a particular Event
, I would pass the target of the event to the constructor where it would be saved and then used during do_event
. Obviously, this doesn't work in Rust because the targets are all owned by World
so we cannot store mutable references elsewhere.
I could add some ID system and a lookup table so World
knows which objects to pass to which Event
s but this is messy and costly. I suspect my entire way of approaching this class of problem needs to change, however, I am stumped.
A sketch of my code:
trait Object {
fn update(&mut self);
fn draw(&self);
}
trait Event {
fn do_event(&mut self);
}
struct World {
objects: Vec<Box<dyn Object + 'a>>,
events: Vec<Box<dyn Event + 'a>>,
}
impl World {
fn update(&mut self) {
for obj in self.objects.iter_mut() {
obj.update();
}
for evt in self.events.iter_mut() {
evt.do_event();
}
}
fn draw(&mut self) { /*...*/ }
}
struct SomeEvent<'a, T: Object> {
target: &'a mut T,
}
impl<'a, T: Object> Event for SomeEvent<'a, T> {
fn update(&self) {
self.target.do_shrink(); // need mutable reference here
}
}
I know that RefCell
enables multiple objects to obtain mutable references. Perhaps that is the direction I should go. However, based on what I learned from the Rust book, I got the sense that RefCell
s break some of Rust's main ideas by introducing unsafe code and circular references. I guess I was just wondering if there was some other obvious design pattern that adhered more to the idiomatic way of doing things.
答案1
得分: 1
在阅读问题评论并观看kyren的RustConf 2018演讲(感谢trentcl)后,我意识到我对游戏引擎的面向对象方法与Rust基本不兼容(或者至少相当困难)。
在花了一天左右的时间后,我强烈建议观看发布的视频,然后使用specs库(这是用户2722968提到的ECS系统的实现)。
希望这对将来的其他人有所帮助。
英文:
After reading the question comments and watching kyren's RustConf 2018 talk (thanks trentcl), I've realized the OO approach to my game engine is fundamentally incompatible with Rust (or at least quite difficult).
After working on this for a day or so, I would highly recommend watching the posted video and then using the specs library (an implementation of the ECS system mentioned by user2722968).
Hope this helps someone else in the future.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论