Events Rust-向webassembly游戏添加事件侦听器

Events Rust-向webassembly游戏添加事件侦听器,events,rust,webassembly,game-loop,Events,Rust,Webassembly,Game Loop,我正在尝试在web assembly中创建一个游戏。我选择在rust中编写它,并使用cargo web进行编译。我设法获得了一个工作的游戏循环,但由于借用机制不可靠,我在添加MouseDownEvent侦听器时遇到了问题。我非常喜欢编写安全代码而不使用不安全关键字 此时,游戏将一个红色方框从0,0移动到700500,速度取决于距离。我希望下一步使用用户单击更新目标 这是游戏的简化和工作代码 static/index.html <!DOCTYPE html> <html lang

我正在尝试在web assembly中创建一个游戏。我选择在rust中编写它,并使用cargo web进行编译。我设法获得了一个工作的游戏循环,但由于借用机制不可靠,我在添加MouseDownEvent侦听器时遇到了问题。我非常喜欢编写安全代码而不使用不安全关键字

此时,游戏将一个红色方框从0,0移动到700500,速度取决于距离。我希望下一步使用用户单击更新目标

这是游戏的简化和工作代码

static/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/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的注释部分是我尝试添加MousedowneEvent侦听器的一部分。不幸的是,它生成了一个编译错误:

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
我非常想知道如何正确地实现一种在游戏中读取用户输入的方法。它不需要是异步的

在您的示例游戏中,当游戏移动到循环中时,循环拥有游戏。所以任何应该改变游戏的事情都需要在游戏循环中发生。要将事件处理融入其中,您有多个选项:

选择1 让游戏循环轮询事件

你创建了一个事件队列,你的游戏循环将有一些逻辑来获取第一个事件并处理它

您必须在这里处理同步问题,因此我建议您仔细阅读。但一旦你掌握了窍门,这应该是一项相当容易的任务。您的循环得到一个引用,每个事件处理程序得到一个引用,所有这些都尝试解锁互斥锁,然后访问队列向量

这将使你的游戏循环成为一个完整的事实,这是一个流行的引擎设计,因为它很容易推理和开始

但也许你不想那么集中

选择2 让事件发生在循环之外

这个想法将是一个更大的重构。你可以把你的游戏放在一个有互斥锁的盒子里

每次调用game_循环时,它都会尝试获得所述互斥锁的锁,然后执行游戏计算

当输入事件发生时,该事件也会尝试获取游戏中的互斥锁。这意味着,在处理game_循环时,不会处理任何输入事件,但它们会尝试在滴答声之间切换

这里的一个挑战是保持输入顺序,并确保输入处理足够快。要完全正确,这可能是一个更大的挑战。但是这个设计会给你一些可能性


这个想法的一个具体版本是,它是大规模并行的,有利于一个干净的设计。但是他们在引擎后面采用了一个相当复杂的设计。

我认为在这种情况下,编译器错误信息非常清楚。你试图在“静态生命周期”的结尾借用游戏,然后你还试图移动游戏。这是不允许的。我建议你再读一遍这本书。关注第4章-理解所有权

简而言之,你的问题可以归结为这样一个问题——如何共享一个状态,这个状态可以变异。有很多方法可以实现这一目标,但这实际上取决于您的需要—单线程或多线程,等等。。我将使用Rc&RefCell解决这个问题

Rc:

Rc类型提供在堆中分配的T类型值的共享所有权。在Rc上调用clone会生成一个指向堆中相同值的新指针。当指向给定值的最后一个Rc指针被销毁时,指向的值也被销毁

参考单元格:

Cell和RefCell类型的值可以通过共享引用(即公共和T类型)进行变异,而大多数锈菌类型只能通过唯一和多T引用进行变异。我们说,Cell和RefCell提供了“内部变异性”,而典型的锈病类型表现出“遗传变异性”

以下是我对你的结构所做的:

结构内部{ 时间:f64,, 地点:点,, 目的地:点, } [德里维克隆] 酒吧结构游戏{ 内部:Rc, } 这是什么意思?内部持有与旧游戏相同的游戏状态字段。新游戏只有一个内部字段,其中包含共享状态

在本例中,Rc T是RefCell-允许我多次克隆内部,但它不会克隆T 在本例中,RefCell T是内部的-允许我不可变地或可变地借用T,检查是在运行时完成的 我现在可以多次克隆游戏结构,它不会克隆RefCell,只克隆游戏&Rc。这就是我所说的!宏正在更新的main.rs中执行:

