Пример #1
0
 /// <summary>
 /// We received some data from another client in the session. This method
 /// assumes all messages in the session are sent via our <c>Send()</c>
 /// methods, i.e. that only commands are sent. We try to parse these here,
 /// then forward them to the <c>HandleCommand()</c> method.
 ///
 /// <para>
 /// To take influence on how messages are sent and received (add another
 /// layer to the protocol), use the <c>WrapDataForSend()</c> and
 /// <c>UnwrapDataForReceive()</c> methods.
 /// </para>
 /// </summary>
 private void HandlePlayerData(object sender, SessionDataEventArgs e)
 {
     try
     {
         // Delegate unwrapping of the message, and if this yields a command object
         // try to handle it.
         var command = UnwrapDataForReceive(e);
         if (command != null)
         {
             HandleRemoteCommand(command);
         }
     }
     catch (PacketException ex)
     {
         Logger.WarnException("Failed parsing received packet.", ex);
     }
     catch (Exception ex)
     {
         Logger.WarnException("Failed deserializing data.", ex);
     }
 }
Пример #2
0
        /// <summary>Takes care of server side TSS synchronization logic.</summary>
        protected override FrameCommand UnwrapDataForReceive(SessionDataEventArgs e)
        {
            var args = (ServerDataEventArgs)e;
            var type = (TssControllerMessage)args.Data.ReadByte();

            switch (type)
            {
            case TssControllerMessage.Command:
            {
                // Normal command, forward it if it's valid.
                var command = base.UnwrapDataForReceive(e);

                // Validate player number (avoid command injection for
                // other players).
                if (command.PlayerNumber == args.Player.Number)
                {
                    // All green.
                    return(command);
                }

                Logger.Warn("Received invalid packet (player number mismatch).");

                // Disconnect the player, as this might have been a
                // hacking attempt.
                Session.Disconnect(Session.GetPlayer(args.Player.Number));

                // Ignore the command.
                return(null);
            }

            case TssControllerMessage.Synchronize:
            {
                // Client re-synchronizing.

                // Get the frame the client is at.
                var clientFrame = args.Data.ReadInt64();

                // Get performance information of the client.
                _clientLoads[args.Player.Number] = args.Data.ReadSingle();

                // Re-evaluate at what speed we want to run.
                AdjustSpeed();

                // Send our reply.
                using (var packet = new Packet())
                {
                    packet
                    // Message type.
                    .Write((byte)TssControllerMessage.Synchronize)
                    // For reference, the frame the client sent this message.
                    .Write(clientFrame)
                    // The current server frame, to allow the client to compute
                    // the round trip time.
                    .Write(Tss.CurrentFrame)
                    // The current speed the game should run at.
                    .Write(AdjustedSpeed);
                    Session.SendTo(args.Player, packet);
                }
                break;
            }

            case TssControllerMessage.GameState:
            {
                // Client needs game state.
                var hasher = new Hasher();
                hasher.Write(Tss.TrailingSimulation);
                using (var packet = new Packet())
                {
                    packet
                    // Message type.
                    .Write((byte)TssControllerMessage.GameState)
                    // Hash value for validation.
                    .Write(hasher.Value)
                    // Actual game state, including the TSS wrapper.
                    .Write(Tss);
                    Session.SendTo(args.Player, packet);
                }
                break;
            }

            case TssControllerMessage.GameStateDump:
            {
                // Got a game state dump from a client due to hash check failure.
                var frame = args.Data.ReadInt64();
                if (!_gameStates.ContainsKey(frame))
                {
                    Logger.Warn("Got a game state dump for a frame we don't have the local dump for anymore.");
                    return(null);
                }

                // Get the id for the dump for easier file matching.
                var dumpId = args.Data.ReadString();

                try
                {
                    // Create actual game dump and write it to file.
                    WriteGameState(frame, _gameStates[frame], dumpId + "_server.txt");
                }
                catch (Exception ex)
                {
                    Logger.ErrorException("Failed writing server desynchronization dump.", ex);
                }

                break;
            }
            }
            return(null);
        }
Пример #3
0
 /// <summary>
 /// Takes care of client side TSS synchronization logic.
 /// </summary>
 protected override FrameCommand UnwrapDataForReceive(SessionDataEventArgs e)
 {
     // Ignore, we use the server's simulation for rendering.
     return(null);
 }
