public bool UpdateConnection(NetConnection connection)
        {
            if (connection == null)
            {
                throw new ArgumentNullException(nameof(connection));
            }

            if (connection.ProtocolState != ProtocolState.Closing)
            {
                return(true);
            }

            bool connected = connection.Socket.Connected;

            if (!connected || connection.SendBuffer.Length == 0)
            {
                lock (ConnectionMutex)
                {
                    if (!_connections.Remove(connection))
                    {
                        throw new InvalidOperationException();
                    }
                }

                // There won't be a queue if there was no packet send attempt during connection.
                if (Orchestrator.PacketSendQueues.TryRemove(connection, out var removedQueue))
                {
                    // There may be packet holders queued if the socket gets
                    // closed before everything is sent.
                    // The orchestrator should empty the queue.
                    Orchestrator.QueuesToFlush.Enqueue(removedQueue);
                }

                connection.Close(immediate: true);
            }
            return(false);
        }
        /// <summary>
        /// Connection flow/lifetime:
        /// Every connection begins in <see cref="EngageClientConnection"/>, where a loop reads
        /// asynchrounsly from the client socket.
        /// Successful reads get copied to the connection's receive buffer.
        /// </summary>
        public async Task EngageClientConnection(NetConnection connection, CancellationToken cancellationToken)
        {
            if (connection == null)
            {
                throw new ArgumentNullException(nameof(connection));
            }

            var readBuffer = new byte[1024 * 4]; // Clients don't really need a big read buffer.
            var readMemory = readBuffer.AsMemory();

            var socket        = connection.Socket;
            var receiveBuffer = connection.ReceiveBuffer;
            var state         = new ReceiveState(
                connection,
                new NetBinaryReader(receiveBuffer),
                cancellationToken);

            try
            {
                int read;
                while ((read = await socket.ReceiveAsync(
                            readMemory, SocketFlags.None, state.CancellationToken).Unchain()) != 0)
                {
                    // Insert received data into beginning of receive buffer.
                    var readSlice = readMemory.Slice(0, read);
                    receiveBuffer.Seek(0, SeekOrigin.End);
                    receiveBuffer.Write(readSlice.Span);
                    receiveBuffer.Seek(0, SeekOrigin.Begin);
                    connection.BytesReceived += readSlice.Length;

                    // We process by the message length (unless it's a legacy server list ping),
                    // so don't worry if we received parts of the next message.
                    OperationStatus handleStatus;
                    while ((handleStatus = HandlePacket(ref state)) == OperationStatus.Done)
                    {
                        if (!connection.IsAlive)
                        {
                            break;
                        }
                    }

                    if (!connection.IsAlive)
                    {
                        break;
                    }

                    receiveBuffer.TrimStart((int)receiveBuffer.Position);
                }
            }
            catch (SocketException sockEx) when(sockEx.SocketErrorCode == SocketError.ConnectionReset)
            {
                // TODO: increment statistic?
            }
            catch (SocketException sockEx) when(sockEx.SocketErrorCode == SocketError.ConnectionAborted)
            {
                Console.WriteLine("Connection aborted for " + connection.RemoteEndPoint);
                // TODO: increment statistic?
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                connection.Kick(ex);
                return;
            }

            connection.Close(immediate: false);
        }