1 个不稳定版本

使用旧的 Rust 2015

0.1.0 2018年8月31日

#294#buffer

MIT 许可证

14KB
145

dubble 泛型双缓冲

dubble 是一个提供双缓冲泛型实现的 crate。

什么是双缓冲?

通常在一个游戏引擎中,有一个核心循环,每帧更新游戏世界中的所有内容。然而,这创造了一个小悖论。让我们假设对象 B 的位置取决于对象 A 的位置,但 A 的位置也多少取决于 B 的位置。例如,考虑一个太阳系的模拟,其中 A 和 B 之间的引力作用会影响彼此的运动。

我们在核心游戏循环中决定先更新哪个位置?A 和 B 的最终位置将取决于对象更新的顺序。

双缓冲是解决这个问题的方法。我们存储 A 和 B 的两个版本。一个版本用于读取,另一个用于写入。当我们更新 A 的位置时,我们从 B 的读取版本中读取,并将新的位置存储在 A 的写入版本中。同样,当我们更新 B 的位置时,我们从 A 的读取版本中读取,并将新的位置存储在 B 的写入版本中。

最后,在核心游戏循环的下一迭代之前,A 和 B 的读取版本都更新为它们对应写入版本中的值。

请注意,当更新 A 的位置时,A 的读取版本没有改变,只有写入版本。同样,当更新 B 的位置时,我们使用了 A 的读取版本,当更新 A 的位置时,它保持不变。注意,现在,更新的顺序不重要,因为它们都使用了它们所依赖的“旧”版本的对象。

这种技术被称为双缓冲,因为它需要两个副本(缓冲区)的问题。

用法

初始化

最基本的初始化器是 new(),它初始化要双缓冲的对象的读取和写入版本。

use dubble::DoubleBuffered;

// assuming you have a `Player` struct
let mut player = DoubleBuffered::new(Player::new());

还有 construct_with(),它接受任何 Fn() -> T 并使用它来构造读取和写入版本。

let mut player = DoubleBuffered::construct_with(Player::new);

最后,只要包装的类型实现了 DefaultDoubleBuffered 还实现了 Default,所以你也可以这样做

let mut player = DoubleBuffered::<Player>::default();

读取和写入

read()write() 是基本方法,分别返回读取和写入版本的引用。这些引用分别是不变/可变的。

// reading
// read() -> &T
player.read().attack(monster);

// writing
// write() -> &mut T
player.write().dmg_hp(10.0);

但为了方便,缓冲区还实现了 Deref<Target=T>DerefMut<Target=T>,因此可以省略对 read()write() 的调用。特质的实现只是围绕 read()write() 分别进行包装。

player.attack(monster);
player.dmg_hp(10.0);

使用写入版本更新读取版本。

update() 将将写入版本复制到读取版本。

let mut player = DoubleBuffered::construct_with(Player::new);

// player starts with 100 hp
assert!(player.health == 100);

// do some damage to the player
player.dmg_hp(10);

// before the call to `update()`, the health will not have changed
assert!(player.health == 100);

// ... update other stuff ...

// now update the player's health
player.update();
assert!(player.health == 90);

与容器类型一起使用

DoubleBuffered 实现了 IndexIndexMut,只要包装类型也实现,因此可以使用 DoubleBuffered<Vec<Actor>> 并在单个 update() 调用中更新所有内容。

let mut actors = DoubleBuffered::construct_with(Vec::<Actor>::new);
actors.push(the_player);
actors.push(monster_1);
actors.push(monster_2);
actors.push(monster_3);

// ... player and monsters update based on reads of the other's state ...

// update everyone
actors.update();

注意事项

线程

尽管双缓冲本身可以在线程之间传递(只要 T 也是这样,它就是 SendSync),但不能违反别名和可变性规则,其他线程无法访问其内容。目前的解决方案是将此类型包装在 Mutex 中。

没有运行时依赖