Files
NebulaLauncher/Nebula.Shared/Utils/BandwidthStream.cs

138 lines
3.5 KiB
C#
Raw Normal View History

2024-12-22 16:38:47 +03:00
using System.Diagnostics;
2025-01-05 17:05:23 +03:00
namespace Nebula.Shared.Utils;
2024-12-22 16:38:47 +03:00
public sealed class BandwidthStream : Stream
{
private const int NumSeconds = 8;
private const int BucketDivisor = 2;
private const int BucketsPerSecond = 2 << BucketDivisor;
// TotalBuckets MUST be power of two!
private const int TotalBuckets = NumSeconds * BucketsPerSecond;
private readonly Stream _baseStream;
private readonly long[] _buckets;
private readonly Stopwatch _stopwatch;
private long _bucketIndex;
public BandwidthStream(Stream baseStream)
{
_stopwatch = Stopwatch.StartNew();
_baseStream = baseStream;
_buckets = new long[TotalBuckets];
}
public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => _baseStream.CanSeek;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;
public override long Position
{
get => _baseStream.Position;
set => _baseStream.Position = value;
}
private void TrackBandwidth(long value)
{
const int bucketMask = TotalBuckets - 1;
var bucketIdx = CurBucketIdx();
// Increment to bucket idx, clearing along the way.
if (bucketIdx != _bucketIndex)
{
var diff = bucketIdx - _bucketIndex;
if (diff > TotalBuckets)
for (var i = _bucketIndex; i < bucketIdx; i++)
_buckets[i & bucketMask] = 0;
else
// We managed to skip so much time the whole buffer is empty.
Array.Clear(_buckets);
_bucketIndex = bucketIdx;
}
// Write value.
_buckets[bucketIdx & bucketMask] += value;
}
private long CurBucketIdx()
{
var elapsed = _stopwatch.Elapsed.TotalSeconds;
return (long)(elapsed / BucketsPerSecond);
}
public long CalcCurrentAvg()
{
var sum = 0L;
for (var i = 0; i < TotalBuckets; i++) sum += _buckets[i];
return sum >> BucketDivisor;
}
public override void Flush()
{
_baseStream.Flush();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return _baseStream.FlushAsync(cancellationToken);
}
protected override void Dispose(bool disposing)
{
if (disposing)
_baseStream.Dispose();
}
public override ValueTask DisposeAsync()
{
return _baseStream.DisposeAsync();
}
public override int Read(byte[] buffer, int offset, int count)
{
var read = _baseStream.Read(buffer, offset, count);
TrackBandwidth(read);
return read;
}
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
var read = await base.ReadAsync(buffer, cancellationToken);
TrackBandwidth(read);
return read;
}
public override long Seek(long offset, SeekOrigin origin)
{
return _baseStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
_baseStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
_baseStream.Write(buffer, offset, count);
TrackBandwidth(count);
}
public override async ValueTask WriteAsync(
ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken = default)
{
await _baseStream.WriteAsync(buffer, cancellationToken);
TrackBandwidth(buffer.Length);
}
}