Пример #4
0
 /// <summary>
 /// May be overridden to implement the other end of a protocol layer as
 /// added via <c>WrapDataForSend()</c>. You should follow the same pattern
 /// as there.
 /// </summary>
 /// <param name="args">the originally received network data.</param>
 /// <returns>the parsed command, or null, if the message
 /// was not a command (i.e. some other message type).</returns>
 protected virtual TCommand UnwrapDataForReceive(SessionDataEventArgs args)
 {
     // Parse the actual command.
     return(args.Data.ReadPacketizableWithTypeInfo <TCommand>());
 }
Пример #5
0
        /// <summary>
        /// Takes care of client side TSS synchronization logic.
        /// </summary>
        protected override FrameCommand UnwrapDataForReceive(SessionDataEventArgs e)
        {
            var args = (ClientDataEventArgs)e;
            var type = (TssControllerMessage)args.Data.ReadByte();

            switch (type)
            {
            case TssControllerMessage.Command:
            {
                // Normal command, forward it.
                var command = base.UnwrapDataForReceive(e);

                // Test if we got the message from the server, to mark the command accordingly.
                command.IsAuthoritative = args.IsAuthoritative;

                // Return the deserialized command.
                return(command);
            }

            case TssControllerMessage.Synchronize:
            {
                // Answer to a synchronization request.
                // Only accept these when they come from the server, and disregard if
                // we're waiting for a snapshot of the simulation.
                if (args.IsAuthoritative && !Tss.WaitingForSynchronization)
                {
                    // This calculation follows algorithm described here:
                    // http://www.mine-control.com/zack/timesync/timesync.html
                    var sentFrame   = args.Data.ReadInt64();
                    var serverFrame = args.Data.ReadInt64();

                    // We also adjust the game speed to accommodate slow
                    // machines. That's the speed we get in this step.
                    AdjustedSpeed = args.Data.ReadSingle();

                    var latency           = (Tss.CurrentFrame - sentFrame) / 2;
                    var clientServerDelta = (serverFrame - Tss.CurrentFrame);
                    var frameDelta        = clientServerDelta + latency / 2;

                    _frameDiff.Put((int)frameDelta);
                    var median = _frameDiff.Median();
                    var stdDev = _frameDiff.StandardDeviation();

                    if (System.Math.Abs(frameDelta) > 1 && frameDelta < (int)(median + stdDev))
                    {
                        Logger.Trace("Correcting for {0} frames.", frameDelta);

                        // Adjust the current frame of the simulation.
                        ScheduleFrameskip(frameDelta);
                    }
                }
                break;
            }

            case TssControllerMessage.HashCheck:
            {
                // Only accept these when they come from the server.
                if (args.IsAuthoritative)
                {
                    // Get the frame this hash data is for.
                    var hashFrame = args.Data.ReadInt64();
                    Debug.Assert(hashFrame > _lastServerHashedFrame);
                    _lastServerHashedFrame = hashFrame;

                    // Read hash values.
                    var hashValue = args.Data.ReadUInt32();

                    // And perform hash check.
                    PerformHashCheck(hashValue, hashFrame, Tss.TrailingFrame < _lastServerHashedFrame);
                }
                break;
            }

            case TssControllerMessage.GameState:
            {
                // Got a simulation snap shot (normally after requesting it due to
                // our simulation going out of scope for an older event).
                // Only accept these when they come from the server.
                if (args.IsAuthoritative)
                {
                    // Read data.
                    var serverHash = args.Data.ReadUInt32();
                    args.Data.ReadPacketizableInto(Tss);

                    // Validate the data we got.
                    var hasher = new Hasher();
                    hasher.Write(Tss.TrailingSimulation);
                    if (hasher.Value != serverHash)
                    {
                        Logger.Error("Hash mismatch after deserialization.");
                        Session.Leave();
                    }

                    // Run to current frame to avoid slow interpolation to current frame.
                    // Take into account the time we need to get there.
                    var delta = 0L;
                    do
                    {
                        // Remember when we started, see below.
                        var started = DateTime.UtcNow;

                        // Do the actual update, run to where we want to be.
                        Tss.RunToFrame(Tss.CurrentFrame + delta);

                        // See how long we took for this update, and compute the number
                        // of updates we'd otherwise have made in that time.
                        delta = (long)((DateTime.UtcNow - started).TotalMilliseconds / TargetElapsedMilliseconds);

                        // Continue until we're close enough with the *actual* current
                        // frame to what we want to be at.
                    } while (delta > 10);
                }
                break;
            }

            case TssControllerMessage.RemoveGameObject:
            {
                // Only accept these when they come from the server.
                if (args.IsAuthoritative)
                {
                    var removeFrame = args.Data.ReadInt64();
                    var entityUid   = args.Data.ReadInt32();
                    Tss.RemoveEntity(entityUid, removeFrame);
                }
                break;
            }
            }
            return(null);
        }