/*
* Copyright (c) 2006-2014, 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 : ulong
{
/// 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,
/// Unknown: Related to the availability of an overview world map tile.(Think mainland images when zoomed out.)
NullLayer = 1 << 9,
/// Unknown: Related to region debug flags. Possibly to skip processing of agent interaction with world.
SkipAgentAction = 1 << 10,
/// Region does not update agent prim interest lists. Internal debugging option.
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,
/// Region can be seen from other regions on world map. (Legacy world map option?)
ExternallyVisible = 1 << 15,
/// Region can be seen from mainland on world map. (Legacy world map option?)
MainlandVisible = 1 << 16,
/// Agents not explicitly on the access list can visit the region.
PublicAllowed = 1 << 17,
/// Traffic calculations are not run across entire region, overrides parcel settings.
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,
/// Restricts the usage of the LSL llPushObject function, applies to whole region.
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,
/// Parcels within the region may be joined or divided by anyone, not just estate owners/managers.
AllowParcelChanges = 1 << 26,
/// Abuse reports sent from within this region are sent to the estate owner defined email.
AbuseEmailToEstateOwner = 1 << 27,
/// Region is Voice Enabled
AllowVoice = 1 << 28,
/// Removes the ability from parcel owners to set their parcels to show in search.
BlockParcelSearch = 1 << 29,
/// Deny agents who have not been age verified from entering the region.
DenyAgeUnverified = 1 << 30
}
///
/// Region protocol flags
///
[Flags]
public enum RegionProtocols : ulong
{
/// Nothing special
None = 0,
/// Region supports Server side Appearance
AgentAppearanceService = 1 << 0,
/// Viewer supports Server side Appearance
SelfAppearanceSupport = 1 << 2
}
///
/// Access level for a simulator
///
public enum SimAccess : byte
{
/// Unknown or invalid access level
Unknown = 0,
/// Trial accounts allowed
Trial = 7,
/// PG rating
PG = 13,
/// Mature rating
Mature = 21,
/// Adult rating
Adult = 42,
/// 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)
/// - Mainland / Linden Homes (Sku: 131)
///
///
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)
/// - 131 Linden Homes / Full Region
///
///
public string ProductSku;
///
/// Flags indicating which protocols this region supports
///
public RegionProtocols Protocols;
/// 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;
///
/// A thread-safe dictionary containing avatars in a simulator
///
public InternalDictionary ObjectsAvatars = new InternalDictionary();
///
/// A thread-safe dictionary containing primitives in a simulator
///
public InternalDictionary ObjectsPrimitives = new InternalDictionary();
public readonly TerrainPatch[] Terrain;
public readonly Vector2[] WindSpeeds;
///
/// Provides access to an internal thread-safe dictionary containing parcel
/// information found in this simulator
///
public InternalDictionary Parcels
{
get
{
if (Client.Settings.POOL_PARCEL_DATA)
{
return DataPool.Parcels;
}
if (_Parcels == null) _Parcels = new InternalDictionary();
return _Parcels;
}
}
private InternalDictionary _Parcels;
///
/// Provides access to an internal thread-safe multidimensional array containing a x,y grid mapped
/// to each 64x64 parcel's LocalID.
///
public int[,] ParcelMap
{
get
{
lock (this)
{
if (Client.Settings.POOL_PARCEL_DATA)
{
return DataPool.ParcelMap;
}
if (_ParcelMap == null) _ParcelMap = new int[64, 64];
return _ParcelMap;
}
}
}
///
/// 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;
}
///
/// Is it safe to send agent updates to this sim
/// AgentMovementComplete message received
///
public bool AgentMovementComplete;
#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; } }
/// Indicates if UDP connection to the sim is fully established
public bool HandshakeComplete { get { return handshakeComplete; } }
#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 ManualResetEvent ConnectedEvent = new ManualResetEvent(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;
/// Indicates if UDP connection to the sim is fully established
internal bool handshakeComplete;
private NetworkManager Network;
private Queue InBytes, OutBytes;
// ACKs that are queued up to be sent to the simulator
private LocklessQueue PendingAcks = new LocklessQueue();
private Timer AckTimer;
private Timer PingTimer;
private Timer StatsTimer;
// simulator <> parcel LocalID Map
private int[,] _ParcelMap;
public readonly SimulatorDataPool DataPool;
internal bool DownloadingParcelMap
{
get
{
return Client.Settings.POOL_PARCEL_DATA ? DataPool.DownloadingParcelMap : _DownloadingParcelMap;
}
set
{
if (Client.Settings.POOL_PARCEL_DATA) DataPool.DownloadingParcelMap = value;
_DownloadingParcelMap = value;
}
}
internal bool _DownloadingParcelMap = false;
private ManualResetEvent GotUseCircuitCodeAck = new ManualResetEvent(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;
if (Client.Settings.POOL_PARCEL_DATA || Client.Settings.CACHE_PRIMITIVES)
{
SimulatorDataPool.SimulatorAdd(this);
DataPool = SimulatorDataPool.GetSimulatorData(Handle);
}
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);
if (client.Settings.STORE_LAND_PATCHES)
{
Terrain = new TerrainPatch[16 * 16];
WindSpeeds = new Vector2[16 * 16];
}
}
///
/// Called when this Simulator object is being destroyed
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public virtual void Dispose(bool disposing)
{
if (disposing)
{
if (AckTimer != null)
AckTimer.Dispose();
if (PingTimer != null)
PingTimer.Dispose();
if (StatsTimer != null)
StatsTimer.Dispose();
if (ConnectedEvent != null)
ConnectedEvent.Close();
// 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)
{
handshakeComplete = false;
if (connected)
{
UseCircuitCode(true);
if (moveToSim) 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;
// Initiate connection
UseCircuitCode(true);
Stats.ConnectTime = Environment.TickCount;
// Move our agent in to the sim to complete the connection
if (moveToSim) Client.Self.CompleteAgentMovement(this);
if (!ConnectedEvent.WaitOne(Client.Settings.LOGIN_TIMEOUT, false))
{
Logger.Log("Giving up on waiting for RegionHandshake for " + this.ToString(),
Helpers.LogLevel.Warning, Client);
//Remove the simulator from the list, not useful if we haven't recieved the RegionHandshake
lock (Client.Network.Simulators) {
Client.Network.Simulators.Remove(this);
}
}
if (Client.Settings.SEND_AGENT_THROTTLE)
Client.Throttle.Set(this);
if (Client.Settings.SEND_AGENT_UPDATES)
Client.Self.Movement.SendUpdate(true, this);
return true;
}
catch (Exception e)
{
Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e);
}
return false;
}
///
/// Initiates connection to the simulator
///
/// Should we block until ack for this packet is recieved
public void UseCircuitCode(bool waitForAck)
{
// 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;
if (waitForAck)
{
GotUseCircuitCodeAck.Reset();
}
// Send the initial packet out
SendPacket(use);
if (waitForAck)
{
if (!GotUseCircuitCodeAck.WaitOne(Client.Settings.LOGIN_TIMEOUT, false))
{
Logger.Log("Failed to get ACK for UseCircuitCode packet", Helpers.LogLevel.Error, Client);
}
}
}
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);
}
if (Client.Settings.POOL_PARCEL_DATA || Client.Settings.CACHE_PRIMITIVES)
{
SimulatorDataPool.SimulatorRelease(this);
}
// 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);
}
///
/// Retrieve the terrain height at a given coordinate
///
/// Sim X coordinate, valid range is from 0 to 255
/// Sim Y coordinate, valid range is from 0 to 255
/// The terrain height at the given point if the
/// lookup was successful, otherwise 0.0f
/// True if the lookup was successful, otherwise false
public bool TerrainHeightAtPoint(int x, int y, out float height)
{
if (Terrain != null && x >= 0 && x < 256 && y >= 0 && y < 256)
{
int patchX = x / 16;
int patchY = y / 16;
x = x % 16;
y = y % 16;
TerrainPatch patch = Terrain[patchY * 16 + patchX];
if (patch != null)
{
height = patch.Data[y * 16 + x];
return true;
}
}
height = 0.0f;
return false;
}
#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, type);
// 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
#region Stats Tracking
if (Client.Settings.TRACK_UTILIZATION)
{
Client.Stats.Update(type.ToString(), OpenMetaverse.Stats.Type.Packet, dataLength, 0);
}
#endregion
}
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.TryDequeue(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] |= 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++)
{
if (NeedAck.ContainsKey(packet.Header.AckList[i]) && NeedAck[packet.Header.AckList[i]].Type == PacketType.UseCircuitCode)
{
GotUseCircuitCodeAck.Set();
}
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++)
{
if (NeedAck.ContainsKey(ackPacket.Packets[i].ID) && NeedAck[ackPacket.Packets[i].ID].Type == PacketType.UseCircuitCode)
{
GotUseCircuitCodeAck.Set();
}
NeedAck.Remove(ackPacket.Packets[i].ID);
}
}
}
#endregion ACK Receiving
if (packet.Header.Reliable)
{
#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);
// Send out ACKs if we have a lot of them
if (PendingAcks.Count >= 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 (!PacketArchive.TryEnqueue(packet.Header.Sequence))
{
if (packet.Header.Resent)
Logger.DebugLog(
string.Format(
"Received a resend of already processed packet #{0}, type: {1} from {2}",
packet.Header.Sequence, packet.Type, Name));
else
Logger.Log(
string.Format(
"Received a duplicate (not marked as resend) of packet #{0}, type: {1} for {2} from {3}",
packet.Header.Sequence, packet.Type, Client.Self.Name, Name),
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
#region Stats Tracking
if (Client.Settings.TRACK_UTILIZATION)
{
Client.Stats.Update(packet.Type.ToString(), OpenMetaverse.Stats.Type.Packet, 0, packet.Length);
}
#endregion
}
protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent)
{
// Stats tracking
Interlocked.Add(ref Stats.SentBytes, bytesSent);
Interlocked.Increment(ref Stats.SentPackets);
Client.Network.RaisePacketSentEvent(buffer.Data, bytesSent, this);
}
///
/// Sends out pending acknowledgements
///
/// Number of ACKs sent
private int SendAcks()
{
uint ack;
int ackCount = 0;
if (PendingAcks.TryDequeue(out ack))
{
List blocks = new List();
PacketAckPacket.PacketsBlock block = new PacketAckPacket.PacketsBlock();
block.ID = ack;
blocks.Add(block);
while (PendingAcks.TryDequeue(out ack))
{
block = new PacketAckPacket.PacketsBlock();
block.ID = ack;
blocks.Add(block);
}
PacketAckPacket packet = new PacketAckPacket();
packet.Header.Reliable = false;
packet.Packets = blocks.ToArray();
ackCount = blocks.Count;
SendPacket(packet);
}
return ackCount;
}
///
/// 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 {2} packet #{0}, {1}ms have passed",
outgoing.SequenceNumber, now - outgoing.TickCount, outgoing.Type), Client);
}
// 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);
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
if (null == AckTimer) return;
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;
}
}
public class SimulatorDataPool
{
private static Timer InactiveSimReaper;
private static void RemoveOldSims(object state)
{
lock (SimulatorDataPools)
{
int SimTimeout = Settings.SIMULATOR_POOL_TIMEOUT;
List reap = new List();
foreach (var pool in SimulatorDataPools.Values)
{
if (pool.InactiveSince != DateTime.MaxValue && pool.InactiveSince.AddMilliseconds(SimTimeout) < DateTime.Now)
{
reap.Add(pool.Handle);
}
}
foreach (var hndl in reap)
{
SimulatorDataPools.Remove(hndl);
}
}
}
public static void SimulatorAdd(Simulator sim)
{
lock (SimulatorDataPools)
{
if (InactiveSimReaper == null)
{
InactiveSimReaper = new Timer(RemoveOldSims, null, TimeSpan.FromMinutes(3), TimeSpan.FromMinutes(3));
}
SimulatorDataPool pool = GetSimulatorData(sim.Handle);
if (pool.ActiveClients < 1) pool.ActiveClients = 1; else pool.ActiveClients++;
pool.InactiveSince = DateTime.MaxValue;
}
}
public static void SimulatorRelease(Simulator sim)
{
ulong hndl = sim.Handle;
lock (SimulatorDataPools)
{
SimulatorDataPool dataPool = GetSimulatorData(hndl);
dataPool.ActiveClients--;
if (dataPool.ActiveClients <= 0)
{
dataPool.InactiveSince = DateTime.Now;
}
}
}
static public Dictionary SimulatorDataPools = new Dictionary();
///
/// Simulator handle
///
readonly public ulong Handle;
///
/// Number of GridClients using this datapool
///
public int ActiveClients;
///
/// Time that the last client disconnected from the simulator
///
public DateTime InactiveSince = DateTime.MaxValue;
#region Pooled Items
///
/// The cache of prims used and unused in this simulator
///
public Dictionary PrimCache = new Dictionary();
///
/// Shared parcel info only when POOL_PARCEL_DATA == true
///
public InternalDictionary Parcels = new InternalDictionary();
public int[,] ParcelMap = new int[64, 64];
public bool DownloadingParcelMap = false;
#endregion Pooled Items
private SimulatorDataPool(ulong hndl)
{
this.Handle = hndl;
}
public static SimulatorDataPool GetSimulatorData(ulong hndl)
{
SimulatorDataPool dict;
lock (SimulatorDataPools)
{
if (!SimulatorDataPools.TryGetValue(hndl, out dict))
{
dict = SimulatorDataPools[hndl] = new SimulatorDataPool(hndl);
}
}
return dict;
}
#region Factories
internal Primitive MakePrimitive(uint localID)
{
var dict = PrimCache;
lock (dict)
{
Primitive prim;
if (!dict.TryGetValue(localID, out prim))
{
dict[localID] = prim = new Primitive { RegionHandle = Handle, LocalID = localID };
}
return prim;
}
}
internal bool NeedsRequest(uint localID)
{
var dict = PrimCache;
lock (dict) return !dict.ContainsKey(localID);
}
#endregion Factories
internal void ReleasePrims(List removePrims)
{
lock (PrimCache)
{
foreach (uint u in removePrims)
{
Primitive prim;
if (PrimCache.TryGetValue(u, out prim)) prim.ActiveClients--;
}
}
}
}
}