packages feed

moonlight-triangulation-0.1.0.0: src-parallel/Moonlight/Triangulation/Parallel.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}

-- | Bounded concurrent interpretation of the canonical join tournament. This
-- layer owns effects only: pairing and pair-schedule semantics remain in the
-- pure build planner.
module Moonlight.Triangulation.Parallel
  ( unionsConcurrently
  ) where

import Control.Concurrent.Async (concurrently)
import Control.DeepSeq (NFData, force)
import Control.Exception (evaluate)
import Data.List.NonEmpty (NonEmpty)
import Moonlight.Triangulation.Internal.Join
  ( TournamentPlan (..)
  , joinNormalForm
  , planTournament
  )
import Moonlight.Triangulation.Internal.Representation (Triangulation)
import Moonlight.Triangulation.Internal.Types (BuildError, ConstraintMode (Unconstrained))
import Moonlight.Triangulation.JoinSemilattice (JoinSemilattice)

-- | Execute the same deterministic tournament as sequential @unions@, with
-- no more than the requested number of leaf computations live at once. A
-- non-positive request means one worker; requesting more workers than leaves
-- is harmless. Each child is fully evaluated before its parent becomes
-- runnable, so thunks do not smuggle unbounded work across the boundary.
unionsConcurrently
  :: (JoinSemilattice annotation, NFData annotation)
  => Int
  -> NonEmpty (Triangulation 'Unconstrained annotation () () ())
  -> IO (Either BuildError (Triangulation 'Unconstrained annotation () () ()))
unionsConcurrently requestedWorkers operands =
  executeTournamentConcurrently
    (max 1 requestedWorkers)
    (planTournament operands)

executeTournamentConcurrently
  :: (JoinSemilattice annotation, NFData annotation)
  => Int
  -> TournamentPlan (Triangulation 'Unconstrained annotation () () ())
  -> IO (Either BuildError (Triangulation 'Unconstrained annotation () () ()))
executeTournamentConcurrently workers tournament =
  case tournament of
    TournamentLeaf mesh -> Right <$> evaluate (force mesh)
    TournamentNode left right
      | workers <= 1 ->
            publishUnion
            =<< ((,)
                  <$> executeTournamentConcurrently 1 left
                  <*> executeTournamentConcurrently 1 right
                )
      | otherwise -> do
          let !leftLeaves = tournamentLeaves left
              !rightLeaves = tournamentLeaves right
              !available = min workers (leftLeaves + rightLeaves)
              !leftWorkers =
                max 1 (min leftLeaves (available * leftLeaves `quot` (leftLeaves + rightLeaves)))
              !rightWorkers = max 1 (available - leftWorkers)
          concurrently
            (executeTournamentConcurrently leftWorkers left)
            (executeTournamentConcurrently rightWorkers right)
            >>= publishUnion

publishUnion
  :: (JoinSemilattice annotation, NFData annotation)
  => ( Either BuildError (Triangulation 'Unconstrained annotation () () ())
     , Either BuildError (Triangulation 'Unconstrained annotation () () ())
     )
  -> IO (Either BuildError (Triangulation 'Unconstrained annotation () () ()))
publishUnion (Left failure, _) = pure (Left failure)
publishUnion (Right _, Left failure) = pure (Left failure)
publishUnion (Right leftMesh, Right rightMesh) =
  evaluate (force (joinNormalForm leftMesh rightMesh))

tournamentLeaves :: TournamentPlan mesh -> Int
tournamentLeaves tournament =
  case tournament of
    TournamentLeaf _ -> 1
    TournamentNode left right -> tournamentLeaves left + tournamentLeaves right
{-# INLINE tournamentLeaves #-}