当Cell无法使用时,你如何改变(或避免变异)Rust中的嵌套构造字段,而不会使所有内容都变得可变?

时间:2016-02-03 00:00:54

标签: rust

我是Rust的新手,我一直在慢慢关注an arcade game tutorial,这对它所经历的概念有很大的帮助。

在创建主菜单的教程的part nine中,作者为主菜单(“新游戏”,“退出”)制作标签的读者建议“作业”。在聚焦和不聚焦时改变大小,而不是跳到他们的闲置/聚焦大小。这是我遇到困难的地方......

在我开始实施更改之前,代码相关部分的基本布局如下:

// equivalent to 'menu option'
struct Action {
    /// function executed if action chosen
    func: Box<Fn(&mut Phi) -> ViewAction>,
    label: &'static str,
    idle_sprite: Sprite, // smaller (32)
    focus_sprite: Sprite, // larger (38)
    // ...
}
impl Action {
    fn new(phi: &mut Phi, label: &'static str, func: Box<Fn(&mut Phi) -> ViewAction>) -> Action {
        // ...
    }

struct MainMenuView {
    actions: Vec<Action>,
    selected: i8,
    // ...
}
impl MainMenuView {
    pub fn new(phi: &mut Phi) -> MainMenuView {
        // ...
    }
}
impl View for MainMenuView {
    fn render(&mut self, phi: &mut Phi, elapsed: f64) -> ViewAction {
        // ...
        for (i, action) in self.actions.iter().enumerate() {
            // ...
        }
    }
}

fn main() {
    ::phi::spawn("Arcade Shooter", |phi| {
        Box::new(::views::main_menu::MainMenuView::new(phi))
    });
}

我对动画的第一个想法是使用idle_sizefocus_size之间的插值大小动态创建一个精灵,使用自焦点更改后经过的时间使用Action上的方法来聚焦并散焦以更改用于为current_size字段生成精灵的sprite字段。

这需要Action结构的可变绑定,这需要我一段时间才能解决,因为在任何地方都没有let绑定,但似乎可以通过更改构造函数来实现: Action::new(...) -> &mut action,以及许多明确标记生命周期(它有自己的问题,但这已经太久了)。然后我意识到MainMenuView也必须是可变绑定的,此时我停止了这条路径(自启动以来我没有成功编译),因为这似乎是一个非常不优雅的解决方案,基本上一切都是可变的,肯定会扼杀生锈的不变性默认......

然后我想知道我是否可以使用新的MainMenuView创建一个新Actionsprite,这可能有效(将视图更改为另一个MainMenuView) ,但这似乎是一种非常浪费的方式,只是改变一些文本的大小,再次是非常不优雅。

之后,我记得Cell,但在尝试将此actions MainMenuView Vec<Cell<Actions>> Cell时,我发现Copy仅适用于func {1}}类型。这可能没问题(我没有足够的经验知道),但Action的{​​{1}}字段未实现Copy(我不确定它是否可以? )所以Action不能#[derive(Copy)]。死胡同没有重组程序的大部分而没有func中的Action

这是我的主要问题的结束 - 基本上,当你有嵌套结构并且你想要一个深场变异,但你不能在它周围放置一个Cell时你会怎么做(据我所知)?这是代码的结构性问题,我应该首先避免这个问题吗?

我还意识到,Vec<Sprite>Action的一个解决方案,其中有许多不同大小的精灵用于转换,这样就不需要任何上述内容是可变的。这本能地感觉有点hacky,因为它实际上是硬编码不应该的东西。我还可以通过正确对齐帧来看实现中的问题(我是新的同步帧定时),并且工作最大fps - 尽管可以根据最大fps动态创建精灵的数量。构建MainMenuView ......

3 个答案:

答案 0 :(得分:4)

对非Copy类型使用RefCell

struct Action {
    func: Box<Fn(&mut Phi) -> ViewAction>,
    label: &'static str,
    sprite: RefCell<Sprite>,
    // ...
}

答案 1 :(得分:3)

If you can mutate a struct (you either own it or have a mutable reference to it) then you can mutably borrow any of its fields. What that means in this case is that if you are ever given the opportunity to mutate MainMenuView, then you can take a moment to mutate any of the actions as well. Using RefCell or Cell on a field also works when you can't mutate a struct, but obscures when the value may be changing. RefCell also runs the risk of runtime borrow panics. You should avoid RefCell if possible!

I don't know how this framework works, which affects how this question can be answered. It looks like phi takes ownership over your MainMenuView, which means phi decides when you get to mutate it from then on. If you're never given the opportunity to mutate the MainMenuView regularly to perform animation, it may still be possible. Another option that avoids RefCell might be to encode the animation when you mutate selected and compute how it should affect the drawing during the draw call. For example, if you store the timestamp when the selection was changed then you can compute at draw time how the sprite should be drawn.

答案 2 :(得分:2)

在您的教程中,val a: Seq[Any] = Seq(1) a match { case b: Seq[_] => b.map(c => c match { case s: Int => print(2 * s) case _ => print("Not matched") }) case _ => print("Not matched") } 看起来已经将View::render作为可变引用。 MainMenuView通过MainMenuView拥有所有Action值的所有权,这意味着可变性会转移到它们。这意味着您实际上不必更改任何内容以获得对Vec值的可变访问权限,除非在Action循环中调用iter_mut()而不是iter() for的{​​{1}}的实施。