packages feed

apecs-stm-0.3: src/Apecs/Stores/STM.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}

{-| Stores and utilities that can partipicate in STM transactions.

Use sparingly!

Although this may be faster than always putting world under MVar,
grabbing too much entities and values in a single transaction would
incur quadratic costs.
-}
module Apecs.Stores.STM
  ( -- * Stores
    TMap (..)
  , TUnique (..)
  , TGlobal (..)

    -- * EntityCounter transactions
  , EntityCounter (..)
  , nextEntity
  , newEntity
  , newEntity_

    -- * STM conveniences
  , atomically
  , retry
  , check
  , forkSys
  , threadDelay
  , STM
  ) where

import qualified Control.Concurrent as S
import Control.Concurrent.STM (STM)
import qualified Control.Concurrent.STM as S
import Control.Concurrent.STM.TVar as S
import Control.Monad
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.Trans.Class (lift)
import qualified Data.IntSet as IS
import Data.Maybe
import Data.Typeable (Typeable, typeRep)
import qualified Data.Vector.Unboxed as U
import GHC.Conc (unsafeIOToSTM)
import qualified ListT as L
import qualified StmContainers.Map as M

import Apecs (ask, runSystem, set)
import Apecs.Core
import Apecs.Util (EntityCounter (..), nextEntityIO)

newtype TMap c = TMap (M.Map Int c)
type instance Elem (TMap c) = c

instance ExplInit STM (TMap c) where
  explInit = TMap <$> M.new
