Codecs¶
A codec is a type with bounds. The bounds decide how many bits a value costs, what a sender may send, and what a receiver accepts.
Bounds are bits¶
A bounded value costs the bits needed to count its possible values. Narrower bounds mean fewer bits.
| Codec | Values | Bits |
|---|---|---|
bool() |
2 | 1 |
enum({ "red", "blue", "green" }) |
3 | 2 |
int(0, 100) |
101 | 7 |
quant(0, 1, 0.01) |
101 | 7 |
angle(1) |
360 | 9 |
quant(-512, 512, 0.1) |
10241 | 14 |
f32() |
any float | 32 |
Fields pack across byte boundaries, so struct({ hp = int(0, 100), alive = bool() }) takes one
byte. Choose bounds from the range your game uses, not from what the type could hold.
Choosing a codec¶
| To send | Use | Not |
|---|---|---|
| A count or index with a known range | int(min, max) |
f32, which is always 32 bits |
| A position or speed | quant(min, max, step), or vec3(quant(...)) |
vec3(), which is 96 bits |
| A heading, direction or rotation | angle, vec3.unit, rotation.quat, at a precision in degrees |
Raw floats |
| An id or total with no useful bound | vlq(), or vli() if it can be negative |
f64, which is always 8 bytes |
| One of a fixed set of names | enum |
str |
| Text | str(min, max), or str.alphabet when only some characters appear |
|
| One of several shapes | tagged |
A struct of optionals |
Every codec and its cost is in the Codecs reference.
Values off the grid¶
| Codec | Given a value between steps |
|---|---|
int |
Refuses a fraction. |
quant |
Rounds to the nearest step. |
angle |
Rounds, and wraps: 370 arrives as 10, -10 as 350. |
A value outside the bounds is refused: sending it throws at the call, and receiving it drops it before your code sees it. See Validation.
Modifiers¶
A modifier returns a new codec and leaves the original alone, so one base codec can be shared.
| Modifier | Does |
|---|---|
:validate(fn) |
Runs a check of your own on arrival. See Validation. |
:as(lift, lower) |
Carries your own type over a wire codec. |
:newest(hz?) |
Sends a set field as latest value only. See Sets. |
:as takes two functions. lift turns the decoded wire value into your type, and lower turns
your type back. It is also how to send a Roblox type with no codec of its own.
local UDimCodec = Lync.struct({
scale = Lync.f32(),
offset = Lync.int(-4096, 4096),
}):as(function(wire)
return UDim.new(wire.scale, wire.offset)
end, function(udim)
return { scale = udim.Scale, offset = udim.Offset }
end)
Encoding without the network¶
Lync.encode turns a value into bytes, and Lync.decode turns them back.
local Settings = Lync.struct({
volume = Lync.quant(0, 1, 0.01),
keys = Lync.map(Lync.str(1, 16), Lync.str(1, 16), 0, 32),
})
local bytes = Lync.encode(Settings, { volume = 0.8, keys = { jump = "Space" } })
local settings = Lync.decode(Settings, bytes)
- The buffer is exactly as long as the value.
- A codec holding
instalso returns the instances, as a second value, for that call only. Pass them todecode. Otherwise the second value is nil. encodethrows on a value the codec refuses.decoderuns every:validateand throws a readable message on bytes that do not decode or that have bytes left over.
Not a storage format
The bytes can change between Lync versions. Do not save them where a later build has to read them, such as a DataStore. Use them for bytes that live and die with one build.