Skip to content

Commit

Permalink
feat: Added bandwidth tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
TwoTenPvP committed Jul 22, 2020
1 parent 6135da4 commit ea821b7
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 16 deletions.
15 changes: 15 additions & 0 deletions Ruffles/BandwidthTracking/IBandwidthTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Ruffles.BandwidthTracking
{
/// <summary>
/// An interface for creating bandwidth trackers.
/// </summary>
public interface IBandwidthTracker
{
/// <summary>
/// Asks the tracker if it can send a certain amount of bytes.
/// </summary>
/// <returns><c>true</c>, if send was allowed, <c>false</c> otherwise.</returns>
/// <param name="size">The requested size in bytes.</param>
bool TrySend(int size);
}
}
59 changes: 59 additions & 0 deletions Ruffles/BandwidthTracking/SimpleBandwidthTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Ruffles.Time;

namespace Ruffles.BandwidthTracking
{
/// <summary>
/// A simple bandwidth tracker capturing an resetting average with an optional carry between resets.
/// </summary>
public class SimpleBandwidthTracker : IBandwidthTracker
{
private NetTime _startTime { get; set; }
private int _sentBytes { get; set; }
private int _lastPeriodBuffer { get; set; }

private readonly int maxBytesPerSecond;
private readonly int averageIntervalSeconds;
private readonly float remainderCarry;

private readonly object _lock = new object();

/// <summary>
/// Initializes a new instance of the <see cref="T:Ruffles.BandwidthTracking.SimpleBandwidthTracker"/> class.
/// </summary>
/// <param name="maxBytesPerSecond">The max bytes per second allowed.</param>
/// <param name="averageIntervalSeconds">The amount of seconds before the average interval.</param>
/// <param name="remainderCarry">The percentage of the remaining quota for the current average period that is carried to the next period on a reset.</param>
public SimpleBandwidthTracker(int maxBytesPerSecond, int averageIntervalSeconds, float remainderCarry)
{
this.maxBytesPerSecond = maxBytesPerSecond;
this.averageIntervalSeconds = averageIntervalSeconds;
this.remainderCarry = remainderCarry;
}

public bool TrySend(int size)
{
lock (_lock)
{
double secondsSinceStart = (NetTime.Now - _startTime).TotalSeconds;
double bytesPerSecond = (_sentBytes + size) / secondsSinceStart;

bool allowPacket = bytesPerSecond <= maxBytesPerSecond + _lastPeriodBuffer;

if (allowPacket)
{
_sentBytes += size;
}

if (secondsSinceStart >= averageIntervalSeconds)
{
// Give half of what was left of the previous period as a bonus to this period
_lastPeriodBuffer = (int)((maxBytesPerSecond - _sentBytes) * remainderCarry);
_sentBytes = 0;
_startTime = NetTime.Now;
}

return allowPacket;
}
}
}
}
23 changes: 20 additions & 3 deletions Ruffles/Configuration/SocketConfig.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Net;
using Ruffles.BandwidthTracking;
using Ruffles.Channeling;
using Ruffles.Simulation;
using Ruffles.Utils;
Expand Down Expand Up @@ -251,7 +253,13 @@ public class SocketConfig
/// <summary>
/// The minimum delay before an ack is resent.
/// </summary>
public int ReliabilityMinAckResendDelay = 100;
public int ReliabilityMinAckResendDelay = 100;

// Bandwidth limitation
/// <summary>
/// Constructor delegate for creating the desired bandwidth tracker.
/// </summary>
public Func<IBandwidthTracker> CreateBandwidthTracker = () => new SimpleBandwidthTracker(1024 * 1024 * 8, 2, 0.5f); // 8 kilobytes per second allowed, resets every 2 seconds with a 50% remainder carry