instance (Typeable c) => ExplGet STM (TMap c) where
  {-# INLINE explExists #-}
  {-# INLINE explGet #-}
  explExists (TMap m) ety = isJust <$> M.lookup ety m
  explGet (TMap m) ety = flip fmap (M.lookup ety m) $ \case
    Just c -> c
    notFound ->
      error $
        unwords
          [ "Reading non-existent STM Map component"
          , show (typeRep notFound)
          , "for entity"
          , show ety
          ]

instance ExplSet STM (TMap c) where
  {-# INLINE explSet #-}
  explSet (TMap m) ety x = M.insert x ety m
instance ExplDestroy STM (TMap c) where
  {-# INLINE explDestroy #-}
  explDestroy (TMap m) ety = M.delete ety m
instance ExplMembers STM (TMap c) where
  {-# INLINE explMembers #-}
  explMembers (TMap m) = U.unfoldrM L.uncons $ fst <$> M.listT m
  {-# INLINE explMemberSet #-}
  explMemberSet (TMap m) = IS.fromList <$> L.toList (fst <$> M.listT m)

instance ExplInit IO (TMap c) where
  {-# INLINE explInit #-}
  explInit = S.atomically explInit
instance (Typeable c) => ExplGet IO (TMap c) where
  {-# INLINE explExists #-}
  {-# INLINE explGet #-}
  explExists m e = S.atomically $ explExists m e
  explGet m e = S.atomically $ explGet m e
instance ExplSet IO (TMap c) where
  {-# INLINE explSet #-}
  explSet m e x = S.atomically $ explSet m e x
instance ExplDestroy IO (TMap c) where
  {-# INLINE explDestroy #-}
  explDestroy m e = S.atomically $ explDestroy m e
instance ExplMembers IO (TMap c) where
  {-# INLINE explMembers #-}
  explMembers (TMap m) = U.unfoldrM L.uncons $ fst <$> M.listTNonAtomic m
  {-# INLINE explMemberSet #-}
  explMemberSet (TMap m) =
    L.fold (\a b -> pure $! IS.insert b a) IS.empty $ fst <$> M.listTNonAtomic m

newtype TUnique c = TUnique (TVar (Maybe (Int, c)))
type instance Elem (TUnique c) = c
instance ExplInit STM (TUnique c) where
  explInit = TUnique <$> newTVar Nothing

instance (Typeable c) => ExplGet STM (TUnique c) where
  {-# INLINE explGet #-}
  explGet (TUnique ref) _ = flip fmap (readTVar ref) $ \case
    Just (_, c) -> c
    notFound ->
      error $
        unwords
          [ "Reading non-existent STM TUnique component"
          , show (typeRep notFound)
          ]
  {-# INLINE explExists #-}
  explExists (TUnique ref) ety = maybe False ((== ety) . fst) <$> readTVar ref

instance ExplSet STM (TUnique c) where
  {-# INLINE explSet #-}
  explSet (TUnique ref) ety c = writeTVar ref (Just (ety, c))

instance ExplDestroy STM (TUnique c) where
  {-# INLINE explDestroy #-}
  explDestroy (TUnique ref) ety =
    readTVar ref
      >>= mapM_ (flip when (writeTVar ref Nothing) . (== ety) . fst)

instance ExplMembers STM (TUnique c) where
  {-# INLINE explMembers #-}
  explMembers (TUnique ref) = flip fmap (readTVar ref) $ \case
    Nothing -> mempty
    Just (ety, _) -> U.singleton ety
  {-# INLINE explMemberSet #-}
  explMemberSet (TUnique ref) = flip fmap (readTVar ref) $ \case
    Nothing -> mempty
    Just (ety, _) -> IS.singleton ety

instance ExplInit IO (TUnique c) where
  {-# INLINE explInit #-}
  explInit = S.atomically explInit
instance (Typeable c) => ExplGet IO (TUnique c) where
  {-# INLINE explExists #-}
  explExists (TUnique ref) ety = maybe False ((== ety) . fst) <$> readTVarIO ref
  {-# INLINE explGet #-}
  explGet m e = S.atomically $ explGet m e
instance ExplSet IO (TUnique c) where
  {-# INLINE explSet #-}
  explSet m e x = S.atomically $ explSet m e x
instance ExplDestroy IO (TUnique c) where
  {-# INLINE explDestroy #-}
  explDestroy m e = S.atomically $ explDestroy m e
instance ExplMembers IO (TUnique c) where
  {-# INLINE explMembers #-}
  explMembers (TUnique ref) = flip fmap (readTVarIO ref) $ \case
    Nothing -> mempty
    Just (ety, _) -> U.singleton ety
  {-# INLINE explMemberSet #-}
  explMemberSet (TUnique ref) = flip fmap (readTVarIO ref) $ \case
    Nothing -> mempty
    Just (ety, _) -> IS.singleton ety

newtype TGlobal c = TGlobal (TVar c)
type instance Elem (TGlobal c) = c
instance (Monoid c) => ExplInit STM (TGlobal c) where
  {-# INLINE explInit #-}
  explInit = TGlobal <$> newTVar mempty
instance ExplGet STM (TGlobal c) where
  {-# INLINE explGet #-}
  explGet (TGlobal ref) _ = readTVar ref
  {-# INLINE explExists #-}
  explExists _ _ = return True
instance ExplSet STM (TGlobal c) where
  {-# INLINE explSet #-}
  explSet (TGlobal ref) _ c = writeTVar ref c

instance (Monoid c) => ExplInit IO (TGlobal c) where
  {-# INLINE explInit #-}
  explInit = TGlobal <$> newTVarIO mempty
instance ExplGet IO (TGlobal c) where
  {-# INLINE explExists #-}
  explExists _ _ = return True
  {-# INLINE explGet #-}
  explGet (TGlobal ref) _ = readTVarIO ref
instance ExplSet IO (TGlobal c) where
  {-# INLINE explSet #-}
  explSet m e x = S.atomically $ explSet m e x

{-# INLINE nextEntity #-}
nextEntity :: (Has w IO EntityCounter) => SystemT w STM Entity
nextEntity = ask >>= lift . unsafeIOToSTM . runSystem nextEntityIO

{-# INLINE newEntity #-}
newEntity :: (Set w STM c, Has w IO EntityCounter) => c -> SystemT w STM Entity
newEntity c = do
  ety <- nextEntity
  set ety c
  return ety

{-# INLINE newEntity_ #-}
newEntity_ :: (Set w STM c, Has w IO EntityCounter) => c -> SystemT w STM ()
newEntity_ c = do
  ety <- nextEntity
  set ety c

-- -- | Like @makeWorld@ from @Apecs@, but uses the STM @EntityCounter@
-- makeWorld :: String -> [Name] -> Q [Dec]
-- makeWorld worldName cTypes = makeWorldNoEC worldName (cTypes ++ [''EntityCounter])

-- -- | Like @makeWorldAndComponents@ from @Apecs@, but uses the STM @EntityCounter@ and the STM @Map@
-- makeWorldAndComponents :: String -> [Name] -> Q [Dec]
-- makeWorldAndComponents worldName cTypes = do
--   wdecls <- makeWorld worldName cTypes
--   cdecls <- makeMapComponentsFor ''Map cTypes
--   pure $ wdecls ++ cdecls

-- | @atomically@ from STM, lifted to the System level.
atomically :: SystemT w STM a -> SystemT w IO a
atomically sys = ask >>= liftIO . S.atomically . runSystem sys

-- | @retry@ from STM, lifted to the System level.
retry :: SystemT w STM a
retry = lift S.retry

-- | @check@ from STM, lifted to the System level.
check :: Bool -> SystemT w STM ()
check = lift . S.check

-- | Runs a system on a new thread.
forkSys :: SystemT w IO () -> SystemT w IO S.ThreadId
forkSys sys = ask >>= liftIO . S.forkIO . runSystem sys

-- | Suspends the current thread for a number of microseconds.
threadDelay :: Int -> SystemT w IO ()
threadDelay = liftIO . S.threadDelay