Fil*_*nik 2 events game-loop rust webassembly
我正在尝试在网络程序集中创建一个游戏。我选择用 Rust 来准备它并使用 Cargo-Web 进行编译。我设法获得了一个有效的游戏循环,但由于 Rust 借用机制,我在添加 MouseDownEvent 侦听器时遇到了问题。我非常喜欢编写“安全”代码(不使用“不安全”关键字)
此时,游戏只需将红色框从 (0,0) 移动到 (700,500),速度取决于距离。我希望下一步使用用户单击更新目的地。
这是游戏的简化且有效的代码。
静态/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <title>The Game!</title>
</head>
<body>
    <canvas id="canvas" width="600" height="600">
    <script src="game.js"></script>
</body>
</html>
src/main.rs
mod game;
use game::Game;
use stdweb::console;
use stdweb::traits::*;
use stdweb::unstable::TryInto;
use stdweb::web::document;
use stdweb::web::CanvasRenderingContext2d;
use stdweb::web::html_element::CanvasElement;
use stdweb::web::event::MouseDownEvent;
fn main()
{
    let canvas: CanvasElement = document()
        .query_selector("#canvas")
        .unwrap()
        .unwrap()
        .try_into()
        .unwrap();
    canvas.set_width(800u32);
    canvas.set_height(600u32);
    let context = canvas.get_context().unwrap();
    let game: Game = Game::new();
    // canvas.add_event_listener(|event: MouseDownEvent|
    // {
    //     game.destination.x = (event.client_x() as f64);
    //     game.destination.y = (event.client_y() as f64);
    // });
    game_loop(game, context, 0f64);
}
fn game_loop(mut game : Game, context : CanvasRenderingContext2d, timestamp : f64)
{
    game.cycle(timestamp);
    draw(&game,&context);
    stdweb::web::window().request_animation_frame( |time : f64| { game_loop(game, context, time); } );
}
fn draw(game : &Game, context: &CanvasRenderingContext2d)
{
    context.clear_rect(0f64,0f64,800f64,800f64);
    context.set_fill_style_color("red");
    context.fill_rect(game.location.x, game.location.y, 5f64, 5f64);
}
src/game.rs
pub struct Point
{
    pub x: f64,
    pub y: f64,
}
pub struct Game
{
    pub time: f64,
    pub location: Point,
    pub destination: Point,
}
impl Game
{
    pub fn new() -> Game
    {
        let game = Game
        {   
            time: 0f64,
            location: Point{x: 0f64, y: 0f64},
            destination: Point{x: 700f64, y: 500f64},
        };
        return game;
    }
    pub fn cycle(&mut self, timestamp : f64)
    {
        if timestamp - self.time > 10f64
        {
            self.location.x += (self.destination.x - self.location.x) / 10f64; 
            self.location.y += (self.destination.y - self.location.y) / 10f64;
            self.time = timestamp;
        }
    }
}
main.rs 中注释掉的部分是我尝试添加 MouseDownEvent 侦听器。不幸的是它会产生编译错误:
error[E0505]: cannot move out of `game` because it is borrowed
  --> src\main.rs:37:15
   |
31 |       canvas.add_event_listener(|event: MouseDownEvent|
   |       -                         ----------------------- borrow of `game` occurs here
   |  _____|
   | |
32 | |     {
33 | |         game.destination.x = (event.client_x() as f64);
   | |         ---- borrow occurs due to use in closure
34 | |         game.destination.y = (event.client_y() as f64);
35 | |     });
   | |______- argument requires that `game` is borrowed for `'static`
36 |
37 |       game_loop(game, context, 0f64);
   |                 ^^^^ move out of `game` occurs here
