When you first approach networking you envision each event, or each change, occurring as a separate packet sent out over the network. Bolt (or any other industry action game) doesn’t work this way. Under the hood Bolt packs up packets based on the
Send Rate specified in the Bolt settings (by default this is 20 times a second). I.e. by default Bolt will send, at default settings, a packet every three simulation ticks (i.e.
FixedUpdate ticks) out over the network to each connection (20 packets/sec). In this case the setting is
3 - it represents how many simulation ticks occur before a packet is sent. Bolt packs everything into a single packet that must be under the MTU - this includes events and state updates, command input (from client) results (from server), proxy creation/destroy, everything (except streaming). So your maximum outgoing bandwidth per second from the server will be:
MaxBandwidth = SendRate * MaxPacketSize * NumPlayers;
MaxPacketSize is defined in Bolt Settings (must be under Maximum Transmission Unit), and NumPlayers is the number of connected players to the server (+1 if the server has a ingame player).
Bolt does no packet recombination on the other end and hence cannot deal with fragmented packets. This means the packet size must be under Maximum Transmission Unit. This is typical industry practice. However, this does mean that Bolt has to prioritize what it can fit in that packet, since often it cannot fit everything into it each send tick. This can become exacerbated when you have a lot of synced transforms - since they are heavy, and even heavily compressed will use ~20+ bytes per (plus overhead for the network id, etc). This is where the property priorities come in (and
IPriorityCalculator) so you can tell Bolt how important the data is as well as property compression. A state property is considered higher priority when its priority number is larger - i.e. a priority of 50 is a higher priority than a priority of 1. Keep in mind that Bolt will increase the priority of a dirty property by one each send tick that the property isn’t sent. So be careful with how you assign property priorities - since depending on the amount of data being sent, too many properties in an entity with much higher priorities than other properties in the entity can starve out other properties for many send ticks. Also keep in mind Bolt will only pack Max Properties Per Packet properties (in Bolt Settings). Once it reaches this limit it will move on to packing the next entity with changes.
Under the hood Bolt also has a staleness modifier, that basically guarantees lower priority state properties will eventually get into the packet (basically, by increasing their priority each time they are skipped and cannot make it into the current packet).
Since events are packed into this single packet (and not sent separately), unreliable events are also included in this packing. Bolt will attempt to fit an unreliable event into a packet twice before dropping the event.
Each connection in Bolt implements four internal channels. They are:
- Scene load channel
- Command channel
- Event channel
- State channel
Each channel is packed in turn from #1 to #4. If the packet runs out of space for the send tick it will cease packing. Keep in mind this means that events will always have priority over state - so if you send a lot of events at once you can starve the packet for state data for that send tick. The scene load channel is not used for much but some of the handshaking callbacks for new connections.