packages feed

second-transfer-0.10.0.1: hs-src/SecondTransfer/Http2/OutputTray.hs

{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}
module SecondTransfer.Http2.OutputTray (
                 TrayEntry                                  (..)
               , systemPriority_TyE
               , streamPriority_TyE
               , streamOrdinal_TyE
               , payload_TyE
               , streamId_TyE

               , OutputTray                                 (..)
               , filling_OuT
               , maxLength_OuT
               , entries_OuT

               , newOutputTray
               , splitOverSize
               , addEntry
               , lowestCalmValue
       ) where

import           Control.Lens
--import           Control.Concurrent

-- import qualified Data.Vector.Mutable                         as MVec
import qualified Data.Vector                                 as DVec
import qualified Data.Vector.Algorithms.Merge                as Dam


--import qualified Data.ByteString                             as B
import qualified Data.ByteString.Lazy                        as LB


-- | The output tray.... all data is sorted according to the priority
--   assigned here ....
--
--
--   System priorities:  0 for data frames
--               -1 for header and other HTTP/2 low level
--               -2 for go-away frame. The deliverer is expected to close
--                  the connection inmmediately after.
--               - something_else for the PingFrame (have to look it put)
--
--   Stream Ordinary priorities are assigned by the worker.
--
data TrayEntry = TrayEntry {
    _systemPriority_TyE           :: !Int
  , _streamPriority_TyE           :: !Int
  , _streamOrdinal_TyE            :: !Int

  -- Informative ....
  , _payload_TyE                  :: !LB.ByteString
  , _streamId_TyE                 :: !Int
  -- Maybe: delivery callback
    }
    deriving (Show)

makeLenses ''TrayEntry

type EntryListBuilder = [TrayEntry] -> [TrayEntry]

data OutputTray = OutputTray {
    _filling_OuT         :: Int
  , _maxLength_OuT       :: Int
  , _entries_OuT         :: !EntryListBuilder
    }

makeLenses ''OutputTray

trayCompareKey :: TrayEntry -> (Int, Int)
trayCompareKey te =
  (te ^. systemPriority_TyE, te ^. streamPriority_TyE )


trayEntryCompare :: TrayEntry -> TrayEntry -> Ordering
trayEntryCompare te0 te1 = compare (trayCompareKey te0) (trayCompareKey te1)


newOutputTray ::  Int -> OutputTray
newOutputTray max_length =
    OutputTray {
        _filling_OuT = 0
      , _maxLength_OuT = max_length
      , _entries_OuT = id
    }


-- You are in carge of protecting the access to the tray!
addEntry :: OutputTray -> TrayEntry -> OutputTray
addEntry tray entry =
  let
    ot1 = over entries_OuT ( . (entry : ) ) tray
    ot2 = over filling_OuT ( + 1 ) ot1
  in ot2


-- Returns the highest value of the calm (aka priority).
-- Unfortunately fetching this value drops the structure into a concrete
-- state....
lowestCalmValue :: OutputTray -> (OutputTray, Int)
lowestCalmValue output_tray =
  let
    entries_list = (output_tray ^. entries_OuT) []

    result = foldl (\ hc entry ->
              let
                 stream_priority = entry ^. streamPriority_TyE
              in if (entry ^. systemPriority_TyE >= 0)
                 &&
                 stream_priority < hc
                 then
                   stream_priority
                 else
                   hc
          ) ( 4000000000 ) entries_list

    new_tray_fn incoming =
        foldr (:) incoming entries_list

    new_tray =
        set entries_OuT new_tray_fn output_tray
    in (new_tray,  result )


splitOverSize :: Int -> OutputTray -> (OutputTray, [TrayEntry])
splitOverSize sz ot0 =
  let
    entries_list = (ot0 ^. entries_OuT) []
    -- First let's get a vector from here
    entries_vector = DVec.fromList entries_list
    had_entries_count = DVec.length entries_vector
    -- Then let's sort it, stably so that ordinals are automatically
    -- kept (otherwise the data will break), but system and high priority
    -- ones go before everybody else
    sorted_entries :: DVec.Vector TrayEntry
    sorted_entries =
        DVec.modify
        (\ mvec -> Dam.sortBy trayEntryCompare mvec)
        entries_vector

    -- And let' returns the ones which are interesting....
    go :: Int -> Int -> Int
    go taken_size idx
      | taken_size < sz && idx < DVec.length sorted_entries
          = go (taken_size + (sorted_entries DVec.! idx) ^. payload_TyE . to (fromIntegral . LB.length) ) (idx+1)
      | taken_size >= sz
          = idx
      | otherwise
          = idx

    take_this_many = go 0 0
    entries_to_take = DVec.toList . DVec.take take_this_many $ sorted_entries
    entries_to_leave = DVec.drop take_this_many sorted_entries

    entries_to_leave_list_builder :: [TrayEntry] -> [TrayEntry]
    entries_to_leave_list_builder incoming = DVec.foldr' (:) incoming entries_to_leave
    new_tray =
      (set filling_OuT (had_entries_count - take_this_many) ) .
      (set entries_OuT entries_to_leave_list_builder)
      $ ot0
  in (new_tray, entries_to_take)