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(); } } }