jiowchern/Regulus

View on GitHub
Regulus.Network/CongestionRecorder.cs

Summary

Maintainability
A
2 hrs
Test Coverage
using Regulus.Network.Package;
using System.Collections.Generic;
using System.Linq;

namespace Regulus.Network
{
    public class CongestionRecorder
    {
        private readonly int m_HungryLimit;
        private int m_Capacity;

        private class Item
        {
            public readonly SocketMessage Message;
            public readonly long EndTicks;
            public readonly long StartTicks;
            public int Hungry { get; private set; }
            public Item(SocketMessage message, long start_ticks, long end_ticks)
            {
                StartTicks = start_ticks;
                Message = message;
                EndTicks = end_ticks;
            }



            public bool IsTimeout(long Ticks)
            {
                return EndTicks <= Ticks;
            }

            public void Padding()
            {
                Hungry++;
            }
        }
        private readonly Dictionary<ushort, Item> m_Items;

        private readonly RetransmissionTimeOut m_Rto;


        public CongestionRecorder(int HungryLimit)
        {
            m_HungryLimit = HungryLimit;
            m_Items = new Dictionary<ushort, Item>();
            m_Rto = new RetransmissionTimeOut();
        }

        public int Count { get { return m_Items.Count; } }
        public long Srtt
        {
            get { return m_Rto.Rtt; }
        }
        public long Rto { get { return m_Rto.Value; } }

        public long LastRtt { get; private set; }
        public long LastRto { get; private set; }


        public void PushWait(SocketMessage Message, long TimeTicks)
        {
            Item item = new Item(Message, TimeTicks, TimeTicks + m_Rto.Value);
            m_Items.Add(item.Message.GetSeq(), item);
        }


        public bool Reply(ushort Package, long TimeTicks, long TimeDelta)
        {
            Item item;
            if (m_Items.TryGetValue(Package, out item))
            {
                _Reply(Package, TimeTicks, TimeDelta, item);
                return true;
            }

            return false;
        }

        private void _Reply(ushort Package, long TimeTicks, long TimeDelta, Item Item)
        {
            m_Capacity++;
            long rtt = TimeTicks - Item.StartTicks;
            m_Rto.Update(rtt, TimeDelta);
            LastRtt = rtt;
            m_Items.Remove(Package);
        }


        public void ReplyBefore(ushort PackageId, long TimeTicks, long TimeDelta)
        {
            Item pkg = m_Items.Values.FirstOrDefault(Item => Item.Message.GetSeq() == PackageId);
            if (pkg != null)
                foreach (Item item in m_Items.Values.Where(Item => Item.EndTicks <= pkg.EndTicks).Select(Item => Item).ToArray())
                    _Reply(item.Message.GetSeq(), TimeTicks, TimeDelta, item);
        }

        public List<SocketMessage> PopLost(long Ticks, long Delta)
        {
            int count = m_Capacity;
            List<SocketMessage> packages = new List<SocketMessage>();
            foreach (Item item in m_Items.Values)
            {

                if (item.IsTimeout(Ticks))
                {
                    long rto = Ticks - item.StartTicks;
                    m_Rto.Update(rto, Delta);
                    LastRtt = rto;
                    LastRto = rto;
                    packages.Add(item.Message);


                }
                else if (item.Hungry > m_HungryLimit)
                {
                    long rto = Ticks - item.StartTicks;

                    m_Rto.Update(rto, Delta);
                    LastRtt = rto;
                    LastRto = rto;
                    packages.Add(item.Message);
                }
                if (count-- == 0)
                    break;
            }


            if (packages.Count > 0)
                m_Capacity /= 2;

            foreach (SocketMessage package in packages)
            {
                m_Items.Remove(package.GetSeq());
                PushWait(package, Ticks);
            }

            return packages;
        }

        public void Padding()
        {
            foreach (Item itemsValue in m_Items.Values)
                itemsValue.Padding();
        }

        public void ReplyAfter(ushort Ack, uint Fields, long TimeTicks, long TimeDelta)
        {
            ushort mark = 1;
            for (ushort i = 0; i < 32; i++)
            {
                if ((mark & Fields) != 0)
                {
                    ushort id = (ushort)(Ack + i + 1);
                    Item item;
                    if (m_Items.TryGetValue(id, out item))
                        _Reply(id, TimeTicks, TimeDelta, item);
                }
                mark <<= 1;
            }
        }

        public bool IsFull()
        {
            return m_Items.Count > m_Capacity;
        }
    }
}