使用状态机模式

时间:2017-11-04 03:06:58

标签: rust state lifetime ownership-semantics

这个问题是关于在Rust中为视频游戏实现状态机时可能出现的特定所有权模式,其中各州可以引用" global"借用的上下文和状态机拥有其状态的地方。我一直试图尽可能多地删除细节,同时仍在激发问题,但这是一个相当大且纠结的问题。

这是州特质:

pub trait AppState<'a> {
    fn update(&mut self, Duration) -> Option<Box<AppState<'a> + 'a>>;
    fn enter(&mut self, Box<AppState<'a> + 'a>);
    //a number of other methods
}

我使用盒装特征对象而不是枚举来实现状态,因为我希望它们有很多。状态在其更新方法中返回Some(State),以使其拥有的状态机切换到新状态。我添加了一个生命周期参数,因为没有它,编译器生成类型为Box<AppState + 'static>的框,使得框无用,因为状态包含可变状态。

说到状态机,这里是:

pub struct StateMachine<'s> {
    current_state: Box<AppState<'s> + 's>,
}

impl<'s> StateMachine<'s> {
    pub fn switch_state(&'s mut self, new_state: Box<AppState<'s> + 's>) -> Box<AppState<'s> + 's> {
        mem::replace(&mut self.current_state, new_state);
    }
}

状态机始终具有有效状态。默认情况下,它以Box<NullState>开头,这是一个什么都不做的状态。为简洁起见,我省略了NullState。就其本身而言,这似乎编译得很好。

InGame状态旨在实现基本的游戏情景:

type TexCreator = TextureCreator<WindowContext>;

pub struct InGame<'tc> {
    app: AppControl,
    tex_creator: &'tc TexCreator,

    tileset: Tileset<'tc>,
}

impl<'tc> InGame<'tc> {
    pub fn new(app: AppControl, tex_creator: &'tc TexCreator) -> InGame<'tc> {
        // ... load tileset ...

        InGame {
            app,
            tex_creator,
            tileset,
        }
    }
}

这个游戏取决于Rust SDL2。这组特殊的绑定要求纹理由TextureCreator创建,并且纹理不会比其创建者更长。纹理需要生命周期参数来确保这一点。 Tileset包含纹理,因此导出此要求。这意味着我无法在州内存储TextureCreator(虽然我也愿意),因为可变借用的InGame可能会将纹理创建者移出。因此,纹理创建者在main中拥有,在创建主状态时,对它的引用将传递给它:

fn main() {
    let app_control = // ...
    let tex_creator = // ...
    let in_game = Box::new(states::InGame::new(app_control, &tex_creator));
    let state_machine = states::StateMachine::new();
    state_machine.switch_state(in_game);
}

我觉得这个程序应该是有效的,因为我已经确保tex_creator比任何可能的状态都要长,并且状态机是最不长期存在的变量。但是,我收到以下错误:

error[E0597]: `state_machine` does not live long enough
  --> src\main.rs:46:1
   |
39 |     state_machine.switch_state( in_game );
   |     ------------- borrow occurs here
...
46 | }
   | ^ `state_machine` dropped here while still borrowed
   |
   = note: values in a scope are dropped in the opposite order they are created

这对我没有意义,因为state_machine只是被方法调用借用,但是编译器说它在方法结束时仍然借用了它。我希望它能让我在错误信息中追踪借款人 - 我不明白为什么在方法返回时没有借用借款。

基本上,我想要以下内容:

  • 该状态由特质实施。
  • 该州属于国家机器。
  • 该状态能够包含对生命周期大于状态机的任意非静态数据的引用。
  • 当状态被换出时,旧框仍然有效,以便可以将其移动到新状态的构造函数中。这将允许新状态切换回先前状态而不需要重新构造。
  • 状态可以通过从“更新”返回新状态来指示状态更改。旧的国家必须能够在自己内部构建这种新的国家。

这些约束是否可以满足,如果是,如何?

我为这个啰嗦的问题以及我错过了一些显而易见的事情的可能性道歉,因为在上面的实现中有很多决定,我不相信我理解了语义。寿命。我试图在网上搜索这种模式的例子,但它似乎比我见过的玩具例子复杂得多,也受到限制。

1 个答案:

答案 0 :(得分:1)

StateMachine::switch_state中,您 希望在's上使用&mut self生命周期; 's表示状态借用的资源的生命周期,而不是状态机的生命周期。请注意,通过这样做,self的类型会以's两次结束:完整类型为&'s mut StateMachine<'s>;您只需要在's上使用StateMachine,在参考上使用而不是

在可变引用(&'a T)中,Tinvariant,因此's也是不变的。这意味着编译器认为状态机与其借用的生命周期相同。因此,在调用switch_state之后,编译器认为状态机最终借用了自己

简而言之,将&'s mut self更改为&mut self

impl<'s> StateMachine<'s> {
    pub fn switch_state(&mut self, new_state: Box<AppState<'s> + 's>) -> Box<AppState<'s> + 's> {
        mem::replace(&mut self.current_state, new_state)
    }
}

您还需要在state_machine中声明main为可变:

let mut state_machine = states::StateMachine::new();