我非常想知道如何正确实现一种读取游戏用户输入的方法。它不需要是异步的。
我认为在这种情况下编译器错误消息非常清楚。您试图在终生中借用闭包game中的'static,然后您还试图移动game. 这是不允许的。我建议再次阅读《Rust 编程语言》一书。重点关注第 4 章 - 了解所有权。
为了让它更短,你的问题可以归结为 -如何共享一个可以改变的状态。有很多方法可以实现这个目标,但这实际上取决于您的需求(单线程或多线程等)。我将使用Rc&RefCell来解决这个问题。
Rc( std::rc ):
该类型提供在堆中分配的
Rc<T>type 值的共享所有权。T调用cloneonRc会生成一个指向堆中相同值的新指针。当指向给定值的最后一个Rc指针被销毁时,指向的值也被销毁。
RefCell(标准::细胞):
Cell<T>和类型的值RefCell<T>可以通过共享引用(即公共&T类型)进行改变,而大多数 Rust 类型只能通过唯一的 (&mut T) 引用进行改变。我们这样说Cell<T>并RefCell<T>提供“内部可变性”,与表现出“继承可变性”的典型 Rust 类型形成鲜明对比。
这是我对你的结构所做的事情:
struct Inner {
    time: f64,
    location: Point,
    destination: Point,
}
#[derive(Clone)]
pub struct Game {
    inner: Rc<RefCell<Inner>>,
}
这是什么意思?Inner保存游戏状态(与旧的字段相同Game)。NewGame只有一个字段inner,其中包含共享状态。
Rc<T>(在这种情况下T是RefCell<Inner>)-允许我多次克隆inner,但它不会克隆TRefCell<T>(T在Inner这种情况下)-允许我不可变或可变地借用T,检查是在运行时完成的我Game现在可以多次克隆该结构,但它不会克隆RefCell<Inner>,而只会克隆Game& Rc。这就是enclose!宏在更新后所做的事情main.rs:
let game: Game = Game::default();
canvas.add_event_listener(enclose!( (game) move |event: MouseDownEvent| {
    game.set_destination(event);
}));
game_loop(game, context, 0.);
没有enclose!宏:
let game: Game = Game::default();
// game_for_mouse_down_event_closure holds the reference to the
// same `RefCell<Inner>` as the initial `game`
let game_for_mouse_down_event_closure = game.clone();
canvas.add_event_listener(move |event: MouseDownEvent| {
    game_for_mouse_down_event_closure.set_destination(event);
});
game_loop(game, context, 0.);
更新game.rs:
use std::{cell::RefCell, rc::Rc};
use stdweb::traits::IMouseEvent;
use stdweb::web::event::MouseDownEvent;
#[derive(Clone, Copy)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}
impl From<MouseDownEvent> for Point {
    fn from(e: MouseDownEvent) -> Self {
        Self {
            x: e.client_x() as f64,
            y: e.client_y() as f64,
        }
    }
}
struct Inner {
    time: f64,
    location: Point,
    destination: Point,
}
impl Default for Inner {
    fn default() -> Self {
        Inner {
            time: 0.,
            location: Point { x: 0., y: 0. },
            destination: Point { x: 700., y: 500. },
        }
    }
}
#[derive(Clone)]
pub struct Game {
    inner: Rc<RefCell<Inner>>,
}
impl Default for Game {
    fn default() -> Self {
        Game {
            inner: Rc::new(RefCell::new(Inner::default())),
        }
    }
}
impl Game {
    pub fn update(&self, timestamp: f64) {
        let mut inner = self.inner.borrow_mut();
        if timestamp - inner.time > 10f64 {
            inner.location.x += (inner.destination.x - inner.location.x) / 10f64;
            inner.location.y += (inner.destination.y - inner.location.y) / 10f64;
            inner.time = timestamp;
        }
    }
    pub fn set_destination<T: Into<Point>>(&self, location: T) {
        let mut inner = self.inner.borrow_mut();
        inner.destination = location.into();
    }
    pub fn location(&self) -> Point {
        self.inner.borrow().location
    }
}
更新main.rs:
use stdweb::traits::*;
use stdweb::unstable::TryInto;
use stdweb::web::document;
use stdweb::web::event::MouseDownEvent;
use stdweb::web::html_element::CanvasElement;
use stdweb::web::CanvasRenderingContext2d;
use game::Game;
mod game;
// https://github.com/koute/stdweb/blob/master/examples/todomvc/src/main.rs#L31-L39
macro_rules! enclose {
    ( ($( $x:ident ),*) $y:expr ) => {
        {
            $(let $x = $x.clone();)*
            $y
        }
    };
}
fn game_loop(game: Game, context: CanvasRenderingContext2d, timestamp: f64) {
    game.update(timestamp);
    draw(&game, &context);
    stdweb::web::window().request_animation_frame(|time: f64| {
        game_loop(game, context, time);
    });
}
fn draw(game: &Game, context: &CanvasRenderingContext2d) {
    context.clear_rect(0., 0., 800., 800.);
    context.set_fill_style_color("red");
    let location = game.location();
    context.fill_rect(location.x, location.y, 5., 5.);
}
fn main() {
    let canvas: CanvasElement = document()
        .query_selector("#canvas")
        .unwrap()
        .unwrap()
        .try_into()
        .unwrap();
    canvas.set_width(800);
    canvas.set_height(600);
    let context = canvas.get_context().unwrap();
    let game: Game = Game::default();
    canvas.add_event_listener(enclose!( (game) move |event: MouseDownEvent| {
        game.set_destination(event);
    }));
    game_loop(game, context, 0.);
}
PS 请在将来共享任何代码之前,安装并使用rustfmt。