using System;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using MLAPI.Transports;
using MLAPI.Hashing;
using MLAPI.Serialization;
using MLAPI.Serialization.Pooled;
using UnityEngine.Serialization;
namespace MLAPI.Configuration
{
[Serializable]
internal class NullableBoolSerializable
{
[SerializeField]
public ulong Value;
}
///
/// The configuration object used to start server, client and hosts
///
[Serializable]
public class NetworkConfig
{
///
/// The protocol version. Different versions doesn't talk to each other.
///
[Tooltip("Use this to make two builds incompatible with each other")]
public ushort ProtocolVersion = 0;
///
/// The transport hosts the sever uses
///
[Tooltip("The NetworkTransport to use")]
public NetworkTransport NetworkTransport = null;
///
/// A list of SceneNames that can be used during networked games.
///
[Tooltip("The Scenes that can be switched to by the server")]
public List RegisteredScenes = new List();
///
/// Whether or not runtime scene changes should be allowed and expected.
/// If this is true, clients with different initial configurations will not work together.
///
[Tooltip("Whether or not runtime scene changes should be allowed and expected.\n " +
"If this is true, clients with different initial configurations will not work together.")]
public bool AllowRuntimeSceneChanges = false;
///
/// A list of spawnable prefabs
///
[Tooltip("The prefabs that can be spawned across the network")]
[FormerlySerializedAs("NetworkedPrefabs")]
public List NetworkPrefabs = new List();
///
/// The default player prefab
///
[SerializeReference]
internal NullableBoolSerializable PlayerPrefabHash;
///
/// Whether or not a player object should be created by default. This value can be overriden on a case by case basis with ConnectionApproval.
///
[Tooltip("Whether or not a player object should be created by default. This value can be overriden on a case by case basis with ConnectionApproval.")]
public bool CreatePlayerPrefab = true;
///
/// Amount of times per second the receive queue is emptied and all messages inside are processed.
///
[Tooltip("The amount of times per second the receive queue is emptied from pending incoming messages")]
public int ReceiveTickrate = 64;
///
/// Duration in seconds between network ticks.
///
[Tooltip("Duration in seconds between network ticks")]
public float NetworkTickIntervalSec = 0.050f;
///
/// The max amount of messages to process per ReceiveTickrate. This is to prevent flooding.
///
[Tooltip("The maximum amount of Receive events to poll per Receive tick. This is to prevent flooding and freezing on the server")]
public int MaxReceiveEventsPerTickRate = 500;
///
/// The amount of times per second internal frame events will occur, e.g. send checking.
///
[Tooltip("The amount of times per second the internal event loop will run. This includes for example NetworkVariable checking.")]
public int EventTickrate = 64;
///
/// The amount of seconds to wait for handshake to complete before timing out a client
///
[Tooltip("The amount of seconds to wait for the handshake to complete before the client times out")]
public int ClientConnectionBufferTimeout = 10;
///
/// Whether or not to use connection approval
///
[Tooltip("Whether or not to force clients to be approved before they connect")]
public bool ConnectionApproval = false;
///
/// The data to send during connection which can be used to decide on if a client should get accepted
///
[Tooltip("The connection data sent along with connection requests")]
public byte[] ConnectionData = new byte[0];
///
/// If your logic uses the NetworkTime, this should probably be turned off. If however it's needed to maximize accuracy, this is recommended to be turned on
///
[Tooltip("Enable this to resync the NetworkTime after the initial sync")]
public bool EnableTimeResync = false;
///
/// If time resync is turned on, this specifies the interval between syncs in seconds.
///
[Tooltip("The amount of seconds between resyncs of NetworkTime, if enabled")]
public int TimeResyncInterval = 30;
///
/// Whether or not to enable the NetworkVariable system. This system runs in the Update loop and will degrade performance, but it can be a huge convenience.
/// Only turn it off if you have no need for the NetworkVariable system.
///
[Tooltip("Whether or not to enable the NetworkVariable system")]
public bool EnableNetworkVariable = true;
///
/// Whether or not to ensure that NetworkVariables can be read even if a client accidentally writes where its not allowed to. This costs some CPU and bandwdith.
///
[Tooltip("Ensures that NetworkVariables can be read even if a client accidental writes where its not allowed to. This will cost some CPU time and bandwidth")]
public bool EnsureNetworkVariableLengthSafety = false;
///
/// Enables scene management. This will allow network scene switches and automatic scene diff corrections upon connect.
/// SoftSynced scene objects wont work with this disabled. That means that disabling SceneManagement also enables PrefabSync.
///
[Tooltip("Enables scene management. This will allow network scene switches and automatic scene diff corrections upon connect.\n" +
"SoftSynced scene objects wont work with this disabled. That means that disabling SceneManagement also enables PrefabSync.")]
public bool EnableSceneManagement = true;
///
/// Whether or not the MLAPI should check for differences in the prefabs at connection.
/// If you dynamically add prefabs at runtime, turn this OFF
///
[Tooltip("Whether or not the MLAPI should check for differences in the prefab lists at connection")]
public bool ForceSamePrefabs = true;
///
/// If true, all NetworkObjects need to be prefabs and all scene objects will be replaced on server side which causes all serialization to be lost. Useful for multi project setups
/// If false, Only non scene objects have to be prefabs. Scene objects will be matched using their PrefabInstanceId which can be precomputed globally for a scene at build time. Useful for single projects
///
[Tooltip("If true, all NetworkObjects need to be prefabs and all scene objects will be replaced on server side which causes all serialization to be lost. Useful for multi project setups\n" +
"If false, Only non scene objects have to be prefabs. Scene objects will be matched using their PrefabInstanceId which can be precomputed globally for a scene at build time. Useful for single projects")]
public bool UsePrefabSync = false;
///
/// If true, NetworkIds will be reused after the NetworkIdRecycleDelay.
///
[Tooltip("If true, NetworkIds will be reused after the NetworkIdRecycleDelay")]
public bool RecycleNetworkIds = true;
///
/// The amount of seconds a NetworkId has to be unused in order for it to be reused.
///
[Tooltip("The amount of seconds a NetworkId has to unused in order for it to be reused")]
public float NetworkIdRecycleDelay = 120f;
///
/// Decides how many bytes to use for Rpc messaging. Leave this to 2 bytes unless you are facing hash collisions
///
[Tooltip("The maximum amount of bytes to use for RPC messages. Leave this to 2 unless you are facing hash collisions")]
public HashSize RpcHashSize = HashSize.VarIntTwoBytes;
///
/// The amount of seconds to wait on all clients to load requested scene before the SwitchSceneProgress onComplete callback, that waits for all clients to complete loading, is called anyway.
///
[Tooltip("The amount of seconds to wait for all clients to load a requested scene")]
public int LoadSceneTimeOut = 120;
///
/// Whether or not message buffering should be enabled. This will resolve most out of order messages during spawn.
///
[Tooltip("Whether or not message buffering should be enabled. This will resolve most out of order messages during spawn")]
public bool EnableMessageBuffering = true;
///
/// The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped.
///
[Tooltip("The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped")]
public float MessageBufferTimeout = 20f;
///
/// Whether or not to enable network logs.
///
public bool EnableNetworkLogs = true;
private void Sort()
{
RegisteredScenes.Sort(StringComparer.Ordinal);
}
///
/// Returns a base64 encoded version of the config
///
///
public string ToBase64()
{
NetworkConfig config = this;
using (var buffer = PooledNetworkBuffer.Get())
using (var writer = PooledNetworkWriter.Get(buffer))
{
writer.WriteUInt16Packed(config.ProtocolVersion);
writer.WriteUInt16Packed((ushort)config.RegisteredScenes.Count);
for (int i = 0; i < config.RegisteredScenes.Count; i++)
{
writer.WriteString(config.RegisteredScenes[i]);
}
writer.WriteInt32Packed(config.ReceiveTickrate);
writer.WriteInt32Packed(config.MaxReceiveEventsPerTickRate);
writer.WriteInt32Packed(config.EventTickrate);
writer.WriteInt32Packed(config.ClientConnectionBufferTimeout);
writer.WriteBool(config.ConnectionApproval);
writer.WriteInt32Packed(config.LoadSceneTimeOut);
writer.WriteBool(config.EnableTimeResync);
writer.WriteBool(config.EnsureNetworkVariableLengthSafety);
writer.WriteBits((byte)config.RpcHashSize, 2);
writer.WriteBool(ForceSamePrefabs);
writer.WriteBool(UsePrefabSync);
writer.WriteBool(EnableSceneManagement);
writer.WriteBool(RecycleNetworkIds);
writer.WriteSinglePacked(NetworkIdRecycleDelay);
writer.WriteBool(EnableNetworkVariable);
writer.WriteBool(AllowRuntimeSceneChanges);
writer.WriteBool(EnableNetworkLogs);
buffer.PadBuffer();
return Convert.ToBase64String(buffer.ToArray());
}
}
///
/// Sets the NetworkConfig data with that from a base64 encoded version
///
/// The base64 encoded version
public void FromBase64(string base64)
{
NetworkConfig config = this;
byte[] binary = Convert.FromBase64String(base64);
using (var buffer = new NetworkBuffer(binary))
using (var reader = PooledNetworkReader.Get(buffer))
{
config.ProtocolVersion = reader.ReadUInt16Packed();
ushort sceneCount = reader.ReadUInt16Packed();
config.RegisteredScenes.Clear();
for (int i = 0; i < sceneCount; i++)
{
config.RegisteredScenes.Add(reader.ReadString().ToString());
}
config.ReceiveTickrate = reader.ReadInt32Packed();
config.MaxReceiveEventsPerTickRate = reader.ReadInt32Packed();
config.EventTickrate = reader.ReadInt32Packed();
config.ClientConnectionBufferTimeout = reader.ReadInt32Packed();
config.ConnectionApproval = reader.ReadBool();
config.LoadSceneTimeOut = reader.ReadInt32Packed();
config.EnableTimeResync = reader.ReadBool();
config.EnsureNetworkVariableLengthSafety = reader.ReadBool();
config.RpcHashSize = (HashSize)reader.ReadBits(2);
config.ForceSamePrefabs = reader.ReadBool();
config.UsePrefabSync = reader.ReadBool();
config.EnableSceneManagement = reader.ReadBool();
config.RecycleNetworkIds = reader.ReadBool();
config.NetworkIdRecycleDelay = reader.ReadSinglePacked();
config.EnableNetworkVariable = reader.ReadBool();
config.AllowRuntimeSceneChanges = reader.ReadBool();
config.EnableNetworkLogs = reader.ReadBool();
}
}
private ulong? m_ConfigHash = null;
///
/// Gets a SHA256 hash of parts of the NetworkConfig instance
///
///
///
public ulong GetConfig(bool cache = true)
{
if (m_ConfigHash != null && cache) return m_ConfigHash.Value;
Sort();
using (var buffer = PooledNetworkBuffer.Get())
using (var writer = PooledNetworkWriter.Get(buffer))
{
writer.WriteUInt16Packed(ProtocolVersion);
writer.WriteString(NetworkConstants.PROTOCOL_VERSION);
if (EnableSceneManagement && !AllowRuntimeSceneChanges)
{
for (int i = 0; i < RegisteredScenes.Count; i++)
{
writer.WriteString(RegisteredScenes[i]);
}
}
if (ForceSamePrefabs)
{
var sortedPrefabList = NetworkPrefabs.OrderBy(x => x.Hash).ToList();
for (int i = 0; i < sortedPrefabList.Count; i++)
{
writer.WriteUInt64Packed(sortedPrefabList[i].Hash);
}
}
writer.WriteBool(EnableNetworkVariable);
writer.WriteBool(ForceSamePrefabs);
writer.WriteBool(UsePrefabSync);
writer.WriteBool(EnableSceneManagement);
writer.WriteBool(EnsureNetworkVariableLengthSafety);
writer.WriteBits((byte)RpcHashSize, 2);
buffer.PadBuffer();
if (cache)
{
m_ConfigHash = buffer.ToArray().GetStableHash64();
return m_ConfigHash.Value;
}
return buffer.ToArray().GetStableHash64();
}
}
///
/// Compares a SHA256 hash with the current NetworkConfig instances hash
///
///
///
public bool CompareConfig(ulong hash)
{
return hash == GetConfig();
}
}
}