packages feed

mischief-ecs-0.2.0.0: src/Mischief/ECS/World/Par.hs

module Mischief.ECS.World.Par
  ( par,
    par',
    parIterList,
    parFor,
    parFor_,
    parFor',
    parFor'_,
  )
where

import Control.Concurrent.Async (async, wait)
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Reader
import Data.Foldable
import Data.IORef (modifyIORef', newIORef, readIORef)
import Data.Traversable
import GHC.Conc (numCapabilities)
import Mischief.ECS.Hidden
import Mischief.ECS.World
import Mischief.ECS.World.Defer

-- | Receives a list of @ParSystems@ and runs them in parallel. If ran in 'System', flushes the deferred queue when done!
par :: (MonadSystem w m) => [ParSystem ()] -> m ()
par !x =
  systemAny
    ( do
        par' x
        flush
    )
    (par' x)

-- | Receives a list of @ParSystems@ and runs them in parallel. Doesn't flush the deferred queue when done!
par' :: (MonadSystem w m) => [ParSystem ()] -> m ()
par' !parSystems = do
  !world <- unsafeGetWorld

  !x <- forM parSystems $ \(ParSystem !p) -> do
    !systems <- liftIO $ newIORef []
    !id <- liftIO $ async $ runReaderT p ParWorld {world = Hidden world, parDeferred = systems}
    pure (id, systems)

  for_ x $ \(!id, !systems) -> do
    liftIO $ wait id
    !systems <- liftIO $ readIORef systems
    liftIO $ modifyIORef' world.deferred (++ systems)

parIterList :: (MonadSystem w m, Foldable t) => t a -> ([a] -> ParSystem b) -> m [b]
parIterList !list !s = do
  !world <- unsafeGetWorld

  let !n = numCapabilities
  let !len = length list

  let !chunks = group (len `div` n) (toList list)

  !x <- forM chunks $ \(!chunk) -> do
    !systems <- liftIO $ newIORef []
    let ParSystem !p = s chunk
    !id <- liftIO $ async $ runReaderT p ParWorld {world = Hidden world, parDeferred = systems}
    pure (id, systems)

  for x $ \(!id, !systems) -> do
    !a <- liftIO $ wait id
    !systems <- liftIO $ readIORef systems
    liftIO $ modifyIORef' world.deferred (++ systems)
    pure a

group :: Int -> [a] -> [[a]]
group _ [] = []
group 0 !l = [l]
group !n !l = take n l : group n (drop n l)

-- | Iterates over a list in parallel. If ran from 'System', flushes the deferred queue when done.
parFor :: (Foldable t, MonadSystem w m) => t a -> (a -> ParSystem b) -> m [b]
parFor !x !s =
  systemAny
    ( do
        !a <- parFor' x s
        flush
        pure a
    )
    (parFor' x s)

-- | Iterates over a list in parallel and discards the results. If ran from 'System', flushes the deffered queue when done.
parFor_ :: (Foldable t) => t a -> (a -> ParSystem b) -> System ()
parFor_ !x !s = do
  void $ parFor' x s
  flush

-- | Iterates over a list in parallel. Doesn't flush the deferred queue when done.
parFor' :: (MonadSystem w m, Foldable t) => t a -> (a -> ParSystem b) -> m [b]
parFor' !x !s = concat <$> parIterList x (`for` s)

-- | Iterates over a list in parallel and discards the results. Doesn't flush the deffered queue when done.
parFor'_ :: (MonadSystem w m, Foldable t) => t a -> (a -> ParSystem b) -> m ()
parFor'_ !x !s = void (parIterList x (`for_` s))