PUN Classic (v1), PUN 2 and Bolt are in maintenance mode. PUN 2 will support Unity 2019 to 2022, but no new features will be added. Of course all your PUN & Bolt projects will continue to work and run with the known performance in the future. For any upcoming or new projects: please switch to Photon Fusion or Quantum.

Bolt Stream System

Photon Bolt has builtin streaming capabilities that can be used to transfer large amounts of data and works alongside the normal state transfer procedure.
If you need to send serialized data, such as images, dictionaries, map information, inventory lists, for example, the normal Bolt State properties are not the best option on those cases, for this reason, streaming is invaluable.

Description

The Bolt Stream System is has a few key aspects that you need to be aware of when using and consuming data from it.
Here we describe those settings that later will be translated to arguments to setup the streaming structure.

  • Stream Channel: the stream system is based on what we call stream channel, which is basically an identifiable structure that you can use to send and receive the blocks of data in parallel with other channels. This gives you a very versatile and powerful system where different kinds of data can use specific channels for transmission.
  • Channel Name: each stream channel is identified by a unique name that can be useful for debugging purposes.
  • Channel Mode: when creating a new channel, you are able to choose between to types:
    • Unreliable: this type of channel will send the data block using a one-fire behavior, meaning that there is no guarantee of arrival at the other end. It's also more limited, as you can't send data bigger than Bolt Packet Size.
    • Reliable: channel created with this type will let you send any size of data blocks and will also guarantee it's integrity, retransmitting data when necessary, making sure the complete information arrives at its destination.
  • Channel Priority: each channel can be configured with a different priority order, meaning that certain channels have a preference when sending its data than others. This can be useful if you need a low rate transfer, and another more critical channel sending information at the same time. A bigger priority number means more importance.
  • Connection Bandwidth: another important aspect of the stream system is how much of your bandwidth you want to dedicate to the data streaming. This can be easily changed per connection and basically configures the max number of bytes that are sent per second.

Stream System API

The Bolt Stream System is made of some different APIs that can mainly be accessed via the BoltNetwork class but also presents some callbacks and methods on other objects that work together.
Being aware of this API is important to get most of what the streaming can offer.

Change Stream Packet Size

Starting on Bolt SDK v1.3, you are able to change the default Stream Packet Size from the default size of 4096 bytes to any desired size.
This can be done by changing the default value on the BoltConfig reference found inside the BoltRuntimeSettings as shown below:

C#

public BoltConfig GetConfig
{
    get
    {
        var config = BoltRuntimeSettings.instance.GetConfigCopy();

        config.packetStreamSize = 1024; // your desired size in bytes

        return config;
    }
}

Stream Bandwidth

One of the key aspects for transmitting data between peers it how much data per unit time you can send, this is called you max bandwidth.
For each BoltConnection that is currently connected to a peer (for the Game Server, this is a list of all Clients, for a client is the connection with the Game Server) you can specify the max stream bandwidth that you desire to send data.
The default value is set to around 80kb/s.
This can be easily done as show bellow by using the BoltConnection.SetStreamBandwidth:

C#

public class CustomStreamManager : Bolt.GlobalEventListener
{
    public override void Connected(BoltConnection connection)
    {
        // Configure this connection to send at max 20kb/s
        connection.SetStreamBandwidth(1024 * 20);
    }
}

Stream Channel Creation

The central point of the streaming system are the Stream Channels.
In order to create a new channel, you make use of the BoltNetwork.CreateStreamChannel method, and pass all arguments that we've already discussed in the section above.
For later identification and use, you need to store the UdpKit.UdpChannelName reference, this will signal to Bolt which channel you want to send a particular piece of information.

Here we show how to create a streaming channel on the client and the game server.

C#

public class CustomStreamManager : Bolt.GlobalEventListener
{
    private const string VoiceChannelName = "Voice";
    private const string RandomChannelName = "RandomChannelName";

    private static UdpKit.UdpChannelName Voice;
    private static UdpKit.UdpChannelName RandomChannel;

