1
1
mirror of https://github.com/aelve/guide.git synced 2024-11-27 10:10:50 +03:00
guide/src/Cache.hs

139 lines
4.6 KiB
Haskell
Raw Normal View History

2016-05-01 16:28:10 +03:00
{-# LANGUAGE
DeriveGeneric,
NoImplicitPrelude
#-}
module Cache
(
CacheKey(..),
invalidateCache,
2016-07-21 16:26:08 +03:00
emptyCache,
2016-05-01 16:28:10 +03:00
cached,
)
where
-- General
import BasePrelude hiding (Category)
-- Lenses
import Lens.Micro.Platform hiding ((&))
-- Monads and monad transformers
import Control.Monad.IO.Class
import Control.Monad.Trans
-- ByteString
import qualified Data.ByteString.Lazy as BSL
-- Concurrent map
import qualified STMContainers.Map as STMMap
import Data.Hashable
-- Lucid
import Lucid.Base
-- local
import Types
import Utils
type Cache = STMMap.Map CacheKey BSL.ByteString
cache :: Cache
{-# NOINLINE cache #-}
cache = unsafePerformIO STMMap.newIO
data CacheKey
= CacheCategoryList
-- categories
2016-05-05 16:50:10 +03:00
| CacheCategory (Uid Category)
| CacheCategoryInfo (Uid Category)
| CacheCategoryNotes (Uid Category)
2016-05-01 16:28:10 +03:00
-- items
| CacheItem (Uid Item)
| CacheItemInfo (Uid Item)
| CacheItemDescription (Uid Item)
| CacheItemEcosystem (Uid Item)
| CacheItemTraits (Uid Item)
| CacheItemNotes (Uid Item)
deriving (Show, Eq, Generic)
instance Hashable CacheKey
cacheDepends :: GlobalState -> CacheKey -> [CacheKey]
cacheDepends gs key = case key of
2016-05-05 16:50:10 +03:00
CacheCategoryList -> [key]
CacheCategory _ -> [key, CacheCategoryList]
2016-05-22 14:43:46 +03:00
-- If the category's prosConsEnabled/ecosystemEnabled have changed, we'd
-- have to render *all* items differently (and CacheCategoryInfo includes
-- prosConsEnabled/ecosystemEnabled). See Note [enabled sections].
CacheCategoryInfo x ->
[key, CacheCategory x, CacheCategoryList] ++
-- A convoluted way to say “find category with uid x”
map CacheItem (gs ^.. categories.each.filtered(hasUid x).items.each.uid)
2016-05-05 16:50:10 +03:00
CacheCategoryNotes x -> [key, CacheCategory x, CacheCategoryList]
2016-05-01 16:28:10 +03:00
-- If the item's group has been changed, it can influence how other items
-- in the same category are rendered (specifically, their lists of groups
-- in iteminfo will change)
CacheItem x ->
let cat = findCategoryByItem x gs
in CacheCategory (cat^.uid) :
-- This will cover (CacheItem x) so we don't need to prepend it
-- manually like we did before with including 'key' into lists
map CacheItem (cat^..items.each.uid) ++
map CacheItemInfo (cat^..items.each.uid)
-- Be careful if the presentation of the category list ever changes
-- (e.g. to include lists of items in categories, or their counts, or
-- something), we'd have to invalidate CacheCategoryList here too.
CacheItemInfo x ->
let cat = findCategoryByItem x gs
in CacheCategory (cat^.uid) :
map CacheItem (cat^..items.each.uid) ++
map CacheItemInfo (cat^..items.each.uid)
CacheItemDescription x ->
let cat = findCategoryByItem x gs
in [key, CacheItem x, CacheCategory (cat^.uid)]
CacheItemEcosystem x ->
let cat = findCategoryByItem x gs
in [key, CacheItem x, CacheCategory (cat^.uid)]
CacheItemTraits x ->
let cat = findCategoryByItem x gs
in [key, CacheItem x, CacheCategory (cat^.uid)]
CacheItemNotes x ->
let cat = findCategoryByItem x gs
in [key, CacheItem x, CacheCategory (cat^.uid)]
-- Note that cache invalidation has to happen before the actual action is
-- performed, because otherwise bad things can happen (e.g. if we're deleting
-- an item we have to invalidate the cache for its category, but if we've
-- already deleted the item we can't look up its category).
invalidateCache :: MonadIO m => GlobalState -> CacheKey -> m ()
invalidateCache gs key = liftIO $ atomically $ do
for_ (cacheDepends gs key) $ \k ->
STMMap.delete k cache
2016-07-21 16:26:08 +03:00
emptyCache :: MonadIO m => m ()
2016-07-25 17:55:10 +03:00
emptyCache = liftIO $ atomically $ STMMap.deleteAll cache
2016-07-21 16:26:08 +03:00
2016-05-01 16:28:10 +03:00
cached :: MonadIO m => CacheKey -> HtmlT m () -> HtmlT m ()
cached key gen = do
mbRes <- liftIO . atomically $ STMMap.lookup key cache
case mbRes of
Just res -> toHtmlRaw res
Nothing -> do
bs <- lift $ renderBST gen
-- TODO: this bad situation is *maybe* possible:
--
-- item is changed in request A
-- request A invalidates cache
-- request A starts re-rendering the item
-- 'cached' started by A sees that the cache is empty
-- item is changed in request B
-- request B starts re-rendering the item
-- request B fills the cache
-- 'cached' started by A finishes rendering
-- 'cached' started by A fills the cache with an outdated render
--
-- It's unlikely, but still probably possible. Ideally we'd like to run
-- lookup *and* insert inside the same transaction, but I don't know
-- how to do it.
liftIO . atomically $ STMMap.insert bs key cache
toHtmlRaw bs