1 个不稳定版本

0.1.0 2021年4月12日

78#delta

36 每月下载量
用于 dipa

MIT/Apache

140KB
3K SLoC

dipa Actions 状态 docs

dipa 可以轻松高效地对大型Rust数据结构进行delta编码。

在某些应用中,你发送给客户端的数据通常与上次发送的数据几乎完全相同。

与其反复向客户端发送几乎相同的状态对象,应用可能会计算自上次数据发送以来发生了哪些变化,然后只发送这些更改。

这种方法可以显着减少你和用户的数据传输带宽要求和网络流量成本。

确定两个数据结构实例之间差异的过程称为delta编码。

从历史上看,随着应用程序数据结构的日益复杂,delta编码代码的维护难度越来越大。

这使得它成为一个繁琐的优化,仅供最关注带宽的应用,如网络游戏。

dipa 通过为您生成所有代码来消除高效delta编码代码的维护挑战。

dipa 默认设计为生成非常小的差异。在您对数据结构有特定知识并且可以帮助您生成更小差异的最敏感情况下,您可以自己实现该类型的特性,并让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
    );
}

查看完整API文档

高级用法

对于以极小的有效载荷为首要考虑的应用程序,您可能希望利用对应用程序工作方式的了解来生成更小的差异。

例如,假设您有以下客户端状态数据结构。

#[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,因为大于的整数 16 的整数

问题

如果您不能在五分钟内找到答案,那么这被认为是文档的缺陷。

# Clone the repository
git clone [email protected]:chinedufn/dipa.git
cd dipa

# Run tests
cargo test --all