6 个版本 (breaking)
0.6.0 | 2021年2月27日 |
---|---|
0.5.0 | 2020年11月28日 |
0.4.0 | 2020年9月26日 |
0.3.0 | 2020年9月26日 |
0.1.0 | 2020年2月18日 |
在 数据结构 中排名第 832
每月下载量 39
110KB
2.5K SLoC
功能
- 混合图允许同时进行 Adapton 风格和 Incremental 风格的推送更新。有关内部机制的更多信息,您可以查看相关的博客文章。
- 在图中克隆值几乎是可选的。
map
和then
闭包接收不可变引用,并返回拥有的值。或者,refmap
闭包接收不可变引用,并返回不可变引用。 - 仍在开发中,但应该功能正常(lol)并且速度尚可。但是,仍预期未来几年将会有重大的 API 变更。
示例
// example
use crate::{singlethread::Engine, AnchorExt, Var};
let mut engine = Engine::new();
// create a couple `Var`s
let (my_name, my_name_updater) = Var::new("Bob".to_string());
let (my_unread, my_unread_updater) = Var::new(999usize);
// `my_name` is a `Var`, our first type of `Anchor`. we can pull an `Anchor`'s value out with our `engine`:
assert_eq!(&engine.get(&my_name), "Bob");
assert_eq!(engine.get(&my_unread), 999);
// we can create a new `Anchor` from another one using `map`. The function won't actually run until absolutely necessary.
// also feel free to clone an `Anchor` — the clones will all refer to the same inner state
let my_greeting = my_name.clone().map(|name| {
println!("calculating name!");
format!("Hello, {}!", name)
});
assert_eq!(engine.get(&my_greeting), "Hello, Bob!"); // prints "calculating name!"
// we can update a `Var` with its updater. values are cached unless one of its dependencies changes
assert_eq!(engine.get(&my_greeting), "Hello, Bob!"); // doesn't print anything
my_name_updater.set("Robo".to_string());
assert_eq!(engine.get(&my_greeting), "Hello, Robo!"); // prints "calculating name!"
// a `map` can take values from multiple `Anchor`s. just use tuples:
let header = (&my_greeting, &my_unread)
.map(|greeting, unread| format!("{} You have {} new messages.", greeting, unread));
assert_eq!(
engine.get(&header),
"Hello, Robo! You have 999 new messages."
);
// just like a future, you can dynamically decide which `Anchor` to use with `then`:
let (insulting_name, _) = Var::new("Lazybum".to_string());
let dynamic_name = my_unread.then(move |unread| {
// only use the user's real name if the have less than 100 messages in their inbox
if *unread < 100 {
my_name.clone()
} else {
insulting_name.clone()
}
});
assert_eq!(engine.get(&dynamic_name), "Lazybum");
my_unread_updater.set(50);
assert_eq!(engine.get(&dynamic_name), "Robo");
观察到的节点
您可以向引擎指示希望观察某个节点
engine.mark_observed(&dynamic_name);
现在当您请求它时,它将 减少频繁遍历整个图,这对于具有大型 Anchor
依赖树的场景非常有用。但是,也有一些缺点
- 每次您
get
任何Anchor
时,所有观察到的节点都将更新。 - 如果观察到的依赖项之一是
then
,则由它请求的节点可能会被重新计算,即使它们在严格意义上并不是必要的。
它有多快?
您可以在 bench
文件夹中查看一些微基准测试。这些是运行 stabilize_linear_nodes_simple
的结果,这是一个由许多 map
节点组成的线性链,每个节点将 1
添加到某个变化的输入数字。基准测试在我的 Macbook Air(Intel,2020)上针对 Anchors 0.5.0 8c9801c
运行,使用 lto = true
。
节点数量 | 是否使用 `mark_observed`? | 获取链尾的总时间 | 总时间 / 节点数量 |
---|---|---|---|
10 | 观察到的 | [485.48 ns 491.85 ns 498.49 ns] | 49.185 ns |
100 | 观察到的 | [4.1734 微秒 4.2525 微秒 4.3345 微秒] | 42.525 纳秒 |
1000 | 观察到的 | [42.720 微秒 43.456 微秒 44.200 微秒] | 43.456 纳秒 |
10 | 未观察 | [738.02 纳秒 752.40 纳秒 767.86 纳秒] | 75.240 纳秒 |
100 | 未观察 | [6.5952 微秒 6.7178 微秒 6.8499 微秒] | 67.178 纳秒 |
1000 | 未观察 | [74.256 微秒 75.360 微秒 76.502 微秒] | 75.360 纳秒 |
大致看来,观察到的节点大约有 ~42-50ns
的开销,而未观察到的节点大约有 74-76ns
的开销。这可能是相当大的改进;理想情况下,我们可以将这些数字降低到每个观察节点大约 ~15ns
,这是 Incremental 实现的。
对于任何增量程序,减慢的可能来自于框架的其他方面,这些方面并没有用这个非常简单的微基准测试来衡量。
在 M1 mac 上有多快?
可能快两倍?
节点数量 | 是否使用 `mark_observed`? | 获取链尾的总时间 | 总时间 / 节点数量 |
---|---|---|---|
10 | 观察到的 | [242.68 纳秒 242.98 纳秒 243.37 纳秒] | 24.30 纳秒 |
100 | 观察到的 | [1.9225 微秒 1.9232 微秒 1.9239 微秒] | 19.232 纳秒 |
1000 | 观察到的 | [20.421 微秒 20.455 微秒 20.489 微秒] | 20.46 纳秒 |
10 | 未观察 | [354.05 纳秒 354.21 纳秒 354.37 纳秒] | 35.42 |
100 | 未观察 | [3.3810 微秒 3.3825 微秒 3.3841 微秒] | 33.83 纳秒 |
1000 | 未观察 | [41.429 微秒 41.536 微秒 41.642 微秒] | 41.54 纳秒 |
参见
依赖
~1MB
~25K SLoC