aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs6
-rw-r--r--OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs51
-rw-r--r--OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs98
-rw-r--r--OpenSim/Region/ClientStack/LindenUDP/TokenBucket.cs16
4 files changed, 121 insertions, 50 deletions
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs
index dce9469..38bbce0 100644
--- a/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs
+++ b/OpenSim/Region/ClientStack/LindenUDP/LLClientView.cs
@@ -3320,6 +3320,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
3320 // If we received an update about our own avatar, process the avatar update priority queue immediately 3320 // If we received an update about our own avatar, process the avatar update priority queue immediately
3321 if (data.AgentID == m_agentId) 3321 if (data.AgentID == m_agentId)
3322 ProcessAvatarTerseUpdates(); 3322 ProcessAvatarTerseUpdates();
3323 else
3324 m_udpServer.SignalOutgoingPacketHandler();
3323 } 3325 }
3324 3326
3325 private void ProcessAvatarTerseUpdates() 3327 private void ProcessAvatarTerseUpdates()
@@ -3407,6 +3409,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
3407 3409
3408 lock (m_primFullUpdates.SyncRoot) 3410 lock (m_primFullUpdates.SyncRoot)
3409 m_primFullUpdates.Enqueue(data.priority, objectData, data.localID); 3411 m_primFullUpdates.Enqueue(data.priority, objectData, data.localID);
3412
3413 m_udpServer.SignalOutgoingPacketHandler();
3410 } 3414 }
3411 3415
3412 void ProcessPrimFullUpdates() 3416 void ProcessPrimFullUpdates()
@@ -3450,6 +3454,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
3450 3454
3451 lock (m_primTerseUpdates.SyncRoot) 3455 lock (m_primTerseUpdates.SyncRoot)
3452 m_primTerseUpdates.Enqueue(data.Priority, objectData, data.LocalID); 3456 m_primTerseUpdates.Enqueue(data.Priority, objectData, data.LocalID);
3457
3458 m_udpServer.SignalOutgoingPacketHandler();
3453 } 3459 }
3454 3460
3455 void ProcessPrimTerseUpdates() 3461 void ProcessPrimTerseUpdates()
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs
index 134cfe5..b9d2c15 100644
--- a/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs
+++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPClient.cs
@@ -105,9 +105,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP
105 public int TickLastPacketReceived; 105 public int TickLastPacketReceived;
106 /// <summary>Environment.TickCount of the last time the outgoing packet handler executed for this client</summary> 106 /// <summary>Environment.TickCount of the last time the outgoing packet handler executed for this client</summary>
107 public int TickLastOutgoingPacketHandler; 107 public int TickLastOutgoingPacketHandler;
108 /// <summary>Keeps track of the number of elapsed milliseconds since the last time the outgoing packet handler executed for this client</summary>
109 public int ElapsedMSOutgoingPacketHandler;
110 /// <summary>Keeps track of the number of 100 millisecond periods elapsed in the outgoing packet handler executed for this client</summary>
111 public int Elapsed100MSOutgoingPacketHandler;
112 /// <summary>Keeps track of the number of 500 millisecond periods elapsed in the outgoing packet handler executed for this client</summary>
113 public int Elapsed500MSOutgoingPacketHandler;
108 114
109 /// <summary>Timer granularity. This is set to the measured resolution of Environment.TickCount</summary>
110 public readonly float G;
111 /// <summary>Smoothed round-trip time. A smoothed average of the round-trip time for sending a 115 /// <summary>Smoothed round-trip time. A smoothed average of the round-trip time for sending a
112 /// reliable packet to the client and receiving an ACK</summary> 116 /// reliable packet to the client and receiving an ACK</summary>
113 public float SRTT; 117 public float SRTT;
@@ -182,15 +186,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP
182 m_throttleCategories[i] = new TokenBucket(m_throttle, rates.GetLimit(type), rates.GetRate(type)); 186 m_throttleCategories[i] = new TokenBucket(m_throttle, rates.GetLimit(type), rates.GetRate(type));
183 } 187 }
184 188
185 // Set the granularity variable used for retransmission calculations to
186 // the measured resolution of Environment.TickCount
187 G = server.TickCountResolution;
188
189 // Default the retransmission timeout to three seconds 189 // Default the retransmission timeout to three seconds
190 RTO = 3000; 190 RTO = 3000;
191 191
192 // Initialize this to a sane value to prevent early disconnects 192 // Initialize this to a sane value to prevent early disconnects
193 TickLastPacketReceived = Environment.TickCount; 193 TickLastPacketReceived = Environment.TickCount & Int32.MaxValue;
194 ElapsedMSOutgoingPacketHandler = 0;
195 Elapsed100MSOutgoingPacketHandler = 0;
196 Elapsed500MSOutgoingPacketHandler = 0;
194 } 197 }
195 198
196 /// <summary> 199 /// <summary>
@@ -391,6 +394,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
391 { 394 {
392 // Not enough tokens in the bucket, queue this packet 395 // Not enough tokens in the bucket, queue this packet
393 queue.Enqueue(packet); 396 queue.Enqueue(packet);
397 m_udpServer.SignalOutgoingPacketHandler();
394 return true; 398 return true;
395 } 399 }
396 } 400 }
@@ -407,13 +411,15 @@ namespace OpenSim.Region.ClientStack.LindenUDP
407 /// </summary> 411 /// </summary>
408 /// <remarks>This function is only called from a synchronous loop in the 412 /// <remarks>This function is only called from a synchronous loop in the
409 /// UDPServer so we don't need to bother making this thread safe</remarks> 413 /// UDPServer so we don't need to bother making this thread safe</remarks>
410 /// <returns>True if any packets were sent, otherwise false</returns> 414 /// <returns>The minimum amount of time before the next packet
411 public bool DequeueOutgoing() 415 /// can be sent to this client</returns>
416 public int DequeueOutgoing()
412 { 417 {
413 OutgoingPacket packet; 418 OutgoingPacket packet;
414 OpenSim.Framework.LocklessQueue<OutgoingPacket> queue; 419 OpenSim.Framework.LocklessQueue<OutgoingPacket> queue;
415 TokenBucket bucket; 420 TokenBucket bucket;
416 bool packetSent = false; 421 int dataLength;
422 int minTimeout = Int32.MaxValue;
417 423
418 //string queueDebugOutput = String.Empty; // Serious debug business 424 //string queueDebugOutput = String.Empty; // Serious debug business
419 425
@@ -428,12 +434,18 @@ namespace OpenSim.Region.ClientStack.LindenUDP
428 // leaving a dequeued packet still waiting to be sent out. Try to 434 // leaving a dequeued packet still waiting to be sent out. Try to
429 // send it again 435 // send it again
430 OutgoingPacket nextPacket = m_nextPackets[i]; 436 OutgoingPacket nextPacket = m_nextPackets[i];
431 if (bucket.RemoveTokens(nextPacket.Buffer.DataLength)) 437 dataLength = nextPacket.Buffer.DataLength;
438 if (bucket.RemoveTokens(dataLength))
432 { 439 {
433 // Send the packet 440 // Send the packet
434 m_udpServer.SendPacketFinal(nextPacket); 441 m_udpServer.SendPacketFinal(nextPacket);
435 m_nextPackets[i] = null; 442 m_nextPackets[i] = null;
436 packetSent = true; 443 minTimeout = 0;
444 }
445 else if (minTimeout != 0)
446 {
447 // Check the minimum amount of time we would have to wait before this packet can be sent out
448 minTimeout = Math.Min(minTimeout, ((dataLength - bucket.Content) / bucket.DripPerMS) + 1);
437 } 449 }
438 } 450 }
439 else 451 else
@@ -445,16 +457,23 @@ namespace OpenSim.Region.ClientStack.LindenUDP
445 { 457 {
446 // A packet was pulled off the queue. See if we have 458 // A packet was pulled off the queue. See if we have
447 // enough tokens in the bucket to send it out 459 // enough tokens in the bucket to send it out
448 if (bucket.RemoveTokens(packet.Buffer.DataLength)) 460 dataLength = packet.Buffer.DataLength;
461 if (bucket.RemoveTokens(dataLength))
449 { 462 {
450 // Send the packet 463 // Send the packet
451 m_udpServer.SendPacketFinal(packet); 464 m_udpServer.SendPacketFinal(packet);
452 packetSent = true; 465 minTimeout = 0;
453 } 466 }
454 else 467 else
455 { 468 {
456 // Save the dequeued packet for the next iteration 469 // Save the dequeued packet for the next iteration
457 m_nextPackets[i] = packet; 470 m_nextPackets[i] = packet;
471
472 if (minTimeout != 0)
473 {
474 // Check the minimum amount of time we would have to wait before this packet can be sent out
475 minTimeout = Math.Min(minTimeout, ((dataLength - bucket.Content) / bucket.DripPerMS) + 1);
476 }
458 } 477 }
459 478
460 // If the queue is empty after this dequeue, fire the queue 479 // If the queue is empty after this dequeue, fire the queue
@@ -473,7 +492,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
473 } 492 }
474 493
475 //m_log.Info("[LLUDPCLIENT]: Queues: " + queueDebugOutput); // Serious debug business 494 //m_log.Info("[LLUDPCLIENT]: Queues: " + queueDebugOutput); // Serious debug business
476 return packetSent; 495 return minTimeout;
477 } 496 }
478 497
479 /// <summary> 498 /// <summary>
@@ -504,7 +523,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
504 } 523 }
505 524
506 // Always round retransmission timeout up to two seconds 525 // Always round retransmission timeout up to two seconds
507 RTO = Math.Max(2000, (int)(SRTT + Math.Max(G, K * RTTVAR))); 526 RTO = Math.Max(2000, (int)(SRTT + Math.Max(m_udpServer.TickCountResolution, K * RTTVAR)));
508 //m_log.Debug("[LLUDPCLIENT]: Setting agent " + this.Agent.FullName + "'s RTO to " + RTO + "ms with an RTTVAR of " + 527 //m_log.Debug("[LLUDPCLIENT]: Setting agent " + this.Agent.FullName + "'s RTO to " + RTO + "ms with an RTTVAR of " +
509 // RTTVAR + " based on new RTT of " + r + "ms"); 528 // RTTVAR + " based on new RTT of " + r + "ms");
510 } 529 }
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs
index 952d147..7d5c11e 100644
--- a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs
+++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs
@@ -96,6 +96,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP
96 96
97 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 97 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
98 98
99 /// <summary>The measured resolution of Environment.TickCount</summary>
100 public readonly float TickCountResolution;
101
99 /// <summary>Handlers for incoming packets</summary> 102 /// <summary>Handlers for incoming packets</summary>
100 //PacketEventDictionary packetEvents = new PacketEventDictionary(); 103 //PacketEventDictionary packetEvents = new PacketEventDictionary();
101 /// <summary>Incoming packets that are awaiting handling</summary> 104 /// <summary>Incoming packets that are awaiting handling</summary>
@@ -112,20 +115,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP
112 private Scene m_scene; 115 private Scene m_scene;
113 /// <summary>The X/Y coordinates of the scene this UDP server is attached to</summary> 116 /// <summary>The X/Y coordinates of the scene this UDP server is attached to</summary>
114 private Location m_location; 117 private Location m_location;
115 /// <summary>The measured resolution of Environment.TickCount</summary>
116 private float m_tickCountResolution;
117 /// <summary>The size of the receive buffer for the UDP socket. This value 118 /// <summary>The size of the receive buffer for the UDP socket. This value
118 /// is passed up to the operating system and used in the system networking 119 /// is passed up to the operating system and used in the system networking
119 /// stack. Use zero to leave this value as the default</summary> 120 /// stack. Use zero to leave this value as the default</summary>
120 private int m_recvBufferSize; 121 private int m_recvBufferSize;
121 /// <summary>Flag to process packets asynchronously or synchronously</summary> 122 /// <summary>Flag to process packets asynchronously or synchronously</summary>
122 private bool m_asyncPacketHandling; 123 private bool m_asyncPacketHandling;
123 /// <summary>Track whether or not a packet was sent in the 124 /// <summary>Track the minimum amount of time needed to send the next packet in the
124 /// OutgoingPacketHandler loop so we know when to sleep</summary> 125 /// OutgoingPacketHandler loop so we know when to sleep</summary>
125 private bool m_packetSentLastLoop; 126 private int m_minTimeout = Int32.MaxValue;
127 /// <summary>EventWaitHandle to signify the outgoing packet handler thread that
128 /// there is more work to do</summary>
129 private EventWaitHandle m_outgoingWaitHandle;
126 130
127 /// <summary>The measured resolution of Environment.TickCount</summary>
128 public float TickCountResolution { get { return m_tickCountResolution; } }
129 public Socket Server { get { return null; } } 131 public Socket Server { get { return null; } }
130 132
131 public LLUDPServer(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) 133 public LLUDPServer(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager)
@@ -134,16 +136,17 @@ namespace OpenSim.Region.ClientStack.LindenUDP
134 #region Environment.TickCount Measurement 136 #region Environment.TickCount Measurement
135 137
136 // Measure the resolution of Environment.TickCount 138 // Measure the resolution of Environment.TickCount
137 m_tickCountResolution = 0f; 139 TickCountResolution = 0f;
138 for (int i = 0; i < 5; i++) 140 for (int i = 0; i < 5; i++)
139 { 141 {
140 int start = Environment.TickCount; 142 int start = Environment.TickCount;
141 int now = start; 143 int now = start;
142 while (now == start) 144 while (now == start)
143 now = Environment.TickCount; 145 now = Environment.TickCount;
144 m_tickCountResolution += (float)(now - start) * 0.2f; 146 TickCountResolution += (float)(now - start) * 0.2f;
145 } 147 }
146 m_log.Info("[LLUDPSERVER]: Average Environment.TickCount resolution: " + TickCountResolution + "ms"); 148 m_log.Info("[LLUDPSERVER]: Average Environment.TickCount resolution: " + TickCountResolution + "ms");
149 TickCountResolution = (float)Math.Ceiling(TickCountResolution);
147 150
148 #endregion Environment.TickCount Measurement 151 #endregion Environment.TickCount Measurement
149 152
@@ -171,6 +174,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
171 174
172 base.Start(m_recvBufferSize, m_asyncPacketHandling); 175 base.Start(m_recvBufferSize, m_asyncPacketHandling);
173 176
177 m_outgoingWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
178
174 // Start the incoming packet processing thread 179 // Start the incoming packet processing thread
175 Thread incomingThread = new Thread(IncomingPacketHandler); 180 Thread incomingThread = new Thread(IncomingPacketHandler);
176 incomingThread.Name = "Incoming Packets (" + m_scene.RegionInfo.RegionName + ")"; 181 incomingThread.Name = "Incoming Packets (" + m_scene.RegionInfo.RegionName + ")";
@@ -185,6 +190,8 @@ namespace OpenSim.Region.ClientStack.LindenUDP
185 { 190 {
186 m_log.Info("[LLUDPSERVER]: Shutting down the LLUDP server for " + m_scene.RegionInfo.RegionName); 191 m_log.Info("[LLUDPSERVER]: Shutting down the LLUDP server for " + m_scene.RegionInfo.RegionName);
187 base.Stop(); 192 base.Stop();
193
194 m_outgoingWaitHandle.Close();
188 } 195 }
189 196
190 public void AddScene(IScene scene) 197 public void AddScene(IScene scene)
@@ -768,6 +775,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP
768 packetInbox.Clear(); 775 packetInbox.Clear();
769 } 776 }
770 777
778 public bool SignalOutgoingPacketHandler()
779 {
780 return m_outgoingWaitHandle.Set();
781 }
782
771 private void OutgoingPacketHandler() 783 private void OutgoingPacketHandler()
772 { 784 {
773 // Set this culture for the thread that outgoing packets are sent 785 // Set this culture for the thread that outgoing packets are sent
@@ -778,14 +790,28 @@ namespace OpenSim.Region.ClientStack.LindenUDP
778 { 790 {
779 try 791 try
780 { 792 {
781 m_packetSentLastLoop = false; 793 m_minTimeout = Int32.MaxValue;
782 794
795 // Handle outgoing packets, resends, acknowledgements, and pings for each
796 // client. m_minTimeout will be set to 0 if more packets are waiting in the
797 // queues with bandwidth to spare, or the number of milliseconds we need to
798 // wait before at least one packet can be sent to a client
783 m_scene.ClientManager.ForEachSync(ClientOutgoingPacketHandler); 799 m_scene.ClientManager.ForEachSync(ClientOutgoingPacketHandler);
784 800
785 // If no packets at all were sent, sleep to avoid chewing up CPU cycles 801 // Can't wait for a negative amount of time, and put a 100ms ceiling on our
786 // when there is nothing to do 802 // maximum wait time
787 if (!m_packetSentLastLoop) 803 m_minTimeout = Utils.Clamp(m_minTimeout, 0, 100);
788 Thread.Sleep(20); 804
805 if (m_minTimeout > 0)
806 {
807 // Don't bother waiting for a shorter interval than our TickCountResolution
808 // since the token buckets wouldn't update anyways
809 m_minTimeout = Math.Max(m_minTimeout, (int)TickCountResolution);
810
811 // Wait for someone to signal that packets are ready to be sent, or for our
812 // sleep interval to expire
813 m_outgoingWaitHandle.WaitOne(m_minTimeout);
814 }
789 } 815 }
790 catch (Exception ex) 816 catch (Exception ex)
791 { 817 {
@@ -802,32 +828,48 @@ namespace OpenSim.Region.ClientStack.LindenUDP
802 { 828 {
803 LLUDPClient udpClient = ((LLClientView)client).UDPClient; 829 LLUDPClient udpClient = ((LLClientView)client).UDPClient;
804 830
831 // Update ElapsedMSOutgoingPacketHandler
805 int thisTick = Environment.TickCount & Int32.MaxValue; 832 int thisTick = Environment.TickCount & Int32.MaxValue;
806 int elapsedMS = thisTick - udpClient.TickLastOutgoingPacketHandler; 833 if (udpClient.TickLastOutgoingPacketHandler > thisTick)
834 udpClient.ElapsedMSOutgoingPacketHandler += ((Int32.MaxValue - udpClient.TickLastOutgoingPacketHandler) + thisTick);
835 else
836 udpClient.ElapsedMSOutgoingPacketHandler += (thisTick - udpClient.TickLastOutgoingPacketHandler);
807 837
808 if (udpClient.IsConnected) 838 if (udpClient.IsConnected)
809 { 839 {
810 // Check for pending outgoing resends every 100ms 840 // Check for pending outgoing resends every 100ms
811 if (elapsedMS >= 100) 841 if (udpClient.ElapsedMSOutgoingPacketHandler >= 100)
812 { 842 {
813 ResendUnacked(udpClient); 843 ResendUnacked(udpClient);
844 udpClient.ElapsedMSOutgoingPacketHandler -= 100;
845 udpClient.Elapsed100MSOutgoingPacketHandler += 1;
846 }
814 847
815 // Check for pending outgoing ACKs every 500ms 848 // Check for pending outgoing ACKs every 500ms
816 if (elapsedMS >= 500) 849 if (udpClient.Elapsed100MSOutgoingPacketHandler >= 5)
817 { 850 {
818 SendAcks(udpClient); 851 SendAcks(udpClient);
819 852 udpClient.Elapsed100MSOutgoingPacketHandler -= 5;
820 // Send pings to clients every 5000ms 853 udpClient.Elapsed500MSOutgoingPacketHandler += 1;
821 if (elapsedMS >= 5000) 854 }
822 { 855
823 SendPing(udpClient); 856 // Send pings to clients every 5000ms
824 } 857 if (udpClient.Elapsed500MSOutgoingPacketHandler >= 10)
825 } 858 {
859 SendPing(udpClient);
860 udpClient.Elapsed500MSOutgoingPacketHandler -= 10;
826 } 861 }
827 862
828 // Dequeue any outgoing packets that are within the throttle limits 863 // Dequeue any outgoing packets that are within the throttle limits
829 if (udpClient.DequeueOutgoing()) 864 // and get the minimum time we would have to sleep before this client
830 m_packetSentLastLoop = true; 865 // could send a packet out
866 int minTimeoutThisLoop = udpClient.DequeueOutgoing();
867
868 // Although this is not thread safe, it is cheaper than locking and the
869 // worst that will happen is we sleep for slightly longer than the
870 // minimum necessary interval
871 if (minTimeoutThisLoop < m_minTimeout)
872 m_minTimeout = minTimeoutThisLoop;
831 } 873 }
832 874
833 udpClient.TickLastOutgoingPacketHandler = thisTick; 875 udpClient.TickLastOutgoingPacketHandler = thisTick;
diff --git a/OpenSim/Region/ClientStack/LindenUDP/TokenBucket.cs b/OpenSim/Region/ClientStack/LindenUDP/TokenBucket.cs
index 0a64095..bdbd284 100644
--- a/OpenSim/Region/ClientStack/LindenUDP/TokenBucket.cs
+++ b/OpenSim/Region/ClientStack/LindenUDP/TokenBucket.cs
@@ -98,6 +98,14 @@ namespace OpenSim.Region.ClientStack.LindenUDP
98 } 98 }
99 99
100 /// <summary> 100 /// <summary>
101 /// The speed limit of this bucket in bytes per millisecond
102 /// </summary>
103 public int DripPerMS
104 {
105 get { return tokensPerMS; }
106 }
107
108 /// <summary>
101 /// The number of bytes that can be sent at this moment. This is the 109 /// The number of bytes that can be sent at this moment. This is the
102 /// current number of tokens in the bucket 110 /// current number of tokens in the bucket
103 /// <remarks>If this bucket has a parent bucket that does not have 111 /// <remarks>If this bucket has a parent bucket that does not have
@@ -106,11 +114,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
106 /// </summary> 114 /// </summary>
107 public int Content 115 public int Content
108 { 116 {
109 get 117 get { return content; }
110 {
111 Drip();
112 return content;
113 }
114 } 118 }
115 119
116 #endregion Properties 120 #endregion Properties
@@ -182,7 +186,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP
182 /// call to Drip 186 /// call to Drip
183 /// </summary> 187 /// </summary>
184 /// <returns>True if tokens were added to the bucket, otherwise false</returns> 188 /// <returns>True if tokens were added to the bucket, otherwise false</returns>
185 private bool Drip() 189 public bool Drip()
186 { 190 {
187 if (tokensPerMS == 0) 191 if (tokensPerMS == 0)
188 { 192 {