/// <summary>
        /// Initialize
        /// This should be called during primary initialization period (typically during NetworkManager's Start method)
        /// This will allocate [maxFrameHistory] + [1 currentFrame] number of PooledNetworkBuffers and keep them open until the session ends
        /// Note: For zero frame history set maxFrameHistory to zero
        /// </summary>
        /// <param name="maxFrameHistory"></param>
        public void Initialize(uint maxFrameHistory)
        {
            ClearParameters();

            m_RpcQueueProcessor = new RpcQueueProcessor();
            m_MaxFrameHistory   = maxFrameHistory + k_MinQueueHistory;

            if (!QueueHistory.ContainsKey(RpcQueueHistoryFrame.QueueFrameType.Inbound))
            {
                QueueHistory.Add(RpcQueueHistoryFrame.QueueFrameType.Inbound, new Dictionary <int, Dictionary <NetworkUpdateStage, RpcQueueHistoryFrame> >());
            }

            if (!QueueHistory.ContainsKey(RpcQueueHistoryFrame.QueueFrameType.Outbound))
            {
                QueueHistory.Add(RpcQueueHistoryFrame.QueueFrameType.Outbound, new Dictionary <int, Dictionary <NetworkUpdateStage, RpcQueueHistoryFrame> >());
            }

            for (int i = 0; i < m_MaxFrameHistory; i++)
            {
                if (!QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Outbound].ContainsKey(i))
                {
                    QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Outbound].Add(i, new Dictionary <NetworkUpdateStage, RpcQueueHistoryFrame>());
                    var queueHistoryFrame = new RpcQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
                    queueHistoryFrame.QueueBuffer          = PooledNetworkBuffer.Get();
                    queueHistoryFrame.QueueBuffer.Position = 0;
                    queueHistoryFrame.QueueWriter          = PooledNetworkWriter.Get(queueHistoryFrame.QueueBuffer);
                    queueHistoryFrame.QueueReader          = PooledNetworkReader.Get(queueHistoryFrame.QueueBuffer);
                    queueHistoryFrame.QueueItemOffsets     = new List <uint>();

                    //For now all outbound, we will always have a single update in which they are processed (LATEUPDATE)
                    QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Outbound][i].Add(NetworkUpdateStage.PostLateUpdate, queueHistoryFrame);
                }

                if (!QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Inbound].ContainsKey(i))
                {
                    QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Inbound].Add(i, new Dictionary <NetworkUpdateStage, RpcQueueHistoryFrame>());

                    //For inbound, we create a queue history frame per update stage
                    foreach (NetworkUpdateStage netUpdateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
                    {
                        var rpcQueueHistoryFrame = new RpcQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Inbound, netUpdateStage);
                        rpcQueueHistoryFrame.QueueBuffer          = PooledNetworkBuffer.Get();
                        rpcQueueHistoryFrame.QueueBuffer.Position = 0;
                        rpcQueueHistoryFrame.QueueWriter          = PooledNetworkWriter.Get(rpcQueueHistoryFrame.QueueBuffer);
                        rpcQueueHistoryFrame.QueueReader          = PooledNetworkReader.Get(rpcQueueHistoryFrame.QueueBuffer);
                        rpcQueueHistoryFrame.QueueItemOffsets     = new List <uint>();
                        QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Inbound][i].Add(netUpdateStage, rpcQueueHistoryFrame);
                    }
                }
            }

            //As long as this instance is using the pre-defined update stages
            if (!m_ProcessUpdateStagesExternally)
            {
                //Register with the network update loop system
                this.RegisterAllNetworkUpdates();
            }
        }
        /// <summary>
        /// This will allocate [maxFrameHistory] + [1 currentFrame] number of PooledNetworkBuffers and keep them open until the session ends
        /// Note: For zero frame history set maxFrameHistory to zero
        /// </summary>
        /// <param name="maxFrameHistory"></param>
        private void Initialize(uint maxFrameHistory)
        {
            //This makes sure that we don't exceed a ridiculous value by capping the number of queue history frames to ushort.MaxValue
            //If this value is exceeded, then it will be kept at the ceiling of ushort.Maxvalue.
            //Note: If running at a 60pps rate (16ms update frequency) this would yield 17.47 minutes worth of queue frame history.
            if (maxFrameHistory > ushort.MaxValue)
            {
                if (NetworkLog.CurrentLogLevel == LogLevel.Developer)
                {
                    NetworkLog.LogWarning($"The {nameof(RpcQueueHistoryFrame)} size cannot exceed {ushort.MaxValue} {nameof(RpcQueueHistoryFrame)}s! Capping at {ushort.MaxValue} {nameof(RpcQueueHistoryFrame)}s.");
                }
                maxFrameHistory = ushort.MaxValue;
            }

            ClearParameters();

            m_RpcQueueProcessor = new RpcQueueProcessor(this, NetworkManager);
            m_MaxFrameHistory   = maxFrameHistory + k_MinQueueHistory;

            if (!m_QueueHistory.ContainsKey(RpcQueueHistoryFrame.QueueFrameType.Inbound))
            {
                m_QueueHistory.Add(RpcQueueHistoryFrame.QueueFrameType.Inbound, new Dictionary <int, Dictionary <NetworkUpdateStage, RpcQueueHistoryFrame> >());
            }

            if (!m_QueueHistory.ContainsKey(RpcQueueHistoryFrame.QueueFrameType.Outbound))
            {
                m_QueueHistory.Add(RpcQueueHistoryFrame.QueueFrameType.Outbound, new Dictionary <int, Dictionary <NetworkUpdateStage, RpcQueueHistoryFrame> >());
            }

            for (int i = 0; i < m_MaxFrameHistory; i++)
            {
                if (!m_QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Outbound].ContainsKey(i))
                {
                    m_QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Outbound].Add(i, new Dictionary <NetworkUpdateStage, RpcQueueHistoryFrame>());
                    var queueHistoryFrame = new RpcQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
                    queueHistoryFrame.QueueBuffer          = PooledNetworkBuffer.Get();
                    queueHistoryFrame.QueueBuffer.Position = 0;
                    queueHistoryFrame.QueueWriter          = PooledNetworkWriter.Get(queueHistoryFrame.QueueBuffer);
                    queueHistoryFrame.QueueReader          = PooledNetworkReader.Get(queueHistoryFrame.QueueBuffer);
                    queueHistoryFrame.QueueItemOffsets     = new List <uint>();

                    //For now all outbound, we will always have a single update in which they are processed (LATEUPDATE)
                    m_QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Outbound][i].Add(NetworkUpdateStage.PostLateUpdate, queueHistoryFrame);
                }

                if (!m_QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Inbound].ContainsKey(i))
                {
                    m_QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Inbound].Add(i, new Dictionary <NetworkUpdateStage, RpcQueueHistoryFrame>());

                    //For inbound, we create a queue history frame per update stage
                    foreach (NetworkUpdateStage netUpdateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
                    {
                        var rpcQueueHistoryFrame = new RpcQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Inbound, netUpdateStage);
                        rpcQueueHistoryFrame.QueueBuffer          = PooledNetworkBuffer.Get();
                        rpcQueueHistoryFrame.QueueBuffer.Position = 0;
                        rpcQueueHistoryFrame.QueueWriter          = PooledNetworkWriter.Get(rpcQueueHistoryFrame.QueueBuffer);
                        rpcQueueHistoryFrame.QueueReader          = PooledNetworkReader.Get(rpcQueueHistoryFrame.QueueBuffer);
                        rpcQueueHistoryFrame.QueueItemOffsets     = new List <uint>();
                        m_QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Inbound][i].Add(netUpdateStage, rpcQueueHistoryFrame);
                    }
                }
            }

            //As long as this instance is using the pre-defined update stages
            if (!m_ProcessUpdateStagesExternally)
            {
                //Register with the network update loop system
                this.RegisterAllNetworkUpdates();
            }
        }