// Simulation
/// <summary>
Expand Down Expand Up @@ -296,7 +304,11 @@ public class SocketConfig
/// <summary>
/// Whether or not acks should be reported to the user.
/// </summary>
public bool EnableAckNotifications = true;
public bool EnableAckNotifications = true;
/// <summary>
/// Whether or not to enable bandwidth tracking.
/// </summary>
public bool EnableBandwidthTracking = true;

public List<string> GetInvalidConfiguration()
{
Expand Down Expand Up @@ -570,6 +582,11 @@ public List<string> GetInvalidConfiguration()
messages.Add("ChannelPoolSize cannot be less than 0");
}

if (EnableBandwidthTracking && CreateBandwidthTracker == null)
{
messages.Add("EnableBandwidthTracking is enabled but no CreateBandwidthTracker delegate is provided");
}

return messages;
}
}
Expand Down
48 changes: 35 additions & 13 deletions Ruffles/Connections/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
using System.Diagnostics;
using System.Net;
using System.Threading;
using Ruffles.BandwidthTracking;
using Ruffles.Channeling;
using Ruffles.Channeling.Channels;
using Ruffles.Collections;
using Ruffles.Configuration;
using Ruffles.Core;
using Ruffles.Hashing;
Expand Down Expand Up @@ -104,7 +106,13 @@ public class Connection
/// Gets the highest roundtrip varience recorded.
/// </summary>
/// <value>The highest roundtrip varience.</value>
public int HighestRoundtripVarience { get; private set; }
public int HighestRoundtripVarience { get; private set; }

/// <summary>
/// Gets the current bandwidth tracker.
/// </summary>
/// <value>The current bandwidth tracker.</value>
public IBandwidthTracker BandwidthTracker { get; private set; }

/// <summary>
/// Gets the maximum amount of bytes that can be sent in a single message.
Expand Down Expand Up @@ -193,14 +201,19 @@ internal Connection(ulong id, ConnectionState state, IPEndPoint endpoint, Ruffle
this.PreConnectionChallengeSolved = false;
this.State = state;

if (Config.EnableBandwidthTracking && Config.CreateBandwidthTracker != null)
{
this.BandwidthTracker = Config.CreateBandwidthTracker();
}

if (Config.EnableHeartbeats)
{
HeartbeatChannel = new UnreliableOrderedChannel(0, this, Config, MemoryManager);
this.HeartbeatChannel = new UnreliableOrderedChannel(0, this, Config, MemoryManager);
}

if (Config.EnablePacketMerging)
{
Merger = new MessageMerger(Config.MaxMergeMessageSize, Config.MinimumMTU, Config.MaxMergeDelay);
this.Merger = new MessageMerger(Config.MaxMergeMessageSize, Config.MinimumMTU, Config.MaxMergeDelay);
}
}

Expand Down Expand Up @@ -237,20 +250,29 @@ internal void SendInternal(ArraySegment<byte> payload, bool noMerge)
}
#endif

LastMessageOut = NetTime.Now;
// Check if there is enough bandwidth to spare for the packet
if (BandwidthTracker == null || BandwidthTracker.TrySend(payload.Count))
{
LastMessageOut = NetTime.Now;

bool merged = false;
bool merged = false;

if (!Socket.Config.EnablePacketMerging || noMerge || !(merged = Merger.TryWrite(payload)))
{
if (Socket.Simulator != null)
if (!Socket.Config.EnablePacketMerging || noMerge || !(merged = Merger.TryWrite(payload)))
{
Socket.Simulator.Add(this, payload);
}
else
{
Socket.SendRaw(EndPoint, payload);
if (Socket.Simulator != null)
{
Socket.Simulator.Add(this, payload);
}
else
{
Socket.SendRaw(EndPoint, payload);
}
}
}
else
{
// Packet was dropped
if (Logging.CurrentLogLevel <= LogLevel.Debug) Logging.LogInfo("Message to connection " + this.Id + " was dropped due to bandwidth constraits");
}
}

Expand Down

0 comments on commit ea821b7

Please sign in to comment.