1 个不稳定版本
使用旧的 Rust 2015
0.1.0 | 2018年8月31日 |
---|
#294 在 #buffer
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);
最后,只要包装的类型实现了 Default
,DoubleBuffered
还实现了 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
实现了 Index
和 IndexMut
,只要包装类型也实现,因此可以使用 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
也是这样,它就是 Send
和 Sync
),但不能违反别名和可变性规则,其他线程无法访问其内容。目前的解决方案是将此类型包装在 Mutex
中。