5 个不稳定版本
使用旧 Rust 2015
0.3.2 | 2018年9月28日 |
---|---|
0.2.2 | 2018年9月27日 |
0.2.1 | 2018年9月27日 |
0.2.0 | 2018年9月27日 |
0.1.0 | 2018年9月23日 |
1804 in Rust 模式
1.5MB
1K SLoC
Pollock
糟糕代码的框架
Pollock 是 Processing 理想和 API 在 Rust 中的足够惯用实现,用于制作生成艺术、小型游戏或仅作为玩具来玩耍。它尽量减少对代码的限制,使用它编写代码的感觉就像在动态语言中编写代码一样舒适,但更少错误和更好的性能。尽管如此,目前调试模式下的性能相当糟糕,因为我大量使用了零成本抽象,这严重依赖于优化。但是,发布模式下的性能已经非常出色,并且可能还可以进一步提高。一个限制是,我确实始终想要一个即时模式接口,尽管我很乐意找到一种方法来至少在后台部分使用保留模式。我已经将所有调用批处理为单个绘制调用。
它仍然是预预预alpha版本,但已经有一些酷炫的功能,如保存视频和类似模拟器的保存状态系统,可以用于提供类似热重载的工作流程 - 进行更改,重新运行程序并重新加载状态,以回到您所在的位置。
Pollock 设计理念
- 糟糕的是最好的
- 不要让我思考
- 性能是国王,但可用性是上帝
Pollock 设计允许快速对不打算维护的代码进行修改。它是用于实验和错误的。
如果您之前使用过 Processing,API 将看起来很熟悉。如果您使用过一些流行的 Rust 游戏框架,API 可能看起来完全陌生。首先,您不需要定义一个特质。我选择使用构建器模式来避免需要在每个类型上注解的问题,并意味着您不需要理解 Rust,就能编写出东西,即使它只是简单的东西。
extern crate pollock;
use pollock::*;
fn main() {
Pollock::setup(|p| {
p.size = (600, 480);
// Like
p.background = Fill::default();
p.fill = Fill::none();
p.stroke = Stroke::new(rgb(0, 0, 0), 3);
}).draw(|p| {
// Example spinning square
let mut p = p.push();
let size = 50;
let (w, h) = p.size;
let frame_count = p.frame_count as f64;
p.rotate(frame_count / 100.);
p.translate(v2(w, h) / 2.);
p.rect(v2(-size / 2, -size / 2), size, size);
}).run();
}
v2
创建一个向量,push
是 Processing 的 pushMatrix
的 Rust 版本(当新的 p
超出作用域时,更改会被弹出)。我认为其余部分都是不言而喻的。我使用来自 nalgebra
的 Vector2
类型作为我的向量类型,尽管这可能在将来改变,因为使用此类型时的错误信息是绝对无法辩护的。它确实允许你做到一些在 Processing 中难以实现的好东西。例如,你可以直接对向量进行加减运算,将它们乘以标量,以及其他在处理 2D 图形时非常有用的所有其他好事。我目前没有公开许多 nalgebra
的功能,但随着我对 API 设计的反馈越来越多,我可能会添加更多。
在以下示例中没有展示的是,我没有 startShape
和 endShape
的等价物,所有操作都是通过迭代器完成的。你可以从 V2(即 2D 向量)的迭代器中构建多边形和线条(自动完成连接)。这非常高效,可以说是更加直观,而且不会出现不小心忘记调用
endShape
的可能性。
处理可变状态
在 Python 中处理可变状态的方法是使用全局变量。这在 Pollock 中也适用(尽管它们是在你调用的 Pollock::run
函数的作用域内的可变变量),但 Pollock 还支持状态结构的概念。好处是你可以免费获取保存状态,但这确实增加了额外的类型注解负担,因此对于简单的项目,你可以直接使用全局变量。
使用状态结构就像只是从 setup
函数返回它,然后在主循环中使用 p.state
来访问它一样简单。例如,对于显示一些弹跳球的简单草图,它可能看起来像这样
extern crate pollock;
extern crate serde;
#[macro_use]
extern crate serde_derive;
use pollock::*;
#[derive(Serialize, Deserialize)]
struct State {
balls: Vec<(V2, V2)>,
}
fn main() {
let radius = 10.;
Pollock::setup(|p| {
p.size = (600, 480);
p.background = Fill::default();
p.stroke = Stroke::none();
// We add alpha so we can see where the balls intersect
// (also so I can show off the `rgba` function.
p.fill = Fill::new(rgba(0, 0, 0, 100));
State {
balls: (0..10)
.map(|_| {
(
v2(
p.random_range(0., p.width() as f64),
p.random_range(0., p.height() as f64),
),
v2(
if p.random() { -1 } else { 1 },
if p.random() { -1 } else { 1 },
),
)
}).collect(),
}
}).draw(|p| {
let (width, height) = p.size;
// A limitation of Rust's disjointness analysis means that
// we have to loop twice here.
for (pos, vel) in &mut p.state.balls {
if pos.x < radius {
vel.x = 1.
} else if pos.x > width as f64 - radius {
vel.x = -1.
}
if pos.y < radius {
vel.y = 1.
} else if pos.y > height as f64 - radius {
vel.y = -1.
}
*pos += *vel * 1.;
}
for (pos, _) in p.state.balls.iter() {
p.circle(*pos, radius);
}
})
.on_key_down(Key::O, |p| p.save_state("state"))
.on_key_down(Key::P, |p| p.load_state("state"))
.on_key_down(Key::Space, |p| p.paused = !p.paused)
.run();
}
你可以看到它非常简洁,并且(在我看来)非常易于阅读,尽管你必须知道从 setup
返回状态使其可以在 p.state
中访问,这是相当神奇的。
如果你想知道,你可以在 setup
中访问 p.state
,但它只是具有 ()
类型。
你可以如何帮助
使用它!告诉我哪些功能缺失,以及它是否像我希望的那样易于使用。我确实担心我大量使用泛型和 Deref
可能会使文档难以阅读,但通过仔细使用类型别名,这可能有所帮助。我想将 Processing 教程移植到 Pollock,以找出哪些常见操作不受支持。
以下是一个我从 Processing 草图移植到 Pollock 的示例 gif
未来功能
- 图像
- 文本
- 声音输入(来自文件和/或麦克风)用于可视化
- 鼠标输入
- 模块系统,用于向
PollockState
添加额外功能,使其看起来像是在核心库中实现。我有一些关于如何使用类型系统实现此目的的想法,但我还没有提出具体方案。 - 更多内置数学助手
依赖项
~26–36MB
~440K SLoC