遇到了正确的设计模式问题。

huangapple go评论70阅读模式
英文:

Having trouble with proper design pattern

问题

我正在尝试编写一个简单的游戏引擎,但由于严格的借用规则,我通常用于此目的的常规模式在这里不起作用。

有一个名为World的结构体,它拥有一组Object特征对象。我们可以将这些对象视为游戏引擎中的移动物体。World负责在每个游戏刻调用每个对象的updatedraw方法。

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 Events 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(&amp;mut self);
    fn draw(&amp;self);
}

trait Event {
    fn do_event(&amp;mut self);
}

struct World {
    objects: Vec&lt;Box&lt;dyn Object + &#39;a&gt;&gt;,
    events: Vec&lt;Box&lt;dyn Event + &#39;a&gt;&gt;,
}

impl World {
    fn update(&amp;mut self) {
        for obj in self.objects.iter_mut() {
            obj.update();
        }
        for evt in self.events.iter_mut() {
            evt.do_event();
        }
    }
    fn draw(&amp;mut self) { /*...*/ }
}

struct SomeEvent&lt;&#39;a, T: Object&gt; {
    target: &amp;&#39;a mut T,
}

impl&lt;&#39;a, T: Object&gt; Event for SomeEvent&lt;&#39;a, T&gt; {
    fn update(&amp;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 RefCells 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.

huangapple
  • 本文由 发表于 2020年1月7日 02:39:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/59617276.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定