/// <summary> /// this function splits FILES into MEMORY SAFE sized chunks and safely sends one before starting another /// /// files receipient needs to receive the same number of headers with each header packet (packet 1 counts as a header packet) /// </summary> public IEnumerator SendFilesDownloadRoutine(string[] _paths, ulong _clientID) { Debug.Log("coroutine started"); if (State != LargeRPCState.Idle) { Debug.LogWarning("Cannot start sending files while files are being sent, waiting for Idle state to begin"); yield break; } ReceiverID = _clientID; ChangeState(LargeRPCState.Send_SendingHeaders); #region comment -- header sizes /* -- Header sizes -- * packet 1 * int fileCount 4b | long downloadSize 8b | <start headers> * * header packets * int fileID 4b | string filename varsize | byte[256] hash 32b | long fileLength 8b | bool isLastInPacket 1bit * * subsequent packets * int fileID 4b | int filedata_length 4b | byte[var] filedata <=netChunkSize | bool isLastInPacket 1byte */ #endregion #region Grab Download Information // grab info for headers foreach (var path in _paths) { if (File.Exists(path)) { using (FileStream fs = File.Open(path, FileMode.Open)) { Debug.Log(fs.Name); int id = Headers.Count; byte[] fileHash = fs.sha256(); yield return(new WaitForEndOfFrame()); FileHeader header = new FileHeader(id, Path.GetFileName(path), fileHash, fs.Length); Headers.Add(header); DownloadSize += header.fileSize; } // let it exit if it needs to, giving StopRoutine() a chance yield return(0); } else { Debug.LogWarning("File not found, skipping: " + path); } } #endregion #region send headers PooledBitStream bitStream = PooledBitStream.Get(); PooledBitWriter writer = PooledBitWriter.Get(bitStream); // fileCount writer.WriteInt32(Headers.Count); // downloadSize writer.WriteInt64(DownloadSize); var headersThisPacket = 0; var packetsSent = 0; Debug.Log("Sending headers"); foreach (var header in Headers) { // let it exit if it needs to, giving StopRoutine() a chance yield return(0); Debug.Log(headersThisPacket + " " + packetsSent); var path = header.path; var id = header.id; headersThisPacket++; // fileID writer.WriteInt32(header.id); // filename writer.WriteString(path); Debug.Log(Encoding.Unicode.GetString(header.hash)); // hash writer.WriteByteArray(header.hash, 32); // fileLength writer.WriteInt64(header.fileSize); bool isLastPacket = id >= Headers.Count - 1; // send it off if we've filled up a packet if (headersThisPacket >= headersPerPacket || isLastPacket) { Debug.Log("message going out"); // isLastInPacket writer.WriteBit(true); CustomMessagingManager.SendNamedMessage(MessageName, _clientID, bitStream, "MLAPI_INTERNAL"); /* headers are pretty small, they really don't need the receiver to check in here unless it becomes a problem * * // if we haven't sent any packets yet when we get here, wait for an okay from the receiver * if (packetsSent == 0) * { * ChangeState(LargeRPCState.Send_AwaitingOkayToSend); * * while (State == LargeRPCState.Send_AwaitingOkayToSend) * { * yield return new WaitForSeconds(0.5f); * } * }*/ packetsSent++; Debug.Log("headers: " + headersThisPacket + " packets: " + packetsSent); headersThisPacket = 0; writer.Dispose(); bitStream.Dispose(); bitStream = PooledBitStream.Get(); writer = PooledBitWriter.Get(bitStream); // don't wait on the last one if (!isLastPacket) { yield return(new WaitForSeconds(1 / 14)); } } else { writer.WriteBit(false); } } writer.Dispose(); bitStream.Dispose(); #endregion ChangeState(LargeRPCState.Send_AwaitingFilesNeededList); ListenForFilesNeededListOrCompletion(); // loop start while (State != LargeRPCState.Complete) { // let it exit if it needs to, giving StopRoutine() a chance yield return(0); Debug.Log("Not done, running not-complete loop"); #region wait for needed files list while (State == LargeRPCState.Send_AwaitingFilesNeededList || State == LargeRPCState.Send_EnsuringIntegrity) { Debug.Log("waiting for list"); yield return(new WaitForSeconds(0.5f)); } Debug.Log("No longer waiting for list"); #endregion // runs ReceiveFilesNeededListFromReceiver, changes state to either Send_SendingFiles or Complete if (filesToSend.Count > 0) { Debug.Log("client still needs more files, sending"); #region send files bitStream = PooledBitStream.Get(); writer = PooledBitWriter.Get(bitStream); foreach (var header in Headers) { // let it exit if it needs to, giving StopRoutine() a chance yield return(0); Debug.Log("processing header"); if (File.Exists(header.path) && filesToSend.Contains(header.id)) { Debug.Log("file is needed"); using (FileStream fs = File.Open(header.path, FileMode.Open)) { // while loop pulled from fs.Read docs from microsoft, a little confusing to the glance but works and will be fast int numBytesToRead = (int)fs.Length; int numBytesRead = 0; while (numBytesToRead > 0) { Debug.Log("still bytes left"); int thisFileChunkSize = fileChunkSize; thisFileChunkSize = Mathf.Min(thisFileChunkSize, numBytesToRead); byte[] fileChunk = new byte[thisFileChunkSize]; // Read may return anything from 0 to numBytesToRead. int n = fs.Read(fileChunk, numBytesRead, thisFileChunkSize); foreach (byte[] netChunk in fileChunk.Slices(netChunkSize, false)) { Debug.Log("processing next chunk"); // fileID writer.WriteInt32(header.id); //writer.WriteInt32(netChunk.Length); Debug.Log("netchunk len: " + netChunk.Length); // filedata writer.WriteByteArray(netChunk); // isLastInPacket, need to add in its own size bool isLastInPacket = bitStream.Length + 1 >= netChunkSize || netChunk.Length < netChunkSize; writer.WriteBit(isLastInPacket); if (isLastInPacket) { CustomMessagingManager.SendNamedMessage(MessageName, _clientID, bitStream, "MLAPI_INTERNAL"); Debug.Log("packet sent"); yield return(new WaitForSeconds(1 / 14)); writer.Dispose(); bitStream.Dispose(); bitStream = PooledBitStream.Get(); writer = PooledBitWriter.Get(bitStream); } } // Break when the end of the file is reached. if (n == 0) { Debug.Log("end of file reached, this is a failsafe"); break; } numBytesRead += n; numBytesToRead -= n; } } } } Debug.Log("all headers processed"); filesToSend.Clear(); // just failsafing these, should be disposed of already writer.Dispose(); bitStream.Dispose(); #endregion ChangeState(LargeRPCState.Send_EnsuringIntegrity); } Debug.Log("Waiting before checking completion again"); yield return(new WaitForSeconds(1f)); } StopListening(); Debug.Log("files sent"); if (OnDownloadComplete != null) { OnDownloadComplete(SendOrReceiveFlag.Send, ReceiverID); } ChangeState(LargeRPCState.Idle); yield break; }
// Essentially, when this is passed an empty list, the okay will be sent to the server IEnumerator SendNeededFilesListToSender(List <int> _fileIDs) { bool allFilesReceived = _fileIDs.Count == 0; Debug.Log("sending needed files, files: " + _fileIDs.Count); PooledBitStream bitStream = PooledBitStream.Get(); PooledBitWriter writer = PooledBitWriter.Get(bitStream); if (allFilesReceived) { writer.WriteBit(true); writer.WriteIntArray(_fileIDs.ToArray()); writer.WriteBit(true); CustomMessagingManager.SendNamedMessage(MessageName, SenderID, bitStream, "MLAPI_INTERNAL"); bitStream.Dispose(); writer.Dispose(); _fileIDs.Clear(); StopListening(); if (OnDownloadComplete != null) { OnDownloadComplete(SendOrReceiveFlag.Receive, SenderID); } ChangeState(LargeRPCState.Idle); } else { var i = 0; List <int> ids = _fileIDs; foreach (var id in ids) { i++; bool isFinalPacket = i >= _fileIDs.Count; if (i >= _fileIDs.Count) { writer.WriteBit(isFinalPacket); writer.WriteIntArray(_fileIDs.ToArray()); writer.WriteBit(false); CustomMessagingManager.SendNamedMessage(MessageName, SenderID, bitStream, "MLAPI_INTERNAL"); bitStream.Dispose(); writer.Dispose(); bitStream = PooledBitStream.Get(); writer = PooledBitWriter.Get(bitStream); _fileIDs.Clear(); yield return(new WaitForSeconds(1 / 8)); break; } } } yield break; }
public void HandleSpawnMessage(ulong clientID, Stream stream, float receiveTime) { using (PooledBitReader reader = PooledBitReader.Get(stream)) { ulong networkID = reader.ReadUInt64Packed(); //Network ID ulong ownerID = reader.ReadUInt64Packed(); //Owner Type behaviourType = RPCTypeDefinition.GetTypeFromHash(reader.ReadUInt64Packed()); bool hasUniqueHash = reader.ReadBool(); ulong uniqueHash = 0; if (hasUniqueHash) { uniqueHash = reader.ReadUInt64Packed(); } bool ownerCanUnspawn = reader.ReadBool(); bool destroyOnUnspawn = reader.ReadBool(); //Read spawn payload PooledBitStream payloadStream = null; if (reader.ReadBool()) { payloadStream = PooledBitStream.Get(); int payloadLength = reader.ReadInt32Packed(); payloadStream.CopyUnreadFrom(stream, payloadLength); stream.Position += payloadLength; payloadStream.Position = 0; } if (networkManager.enableLogging) { string s = "Received add object event from server. Object: " + behaviourType.ToString() + " | Network ID: " + networkID + " | Owner: " + ownerID + " | Has Unique Hash: " + hasUniqueHash + " | "; if (hasUniqueHash) { s += uniqueHash + " | "; } s += "Owner Can Unspawn: " + ownerCanUnspawn + " | Destroy On Unspawn: " + destroyOnUnspawn; Debug.Log(s); } if (hasUniqueHash) { if (m_LocalPendingBehaviours.TryGetValue(uniqueHash, out PendingNetworkBehaviour pendingBehaviour)) { if (pendingBehaviour.reference.networkBehaviour.GetType() != behaviourType) { Debug.LogError("Received add object message where the remote network behaviour type(" + behaviourType.ToString() + ") does not match up with local network behaviour type (" + pendingBehaviour.reference.networkBehaviour.GetType() + ") with same unique ID(" + pendingBehaviour.reference.networkBehaviour.uniqueID + ").", pendingBehaviour.reference.networkBehaviour); return; } //Clean up pending m_LocalPendingBehaviours.Remove(uniqueHash); m_LocalPendingBehavioursList.Remove(pendingBehaviour.reference.networkBehaviour); OnObjectConnectSuccess(pendingBehaviour.reference, networkID, ownerID, ownerCanUnspawn, destroyOnUnspawn, payloadStream); if (payloadStream != null) { payloadStream.Dispose(); } } else if (m_RemotePendingBehavioursHashes.ContainsKey(uniqueHash)) { Debug.LogError("Received duplicate 'add object' message for hash '" + uniqueHash + "'."); return; } else if (m_RemotePendingBehaviours.ContainsKey(networkID)) { Debug.LogError("Recevied duplicate 'add object' message for network ID '" + networkID + "'."); return; } else { PendingNetworkBehaviour pendingBehaviourReference = new PendingNetworkBehaviour() { isRemoteBehaviour = true, uniqueHash = uniqueHash, ownerID = ownerID, networkID = networkID, ownerCanUnspawn = ownerCanUnspawn, destroyOnUnspawn = destroyOnUnspawn, spawnPayload = payloadStream }; m_RemotePendingBehavioursHashes.Add(uniqueHash, pendingBehaviourReference); m_RemotePendingBehaviours.Add(networkID, pendingBehaviourReference); } } else //No unique hash { //Build network behaviour GameObject behaviourObject = new GameObject("Server Network Object"); //All this stuff just in case the instantiate behaviour also instantiates other network behaviours in its awake function m_TrackAwakeSpawns = true; NetworkBehaviour serverBehaviour = (NetworkBehaviour)behaviourObject.AddComponent(behaviourType); m_TrackAwakeSpawns = false; if (m_BehavioursAwaitingSpawn.Count > 0) { while (m_BehavioursAwaitingSpawn.Count > 0) { NetworkBehaviourReference reference = m_BehavioursAwaitingSpawn.Dequeue(); if (reference.networkBehaviour == serverBehaviour) { OnObjectConnectSuccess(reference, networkID, ownerID, ownerCanUnspawn, destroyOnUnspawn, payloadStream); if (payloadStream != null) { payloadStream.Dispose(); } } else { SpawnOnNetworkClient(reference.networkBehaviour, reference.connectedClientCallback, reference.disconnectedDelegate, reference.localRPCDelegate, reference.ownerChangeDelegate); } } } if (!serverBehaviour.isNetworkSpawned) { serverBehaviour.uniqueID = "NETWORK_SERVER_BEHAVIOUR_" + networkID.ToString(); serverBehaviour.SpawnOnNetwork(); ulong hash = serverBehaviour.uniqueID.GetStableHash(networkManager.config.rpcHashSize); PendingNetworkBehaviour pendingBehaviour = m_LocalPendingBehaviours[hash]; //Clean up pending m_LocalPendingBehaviours.Remove(uniqueHash); m_LocalPendingBehavioursList.Remove(serverBehaviour); OnObjectConnectSuccess(pendingBehaviour.reference, networkID, ownerID, ownerCanUnspawn, destroyOnUnspawn, payloadStream); if (payloadStream != null) { payloadStream.Dispose(); } } } } }