/* * 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 long SentPackets; /// Total number of packets received by this simulator to this agent public long RecvPackets; /// Total number of bytes sent by this simulator to this agent public long SentBytes; /// Total number of bytes received by this simulator to this agent public long 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; /// A Unique Cache identifier for this simulator 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; /// true if your agent has Estate Manager rights on this region public bool IsEstateManager; /// 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; /// The regions Unique ID public UUID RegionID = UUID.Zero; /// The physical data center the simulator is located /// Known values are: /// /// Dallas /// Chandler /// SF /// /// public string ColoLocation; /// The CPU Class of the simulator /// Most full mainland/estate sims appear to be 5, /// Homesteads and Openspace appear to be 501 public int CPUClass; /// The number of regions sharing the same CPU as this one /// "Full Sims" appear to be 1, Homesteads appear to be 4 public int CPURatio; /// The billing product name /// Known values are: /// /// Mainland / Full Region (Sku: 023) /// Estate / Full Region (Sku: 024) /// Estate / Openspace (Sku: 027) /// Estate / Homestead (Sku: 029) /// Mainland / Homestead (Sku: 129) (Linden Owned) /// /// public string ProductName; /// The billing product SKU /// Known values are: /// /// 023 Mainland / Full Region /// 024 Estate / Full Region /// 027 Estate / Openspace /// 029 Estate / Homestead /// 129 Mainland / Homestead (Linden Owned) /// /// public string ProductSku; /// 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 IncomingPacketIDCollection 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 LocklessQueue PendingAcks = new LocklessQueue(); private int PendingAckCount = 0; 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; Network = Client.Network; PacketArchive = new IncomingPacketIDCollection(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 // Timer for sending out queued packet acknowledgements if (AckTimer == null) AckTimer = new Timer(AckTimer_Elapsed, null, Settings.NETWORK_TICK_INTERVAL, Timeout.Infinite); // Timer for recording simulator connection statistics if (StatsTimer == null) StatsTimer = new Timer(StatsTimer_Elapsed, null, 1000, 1000); // Timer for periodically pinging the simulator if (PingTimer == null && 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); 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(); AckTimer = null; StatsTimer = null; PingTimer = null; // 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); } #region Packet Sending /// /// Sends a packet /// /// Packet to be sent public void SendPacket(Packet packet) { // DEBUG: This can go away after we are sure nothing in the library is trying to do this if (packet.Header.AppendedAcks || (packet.Header.AckList != null && packet.Header.AckList.Length > 0)) Logger.Log("Attempting to send packet " + packet.Type + " with ACKs appended before serialization", Helpers.LogLevel.Error); if (packet.HasVariableBlocks) { byte[][] datas; try { datas = packet.ToBytesMultiple(); } catch (NullReferenceException) { Logger.Log("Failed to serialize " + packet.Type + " packet to one or more payloads due to a missing block or field. StackTrace: " + Environment.StackTrace, Helpers.LogLevel.Error); return; } int packetCount = datas.Length; if (packetCount > 1) Logger.DebugLog("Split " + packet.Type + " packet into " + packetCount + " packets"); for (int i = 0; i < packetCount; i++) { byte[] data = datas[i]; SendPacketData(data, data.Length, packet.Type, packet.Header.Zerocoded); } } else { byte[] data = packet.ToBytes(); SendPacketData(data, data.Length, packet.Type, packet.Header.Zerocoded); } } public void SendPacketData(byte[] data, int dataLength, PacketType type, bool doZerocode) { UDPPacketBuffer buffer = new UDPPacketBuffer(remoteEndPoint, Packet.MTU); // Zerocode if needed if (doZerocode) { try { dataLength = Helpers.ZeroEncode(data, dataLength, buffer.Data); } catch (IndexOutOfRangeException) { // The packet grew larger than Packet.MTU bytes while zerocoding. // Remove the MSG_ZEROCODED flag and send the unencoded data // instead data[0] = (byte)(data[0] & ~Helpers.MSG_ZEROCODED); Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength); } } else { Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength); } buffer.DataLength = dataLength; #region Queue or Send NetworkManager.OutgoingPacket outgoingPacket = new NetworkManager.OutgoingPacket(this, buffer); // Send ACK and logout packets directly, everything else goes through the queue if (Client.Settings.THROTTLE_OUTGOING_PACKETS == false || type == PacketType.PacketAck || type == PacketType.LogoutRequest) { SendPacketFinal(outgoingPacket); } else { Network.PacketOutbox.Enqueue(outgoingPacket); } #endregion Queue or Send } internal void SendPacketFinal(NetworkManager.OutgoingPacket outgoingPacket) { UDPPacketBuffer buffer = outgoingPacket.Buffer; byte flags = buffer.Data[0]; bool isResend = (flags & Helpers.MSG_RESENT) != 0; bool isReliable = (flags & Helpers.MSG_RELIABLE) != 0; // Keep track of when this packet was sent out (right now) outgoingPacket.TickCount = Environment.TickCount; #region ACK Appending int dataLength = buffer.DataLength; // Keep appending ACKs until there is no room left in the packet or there are // no more ACKs to append uint ackCount = 0; uint ack; while (dataLength + 5 < Packet.MTU && PendingAcks.Dequeue(out ack)) { Utils.UIntToBytesBig(ack, buffer.Data, dataLength); dataLength += 4; ++ackCount; } if (ackCount > 0) { // Set the last byte of the packet equal to the number of appended ACKs buffer.Data[dataLength++] = (byte)ackCount; // Set the appended ACKs flag on this packet buffer.Data[0] = (byte)(buffer.Data[0] | Helpers.MSG_APPENDED_ACKS); } buffer.DataLength = dataLength; #endregion ACK Appending if (!isResend) { // Not a resend, assign a new sequence number uint sequenceNumber = (uint)Interlocked.Increment(ref Sequence); Utils.UIntToBytesBig(sequenceNumber, buffer.Data, 1); outgoingPacket.SequenceNumber = sequenceNumber; if (isReliable) { // Add this packet to the list of ACK responses we are waiting on from the server lock (NeedAck) NeedAck[sequenceNumber] = outgoingPacket; } } // Put the UDP payload on the wire AsyncBeginSend(buffer); } /// /// /// 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); Stats.LastPingSent = Environment.TickCount; } #endregion Packet Sending /// /// 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, // Only allocate a buffer for zerodecoding if the packet is zerocoded ((buffer.Data[0] & Helpers.MSG_ZEROCODED) != 0) ? new byte[8192] : null); } 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; } Interlocked.Add(ref Stats.RecvBytes, buffer.DataLength); Interlocked.Increment(ref Stats.RecvPackets); #endregion Packet Decoding if (packet.Header.Resent) Interlocked.Increment(ref Stats.ReceivedResends); #region ACK Receiving // Handle appended ACKs if (packet.Header.AppendedAcks && packet.Header.AckList != null) { lock (NeedAck) { for (int i = 0; i < packet.Header.AckList.Length; i++) NeedAck.Remove(packet.Header.AckList[i]); } } // Handle PacketAck packets if (packet.Type == PacketType.PacketAck) { PacketAckPacket ackPacket = (PacketAckPacket)packet; lock (NeedAck) { for (int i = 0; i < ackPacket.Packets.Length; i++) NeedAck.Remove(ackPacket.Packets[i].ID); } } #endregion ACK Receiving #region ACK Sending // Add this packet to the list of ACKs that need to be sent out uint sequence = (uint)packet.Header.Sequence; PendingAcks.Enqueue(sequence); int pendingAckCount = Interlocked.Increment(ref PendingAckCount); // Send out ACKs if we have a lot of them if (pendingAckCount >= Client.Settings.MAX_PENDING_ACKS) SendAcks(); #endregion ACK Sending // Check the archive of received packet IDs to see whether we already received this packet if (packet.Header.Reliable && !PacketArchive.TryEnqueue(packet.Header.Sequence)) { if (packet.Header.Resent) Logger.DebugLog("Received a resend of already processed packet #" + packet.Header.Sequence + ", type: " + packet.Type); else Logger.Log("Received a duplicate (not marked as resend) of packet #" + packet.Header.Sequence + ", type: " + packet.Type, Helpers.LogLevel.Warning); // Avoid firing a callback twice for the same packet return; } #region Inbox Insertion NetworkManager.IncomingPacket incomingPacket; incomingPacket.Simulator = this; incomingPacket.Packet = packet; Network.PacketInbox.Enqueue(incomingPacket); #endregion Inbox Insertion } protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent) { // Stats tracking Interlocked.Add(ref Stats.SentBytes, bytesSent); Interlocked.Increment(ref Stats.SentPackets); Client.Network.PacketSent(buffer.Data, bytesSent, this); } /// /// Sends out pending acknowledgements /// private void SendAcks() { uint ack; if (PendingAcks.Dequeue(out ack)) { Interlocked.Decrement(ref PendingAckCount); List blocks = new List(); PacketAckPacket.PacketsBlock block = new PacketAckPacket.PacketsBlock(); block.ID = ack; blocks.Add(block); while (PendingAcks.Dequeue(out ack)) { Interlocked.Decrement(ref PendingAckCount); block = new PacketAckPacket.PacketsBlock(); block.ID = ack; blocks.Add(block); } PacketAckPacket packet = new PacketAckPacket(); packet.Header.Reliable = false; packet.Packets = blocks.ToArray(); SendPacket(packet); } } /// /// 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) { // The TickCount will be set to the current time when the packet // is actually sent out again outgoing.TickCount = 0; // Set the resent flag outgoing.Buffer.Data[0] = (byte)(outgoing.Buffer.Data[0] | Helpers.MSG_RESENT); // Stats tracking Interlocked.Increment(ref outgoing.ResendCount); Interlocked.Increment(ref Stats.ResentPackets); if (Client.Settings.LOG_RESENDS) { Logger.DebugLog(String.Format("Resending packet #{0}, {1}ms have passed", outgoing.SequenceNumber, now - outgoing.TickCount), Client); } SendPacketFinal(outgoing); } else { Logger.DebugLog(String.Format("Dropping packet #{0} after {1} failed attempts", outgoing.SequenceNumber, outgoing.ResendCount)); lock (NeedAck) NeedAck.Remove(outgoing.SequenceNumber); } } } } } private void AckTimer_Elapsed(object obj) { SendAcks(); ResendUnacked(); // Start the ACK handling functions again after NETWORK_TICK_INTERVAL milliseconds try { AckTimer.Change(Settings.NETWORK_TICK_INTERVAL, Timeout.Infinite); } catch (Exception) { } } private void StatsTimer_Elapsed(object obj) { long old_in = 0, old_out = 0; long recv = Stats.RecvBytes; long sent = Stats.SentBytes; 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(recv); OutBytes.Enqueue(sent); if (old_in > 0 && old_out > 0) { Stats.IncomingBPS = (int)(recv - old_in) / Client.Settings.STATS_QUEUE_SIZE; Stats.OutgoingBPS = (int)(sent - 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(); Interlocked.Increment(ref Stats.SentPings); } } public sealed class IncomingPacketIDCollection { readonly uint[] Items; HashSet hashSet; int first; int next; int capacity; public IncomingPacketIDCollection(int capacity) { this.capacity = capacity; Items = new uint[capacity]; hashSet = new HashSet(); } public bool TryEnqueue(uint ack) { lock (hashSet) { if (hashSet.Add(ack)) { Items[next] = ack; next = (next + 1) % capacity; if (next == first) { hashSet.Remove(Items[first]); first = (first + 1) % capacity; } return true; } } return false; } } }