packages feed

calamity-0.1.10: src/Calamity/Internal/BoundedStore.hs

-- | A thing for storing the last N things with IDs
module Calamity.Internal.BoundedStore
    ( BoundedStore(..)
    , addItem
    , getItem
    , dropItem ) where

import           Calamity.Internal.Utils
import           Calamity.Types.Snowflake

import           Control.Lens
import           Control.Monad.State.Lazy

import           Data.Default.Class
import           Data.Generics.Labels     ()
import           Data.HashMap.Lazy        ( HashMap )
import qualified Data.HashMap.Lazy        as H

import qualified Deque.Lazy               as DQ
import           Deque.Lazy               ( Deque )

import           GHC.Generics

data BoundedStore a = BoundedStore
  { itemQueue :: Deque (Snowflake a)
  , items     :: HashMap (Snowflake a) a
  , limit     :: Int
  , size      :: Int
  }
  deriving ( Show, Generic )

instance Foldable BoundedStore where
  foldr f i = foldr f i . H.elems . items

instance Default (BoundedStore a) where
  def = BoundedStore mempty H.empty 1000 0

type instance (Index (BoundedStore a)) = Snowflake a

type instance (IxValue (BoundedStore a)) = a

instance HasID' a => Ixed (BoundedStore a)

instance HasID' a => At (BoundedStore a) where
  at k f m = f mv <&> \case
    Nothing -> maybe m (const (dropItem k m)) mv
    Just v  -> addItem v m
    where
      mv = getItem k m

  {-# INLINE at #-}

addItem :: HasID' a => a -> BoundedStore a -> BoundedStore a
addItem m = execState $ do
  unlessM (H.member (getID m) <$> use #items) $ do
    #itemQueue %= DQ.cons (getID m)
    #size += 1

  size <- use #size
  limit <- use #limit

  when (size > limit) $ do
    q <- use #itemQueue
    let Just (rid, q') = DQ.unsnoc q
    #itemQueue .= q'
    #items %= sans rid
    #size -= 1

  #items %= H.insert (getID m) m

{-# INLINE addItem #-}

getItem :: Snowflake a -> BoundedStore a -> Maybe a
getItem id s = H.lookup id (s ^. #items)

{-# INLINE getItem #-}

dropItem :: Snowflake a -> BoundedStore a -> BoundedStore a
dropItem id = execState $ do
  whenM (H.member id <$> use #items) $ do
    #size -= 1

  #itemQueue %= DQ.filter (/= id)
  #items %= H.delete id

{-# INLINE dropItem #-}