/// <summary>
        /// Send a message to this exact same netpeer (loopback).
        /// </summary>
        public void SendUnconnectedToSelf(NetOutgoingMessage message)
        {
            if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }
            message.AssertNotSent(nameof(message));

            if (Socket == null)
            {
                throw new InvalidOperationException("No socket bound.");
            }

            message._messageType = NetMessageType.Unconnected;
            message._isSent      = true;

            if (!Configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData))
            {
                return; // dropping unconnected message since it's not enabled for receiving
            }
            var om = CreateIncomingMessage(NetIncomingMessageType.UnconnectedData);

            om.Write(message);
            om.IsFragment       = false;
            om.ReceiveTime      = NetTime.Now;
            om.SenderConnection = null;
            om.SenderEndPoint   = Socket.LocalEndPoint as IPEndPoint;
            LidgrenException.Assert(om.BitLength == message.BitLength);

            ReleaseMessage(om);
        }
        // public double LastSendRespondedTo { get { return m_connection.m_lastSendRespondedTo; } }

        internal void PacketSent(int numBytes, int numMessages)
        {
            LidgrenException.Assert(numBytes > 0 && numMessages > 0);
            _sentPackets++;
            _sentBytes    += numBytes;
            _sentMessages += numMessages;
        }
 internal void PacketReceived(int numBytes, int numMessages, int numFragments)
 {
     LidgrenException.Assert(numBytes > 0 && numMessages > 0);
     _receivedPackets++;
     _receivedBytes     += numBytes;
     _receivedMessages  += numMessages;
     _receivedFragments += numFragments;
 }
        public override int GetAllowedSends()
        {
            int retval = _windowSize - NetUtility.PowOf2Mod(
                _sendStart + NetConstants.SequenceNumbers - _windowStart,
                NetConstants.SequenceNumbers);

            LidgrenException.Assert(retval >= 0 && retval <= _windowSize);
            return(retval);
        }
        // send message immediately
        internal void SendLibraryMessage(NetOutgoingMessage message, IPEndPoint recipient)
        {
            AssertIsOnLibraryThread();
            LidgrenException.Assert(!message._isSent);

            int length = 0;

            message.Encode(_sendBuffer, ref length, 0);
            SendPacket(length, recipient, 1);
        }
        /// <summary>
        /// Compress (lossy) a <see cref="float"/> in the range 0..1 using the specified amount of bits.
        /// </summary>
        public static void WriteUnit(this IBitBuffer buffer, float value, int bitCount)
        {
            LidgrenException.Assert(
                (value >= 0.0) && (value <= 1.0),
                " WriteUnitSingle() must be passed a float in the range 0 to 1; val is " + value);

            int  maxValue = (1 << bitCount) - 1;
            uint writeVal = (uint)(value * maxValue);

            buffer.Write(writeVal, bitCount);
        }
        /// <summary>
        /// Compress a <see cref="float"/> within a specified range using the specified amount of bits.
        /// </summary>
        public static void WriteRanged(this IBitBuffer buffer, float value, float min, float max, int bitCount)
        {
            LidgrenException.Assert(
                (value >= min) && (value <= max),
                " WriteRangedSingle() must be passed a float in the range MIN to MAX; val is " + value);

            float range  = max - min;
            float unit   = (value - min) / range;
            int   maxVal = (1 << bitCount) - 1;

            buffer.Write((uint)(maxVal * unit), bitCount);
        }
        /// <summary>
        /// Compress (lossy) a <see cref="float"/> in the range -1..1 using the specified amount of bits.
        /// </summary>
        public static void WriteSigned(this IBitBuffer buffer, float value, int bitCount)
        {
            LidgrenException.Assert(
                (value >= -1.0) && (value <= 1.0),
                " WriteSignedSingle() must be passed a float in the range -1 to 1; val is " + value);

            float unit     = (value + 1f) * 0.5f;
            int   maxVal   = (1 << bitCount) - 1;
            uint  writeVal = (uint)(unit * maxVal);

            buffer.Write(writeVal, bitCount);
        }
        /// <summary>
        /// Writes an <see cref="int"/> with the least amount of bits needed for the specified range.
        /// Returns the number of bits written.
        /// </summary>
        public static int WriteRanged(this IBitBuffer buffer, int min, int max, int value)
        {
            LidgrenException.Assert(value >= min && value <= max, "Value not within min/max range!");

            uint range   = (uint)(max - min);
            int  numBits = NetBitWriter.BitsForValue(range);

            uint rvalue = (uint)(value - min);

            buffer.Write(rvalue, numBits);

            return(numBits);
        }
        public override int GetAllowedSends()
        {
            if (!_doFlowControl)
            {
                return(int.MaxValue);
            }

            int value = _windowSize - NetUtility.PowOf2Mod(
                _sendStart + NetConstants.SequenceNumbers - _windowStart,
                _windowSize);

            LidgrenException.Assert(value >= 0 && value <= _windowSize);
            return(value);
        }
        // remoteWindowStart is remote expected sequence number; everything below this has arrived properly
        // seqNr is the actual nr received
        public override NetSocketResult ReceiveAcknowledge(TimeSpan now, int seqNr)
        {
            if (!_doFlowControl)
            {
                // we have no use for acks on this channel since we don't respect the window anyway
                _connection.Peer.LogWarning(new NetLogMessage(NetLogCode.SuppressedUnreliableAck, endPoint: _connection));
                return(new NetSocketResult(true, false));
            }

            // late (dupe), on time or early ack?
            int relate = NetUtility.RelativeSequenceNumber(seqNr, _windowStart);

            if (relate < 0)
            {
                //m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr);
                return(new NetSocketResult(true, false)); // late/duplicate ack
            }

            if (relate == 0)
            {
                //m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr);

                // ack arrived right on time
                LidgrenException.Assert(seqNr == _windowStart);

                _receivedAcks[_windowStart] = false;
                _windowStart = NetUtility.PowOf2Mod(_windowStart + 1, NetConstants.SequenceNumbers);
                return(new NetSocketResult(true, false));
            }

            // Advance window to this position
            _receivedAcks[seqNr] = true;

            while (_windowStart != seqNr)
            {
                _receivedAcks[_windowStart] = false;
                _windowStart = NetUtility.PowOf2Mod(_windowStart + 1, NetConstants.SequenceNumbers);
            }

            return(new NetSocketResult(true, false));
        }
        // on user thread
        // the message must not be sent already
        private NetSendResult SendFragmentedMessage(
            NetOutgoingMessage message,
            IEnumerable <NetConnection?> recipients,
            NetDeliveryMethod method,
            int sequenceChannel)
        {
            // determine minimum mtu for all recipients
            int mtu = GetMTU(recipients, out int recipientCount);

            if (recipientCount == 0)
            {
                Recycle(message);
                return(NetSendResult.NoRecipients);
            }

            int group = GetNextFragmentGroup();

            // do not send msg; but set fragmentgroup in case user tries to recycle it immediately
            message._fragmentGroup = group << 2 | 1;

            // create fragmentation specifics
            int totalBytes = message.ByteLength;

            int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu);

            int numChunks = totalBytes / bytesPerChunk;

            if (numChunks * bytesPerChunk < totalBytes)
            {
                numChunks++;
            }

            var retval = NetSendResult.Sent;

            int bitsPerChunk = bytesPerChunk * 8;
            int bitsLeft     = message.BitLength;

            byte[] buffer = message.GetBuffer();

            for (int i = 0; i < numChunks; i++)
            {
                NetOutgoingMessage chunk = CreateMessage();
                chunk.SetBuffer(buffer, false);
                chunk.BitLength = Math.Min(bitsLeft, bitsPerChunk);

                chunk._fragmentGroup          = group << 2 | 1;
                chunk._fragmentGroupTotalBits = totalBytes * 8;
                chunk._fragmentChunkByteSize  = bytesPerChunk;
                chunk._fragmentChunkNumber    = i;

                LidgrenException.Assert(chunk.BitLength != 0);
                LidgrenException.Assert(chunk.GetEncodedSize() <= mtu);

                Interlocked.Add(ref chunk._recyclingCount, recipientCount);

                foreach (NetConnection?recipient in recipients.AsListEnumerator())
                {
                    if (recipient == null)
                    {
                        continue;
                    }

                    NetSendResult result = recipient.EnqueueMessage(chunk, method, sequenceChannel).Result;
                    if (result > retval)
                    {
                        retval = result; // return "worst" result
                    }
                }

                bitsLeft -= bitsPerChunk;
            }
            return(retval);
        }
        // on user thread
        private async ValueTask <NetSendResult> SendFragmentedMessageAsync(
            PipeReader reader,
            NetConnection recipient,
            int sequenceChannel,
            CancellationToken cancellationToken)
        {
            int group = GetNextFragmentGroup();

            (NetSendResult Result, NetSenderChannel?) SendChunk(NetOutgoingMessage chunk)
            {
                return(recipient.EnqueueMessage(chunk, NetDeliveryMethod.ReliableOrdered, sequenceChannel));
            }

            NetSendResult finalResult;
            Exception?    exception = null;

            try
            {
                ReadResult readResult;
                do
                {
                    readResult = await reader.ReadAsync(cancellationToken).ConfigureAwait(false);

                    if (readResult.IsCanceled)
                    {
                        NetOutgoingMessage cancelChunk = CreateStreamChunk(0, group, NetStreamFragmentType.Cancelled);
                        finalResult = SendChunk(cancelChunk).Result;
                        break;
                    }

                    ReadOnlySequence <byte> buffer = readResult.Buffer;

                    int mtu           = recipient.CurrentMTU;
                    int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, (int)buffer.Length, mtu);

                    while (buffer.Length > 0)
                    {
                        long chunkLength         = Math.Min(buffer.Length, bytesPerChunk);
                        NetOutgoingMessage chunk = CreateStreamChunk((int)chunkLength, group, NetStreamFragmentType.Data);
                        foreach (ReadOnlyMemory <byte> memory in buffer.Slice(0, chunkLength))
                        {
                            chunk.Write(memory.Span);
                        }
                        buffer = buffer.Slice(chunkLength);

                        LidgrenException.Assert(chunk.GetEncodedSize() <= mtu);
                        Interlocked.Add(ref chunk._recyclingCount, 1);

                        var(result, channel) = SendChunk(chunk);
                        if (result == NetSendResult.Queued)
                        {
                            await channel !.WaitForIdleAsync(millisecondsTimeout: 10000, cancellationToken);
                        }
                        else if (result != NetSendResult.Sent)
                        {
                            NetOutgoingMessage cancelChunk = CreateStreamChunk(0, group, NetStreamFragmentType.Cancelled);
                            finalResult = SendChunk(cancelChunk).Result;
                            reader.CancelPendingRead();
                            break;
                        }
                    }

                    reader.AdvanceTo(readResult.Buffer.End);
                }while (!readResult.IsCompleted);

                NetOutgoingMessage endChunk = CreateStreamChunk(0, group, NetStreamFragmentType.EndOfStream);
                finalResult = SendChunk(endChunk).Result;
            }
            catch (Exception ex)
            {
                exception = ex;

                NetOutgoingMessage errorChunk = CreateStreamChunk(0, group, NetStreamFragmentType.ServerError);
                finalResult = SendChunk(errorChunk).Result;
            }

            await reader.CompleteAsync(exception).ConfigureAwait(false);

            return(finalResult);
        }