    public override void BoltStartBegin()
    {
        // Creating a Unreliable channel with priority 1
        Voice = BoltNetwork.CreateStreamChannel(VoiceChannelName, UdpKit.UdpChannelMode.Unreliable, 1);

        // Creating a Reliable channel with priority 4
        RandomChannel = BoltNetwork.CreateStreamChannel(RandomChannelName, UdpKit.UdpChannelMode.Reliable, 4);
    }
}

As you've noted, the BoltNetwork.CreateStreamChannel received three parameters: (i) channel name, (ii) channel reliability mode, and (iii) the channel priority.
It will also return the Channel reference of type UdpKit.UdpChannelName, just store it on an accessible variable for later use.
One important restriction that you need to keep in mind is that Channels can only be created inside the BoltStartBegin callback.
That is necessary because Bolt needs to be aware of all channels when starting up.

Stream Channel Usage

After the Channel definition, we present here how you can make use of it and send information to another BoltConnection.
The API for that is BoltConnection.StreamBytes that needs two params:

  • UdpKit.UdpChannelName: the reference stored when creating the Stream Channel;
  • Byte[] data: the byte array that you want to transmit.

Here is an example of sending the binary data to the Game Server using the reference to the RandomChannel channel that was created inside our CustomStreamManager class.

C#

public class CustomStreamManager : Bolt.GlobalEventListener
{
    //...
    public void SendRandomData()
    {
        byte[] data = CreateData(5);

        // Streaming bytes
        BoltNetwork.Server.StreamBytes(RandomChannel, data);
    }

    private byte[] CreateData(int size)
    {
        var data = new byte[1024 * size];
        var rand = new System.Random();

        rand.NextBytes(data);

        return data;
    }
}

From there, you don't need to make any other action, Bolt will take care of fragmenting the information if necessary, and send it over the wire to the other endpoint.

Stream System Callbacks

The Bolt streaming system offers a set of callbacks that can be used to monitor and mainly receive the data block sent by a remote connection.
Here we describe each of those callbacks and how they are related to each other.
As usual, all the following methods are available through the Bolt.GlobalEventListener class via function overriding.

  • StreamDataStarted(BoltConnection connection, UdpChannelName channel, ulong streamID): invoked when a new stream of data has been started. You will receive information about the connection origin, the stream channel it is being transmitted, and a unique ID of the transfer.

C#

public override void StreamDataStarted(BoltConnection connection, UdpChannelName channel, ulong streamID)
{
    BoltLog.Warn("Connection {0} is transfering data on channel {1} :: Transfer {2}...", connection, channel, streamID);
}
  • StreamDataProgress(BoltConnection connection, UdpChannelName channel, ulong streamID, float progress): invoked every time a new piece of data is received. You will also get access to a progress parameter, ranging from 0 to 0.99, signaling the status of the transfer.

C#

public override void StreamDataProgress(BoltConnection connection, UdpChannelName channel, ulong streamID, float progress)
{
    BoltLog.Info("[{3}%] Connection {0} is transfering data on channel {1} :: Transfer ID {2}", connection, channel, streamID, (int)(progress * 100));
}
  • StreamDataAborted(BoltConnection connection, UdpChannelName channel, ulong streamID): invoked when the transfer has been aborted. This may occur if the remote peer has disconnected or the transfer is invalid.

C#

public override void StreamDataAborted(BoltConnection connection, UdpChannelName channel, ulong streamID)
{
    BoltLog.Error("Stream {0} on channel {1} from connection {2} has been aborted.", streamID, channel, connection);
}
  • StreamDataReceived(BoltConnection connnection, UdpStreamData data): this method will be invoked every time you've received a complete data block. It signals the connection origin (BoltConnection) and the data param (UdpStreamData), from where you can get the byte array (data field) and the stream channel (UdpKit.UdpChannelName) used to transmit the information.

C#

public override void StreamDataReceived(BoltConnection connection, UdpStreamData data)
{
    BoltLog.Info("Received data from {0} on channel {1}: {2} bytes", connection, data.Channel, data.Data.Length);
}
Back to top