Serialization
To optimize for bandwidth, it is highly recommended to use buffers to serialize packets.
Thankfully, there are networking libraries such as Bytenet and Blink to handle SerDes (Serialization/Deserialization) easily. In Chrono, we are using our own SerDes to squeeze out even more performance.
CFrame Serialization
CFrames consist of both positional and rotational components.
Position
- Use
f32
for position - While
f16
is smaller, it suffers from precision limits - Many open-source replication systems uses float16 for position, but those are typically just for showcase purposes
TIP
Stick with float32 for positions if you care about accuracy in a production environment.
Rotation
- Best approach: convert rotations to quaternions and serialize as
f16
- Converting between CFrame → Quaternion (AxisAngles) and Quaternion → CFrame is straightforward
- Alternatively, you can just send Y-axis rotation for many battleground games, as it is precise enough
Timestamp Handling
GetServerTimeNow()
- Provides a synchronized clock between client and server
- Avoids the need to manually compensate for clock drift due to latency variations
RenderCache
From my tests, GetServerTimeNow has issues making interpolation jittery. Chrono uses os.clock and predicting the estimated server time and renderTimeError; this system also provides much smoother control over visuals.
Rendering Timestamps
When rendering timestamps:
- Compute remote latency easily by comparing a synced clock time on the server and client
- Subtract your interpolation buffer to compute the correct render time
Performance & Bandwidth Tips
Float Cost
GetServerTimeNow()
returns a f64
, which is more costly than os.clock()
(f32
)
Bandwidth Optimization
- Encode timestamps to reduce bandwidth
-- Example: compress to f16 range
timestamp = GetServerTimeNow() % 255
Time Wrapping
If you implement time wrapping like this: Always account for circularity when comparing timestamps. Failing to do so will result in incorrect snapshot ordering