2

这个问题是关于在 Rust 中为视频游戏实现状态机时可能出现的特定所有权模式,其中状态可以持有对“全局”借用上下文的引用,并且状态机在哪里拥有它们的状态。我试图在激发问题的同时尽可能多地删减细节,但这是一个相当大且纠结的问题。

这是状态特征:

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
}

我正在使用盒装特征对象而不是枚举来实现状态,因为我希望有很多状态。状态在其更新方法中返回 aSome(State)以使它们拥有的状态机切换到新状态。我添加了一个生命周期参数,因为没有它,编译器会生成 type: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);
    }
}

状态机始终具有有效状态。默认情况下,它以 a 开头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。这组特定的绑定要求纹理由 a 创建TextureCreator,并且纹理的寿命不超过其创建者。纹理需要一个生命周期参数来确保这一点。Tileset持有一个纹理,因此导出这个需求。这意味着我不能将 a 存储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只是被方法调用借用了,但是编译器说当方法结束时它仍然是借用的。我希望它能让我在错误消息中追踪借用者的身份——我不明白为什么当方法返回时没有返回借用。

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

  • 那些状态由 trait 实现。
  • 状态归状态机所有。
  • 该状态能够包含对任意非静态数据的引用,其生命周期大于状态机的生命周期。
  • 当一个状态被换出时,旧盒子仍然有效,以便它可以移动到新状态的构造函数中。这将允许新状态切换回之前的状态,而无需重新构建。
  • 一个状态可以通过从“更新”返回一个新状态来发出状态变化的信号。旧状态必须能够在自身内部构建这个新状态。

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

对于这个冗长的问题以及我可能遗漏了一些明显的事情的可能性,我深表歉意,因为在上面的实现中做出了许多决定,我不确定我是否理解生命周期的语义。我试图在网上搜索这种模式的例子,但它似乎比我见过的玩具例子更复杂和受限制。

4

1 回答 1

2

StateMachine::switch_state中,您不想在;上使用's生命周期。表示状态借用资源的生命周期,而不是状态机的生命周期。请注意,通过这样做,类型最终会出现两次:完整类型是; 您只需要使用on ,而不是参考。&mut self'sself's&'s mut StateMachine<'s>'sStateMachine

在可变引用 ( &'a mut T) 中,T不变的,因此'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_machinemain可变:

let mut state_machine = states::StateMachine::new();
于 2017-11-04T05:33:50.030 回答