2个版本
0.1.1 | 2021年9月5日 |
---|---|
0.1.0 | 2021年4月12日 |
#542 in 数据结构
每月38次下载
125KB
3K SLoC
dipa
dipa使您能够轻松高效地对大型Rust数据结构进行delta编码。
在某些应用程序中,您发送给客户端的数据通常与您上次发送的数据几乎完全相同。
而不是反复向客户端发送几乎相同的状态对象,应用程序可能会计算自上次发送数据以来发生了什么变化,然后只发送这些更改。
这种方法可以显着降低您和您的用户对带宽的需求和网络流量成本。
确定数据结构两个实例之间差异的过程称为delta编码。
历史上,随着应用程序数据结构变得越来越复杂,delta编码代码的维护变得越来越困难。
这使得它成为只有对带宽最敏感的应用程序(如网络游戏)才保留的繁琐优化。
dipa通过为您生成所有代码来消除高效delta编码代码的维护难题。
dipa默认设计为生成非常小的diff。在您对数据结构有特定知识,可以帮助您生成更小的diff的最敏感情况下,您可以自己实现该类型的特性和宏,让dipa的derive宏处理其他部分。
请注意,dipa对网络一无所知,没有网络代码。它只专注于编码delta,而不是传输它们。
dipa书籍
dipa书籍将向您介绍该库并教授您如何使用它。
它也可以离线使用
# Do this once while online
git clone [email protected]:chinedufn/dipa.git && cd dipa
cargo install mdbook
# This works offline
./bin/serve-book.sh
快速入门
开始使用dipa的最简单方法是通过使用#[derive(DiffPatch)]
宏。这里有一个快速预览。
点击显示Cargo.toml。
[dependencies]
bincode = "1"
dipa = { version = "0.1", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
use dipa::{DiffPatch};
use std::borrow::Cow;
#[derive(DiffPatch)]
struct MyClientState {
id: u32,
friends: Option<u8>,
position: Position,
notifications: Vec<Cow<&'static, str>>,
emotional_state: EmotionalState
}
#[derive(DiffPatch)]
struct Position {
x: f32,
y: f32,
z: f32
}
#[derive(DiffPatch)]
enum EmotionalState {
Peace { calibration: u128 },
Love(u64),
Courage(u32),
}
fn main() {
let mut old_client_state = MyClientState {
id: 308,
friends: None,
position: Position { x: 1., y: 2., z: 3. }
notifications: vec![Cow::Borrowed("let"), Cow::Owned("go".to_string())],
emotional_state: EmotionalState::Love(100),
};
let new_client_state = MyClientState {
id: 308,
friends: Some(1),
position: Position { x: 4., y: 2., z: 3. }
notifications: vec![Cow::Borrowed("free")]
emotional_state: EmotionalState::Peace { calibration: 10_000 },
};
let delta_created = old_client_state.create_delta_towards(&new_client_state);
// Consider using bincode to serialize your diffs on the server side.
// You can then send them over the wire and deserialize them on the client side.
//
// For the tiniest diffs, be sure to use variable integer encoding.
let bin = bincode::options().with_varint_encoding();
let serialized = bin.serialize(&delta_created.delta).unwrap();
// ... Pretend you send the data to the client ...
let deserialized: <MyClientState as dipa::Diffable<'_, '_, MyClientState>>::DeltaOwned =
bin.deserialize(&serialized).unwrap();
old_client_state.apply_patch(deserialized);
// All of the fields are now equal.
assert_eq!(
old_client_state.notifications,
new_client_state.notifications
);
}
高级用法
对于将极小负载作为首要任务的应用程序,您可能希望利用对应用程序工作方式的知识来生成更小的差异。
例如,假设您有如下客户端状态数据结构。
#[derive(DiffPatch)]
struct ClientState {
hair_length: u128
}
如果头发长度没有变化,差异将只有一个字节。
然而,每当客户端的头发长度发生变化时,负载中可能还会有多达17个额外的字节,用于编码新的u128
值。
但是,如果您知道客户端的头发长度在状态更新之间不可能改变超过100
个单位呢?
并且,您的应用程序要求意味着节省每一个字节都很重要,因此值得您花时间自定义头发长度增量编码。
在这种情况下,您可以尝试以下方法
use dipa::{CreatedDelta, Diffable, Patchable};
#[derive(DiffPatch)]
// Debug + PartialEq are used by DipaImplTester below.
// They are not required otherwise.
#[cfg_attr(test, derive(Debug, PartialEq))]
struct ClientState {
hair_length: DeltaWithI8,
}
// Debug + PartialEq are used by DipaImplTester below.
// They are not required otherwise.
#[cfg_attr(test, derive(Debug, PartialEq))]
struct DeltaWithI8(u128);
impl<'s, 'e> Diffable<'s, 'e, DeltaWithI8> for DeltaWithI8 {
type Delta = i8;
type DeltaOwned = Self::Delta;
fn create_delta_towards(&self, end_state: &Self) -> CreatedDelta<Self::Delta> {
let delta = if self.0 >= end_state.0 {
(self.0 - end_state.0) as i8 * -1
} else {
(end_state.0 - self.0) as i8
};
CreatedDelta {
delta,
did_change: self.0 != end_state.0,
}
}
}
impl Patchable<i8> for DeltaWithI8 {
fn apply_patch(&mut self, patch: i8) {
if patch >= 0 {
self.0 += patch as u128;
} else {
self.0 -= (-1 * patch) as u128;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use dipa::DipaImplTester;
#[test]
fn delta_with_i8() {
DipaImplTester {
label: Some("Unchanged DeltaWithI8"),
start: &mut DeltaWithI8(300),
end: &DeltaWithI8(300),
expected_delta: 0,
expected_serialized_patch_size: 1,
expected_did_change: false,
}
.test();
DipaImplTester {
label: Some("Increase DeltaWithI8"),
start: &mut DeltaWithI8(5_000),
end: &DeltaWithI8(5_050),
expected_delta: 50,
expected_serialized_patch_size: 1,
expected_did_change: true,
}
.test();
DipaImplTester {
label: Some("Decrease DeltaWithI8"),
start: &mut DeltaWithI8(400),
end: &DeltaWithI8(320),
expected_delta: -80,
expected_serialized_patch_size: 1,
expected_did_change: true,
}
.test();
}
}
这种方法可以将头发长度增量从17个字节减少到仅一个字节。
* - 17,而不是16,因为大于u8
的整数在它们的默认DiffPatch
实现中会被包装在Option
中。这优化了整数没有变化的情况,因为None
序列化成1个字节。
问题
如果您在五分钟内找不到答案,那么这被认为是文档错误。
请创建一个问题。
或者,更好的是,创建一个带有代码示例、API文档或书中可能回答您问题的区域的待完善拉取请求。
贡献
如果您有不受支持的使用案例、问题、补丁或其他任何内容,请直接创建一个问题或提交一个拉取请求。
测试
运行测试套件。
# Clone the repository
git clone [email protected]:chinedufn/dipa.git
cd dipa
# Run tests
cargo test --all
许可
dipa的许可协议为以下之一
- Apache许可证版本2.0 (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT许可证 (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
依赖项
~0.4–1MB
~24K SLoC