/// <summary> /// Builds a LockStepMessage from the given List of NetworkData. /// This method guarantees that the constructed message has the correct sequence ID. /// </summary> /// <param name="dataList">The NetworkData contained in the message</param> /// <returns>A new LockStepMessage</returns> private LockStepMessage BuildLockStepMessage(List <NetworkData> dataList) { LockStepMessage msg = new LockStepMessage(); // When the dataList is empty we need to send a No-Op message (to save bandwidth) if (dataList.Count == 0) { msg.seqID = NO_OP_MSG_ID; // We do not need to clone the dataList in this case since it is empty anyways // Furthermore, the special NO_OP_MSG_ID makes sure no NetworkData will be read msg.dataList = dataList; PrintDebug(DebugLevel.DEBUG, "BuildLockStepMessage NO_OP_MSG_ID"); return(msg); } if (IsHost()) { msg.seqID = ServerSideMsgSeqID++; } else { // When we are the client we dont need to count MsgSeqID's since the server // does no verification of order msg.seqID = NO_OP_MSG_ID; } // We clone the dataList because it might get cleared by the LockStepManager msg.dataList = new List <NetworkData>(dataList); PrintDebug(DebugLevel.DEBUG, "BuildLockStepMessage seqID={0}, count={1}", msg.seqID, msg.dataList.Count); return(msg); }
/// <summary> /// Calls the OnReceive method of all NetworkData within the given LockStepMessage. /// </summary> /// <param name="msg">The LockStepMessage to execute</param> private void ExecuteLockStepMessage(LockStepMessage msg) { PrintDebug(DebugLevel.DEBUG, "ExecuteLockStepMessage SeqID={0}", msg.seqID); foreach (var data in msg.dataList) { data.OnReceive(this); } }
/// <summary> /// Called by the H2LogPlayer when a Client has sent a LockStepMessage over the network. /// This method is supposed to buffer the data from the client for the next LockStep. /// </summary> /// <param name="byteArr">bytes from the network</param> public void ServerReceiveMessage(byte[] byteArr) { PrintDebug(DebugLevel.DEBUG, "ServerReceiveMessage (byte[])"); List <object> list = SerializationCtrl.Deserialize(byteArr); ReactToSerializationError(); LockStepMessage msg = (LockStepMessage)list[0]; ServerReceiveMessage(msg); }
/// <summary> /// Called by the H2LogPlayer after byte data was received from the server. /// This method is called on all devices, even the server. /// </summary> /// <param name="byteArr">bytes from the network</param> public void ClientProcessLockStep(byte[] byteArr) { PrintDebug(DebugLevel.DEBUG, "ClientProcessLockStep (byte[])"); List <object> list = SerializationCtrl.Deserialize(byteArr); ReactToSerializationError(); LockStepMessage msg = (LockStepMessage)list[0]; ClientProcessLockStep(msg); }
/// <summary> /// Called by the ClientProcessLockStep method if we are playing over the network /// and we just received data from the server or by the SendLockStep method if we /// are playing locally. /// This method should buffer a message received out of order or execute the network /// data from a message received in order. /// </summary> /// <param name="msg">The LockStepMessage to process</param> private void ClientProcessLockStep(LockStepMessage msg) { if (msg.seqID == NO_OP_MSG_ID) { return; } PrintDebug(DebugLevel.VERBOSE, "ClientProcessLockStep seqID={0}", msg.seqID); if (msg.seqID == ClientSideExpectedMsgSeqID) { ClientSideExpectedMsgSeqID++; ExecuteLockStepMessage(msg); if (ClientSideMsgBuffer.Count > 0) { ClientSideMsgBuffer.Sort(delegate(LockStepMessage m1, LockStepMessage m2) { return(m1.seqID.CompareTo(m2.seqID)); }); while (ClientSideMsgBuffer.Count > 0) { LockStepMessage bufMsg = ClientSideMsgBuffer[0]; if (bufMsg.seqID < ClientSideExpectedMsgSeqID) { ClientSideMsgBuffer.RemoveAt(0); ReactToError("Executed buffered LockStepMessage with sequence ID from the past. SeqID={0}", bufMsg.seqID); } else if (bufMsg.seqID == ClientSideExpectedMsgSeqID) { ClientSideMsgBuffer.RemoveAt(0); ExecuteLockStepMessage(bufMsg); ClientSideExpectedMsgSeqID++; } else { break; } } } } else if (msg.seqID > ClientSideExpectedMsgSeqID) { ClientSideMsgBuffer.Add(msg); PrintDebug(DebugLevel.VERBOSE, "BufferLockStepMessage seqID={0}, currentBufferSize={1}", msg.seqID, ClientSideMsgBuffer.Count); } else { ReactToError("Received LockStepMessage with sequence ID from the past. SeqID={0}", msg.seqID); } }
/// <summary> /// If this device is the host then all currently buffered messages within the /// ServerSideBuffer will be sent out to all devices (including this one!). /// /// If this device is a client then all currently buffered messages within the /// ClientSideBuffer will be sent out to the host device (server). /// /// If networking is disabled this device will simply execute all local messages /// buffered in the ClientSideBuffer. /// </summary> private void SendLockStep() { PrintDebug(DebugLevel.DEBUG, "SendLockStep.IsHost={0}", IsHost()); if (IsHost()) { ServerSideBuffer.AddRange(ClientSideBuffer); LockStepMessage msg = BuildLockStepMessage(ServerSideBuffer); if (IsNetworkEnabled()) { List <byte> byteList = SerializationCtrl.Serialize(msg); ReactToSerializationError(); byte[] byteArr = byteList.ToArray(); foreach (var player in H2LogPlayer.instances) { player.RpcClientReceiveLockStep(byteArr); } } else { ClientProcessLockStep(msg); } ServerSideBuffer.Clear(); } else { LockStepMessage msg = BuildLockStepMessage(ClientSideBuffer); if (IsNetworkEnabled()) { List <byte> byteList = SerializationCtrl.Serialize(msg); ReactToSerializationError(); byte[] byteArr = byteList.ToArray(); H2LogPlayer.localPlayer.CmdServerReceiveLockStep(byteArr); } else { ServerReceiveMessage(msg); } } ClientSideBuffer.Clear(); }
/// <summary> /// Called by the ServerReceiveMessage method when we are playing over the network or /// the SendLockStep method if we are playing without networking. /// </summary> /// <param name="msg">A LockStepMessage sent by a client to the server</param> private void ServerReceiveMessage(LockStepMessage msg) { ServerSideBuffer.AddRange(msg.dataList); }