让游戏:游戏=游戏::默认; canvas.add\u event\u listener!游戏移动|事件:MouseDownEvent |{ game.set_destinationevent; }; 游戏,游戏,上下文,0。; 没有那封信!宏:

让游戏:游戏=游戏::默认; //游戏\u为\u鼠标\u下压\u事件\u闭包保存对 //与最初的“游戏”相同的“RefCell”` 让game_for_mouse_down_event_closure=game.clone; canvas.add\u事件\u列表 enermove |事件:MouseDownEvent |{ 游戏(鼠标)(下)(事件)(关闭)设置(目标事件);; }; 游戏,游戏,上下文,0。; 更新的game.rs:

使用std::{cell::RefCell,rc::rc}; 使用stdweb::traits::imousevent; 使用stdweb::web::event::MouseDownEvent; [德里维克隆,收到] 发布结构点{ 酒吧x:f64, 酒吧y:f64, } 从一点开始{ fn frome:MouseDownEvent->Self{ 自我{ x:e.client_x作为f64, y:e.client_y为f64, } } } 结构内部{ 时间:f64,, 地点:点,, 目的地:点, } impl默认为内部{ fn默认值->自我{ 内在的{ 时间:0,。, 位置:点{x:0,y:0.}, 目的地{x:700,y:500.}, } } } [德里维克隆] 酒吧结构游戏{ 内部:Rc, } 游戏默认的impl{ fn默认值->自我{ 游戏{ 内部:Rc::newRefCell::newInner::default, } } } impl游戏{ 发布fn更新和自更新,时间戳:f64{ 让mut-inner=self.inner.borrow\u-mut; 如果时间戳-internal.time>10f64{ internal.location.x+=internal.destination.x-internal.location.x/10f64; internal.location.y+=internal.destination.y-internal.location.y/10f64; internal.time=时间戳; } } 发布fn设置目的地和自我,位置:T{ 让mut-inner=self.inner.borrow\u-mut; inner.destination=location.into; } 发布fn位置和自->点{ self.internal.borrow.location } } 更新的main.rs:

使用stdweb::traits::*; 使用stdweb::unstable::TryInto; 使用stdweb::web::document; 使用stdweb::web::event::MouseDownEvent; 使用stdweb::web::html\u元素::CanvasElement; 使用stdweb::web::CanvasRenderingContext2d; 使用游戏::游戏; mod游戏; // https://github.com/koute/stdweb/blob/master/examples/todomvc/src/main.rsL31-L39 宏规则!围住{ $$x:ident,*$y:expr=>{ { $let$x=$x.clone* $y } }; } fn game\u loopgame:game,上下文:CanvasRenderingContext2d,时间戳:f64{ game.updatetimestamp; 绘画与游戏,与情境; stdweb::web::window.request_animation|u frame | time:f64 |{ 游戏、背景、时间; }; } fn drawgame:&游戏,上下文:&画布渲染上下文2D{ 上下文。清除0,0,800,800。; context.set\u fill\u style\u colorred; 让位置=game.location; context.fill_rectlocation.x,location.y,5,5。; } fn干线{ 让canvas:CanvasElement=文档 .query\u selector或canvas 打开…的包装 打开…的包装 .试试看 打开…的包装 canvas.set_宽度800; canvas.set_高度600; 让context=canvas.get_context.unwrap; 让游戏:游戏=游戏::默认; canvas.add_event_listenerEnclosure!游戏移动| event:MouseDownEvent |{ game.set_destinationevent; }; 游戏,游戏,上下文,0。; }
请注意,在以后共享任何代码之前,请安装并使用。

我现在明白了将游戏保持为局部变量实际上是多么麻烦。由于在应用程序的整个生命周期中,始终只有一个游戏是活跃的,所以将它作为一个单独的游戏来亲吻就更有意义了。从我看到的情况来看,lazy_静态和互斥是创建单例的完美方式。事实上,我通过这种方式重构了我的代码,并获得了一些对我来说很好的东西。谢谢你的帮助,我会用的。我真的很讨厌克利奥帕特拉的括号。实际上,带互斥的惰性静态不是一个大的代码重构。通过这种方式,我实现了单例模式,对我来说,这是实现这种逻辑的正确方式。
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