# zlo
[![crates.io](http://meritbadge.herokuapp.com/zlo)](https://crates.io/crates/zlo)
[![Docs](https://docs.rs/zlo/badge.svg)](https://docs.rs/zlo)
[![](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT)
An encoder/decoder pair that uses a bit-compact binary encoding scheme. The
size of the encoded object will be almost same or smaller than the size that the
object takes up in memory in a running Rust program. It is a fork
of [bincode](https://github.com/TyOverby/bincode) and so resembles its API and
also includes familiar `SizeLimit` objects.
It was made for use in networking in a multi-player game, fit to encode *diffs
of data sent very frequently but in small pieces* over network, where bincode
produces comparably large chunks of data and compressing that with common
algorithms such as LZO tends to yield very tiny improvements over zlo-encoded
data at expense of added long compression times.
*Stability of the binary format is **NOT** guaranteed across major versions.*
## Example
```rust
#[macro_use]
extern crate serde_derive;
extern crate zlo;
use zlo::{serialize, deserialize, Infinite};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Entity {
x: f32,
y: f32,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct World(Vec);
fn main() {
let world = World(vec![Entity { x: 0.0, y: 4.0 }, Entity { x: 10.0, y: 20.5 }]);
let encoded: Vec = serialize(&world, Infinite).unwrap();
assert!(encoded.len() < 8 + 4 * 4);
let decoded: World = deserialize(&encoded[..]).unwrap();
assert_eq!(world, decoded);
}
```
## Performance, output size
*zlo deserializer is NOT zero-copy and can not be in majority of cases. This is
because bit-unmangled data occurs rarely in this encoding.*
Fastest primitives to process are unsigned ints and bools. Performance also
depends on amount of data written. E.g. encoding small 64 bit int is just
barely slower than it would be with a value of a type it fits.
zlo is primarily oriented to be used to serialize *diffs* of data, that is,
structs with lots of `Option`s, small ints and diffed-zigzagged floats in them.
Under these conditions zlo can yield very compact data.
### Vs Bincode
When encoding large 64 bit ints zlo can be up to 7 times slower than bincode.
On average, zlo can be expected to be 3 to 5 times slower than bincode when
encoding lots integers or floats. When encoding plain bytes zlo has comparable
performance, though, zlo is not zero-copy so it may incur extra allocation
overhead compared to bincode at deserialization.
Output size, on average, in comparison to bincode, can be expected to be up to
1.5 times smaller when encoding numbers and up to 8 times when encoding lots of
bools or `Option::None`s.
### Need to squeeze data in even less bits?
* Consider diffing floats, but rather by their parts (exponent and fraction).
* Or perhaps your value stays in a specific range known ahead? Consider then
serializing it as an integer, unsigned or not.
* Is it multidimensional? Maybe you could infer dependent elements from just a
single record.
* Serializing a multidimensional unit value such as unit vector or quaternion?
Serialize it in a more compact way, for example, 2D unit vector could be made
into just an angle.
See [examples](examples/).
### Lossy fraction coding
There are no plans for it. Additionally, even if lossy fractions are
considered, it can only be expected
after [Rust issue #44580](https://github.com/rust-lang/rust/issues/44580) is
stabilized.
## Details
Booleans are encoded as single bits, integers in a form similar **but not
equal** to LEB128, floats are encoded in a somewhat complicated way described
below, tuples and structs are encoded by encoding their fields one-by-one, and
enums are encoded by first writing out the tag representing the variant and then
the contents.
Unlike bincode, zlo has no configurable byte order because of variable amount of
bytes written.
Implementation details to be aware of:
* Separate bits are written from LSB to MSB.
* Unsigned integers are encoded using the following principle:
| PC | |
| --- | --- |
| 0 | **if** integer > 0, **then** write bit 1, **else** write bit 0 and **return** |
| 1 | write next 8 bits of the integer |
| 2 | **if** this is the most significant byte of the whole type, **then** **return** |
| 3 | **if** there are no more bits to be written, **then** write bit 0 and **return** |
| 4 | write bit 1 |
| 5 | **goto 1** |
* Signed integers are zigzag-encoded (same as protobuf) and then encoded same as
unsigned integers
* Floats are encoded the following way: sign bit is always written, exponent is
written either as 2 bits or full. Fraction is written either as higher 16
bits only in case of `f32`, higher 32 bits in case of `f64` or full if it
doesn't fit.
* `isize`/`usize` are encoded as `variable i64`/`u64`, for portability.
* Enum variants are encoded as a variable `u32` instead of a `usize`. `u32` is
enough for all practical uses.
* `str` is encoded as `(variable u64, &[u8])`, where the `u64` is the number of
bytes contained in the encoded string.
## License
zlo is licensed under MIT license ([LICENSE-MIT](LICENSE-MIT) or
http://opensource.org/licenses/MIT)