/* * Copyright (c) 2007-2008, openmetaverse.org * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the openmetaverse.org nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Threading; using System.Net; using System.Net.Sockets; using OpenMetaverse.Packets; namespace OpenMetaverse { #region Enums /// /// Simulator (region) properties /// [Flags] public enum RegionFlags { /// No flags set None = 0, /// Agents can take damage and be killed AllowDamage = 1 << 0, /// Landmarks can be created here AllowLandmark = 1 << 1, /// Home position can be set in this sim AllowSetHome = 1 << 2, /// Home position is reset when an agent teleports away ResetHomeOnTeleport = 1 << 3, /// Sun does not move SunFixed = 1 << 4, /// No object, land, etc. taxes TaxFree = 1 << 5, /// Disable heightmap alterations (agents can still plant /// foliage) BlockTerraform = 1 << 6, /// Land cannot be released, sold, or purchased BlockLandResell = 1 << 7, /// All content is wiped nightly Sandbox = 1 << 8, /// NullLayer = 1 << 9, /// SkipAgentAction = 1 << 10, /// SkipUpdateInterestList = 1 << 11, /// No collision detection for non-agent objects SkipCollisions = 1 << 12, /// No scripts are ran SkipScripts = 1 << 13, /// All physics processing is turned off SkipPhysics = 1 << 14, /// ExternallyVisible = 1 << 15, /// MainlandVisible = 1 << 16, /// PublicAllowed = 1 << 17, /// BlockDwell = 1 << 18, /// Flight is disabled (not currently enforced by the sim) NoFly = 1 << 19, /// Allow direct (p2p) teleporting AllowDirectTeleport = 1 << 20, /// Estate owner has temporarily disabled scripting EstateSkipScripts = 1 << 21, /// RestrictPushObject = 1 << 22, /// Deny agents with no payment info on file DenyAnonymous = 1 << 23, /// Deny agents with payment info on file DenyIdentified = 1 << 24, /// Deny agents who have made a monetary transaction DenyTransacted = 1 << 25, /// AllowParcelChanges = 1 << 26, /// AbuseEmailToEstateOwner = 1 << 27, /// Region is Voice Enabled AllowVoice = 1 << 28 } /// /// Access level for a simulator /// public enum SimAccess : byte { /// Minimum access level, no additional checks Min = 0, /// Trial accounts allowed Trial = 7, /// PG rating PG = 13, /// Mature rating Mature = 21, /// Simulator is offline Down = 254, /// Simulator does not exist NonExistent = 255 } #endregion Enums /// /// /// public class Simulator : UDPBase, IDisposable { #region Structs /// /// Simulator Statistics /// public struct SimStats { /// Total number of packets sent by this simulator to this agent public ulong SentPackets; /// Total number of packets received by this simulator to this agent public ulong RecvPackets; /// Total number of bytes sent by this simulator to this agent public ulong SentBytes; /// Total number of bytes received by this simulator to this agent public ulong RecvBytes; /// Time in seconds agent has been connected to simulator public int ConnectTime; /// Total number of packets that have been resent public int ResentPackets; /// Total number of resent packets recieved public int ReceivedResends; /// Total number of pings sent to this simulator by this agent public int SentPings; /// Total number of ping replies sent to this agent by this simulator public int ReceivedPongs; /// /// Incoming bytes per second /// /// It would be nice to have this claculated on the fly, but /// this is far, far easier public int IncomingBPS; /// /// Outgoing bytes per second /// /// It would be nice to have this claculated on the fly, but /// this is far, far easier public int OutgoingBPS; /// Time last ping was sent public int LastPingSent; /// ID of last Ping sent public byte LastPingID; /// public int LastLag; /// public int MissedPings; /// Current time dilation of this simulator public float Dilation; /// Current Frames per second of simulator public int FPS; /// Current Physics frames per second of simulator public float PhysicsFPS; /// public float AgentUpdates; /// public float FrameTime; /// public float NetTime; /// public float PhysicsTime; /// public float ImageTime; /// public float ScriptTime; /// public float AgentTime; /// public float OtherTime; /// Total number of objects Simulator is simulating public int Objects; /// Total number of Active (Scripted) objects running public int ScriptedObjects; /// Number of agents currently in this simulator public int Agents; /// Number of agents in neighbor simulators public int ChildAgents; /// Number of Active scripts running in this simulator public int ActiveScripts; /// public int LSLIPS; /// public int INPPS; /// public int OUTPPS; /// Number of downloads pending public int PendingDownloads; /// Number of uploads pending public int PendingUploads; /// public int VirtualSize; /// public int ResidentSize; /// Number of local uploads pending public int PendingLocalUploads; /// Unacknowledged bytes in queue public int UnackedBytes; } #endregion Structs #region Public Members /// A public reference to the client that this Simulator object /// is attached to public GridClient Client; /// public UUID ID = UUID.Zero; /// The capabilities for this simulator public Caps Caps = null; /// public ulong Handle; /// The current version of software this simulator is running public string SimVersion = String.Empty; /// public string Name = String.Empty; /// A 64x64 grid of parcel coloring values. The values stored /// in this array are of the type public byte[] ParcelOverlay = new byte[4096]; /// public int ParcelOverlaysReceived; /// public float TerrainHeightRange00; /// public float TerrainHeightRange01; /// public float TerrainHeightRange10; /// public float TerrainHeightRange11; /// public float TerrainStartHeight00; /// public float TerrainStartHeight01; /// public float TerrainStartHeight10; /// public float TerrainStartHeight11; /// public float WaterHeight; /// public UUID SimOwner = UUID.Zero; /// public UUID TerrainBase0 = UUID.Zero; /// public UUID TerrainBase1 = UUID.Zero; /// public UUID TerrainBase2 = UUID.Zero; /// public UUID TerrainBase3 = UUID.Zero; /// public UUID TerrainDetail0 = UUID.Zero; /// public UUID TerrainDetail1 = UUID.Zero; /// public UUID TerrainDetail2 = UUID.Zero; /// public UUID TerrainDetail3 = UUID.Zero; /// public bool IsEstateManager; /// public EstateTools Estate; /// public RegionFlags Flags; /// public SimAccess Access; /// public float BillableFactor; /// Statistics information for this simulator and the /// connection to the simulator, calculated by the simulator itself /// and the library public SimStats Stats; /// Provides access to two thread-safe dictionaries containing /// avatars and primitives found in this simulator //public ObjectTracker Objects = new ObjectTracker(); public InternalDictionary ObjectsAvatars = new InternalDictionary(); public InternalDictionary ObjectsPrimitives = new InternalDictionary(); /// The current sequence number for packets sent to this /// simulator. Must be Interlocked before modifying. Only /// useful for applications manipulating sequence numbers public int Sequence; /// /// Provides access to an internal thread-safe dictionary containing parcel /// information found in this simulator /// public InternalDictionary Parcels = new InternalDictionary(); /// /// Provides access to an internal thread-safe multidimensional array containing a x,y grid mapped /// each 64x64 parcel's LocalID. /// public int[,] ParcelMap { get { lock (this) return _ParcelMap; } set { lock (this) _ParcelMap = value; } } /// /// Checks simulator parcel map to make sure it has downloaded all data successfully /// /// true if map is full (contains no 0's) public bool IsParcelMapFull() { for (int y = 0; y < 64; y++) { for (int x = 0; x < 64; x++) { if (this.ParcelMap[y, x] == 0) return false; } } return true; } #endregion Public Members #region Properties /// The IP address and port of the server public IPEndPoint IPEndPoint { get { return remoteEndPoint; } } /// Whether there is a working connection to the simulator or /// not public bool Connected { get { return connected; } } /// Coarse locations of avatars in this simulator public InternalDictionary AvatarPositions { get { return avatarPositions; } } /// AvatarPositions key representing TrackAgent target public UUID PreyID { get { return preyID; } } #endregion Properties #region Internal/Private Members /// Used internally to track sim disconnections internal bool DisconnectCandidate = false; /// Event that is triggered when the simulator successfully /// establishes a connection internal AutoResetEvent ConnectedEvent = new AutoResetEvent(false); /// Whether this sim is currently connected or not. Hooked up /// to the property Connected internal bool connected; /// Coarse locations of avatars in this simulator internal InternalDictionary avatarPositions = new InternalDictionary(); /// AvatarPositions key representing TrackAgent target internal UUID preyID = UUID.Zero; /// Sequence numbers of packets we've received /// (for duplicate checking) internal Queue PacketArchive; /// Packets we sent out that need ACKs from the simulator internal SortedDictionary NeedAck = new SortedDictionary(); /// Sequence number for pause/resume internal int pauseSerial; private NetworkManager Network; private Queue InBytes, OutBytes; // ACKs that are queued up to be sent to the simulator private SortedList PendingAcks = new SortedList(); private Timer AckTimer; private Timer PingTimer; private Timer StatsTimer; // simulator <> parcel LocalID Map private int[,] _ParcelMap = new int[64, 64]; internal bool DownloadingParcelMap = false; #endregion Internal/Private Members /// /// /// /// Reference to the GridClient object /// IPEndPoint of the simulator /// handle of the simulator public Simulator(GridClient client, IPEndPoint address, ulong handle) : base(address) { Client = client; Handle = handle; Estate = new EstateTools(Client); Network = Client.Network; PacketArchive = new Queue(Settings.PACKET_ARCHIVE_SIZE); InBytes = new Queue(Client.Settings.STATS_QUEUE_SIZE); OutBytes = new Queue(Client.Settings.STATS_QUEUE_SIZE); } /// /// Called when this Simulator object is being destroyed /// public void Dispose() { // Force all the CAPS connections closed for this simulator if (Caps != null) { Caps.Disconnect(true); } } /// /// Attempt to connect to this simulator /// /// Whether to move our agent in to this sim or not /// True if the connection succeeded or connection status is /// unknown, false if there was a failure public bool Connect(bool moveToSim) { if (connected) { Client.Self.CompleteAgentMovement(this); return true; } #region Start Timers // Destroy the timers if (AckTimer != null) AckTimer.Dispose(); if (StatsTimer != null) StatsTimer.Dispose(); if (PingTimer != null) PingTimer.Dispose(); // Timer for sending out queued packet acknowledgements AckTimer = new Timer(AckTimer_Elapsed, null, Settings.NETWORK_TICK_INTERVAL, Settings.NETWORK_TICK_INTERVAL); // Timer for recording simulator connection statistics StatsTimer = new Timer(StatsTimer_Elapsed, null, 1000, 1000); // Timer for periodically pinging the simulator if (Client.Settings.SEND_PINGS) PingTimer = new Timer(PingTimer_Elapsed, null, Settings.PING_INTERVAL, Settings.PING_INTERVAL); #endregion Start Timers Logger.Log("Connecting to " + this.ToString(), Helpers.LogLevel.Info, Client); try { // Create the UDP connection Start(); // Mark ourselves as connected before firing everything else up connected = true; // Send the UseCircuitCode packet to initiate the connection UseCircuitCodePacket use = new UseCircuitCodePacket(); use.CircuitCode.Code = Network.CircuitCode; use.CircuitCode.ID = Client.Self.AgentID; use.CircuitCode.SessionID = Client.Self.SessionID; // Send the initial packet out SendPacket(use, true); Stats.ConnectTime = Environment.TickCount; // Move our agent in to the sim to complete the connection if (moveToSim) Client.Self.CompleteAgentMovement(this); if (Client.Settings.SEND_AGENT_UPDATES) Client.Self.Movement.SendUpdate(true, this); if (!ConnectedEvent.WaitOne(Client.Settings.SIMULATOR_TIMEOUT, false)) { Logger.Log("Giving up on waiting for RegionHandshake for " + this.ToString(), Helpers.LogLevel.Warning, Client); } return true; } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } return false; } public void SetSeedCaps(string seedcaps) { if (Caps != null) { if (Caps._SeedCapsURI == seedcaps) return; Logger.Log("Unexpected change of seed capability", Helpers.LogLevel.Warning, Client); Caps.Disconnect(true); Caps = null; } if (Client.Settings.ENABLE_CAPS) { // Connect to the new CAPS system if (!String.IsNullOrEmpty(seedcaps)) Caps = new Caps(this, seedcaps); else Logger.Log("Setting up a sim without a valid capabilities server!", Helpers.LogLevel.Error, Client); } } /// /// Disconnect from this simulator /// public void Disconnect(bool sendCloseCircuit) { if (connected) { connected = false; // Destroy the timers if (AckTimer != null) AckTimer.Dispose(); if (StatsTimer != null) StatsTimer.Dispose(); if (PingTimer != null) PingTimer.Dispose(); // Kill the current CAPS system if (Caps != null) { Caps.Disconnect(true); Caps = null; } if (sendCloseCircuit) { // Try to send the CloseCircuit notice CloseCircuitPacket close = new CloseCircuitPacket(); UDPPacketBuffer buf = new UDPPacketBuffer(remoteEndPoint); byte[] data = close.ToBytes(); Buffer.BlockCopy(data, 0, buf.Data, 0, data.Length); buf.DataLength = data.Length; AsyncBeginSend(buf); } // Shut the socket communication down Stop(); } } /// /// Instructs the simulator to stop sending update (and possibly other) packets /// public void Pause() { AgentPausePacket pause = new AgentPausePacket(); pause.AgentData.AgentID = Client.Self.AgentID; pause.AgentData.SessionID = Client.Self.SessionID; pause.AgentData.SerialNum = (uint)Interlocked.Exchange(ref pauseSerial, pauseSerial + 1); Client.Network.SendPacket(pause, this); } /// /// Instructs the simulator to resume sending update packets (unpause) /// public void Resume() { AgentResumePacket resume = new AgentResumePacket(); resume.AgentData.AgentID = Client.Self.AgentID; resume.AgentData.SessionID = Client.Self.SessionID; resume.AgentData.SerialNum = (uint)Interlocked.Exchange(ref pauseSerial, pauseSerial + 1); Client.Network.SendPacket(resume, this); } /// /// Sends a packet /// /// Packet to be sent /// True to set the sequence number, false to /// leave it as is public void SendPacket(Packet packet, bool setSequence) { SendPacket(new NetworkManager.OutgoingPacket(this, packet, setSequence)); } /// /// Sends a packet /// /// Packet to be sent public void SendPacket(NetworkManager.OutgoingPacket outgoingPacket) { // Send ACK and logout packets directly, everything else goes through the queue if (outgoingPacket.Packet.Type == PacketType.PacketAck || outgoingPacket.Packet.Header.AppendedAcks || Client.Settings.THROTTLE_OUTGOING_PACKETS == false || outgoingPacket.Packet.Type == PacketType.LogoutRequest) { SendPacketUnqueued(outgoingPacket); } else { Network.PacketOutbox.Enqueue(outgoingPacket); } } /// /// Sends a packet directly to the simulator without queuing /// /// Packet to be sent public void SendPacketUnqueued(NetworkManager.OutgoingPacket outgoingPacket) { Packet packet = outgoingPacket.Packet; byte[] buffer; int bytes; // Set sequence implies that this is not a resent packet if (outgoingPacket.SetSequence) { // Reset to zero if we've hit the upper sequence number limit Interlocked.CompareExchange(ref Sequence, 0, Settings.MAX_SEQUENCE); // Increment and fetch the current sequence number packet.Header.Sequence = (uint)Interlocked.Increment(ref Sequence); if (packet.Header.Reliable) { // Keep track of when this packet was sent out (right now) outgoingPacket.TickCount = Environment.TickCount; // Add this packet to the list of ACK responses we are waiting on from the server lock (NeedAck) NeedAck[packet.Header.Sequence] = outgoingPacket; if (!packet.Header.Resent) { // This packet is not a resend, check if the conditions are favorable // to ACK appending if (packet.Type != PacketType.PacketAck && packet.Type != PacketType.LogoutRequest) { lock (PendingAcks) { if (PendingAcks.Count > 0 && PendingAcks.Count < Client.Settings.MAX_APPENDED_ACKS) { // Append all of the queued up outgoing ACKs to this packet packet.Header.AckList = new uint[PendingAcks.Count]; for (int i = 0; i < PendingAcks.Count; i++) packet.Header.AckList[i] = PendingAcks.Values[i]; PendingAcks.Clear(); packet.Header.AppendedAcks = true; } } } } } else if (packet.Header.AckList.Length > 0) { // Sanity check for ACKS appended on an unreliable packet, this is bad form Logger.Log("Sending appended ACKs on an unreliable packet", Helpers.LogLevel.Warning); } } if (packet.Header.Resent) { // This packet has already been sent out once, strip any appended ACKs // off it and reinsert them into the outgoing ACK queue under the // assumption that this packet will continually be rejected from the // server or that the appended ACKs are possibly making the delivery fail if (packet.Header.AckList.Length > 0) { Logger.DebugLog(String.Format("Purging ACKs from packet #{0} ({1}) which will be resent.", packet.Header.Sequence, packet.GetType())); lock (PendingAcks) { foreach (uint sequence in packet.Header.AckList) { if (!PendingAcks.ContainsKey(sequence)) PendingAcks[sequence] = sequence; } } packet.Header.AppendedAcks = false; packet.Header.AckList = new uint[0]; } } // Serialize the packet buffer = packet.ToBytes(); bytes = buffer.Length; Stats.SentBytes += (ulong)bytes; ++Stats.SentPackets; UDPPacketBuffer buf = new UDPPacketBuffer(remoteEndPoint); // Zerocode if needed if (packet.Header.Zerocoded) bytes = Helpers.ZeroEncode(buffer, bytes, buf.Data); else Buffer.BlockCopy(buffer, 0, buf.Data, 0, bytes); buf.DataLength = bytes; AsyncBeginSend(buf); } /// /// Send a raw byte array payload as a packet /// /// The packet payload /// Whether the second, third, and fourth bytes /// should be modified to the current stream sequence number public void SendPacketUnqueued(byte[] payload, bool setSequence) { try { if (setSequence && payload.Length > 3) { uint sequence = (uint)Interlocked.Increment(ref Sequence); payload[1] = (byte)(sequence >> 16); payload[2] = (byte)(sequence >> 8); payload[3] = (byte)(sequence % 256); } Stats.SentBytes += (ulong)payload.Length; ++Stats.SentPackets; UDPPacketBuffer buf = new UDPPacketBuffer(remoteEndPoint); Buffer.BlockCopy(payload, 0, buf.Data, 0, payload.Length); buf.DataLength = payload.Length; AsyncBeginSend(buf); } catch (SocketException) { Logger.Log("Tried to send a " + payload.Length + " byte payload on a closed socket, shutting down " + this.ToString(), Helpers.LogLevel.Info, Client); Network.DisconnectSim(this, false); return; } catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } } /// /// /// public void SendPing() { uint oldestUnacked = 0; // Get the oldest NeedAck value, the first entry in the sorted dictionary lock (NeedAck) { if (NeedAck.Count > 0) { SortedDictionary.KeyCollection.Enumerator en = NeedAck.Keys.GetEnumerator(); en.MoveNext(); oldestUnacked = en.Current; } } //if (oldestUnacked != 0) // Logger.DebugLog("Sending ping with oldestUnacked=" + oldestUnacked); StartPingCheckPacket ping = new StartPingCheckPacket(); ping.PingID.PingID = Stats.LastPingID++; ping.PingID.OldestUnacked = oldestUnacked; ping.Header.Reliable = false; SendPacket(ping, true); Stats.LastPingSent = Environment.TickCount; } /// /// Returns Simulator Name as a String /// /// public override string ToString() { if (!String.IsNullOrEmpty(Name)) return String.Format("{0} ({1})", Name, remoteEndPoint); else return String.Format("({0})", remoteEndPoint); } /// /// /// /// public override int GetHashCode() { return Handle.GetHashCode(); } /// /// /// /// /// public override bool Equals(object obj) { Simulator sim = obj as Simulator; if (sim == null) return false; return (remoteEndPoint.Equals(sim.remoteEndPoint)); } public static bool operator ==(Simulator lhs, Simulator rhs) { // If both are null, or both are same instance, return true if (System.Object.ReferenceEquals(lhs, rhs)) { return true; } // If one is null, but not both, return false. if (((object)lhs == null) || ((object)rhs == null)) { return false; } return lhs.remoteEndPoint.Equals(rhs.remoteEndPoint); } public static bool operator !=(Simulator lhs, Simulator rhs) { return !(lhs == rhs); } protected override void PacketReceived(UDPPacketBuffer buffer) { Packet packet = null; // Check if this packet came from the server we expected it to come from if (!remoteEndPoint.Address.Equals(((IPEndPoint)buffer.RemoteEndPoint).Address)) { Logger.Log("Received " + buffer.DataLength + " bytes of data from unrecognized source " + ((IPEndPoint)buffer.RemoteEndPoint).ToString(), Helpers.LogLevel.Warning, Client); return; } // Update the disconnect flag so this sim doesn't time out DisconnectCandidate = false; #region Packet Decoding int packetEnd = buffer.DataLength - 1; try { packet = Packet.BuildPacket(buffer.Data, ref packetEnd, buffer.ZeroData); } catch (MalformedDataException) { Logger.Log(String.Format("Malformed data, cannot parse packet:\n{0}", Utils.BytesToHexString(buffer.Data, buffer.DataLength, null)), Helpers.LogLevel.Error); } // Fail-safe check if (packet == null) { Logger.Log("Couldn't build a message from the incoming data", Helpers.LogLevel.Warning, Client); return; } Stats.RecvBytes += (ulong)buffer.DataLength; ++Stats.RecvPackets; #endregion Packet Decoding #region Reliable Handling if (packet.Header.Reliable) { // Add this packet to the list of ACKs that need to be sent out lock (PendingAcks) { uint sequence = (uint)packet.Header.Sequence; if (!PendingAcks.ContainsKey(sequence)) PendingAcks[sequence] = sequence; } // Send out ACKs if we have a lot of them if (PendingAcks.Count >= Client.Settings.MAX_PENDING_ACKS) SendAcks(); if (packet.Header.Resent) ++Stats.ReceivedResends; } #endregion Reliable Handling #region Inbox Insertion NetworkManager.IncomingPacket incomingPacket; incomingPacket.Simulator = this; incomingPacket.Packet = packet; // TODO: Prioritize the queue Network.PacketInbox.Enqueue(incomingPacket); #endregion Inbox Insertion } protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent) { Client.Network.PacketSent(buffer.Data, bytesSent, this); } /// /// Sends out pending acknowledgements /// private void SendAcks() { lock (PendingAcks) { if (PendingAcks.Count > 0) { if (PendingAcks.Count > 250) { Logger.Log("Too many ACKs queued up!", Helpers.LogLevel.Error, Client); return; } PacketAckPacket acks = new PacketAckPacket(); acks.Header.Reliable = false; acks.Packets = new PacketAckPacket.PacketsBlock[PendingAcks.Count]; for (int i = 0; i < PendingAcks.Count; i++) { acks.Packets[i] = new PacketAckPacket.PacketsBlock(); acks.Packets[i].ID = PendingAcks.Values[i]; } SendPacket(acks, true); PendingAcks.Clear(); } } } /// /// Resend unacknowledged packets /// private void ResendUnacked() { if (NeedAck.Count > 0) { NetworkManager.OutgoingPacket[] array; lock (NeedAck) { // Create a temporary copy of the outgoing packets array to iterate over array = new NetworkManager.OutgoingPacket[NeedAck.Count]; NeedAck.Values.CopyTo(array, 0); } int now = Environment.TickCount; // Resend packets for (int i = 0; i < array.Length; i++) { NetworkManager.OutgoingPacket outgoing = array[i]; if (outgoing.TickCount != 0 && now - outgoing.TickCount > Client.Settings.RESEND_TIMEOUT) { if (outgoing.ResendCount < Client.Settings.MAX_RESEND_COUNT) { if (Client.Settings.LOG_RESENDS) { Logger.DebugLog(String.Format("Resending packet #{0} ({1}), {2}ms have passed", outgoing.Packet.Header.Sequence, outgoing.Packet.GetType(), now - outgoing.TickCount), Client); } // The TickCount will be set to the current time when the packet // is actually sent out again outgoing.TickCount = 0; outgoing.SetSequence = false; outgoing.Packet.Header.Resent = true; ++outgoing.ResendCount; ++Stats.ResentPackets; SendPacket(outgoing); } else { Logger.DebugLog(String.Format("Dropping packet #{0} ({1}) after {2} failed attempts", outgoing.Packet.Header.Sequence, outgoing.Packet.GetType(), outgoing.ResendCount)); lock (NeedAck) NeedAck.Remove(outgoing.Packet.Header.Sequence); } } } } } private void AckTimer_Elapsed(object obj) { SendAcks(); ResendUnacked(); } private void StatsTimer_Elapsed(object obj) { ulong old_in = 0, old_out = 0; if (InBytes.Count >= Client.Settings.STATS_QUEUE_SIZE) old_in = InBytes.Dequeue(); if (OutBytes.Count >= Client.Settings.STATS_QUEUE_SIZE) old_out = OutBytes.Dequeue(); InBytes.Enqueue(Stats.RecvBytes); OutBytes.Enqueue(Stats.SentBytes); if (old_in > 0 && old_out > 0) { Stats.IncomingBPS = (int)(Stats.RecvBytes - old_in) / Client.Settings.STATS_QUEUE_SIZE; Stats.OutgoingBPS = (int)(Stats.SentBytes - old_out) / Client.Settings.STATS_QUEUE_SIZE; //Client.Log("Incoming: " + IncomingBPS + " Out: " + OutgoingBPS + // " Lag: " + LastLag + " Pings: " + ReceivedPongs + // "/" + SentPings, Helpers.LogLevel.Debug); } } private void PingTimer_Elapsed(object obj) { SendPing(); Stats.SentPings++; } } }