MLAPI/MLAPI/MonoBehaviours/Core/NetworkingManager.cs
2018-04-23 16:23:40 +02:00

1271 lines
53 KiB
C#

using MLAPI.Data;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using System.Linq;
using System.Security.Cryptography;
using MLAPI.NetworkingManagerComponents.Cryptography;
using MLAPI.NetworkingManagerComponents.Core;
using UnityEngine.SceneManagement;
using MLAPI.NetworkingManagerComponents.Binary;
namespace MLAPI.MonoBehaviours.Core
{
/// <summary>
/// The main component of the library
/// </summary>
[AddComponentMenu("MLAPI/NetworkingManager", -100)]
public class NetworkingManager : MonoBehaviour
{
/// <summary>
/// A syncronized time, represents the time in seconds since the server application started. Is replicated across all clients
/// </summary>
public float NetworkTime
{
get
{
return networkTime;
}
}
internal float networkTime;
/// <summary>
/// Gets or sets if the NetworkingManager should be marked as DontDestroyOnLoad
/// </summary>
public bool DontDestroy = true;
/// <summary>
/// Gets or sets if the application should be set to run in background
/// </summary>
public bool RunInBackground = true;
/// <summary>
/// The singleton instance of the NetworkingManager
/// </summary>
public static NetworkingManager singleton
{
get
{
return _singleton;
}
}
private static NetworkingManager _singleton;
/// <summary>
/// The clientId the server calls the local client by, only valid for clients
/// </summary>
public uint MyClientId
{
get
{
return myClientId;
}
}
internal uint myClientId;
internal Dictionary<uint, NetworkedClient> connectedClients;
/// <summary>
/// Gets a dictionary of connected clients
/// </summary>
public Dictionary<uint, NetworkedClient> ConnectedClients
{
get
{
return connectedClients;
}
}
internal HashSet<uint> pendingClients;
internal bool _isServer;
internal bool _isClient;
/// <summary>
/// Gets if we are running as host
/// </summary>
public bool isHost
{
get
{
return isServer && isClient;
}
}
/// <summary>
/// Gets wheter or not a client is running
/// </summary>
public bool isClient
{
get
{
return _isClient;
}
}
/// <summary>
/// Gets wheter or not a server is running
/// </summary>
public bool isServer
{
get
{
return _isServer;
}
}
private bool isListening;
private byte[] messageBuffer;
internal int serverConnectionId;
internal int serverHostId;
/// <summary>
/// Gets if we are connected as a client
/// </summary>
public bool IsClientConnected
{
get
{
return _isClientConnected;
}
}
internal bool _isClientConnected;
/// <summary>
/// The callback to invoke once a client connects
/// </summary>
public Action<uint> OnClientConnectedCallback = null;
/// <summary>
/// The callback to invoke when a client disconnects
/// </summary>
public Action<uint> OnClientDisconnectCallback = null;
/// <summary>
/// The callback to invoke once the server is ready
/// </summary>
public Action OnServerStarted = null;
/// <summary>
/// The callback to invoke during connection approval
/// </summary>
public Action<byte[], uint, Action<uint, bool, Vector3, Quaternion>> ConnectionApprovalCallback = null;
/// <summary>
/// The current NetworkingConfiguration
/// </summary>
public NetworkConfig NetworkConfig;
internal EllipticDiffieHellman clientDiffieHellman;
internal Dictionary<uint, byte[]> diffieHellmanPublicKeys;
internal byte[] clientAesKey;
/// <summary>
/// An inspector bool that acts as a Trigger for regenerating RSA keys. Should not be used outside Unity editor.
/// </summary>
public bool RegenerateRSAKeys = false;
private void OnValidate()
{
if (NetworkConfig == null)
return; //May occur when the component is added
if(NetworkConfig.EnableSceneSwitching && !NetworkConfig.RegisteredScenes.Contains(SceneManager.GetActiveScene().name))
{
Debug.LogWarning("MLAPI: The active scene is not registered as a networked scene. The MLAPI has added it");
NetworkConfig.RegisteredScenes.Add(SceneManager.GetActiveScene().name);
}
if(!NetworkConfig.EnableSceneSwitching && NetworkConfig.HandleObjectSpawning)
{
Debug.LogWarning("MLAPI: Please be aware that Scene objects are NOT supported if SceneManagement is turned on, even if HandleObjectSpawning is turned on");
}
if(NetworkConfig.HandleObjectSpawning)
{
for (int i = 0; i < NetworkConfig.NetworkedPrefabs.Count; i++)
{
if (string.IsNullOrEmpty(NetworkConfig.NetworkedPrefabs[i].name))
{
Debug.LogWarning("MLAPI: The NetworkedPrefab " + NetworkConfig.NetworkedPrefabs[i].prefab.name + " does not have a NetworkedPrefabName.");
}
}
int playerPrefabCount = NetworkConfig.NetworkedPrefabs.Count(x => x.playerPrefab == true);
if (playerPrefabCount == 0)
{
Debug.LogWarning("MLAPI: There is no NetworkedPrefab marked as a PlayerPrefab");
}
else if (playerPrefabCount > 1)
{
Debug.LogWarning("MLAPI: Only one networked prefab can be marked as a player prefab");
}
else
NetworkConfig.PlayerPrefabName = NetworkConfig.NetworkedPrefabs.Find(x => x.playerPrefab == true).name;
}
if (!NetworkConfig.EnableEncryption)
{
RegenerateRSAKeys = false;
}
else
{
if(RegenerateRSAKeys)
{
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.PersistKeyInCsp = false;
NetworkConfig.RSAPrivateKey = rsa.ToXmlString(true);
NetworkConfig.RSAPublicKey = rsa.ToXmlString(false);
}
RegenerateRSAKeys = false;
}
}
}
private ConnectionConfig Init(bool server)
{
networkTime = 0f;
lastSendTickTime = 0f;
lastEventTickTime = 0f;
lastReceiveTickTime = 0f;
eventOvershootCounter = 0f;
pendingClients = new HashSet<uint>();
connectedClients = new Dictionary<uint, NetworkedClient>();
messageBuffer = new byte[NetworkConfig.MessageBufferSize];
diffieHellmanPublicKeys = new Dictionary<uint, byte[]>();
Data.Cache.messageAttributeHashes = new Dictionary<string, ulong>();
Data.Cache.messageAttributeNames = new Dictionary<ulong, string>();
MessageManager.channels = new Dictionary<string, int>();
MessageManager.messageTypes = new Dictionary<string, ushort>();
MessageManager.messageCallbacks = new Dictionary<ushort, Dictionary<int, Action<uint, byte[]>>>();
MessageManager.messageHandlerCounter = new Dictionary<ushort, int>();
MessageManager.releasedMessageHandlerCounters = new Dictionary<ushort, Stack<int>>();
MessageManager.reverseChannels = new Dictionary<int, string>();
MessageManager.reverseMessageTypes = new Dictionary<ushort, string>();
SpawnManager.spawnedObjects = new Dictionary<uint, NetworkedObject>();
SpawnManager.releasedNetworkObjectIds = new Stack<uint>();
NetworkPoolManager.Pools = new Dictionary<ushort, Data.NetworkPool>();
NetworkPoolManager.PoolNamesToIndexes = new Dictionary<string, ushort>();
NetworkSceneManager.registeredSceneNames = new HashSet<string>();
NetworkSceneManager.sceneIndexToString = new Dictionary<uint, string>();
NetworkSceneManager.sceneNameToIndex = new Dictionary<string, uint>();
InternalMessageHandler.FinalMessageBuffer = new byte[NetworkConfig.MessageBufferSize];
if(NetworkConfig.HandleObjectSpawning)
{
NetworkConfig.NetworkPrefabIds = new Dictionary<string, int>();
NetworkConfig.NetworkPrefabNames = new Dictionary<int, string>();
NetworkConfig.NetworkedPrefabs.OrderBy(x => x.name);
HashSet<string> networkedPrefabName = new HashSet<string>();
for (int i = 0; i < NetworkConfig.NetworkedPrefabs.Count; i++)
{
if (networkedPrefabName.Contains(NetworkConfig.NetworkedPrefabs[i].name))
{
Debug.LogWarning("MLAPI: Duplicate NetworkedPrefabName " + NetworkConfig.NetworkedPrefabs[i].name);
continue;
}
NetworkConfig.NetworkPrefabIds.Add(NetworkConfig.NetworkedPrefabs[i].name, i);
NetworkConfig.NetworkPrefabNames.Add(i, NetworkConfig.NetworkedPrefabs[i].name);
networkedPrefabName.Add(NetworkConfig.NetworkedPrefabs[i].name);
}
if (NetworkConfig.EnableSceneSwitching)
{
SpawnManager.MarkSceneObjects();
if (NetworkConfig.HandleObjectSpawning)
{
if (server)
{
bool isServerState = _isServer;
_isServer = true;
NetworkedObject[] networkedObjects = FindObjectsOfType<NetworkedObject>();
for (int i = 0; i < networkedObjects.Length; i++)
{
if (networkedObjects[i].sceneObject == null || networkedObjects[i].sceneObject == true)
networkedObjects[i].Spawn();
}
_isServer = isServerState;
}
}
}
}
NetworkTransport.Init();
ConnectionConfig cConfig = new ConnectionConfig()
{
SendDelay = 0
};
//MLAPI channels and messageTypes
List<Channel> internalChannels = new List<Channel>
{
new Channel()
{
Name = "MLAPI_INTERNAL",
Type = QosType.ReliableFragmentedSequenced
},
new Channel()
{
Name = "MLAPI_POSITION_UPDATE",
Type = QosType.StateUpdate
},
new Channel()
{
Name = "MLAPI_ANIMATION_UPDATE",
Type = QosType.ReliableSequenced
},
new Channel()
{
Name = "MLAPI_NAV_AGENT_STATE",
Type = QosType.ReliableSequenced
},
new Channel()
{
Name = "MLAPI_NAV_AGENT_CORRECTION",
Type = QosType.StateUpdate
},
new Channel()
{
Name = "MLAPI_TIME_SYNC",
Type = QosType.Unreliable
}
};
if (NetworkConfig.EnableEncryption)
{
HashSet<string> addedEncryptedChannels = new HashSet<string>();
for (int i = 0; i < NetworkConfig.Channels.Count; i++)
{
if (addedEncryptedChannels.Contains(NetworkConfig.Channels[i].Name))
{
Debug.LogWarning("MLAPI: Duplicate encrypted channel name " + NetworkConfig.Channels[i].Name);
continue;
}
if (NetworkConfig.Channels[i].Encrypted)
{
NetworkConfig.EncryptedChannels.Add(NetworkConfig.Channels[i].Name);
NetworkConfig.EncryptedChannelsHashSet.Add(NetworkConfig.Channels[i].Name);
}
addedEncryptedChannels.Add(NetworkConfig.Channels[i].Name);
}
}
if (NetworkConfig.AllowPassthroughMessages)
{
HashSet<string> addedPassthroughMessages = new HashSet<string>();
for (int i = 0; i < NetworkConfig.MessageTypes.Count; i++)
{
if (addedPassthroughMessages.Contains(NetworkConfig.MessageTypes[i].Name))
{
Debug.LogWarning("MLAPI: Duplicate passthrough message type " + NetworkConfig.MessageTypes[i].Name);
continue;
}
if (NetworkConfig.MessageTypes[i].Passthrough)
{
NetworkConfig.PassthroughMessageHashSet.Add(MessageManager.messageTypes[NetworkConfig.MessageTypes[i].Name]);
addedPassthroughMessages.Add(NetworkConfig.MessageTypes[i].Name);
}
}
}
HashSet<string> channelNames = new HashSet<string>();
//Register internal channels
for (int i = 0; i < internalChannels.Count; i++)
{
if (channelNames.Contains(internalChannels[i].Name))
{
Debug.LogWarning("MLAPI: Duplicate channel name: " + NetworkConfig.Channels[i].Name);
continue;
}
int channelId = cConfig.AddChannel(internalChannels[i].Type);
MessageManager.channels.Add(internalChannels[i].Name, channelId);
channelNames.Add(internalChannels[i].Name);
MessageManager.reverseChannels.Add(channelId, internalChannels[i].Name);
}
MessageManager.messageTypes.Add("MLAPI_CONNECTION_REQUEST", 0);
MessageManager.messageTypes.Add("MLAPI_CONNECTION_APPROVED", 1);
MessageManager.messageTypes.Add("MLAPI_ADD_OBJECT", 2);
MessageManager.messageTypes.Add("MLAPI_CLIENT_DISCONNECT", 3);
MessageManager.messageTypes.Add("MLAPI_DESTROY_OBJECT", 4);
MessageManager.messageTypes.Add("MLAPI_SWITCH_SCENE", 5);
MessageManager.messageTypes.Add("MLAPI_SPAWN_POOL_OBJECT", 6);
MessageManager.messageTypes.Add("MLAPI_DESTROY_POOL_OBJECT", 7);
MessageManager.messageTypes.Add("MLAPI_CHANGE_OWNER", 8);
MessageManager.messageTypes.Add("MLAPI_SYNC_VAR_UPDATE", 9);
MessageManager.messageTypes.Add("MLAPI_ADD_OBJECTS", 10);
MessageManager.messageTypes.Add("MLAPI_TIME_SYNC", 11);
MessageManager.messageTypes.Add("MLAPI_COMMAND", 12);
MessageManager.messageTypes.Add("MLAPI_RPC", 13);
MessageManager.messageTypes.Add("MLAPI_TARGET", 14);
MessageManager.messageTypes.Add("MLAPI_SET_VISIBILITY", 15);
List<MessageType> messageTypes = new List<MessageType>(NetworkConfig.MessageTypes)
{
new MessageType()
{
Name = "MLAPI_OnRecieveTransformFromClient",
Passthrough = false
},
new MessageType()
{
Name = "MLAPI_OnRecieveTransformFromServer",
Passthrough = false
},
new MessageType()
{
Name = "MLAPI_HandleAnimationMessage",
Passthrough = false
},
new MessageType()
{
Name = "MLAPI_HandleAnimationParameterMessage",
Passthrough = false
},
new MessageType()
{
Name = "MLAPI_HandleAnimationTriggerMessage",
Passthrough = false
},
new MessageType()
{
Name = "MLAPI_OnNavMeshStateUpdate",
Passthrough = false
},
new MessageType()
{
Name = "MLAPI_OnNavMeshCorrectionUpdate",
Passthrough = false
}
};
if (NetworkConfig.EnableSceneSwitching)
{
for (int i = 0; i < NetworkConfig.RegisteredScenes.Count; i++)
{
NetworkSceneManager.registeredSceneNames.Add(NetworkConfig.RegisteredScenes[i]);
NetworkSceneManager.sceneIndexToString.Add((uint)i, NetworkConfig.RegisteredScenes[i]);
NetworkSceneManager.sceneNameToIndex.Add(NetworkConfig.RegisteredScenes[i], (uint)i);
}
NetworkSceneManager.SetCurrentSceneIndex();
}
//Register user channels
for (int i = 0; i < NetworkConfig.Channels.Count; i++)
{
if(channelNames.Contains(NetworkConfig.Channels[i].Name))
{
Debug.LogWarning("MLAPI: Duplicate channel name: " + NetworkConfig.Channels[i].Name);
continue;
}
int channelId = cConfig.AddChannel(NetworkConfig.Channels[i].Type);
MessageManager.channels.Add(NetworkConfig.Channels[i].Name, channelId);
channelNames.Add(NetworkConfig.Channels[i].Name);
MessageManager.reverseChannels.Add(channelId, NetworkConfig.Channels[i].Name);
}
//0-32 are reserved for MLAPI messages
ushort messageId = 32;
for (ushort i = 0; i < messageTypes.Count; i++)
{
MessageManager.messageTypes.Add(messageTypes[i].Name, messageId);
MessageManager.reverseMessageTypes.Add(messageId, messageTypes[i].Name);
messageId++;
}
return cConfig;
}
/// <summary>
/// Starts a server
/// </summary>
public void StartServer()
{
if (isServer || isClient)
{
Debug.LogWarning("MLAPI: Cannot start server while an instance is already running");
return;
}
ConnectionConfig cConfig = Init(true);
if (NetworkConfig.ConnectionApproval)
{
if (ConnectionApprovalCallback == null)
{
Debug.LogWarning("MLAPI: No ConnectionApproval callback defined. Connection approval will timeout");
}
}
HostTopology hostTopology = new HostTopology(cConfig, NetworkConfig.MaxConnections);
for (int i = 0; i < NetworkConfig.ServerTransports.Count; i++)
{
if (NetworkConfig.ServerTransports[i].Websockets)
NetworkTransport.AddWebsocketHost(hostTopology, NetworkConfig.ServerTransports[i].Port);
else
NetworkTransport.AddHost(hostTopology, NetworkConfig.ServerTransports[i].Port);
}
_isServer = true;
_isClient = false;
isListening = true;
if (OnServerStarted != null)
OnServerStarted.Invoke();
}
/// <summary>
/// Starts a client
/// </summary>
public void StartClient()
{
if (isServer || isClient)
{
Debug.LogWarning("MLAPI: Cannot start client while an instance is already running");
return;
}
ConnectionConfig cConfig = Init(false);
HostTopology hostTopology = new HostTopology(cConfig, NetworkConfig.MaxConnections);
serverHostId = NetworkTransport.AddHost(hostTopology, 0, null);
_isServer = false;
_isClient = true;
isListening = true;
byte error;
serverConnectionId = NetworkTransport.Connect(serverHostId, NetworkConfig.ConnectAddress, NetworkConfig.ConnectPort, 0, out error);
}
/// <summary>
/// Starts a client using Websockets
/// </summary>
public void StartClientWebsocket()
{
if (isServer || isClient)
{
Debug.LogWarning("MLAPI: Cannot start client while an instance is already running");
return;
}
ConnectionConfig cConfig = Init(false);
HostTopology hostTopology = new HostTopology(cConfig, NetworkConfig.MaxConnections);
serverHostId = NetworkTransport.AddWebsocketHost(hostTopology, 0, null);
_isServer = false;
_isClient = true;
isListening = true;
byte error;
serverConnectionId = NetworkTransport.Connect(serverHostId, NetworkConfig.ConnectAddress, NetworkConfig.ConnectPort, 0, out error);
}
/// <summary>
/// Stops the running server
/// </summary>
public void StopServer()
{
HashSet<uint> disconnectedIds = new HashSet<uint>();
//Don't know if I have to disconnect the clients. I'm assuming the NetworkTransport does all the cleaning on shtudown. But this way the clients get a disconnect message from server (so long it does't get lost)
foreach (KeyValuePair<uint, NetworkedClient> pair in connectedClients)
{
if(!disconnectedIds.Contains(pair.Key))
{
disconnectedIds.Add(pair.Key);
NetId netId = new NetId(pair.Key);
if (netId.IsHost())
continue;
byte error;
NetworkTransport.Disconnect(netId.HostId, netId.ConnectionId, out error);
}
}
foreach (uint clientId in pendingClients)
{
if (!disconnectedIds.Contains(clientId))
{
disconnectedIds.Add(clientId);
NetId netId = new NetId(clientId);
if (netId.IsHost())
continue;
byte error;
NetworkTransport.Disconnect(netId.HostId, netId.ConnectionId, out error);
}
}
_isServer = false;
Shutdown();
}
/// <summary>
/// Stops the running host
/// </summary>
public void StopHost()
{
_isClient = false;
_isServer = false;
StopServer();
//We don't stop client since we dont actually have a transport connection to our own host. We just handle host messages directly in the MLAPI
}
/// <summary>
/// Stops the running client
/// </summary>
public void StopClient()
{
_isClient = false;
byte error;
NetworkTransport.Disconnect(serverHostId, serverConnectionId, out error);
Shutdown();
}
/// <summary>
/// Starts a Host
/// </summary>
public void StartHost(Vector3? pos = null, Quaternion? rot = null)
{
if (isServer || isClient)
{
Debug.LogWarning("MLAPI: Cannot start host while an instance is already running");
return;
}
ConnectionConfig cConfig = Init(true);
if (NetworkConfig.ConnectionApproval)
{
if (ConnectionApprovalCallback == null)
{
Debug.LogWarning("MLAPI: No ConnectionApproval callback defined. Connection approval will timeout");
}
}
HostTopology hostTopology = new HostTopology(cConfig, NetworkConfig.MaxConnections);
for (int i = 0; i < NetworkConfig.ServerTransports.Count; i++)
{
if (NetworkConfig.ServerTransports[i].Websockets)
NetworkTransport.AddWebsocketHost(hostTopology, NetworkConfig.ServerTransports[i].Port);
else
NetworkTransport.AddHost(hostTopology, NetworkConfig.ServerTransports[i].Port);
}
_isServer = true;
_isClient = true;
isListening = true;
NetId netId = new NetId(0, 0, true, false);
connectedClients.Add(netId.GetClientId(), new NetworkedClient()
{
ClientId = netId.GetClientId()
});
if(NetworkConfig.HandleObjectSpawning)
{
SpawnManager.SpawnPlayerObject(netId.GetClientId(), 0, pos.GetValueOrDefault(), rot.GetValueOrDefault());
}
if (OnServerStarted != null)
OnServerStarted.Invoke();
}
private void OnEnable()
{
if (singleton != null)
{
Debug.LogWarning("MLAPI: Multiple NetworkingManagers");
Destroy(this);
return;
}
_singleton = this;
if (DontDestroy)
DontDestroyOnLoad(gameObject);
if (RunInBackground)
Application.runInBackground = true;
}
private void OnDestroy()
{
_singleton = null;
Shutdown();
}
private void Shutdown()
{
isListening = false;
_isClient = false;
_isServer = false;
SpawnManager.DestroyNonSceneObjects();
NetworkTransport.Shutdown();
}
private float lastReceiveTickTime;
private float lastSendTickTime;
private float lastEventTickTime;
private float eventOvershootCounter;
private float lastTimeSyncTime;
private void Update()
{
if(isListening)
{
if((NetworkTime - lastSendTickTime >= (1f / NetworkConfig.SendTickrate)) || NetworkConfig.SendTickrate <= 0)
{
foreach (KeyValuePair<uint, NetworkedClient> pair in connectedClients)
{
NetId netId = new NetId(pair.Key);
if (netId.IsHost() || netId.IsInvalid())
continue;
byte error;
NetworkTransport.SendQueuedMessages(netId.HostId, netId.ConnectionId, out error);
}
lastSendTickTime = NetworkTime;
}
if((NetworkTime - lastReceiveTickTime >= (1f / NetworkConfig.ReceiveTickrate)) || NetworkConfig.ReceiveTickrate <= 0)
{
NetworkEventType eventType;
int processedEvents = 0;
do
{
processedEvents++;
int hostId;
int connectionId;
int channelId;
int receivedSize;
byte error;
eventType = NetworkTransport.Receive(out hostId, out connectionId, out channelId, messageBuffer, messageBuffer.Length, out receivedSize, out error);
NetId netId = new NetId((byte)hostId, (ushort)connectionId, false, false);
NetworkError networkError = (NetworkError)error;
if (networkError == NetworkError.Timeout)
{
//Client timed out.
if (isServer)
{
OnClientDisconnect(netId.GetClientId());
return;
}
else
_isClientConnected = false;
if (OnClientDisconnectCallback != null)
OnClientDisconnectCallback.Invoke(netId.GetClientId());
}
else if (networkError != NetworkError.Ok)
{
Debug.LogWarning("MLAPI: NetworkTransport receive error: " + networkError.ToString());
return;
}
switch (eventType)
{
case NetworkEventType.ConnectEvent:
if (isServer)
{
pendingClients.Add(netId.GetClientId());
StartCoroutine(ApprovalTimeout(netId.GetClientId()));
}
else
{
byte[] diffiePublic = new byte[0];
if(NetworkConfig.EnableEncryption)
{
clientDiffieHellman = new EllipticDiffieHellman(EllipticDiffieHellman.DEFAULT_CURVE, EllipticDiffieHellman.DEFAULT_GENERATOR, EllipticDiffieHellman.DEFAULT_ORDER);
diffiePublic = clientDiffieHellman.GetPublicKey();
}
using (BitWriter writer = new BitWriter())
{
writer.WriteByteArray(NetworkConfig.GetConfig(), true);
if (NetworkConfig.EnableEncryption)
writer.WriteByteArray(diffiePublic);
if (NetworkConfig.ConnectionApproval)
writer.WriteByteArray(NetworkConfig.ConnectionData);
InternalMessageHandler.Send(netId.GetClientId(), "MLAPI_CONNECTION_REQUEST", "MLAPI_INTERNAL", writer.Finalize(), null, null, null, true);
}
}
break;
case NetworkEventType.DataEvent:
HandleIncomingData(netId.GetClientId(), messageBuffer, channelId);
break;
case NetworkEventType.DisconnectEvent:
if (isServer)
OnClientDisconnect(netId.GetClientId());
else
_isClientConnected = false;
if (OnClientDisconnectCallback != null)
OnClientDisconnectCallback.Invoke(netId.GetClientId());
break;
}
// Only do another iteration if: there are no more messages AND (there is no limit to max events or we have processed less than the maximum)
} while (eventType != NetworkEventType.Nothing && (NetworkConfig.MaxReceiveEventsPerTickRate <= 0 || processedEvents < NetworkConfig.MaxReceiveEventsPerTickRate));
lastReceiveTickTime = NetworkTime;
}
if (isServer && ((NetworkTime - lastEventTickTime >= (1f / NetworkConfig.EventTickrate))))
{
eventOvershootCounter += ((NetworkTime - lastEventTickTime) - (1f / NetworkConfig.EventTickrate));
LagCompensationManager.AddFrames();
NetworkedObject.InvokeSyncvarUpdate();
lastEventTickTime = NetworkTime;
}
else if (isServer && eventOvershootCounter >= ((1f / NetworkConfig.EventTickrate)))
{
//We run this one to compensate for previous update overshoots.
eventOvershootCounter -= (1f / NetworkConfig.EventTickrate);
LagCompensationManager.AddFrames();
}
if (NetworkConfig.EnableTimeResync && NetworkTime - lastTimeSyncTime >= 30)
{
SyncTime();
lastTimeSyncTime = NetworkTime;
}
networkTime += Time.unscaledDeltaTime;
}
}
private IEnumerator ApprovalTimeout(uint clientId)
{
float timeStarted = NetworkTime;
//We yield every frame incase a pending client disconnects and someone else gets its connection id
while (NetworkTime - timeStarted < NetworkConfig.ClientConnectionBufferTimeout && pendingClients.Contains(clientId))
{
yield return null;
}
if(pendingClients.Contains(clientId) && !connectedClients.ContainsKey(clientId))
{
//Timeout
DisconnectClient(clientId);
}
}
private void HandleIncomingData(uint clientId, byte[] data, int channelId)
{
BitReader reader = new BitReader(data);
ushort messageType = reader.ReadUShort();
bool targeted = reader.ReadBool();
uint targetNetworkId = 0;
ushort networkOrderId = 0;
if (targeted)
{
targetNetworkId = reader.ReadUInt();
networkOrderId = reader.ReadUShort();
}
bool isPassthrough = reader.ReadBool();
uint passthroughOrigin = 0;
uint passthroughTarget = 0;
if (isPassthrough && isServer)
passthroughTarget = reader.ReadUInt();
else if (isPassthrough && !isServer)
passthroughOrigin = reader.ReadUInt();
//Client tried to send a network message that was not the connection request before he was accepted.
if (isServer && pendingClients.Contains(clientId) && messageType != 0)
{
Debug.LogWarning("MLAPI: Message recieved from clientId " + clientId + " before it has been accepted");
return;
}
//ushort bytesToRead = reader.ReadUShort();
reader.SkipPadded();
byte[] incommingData = reader.ReadByteArray();
if (NetworkConfig.EncryptedChannelsHashSet.Contains(MessageManager.reverseChannels[channelId]))
{
//Encrypted message
if (isServer)
incommingData = CryptographyHelper.Decrypt(incommingData, connectedClients[clientId].AesKey);
else
incommingData = CryptographyHelper.Decrypt(incommingData, clientAesKey);
}
if (isServer && isPassthrough && !NetworkConfig.PassthroughMessageHashSet.Contains(messageType))
{
Debug.LogWarning("MLAPI: Client " + clientId + " tried to send a passthrough message for a messageType not registered as passthrough");
return;
}
else if (isClient && isPassthrough && !NetworkConfig.PassthroughMessageHashSet.Contains(messageType))
{
Debug.LogWarning("MLAPI: Server tried to send a passthrough message for a messageType not registered as passthrough");
return;
}
else if (isServer && isPassthrough)
{
if (!connectedClients.ContainsKey(passthroughTarget))
{
Debug.LogWarning("MLAPI: Passthrough message was sent with invalid target: " + passthroughTarget + " from client " + clientId);
return;
}
uint? netIdTarget = null;
ushort? netOrderId = null;
if (targeted)
{
netIdTarget = targetNetworkId;
netOrderId = networkOrderId;
}
InternalMessageHandler.PassthroughSend(passthroughTarget, clientId, messageType, channelId, incommingData, netIdTarget, netOrderId);
return;
}
if (messageType >= 32)
{
#region CUSTOM MESSAGE
//Custom message, invoke all message handlers
if (targeted)
{
if (!SpawnManager.spawnedObjects.ContainsKey(targetNetworkId))
{
Debug.LogWarning("MLAPI: No target for message found");
return;
}
else if (!SpawnManager.spawnedObjects[targetNetworkId].targetMessageActions.ContainsKey(networkOrderId))
{
Debug.LogWarning("MLAPI: No target messageType for message found");
return;
}
else if (!SpawnManager.spawnedObjects[targetNetworkId].targetMessageActions[networkOrderId].ContainsKey(messageType))
{
Debug.LogWarning("MLAPI: No target found with the given messageType");
return;
}
SpawnManager.spawnedObjects[targetNetworkId].targetMessageActions[networkOrderId][messageType].Invoke(clientId, incommingData);
}
else
{
foreach (KeyValuePair<int, Action<uint, byte[]>> pair in MessageManager.messageCallbacks[messageType])
{
if (isPassthrough)
pair.Value(passthroughOrigin, incommingData);
else
pair.Value(clientId, incommingData);
}
}
#endregion
}
else
{
#region INTERNAL MESSAGE
//MLAPI message
switch (messageType)
{
case 0: //Client to server > sends connection buffer
if (isServer)
{
InternalMessageHandler.HandleConnectionRequest(clientId, incommingData, channelId);
}
break;
case 1: //Server informs client it has been approved:
if (isClient)
{
InternalMessageHandler.HandleConnectionApproved(clientId, incommingData, channelId);
}
break;
case 2:
//Server informs client another client connected
//MLAPI_ADD_OBJECT
if (isClient)
{
InternalMessageHandler.HandleAddObject(clientId, incommingData, channelId);
}
break;
case 3:
//Server informs client another client disconnected
//MLAPI_CLIENT_DISCONNECT
if (isClient)
{
InternalMessageHandler.HandleClientDisconnect(clientId, incommingData, channelId);
}
break;
case 4:
//Server infroms clients to destroy an object
if (isClient)
{
InternalMessageHandler.HandleDestroyObject(clientId, incommingData, channelId);
}
break;
case 5:
//Scene switch
if (isClient)
{
InternalMessageHandler.HandleSwitchScene(clientId, incommingData, channelId);
}
break;
case 6: //Spawn pool object
if (isClient)
{
InternalMessageHandler.HandleSpawnPoolObject(clientId, incommingData, channelId);
}
break;
case 7: //Destroy pool object
if (isClient)
{
InternalMessageHandler.HandleDestroyPoolObject(clientId, incommingData, channelId);
}
break;
case 8: //Change owner
if (isClient)
{
InternalMessageHandler.HandleChangeOwner(clientId, incommingData, channelId);
}
break;
case 9: //Syncvar
if (isClient)
{
InternalMessageHandler.HandleSyncVarUpdate(clientId, incommingData, channelId);
}
break;
case 10:
if (isClient) //MLAPI_ADD_OBJECTS (plural)
{
InternalMessageHandler.HandleAddObjects(clientId, incommingData, channelId);
}
break;
case 11:
if (isClient)
{
InternalMessageHandler.HandleTimeSync(clientId, incommingData, channelId);
}
break;
case 12:
if (isServer)
{
InternalMessageHandler.HandleCommand(clientId, incommingData, channelId);
}
break;
case 13:
if (isClient)
{
InternalMessageHandler.HandleRpc(clientId, incommingData, channelId);
}
break;
case 14:
if (isClient)
{
InternalMessageHandler.HandleTargetRpc(clientId, incommingData, channelId);
}
break;
case 15:
if (isClient)
{
InternalMessageHandler.HandleSetVisibility(clientId, incommingData, channelId);
}
break;
}
#endregion
}
}
internal void DisconnectClient(uint clientId)
{
if (!isServer)
return;
if (pendingClients.Contains(clientId))
pendingClients.Remove(clientId);
if (connectedClients.ContainsKey(clientId))
connectedClients.Remove(clientId);
if (diffieHellmanPublicKeys.ContainsKey(clientId))
diffieHellmanPublicKeys.Remove(clientId);
foreach (KeyValuePair<uint, NetworkedObject> pair in SpawnManager.spawnedObjects)
pair.Value.observers.Remove(clientId);
NetId netId = new NetId(clientId);
if (netId.IsHost() || netId.IsInvalid())
return;
byte error;
NetworkTransport.Disconnect(netId.HostId, netId.ConnectionId, out error);
}
internal void OnClientDisconnect(uint clientId)
{
if (pendingClients.Contains(clientId))
pendingClients.Remove(clientId);
if (connectedClients.ContainsKey(clientId))
{
if(NetworkConfig.HandleObjectSpawning)
{
if (connectedClients[clientId].PlayerObject != null)
Destroy(connectedClients[clientId].PlayerObject);
for (int i = 0; i < connectedClients[clientId].OwnedObjects.Count; i++)
{
if (connectedClients[clientId].OwnedObjects[i] != null)
Destroy(connectedClients[clientId].OwnedObjects[i].gameObject);
}
}
connectedClients.Remove(clientId);
}
if (isServer)
{
foreach (KeyValuePair<uint, NetworkedObject> pair in SpawnManager.spawnedObjects)
pair.Value.observers.Remove(clientId);
using (BitWriter writer = new BitWriter())
{
writer.WriteUInt(clientId);
InternalMessageHandler.Send("MLAPI_CLIENT_DISCONNECT", "MLAPI_INTERNAL", writer.Finalize(), clientId, null);
}
}
}
private void SyncTime()
{
using (BitWriter writer = new BitWriter())
{
writer.WriteFloat(NetworkTime);
int timestamp = NetworkTransport.GetNetworkTimestamp();
writer.WriteInt(timestamp);
byte[] buffer = writer.Finalize();
foreach (KeyValuePair<uint, NetworkedClient> pair in connectedClients)
InternalMessageHandler.Send("MLAPI_TIME_SYNC", "MLAPI_TIME_SYNC", buffer, null);
}
}
internal void HandleApproval(uint clientId, bool approved, Vector3 position, Quaternion rotation)
{
if(approved)
{
//Inform new client it got approved
if (pendingClients.Contains(clientId))
pendingClients.Remove(clientId);
byte[] aesKey = new byte[0];
byte[] publicKey = new byte[0];
byte[] publicKeySignature = new byte[0];
if (NetworkConfig.EnableEncryption)
{
EllipticDiffieHellman diffieHellman = new EllipticDiffieHellman(EllipticDiffieHellman.DEFAULT_CURVE, EllipticDiffieHellman.DEFAULT_GENERATOR, EllipticDiffieHellman.DEFAULT_ORDER);
aesKey = diffieHellman.GetSharedSecret(diffieHellmanPublicKeys[clientId]);
publicKey = diffieHellman.GetPublicKey();
if (diffieHellmanPublicKeys.ContainsKey(clientId))
diffieHellmanPublicKeys.Remove(clientId);
if (NetworkConfig.SignKeyExchange)
{
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.PersistKeyInCsp = false;
rsa.FromXmlString(NetworkConfig.RSAPrivateKey);
publicKeySignature = rsa.SignData(publicKey, new SHA512CryptoServiceProvider());
}
}
}
NetworkedClient client = new NetworkedClient()
{
ClientId = clientId,
AesKey = aesKey
};
connectedClients.Add(clientId, client);
if(NetworkConfig.HandleObjectSpawning)
{
uint networkId = SpawnManager.GetNetworkObjectId();
GameObject go = SpawnManager.SpawnPlayerObject(clientId, networkId, position, rotation);
connectedClients[clientId].PlayerObject = go;
}
int amountOfObjectsToSend = SpawnManager.spawnedObjects.Values.Count;
using (BitWriter writer = new BitWriter())
{
writer.WriteUInt(clientId);
if (NetworkConfig.EnableSceneSwitching)
writer.WriteUInt(NetworkSceneManager.CurrentSceneIndex);
if (NetworkConfig.EnableEncryption)
{
writer.WriteByteArray(publicKey);
if (NetworkConfig.SignKeyExchange)
writer.WriteByteArray(publicKeySignature);
}
writer.WriteFloat(NetworkTime);
writer.WriteInt(NetworkTransport.GetNetworkTimestamp());
writer.WriteInt(connectedClients.Count - 1);
foreach (KeyValuePair<uint, NetworkedClient> item in connectedClients)
{
//Our own ID. Already added as the first one above
if (item.Key == clientId)
continue;
writer.WriteUInt(item.Key); //ClientId
}
if (NetworkConfig.HandleObjectSpawning)
{
writer.WriteInt(amountOfObjectsToSend);
foreach (KeyValuePair<uint, NetworkedObject> pair in SpawnManager.spawnedObjects)
{
pair.Value.RebuildObservers(clientId); //Rebuilds observers for the new client
writer.WriteBool(pair.Value.isPlayerObject);
writer.WriteUInt(pair.Value.NetworkId);
writer.WriteUInt(pair.Value.OwnerClientId);
writer.WriteInt(NetworkConfig.NetworkPrefabIds[pair.Value.NetworkedPrefabName]);
writer.WriteBool(pair.Value.gameObject.activeInHierarchy);
writer.WriteBool(pair.Value.sceneObject == null ? true : pair.Value.sceneObject.Value);
writer.WriteBool(pair.Value.observers.Contains(clientId));
writer.WriteFloat(pair.Value.transform.position.x);
writer.WriteFloat(pair.Value.transform.position.y);
writer.WriteFloat(pair.Value.transform.position.z);
writer.WriteFloat(pair.Value.transform.rotation.eulerAngles.x);
writer.WriteFloat(pair.Value.transform.rotation.eulerAngles.y);
writer.WriteFloat(pair.Value.transform.rotation.eulerAngles.z);
}
}
InternalMessageHandler.Send(clientId, "MLAPI_CONNECTION_APPROVED", "MLAPI_INTERNAL", writer.Finalize(), null, null, null, true);
if (OnClientConnectedCallback != null)
OnClientConnectedCallback.Invoke(clientId);
}
//Inform old clients of the new player
using (BitWriter writer = new BitWriter())
{
if (NetworkConfig.HandleObjectSpawning)
{
writer.WriteBool(true);
writer.WriteUInt(connectedClients[clientId].PlayerObject.GetComponent<NetworkedObject>().NetworkId);
writer.WriteUInt(clientId);
writer.WriteInt(-1);
writer.WriteBool(false);
writer.WriteBool(connectedClients[clientId].PlayerObject.GetComponent<NetworkedObject>().observers.Contains(clientId));
writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.position.x);
writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.position.y);
writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.position.z);
writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.rotation.eulerAngles.x);
writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.rotation.eulerAngles.y);
writer.WriteFloat(connectedClients[clientId].PlayerObject.transform.rotation.eulerAngles.z);
}
else
{
writer.WriteUInt(clientId);
}
InternalMessageHandler.Send("MLAPI_ADD_OBJECT", "MLAPI_INTERNAL", writer.Finalize(), clientId, null);
}
//Flush syncvars:
foreach (KeyValuePair<uint, NetworkedObject> networkedObject in SpawnManager.spawnedObjects)
{
networkedObject.Value.FlushToClient(clientId);
}
}
else
{
if (pendingClients.Contains(clientId))
pendingClients.Remove(clientId);
if (diffieHellmanPublicKeys.ContainsKey(clientId))
diffieHellmanPublicKeys.Remove(clientId);
NetId netId = new NetId(clientId);
byte error;
NetworkTransport.Disconnect(netId.HostId, netId.ConnectionId, out error);
}
}
}
}