rhine-1.6: src/FRP/Rhine/SN.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{- |
Asynchronous signal networks are combinations of clocked signal functions ('ClSF's)
and matching 'ResamplingBuffer's,
all satisfying the appropriate clock type constraints.
This module defines the 'SN' type,
combinators are found in a submodule.
-}
module FRP.Rhine.SN (
module FRP.Rhine.SN,
module FRP.Rhine.SN.Type,
) where
-- base
import Control.Monad (join)
-- transformers
import Control.Monad.Trans.Reader (reader)
-- automata
import Data.Stream.Result (Result (..))
-- rhine
import FRP.Rhine.ClSF.Core
import FRP.Rhine.Clock
import FRP.Rhine.Clock.Proxy
import FRP.Rhine.Clock.Util (genTimeInfo)
import FRP.Rhine.Reactimation.ClockErasure
import FRP.Rhine.ResamplingBuffer
import FRP.Rhine.SN.Type
import FRP.Rhine.Schedule
{- | A synchronous automaton is the basic building block.
For such an 'SN', data enters and leaves the system at the same rate as it is processed.
-}
synchronous ::
forall cl m a b.
(cl ~ In cl, cl ~ Out cl, Monad m, Clock m cl, GetClockProxy cl) =>
ClSF m cl a b ->
SN m cl a b
synchronous clsf = SN $ reader $ \initialTime -> proc (time, tag, Just a) -> do
b <- eraseClockClSF (getClockProxy @cl) initialTime clsf -< (time, tag, a)
returnA -< Just b
{-# INLINE synchronous #-}
-- | Two 'SN's may be sequentially composed if there is a matching 'ResamplingBuffer' between them.
sequential ::
( Clock m clab
, Clock m clcd
, Clock m (Out clab)
, Clock m (Out clcd)
, Clock m (In clab)
, Clock m (In clcd)
, GetClockProxy clab
, GetClockProxy clcd
, Time clab ~ Time clcd
, Time clab ~ Time (Out clab)
, Time clcd ~ Time (In clcd)
, Monad m
) =>
SN m clab a b ->
ResamplingBuffer m (Out clab) (In clcd) b c ->
SN m clcd c d ->
SN m (SequentialClock clab clcd) a d
-- A sequentially composed signal network may either be triggered in its first component,
-- or its second component. In either case,
-- the resampling buffer (which connects the two components) may be triggered,
-- but only if the outgoing clock of the first component ticks,
-- or the incoming clock of the second component ticks.
sequential sn1 resBuf sn2 = SN $ reader $ \initialTime ->
let
proxy1 = toClockProxy sn1
proxy2 = toClockProxy sn2
in
proc (time, tag, maybeA) -> do
resBufIn <- case tag of
Left tagL -> do
maybeB <- eraseClockSN initialTime sn1 -< (time, tagL, maybeA)
returnA -< Left <$> ((time,,) <$> outTag proxy1 tagL <*> maybeB)
Right tagR -> do
returnA -< Right . (time,) <$> inTag proxy2 tagR
maybeC <- mapMaybeS $ eraseClockResBuf (outProxy proxy1) (inProxy proxy2) initialTime resBuf -< resBufIn
case tag of
Left _ -> do
returnA -< Nothing
Right tagR -> do
eraseClockSN initialTime sn2 -< (time, tagR, join maybeC)
{-# INLINE sequential #-}
-- | Two 'SN's with the same input and output data may be parallely composed.
parallel snL snR = SN $ reader $ \initialTime -> proc (time, tag, maybeA) -> do
case tag of
Left tagL -> eraseClockSN initialTime snL -< (time, tagL, maybeA)
Right tagR -> eraseClockSN initialTime snR -< (time, tagR, maybeA)
{-# INLINE parallel #-}
-- | A 'ClSF' can always be postcomposed onto an 'SN' if the clocks match on the output.
postcompose sn clsf = SN $ reader $ \initialTime ->
let
proxy = toClockProxy sn
in
proc input@(time, tag, _) -> do
bMaybe <- eraseClockSN initialTime sn -< input
mapMaybeS $ eraseClockClSF (outProxy proxy) initialTime clsf -< (time,,) <$> outTag proxy tag <*> bMaybe
{-# INLINE postcompose #-}
-- | A 'ClSF' can always be precomposed onto an 'SN' if the clocks match on the input.
precompose clsf sn = SN $ reader $ \initialTime ->
let
proxy = toClockProxy sn
in
proc (time, tag, aMaybe) -> do
bMaybe <- mapMaybeS $ eraseClockClSF (inProxy proxy) initialTime clsf -< (time,,) <$> inTag proxy tag <*> aMaybe
eraseClockSN initialTime sn -< (time, tag, bMaybe)
{-# INLINE precompose #-}
{- | Data can be looped back to the beginning of an 'SN',
but it must be resampled since the 'Out' and 'In' clocks are generally different.
-}
feedbackSN ResamplingBuffer {buffer, put, get} sn = SN $ reader $ \initialTime ->
let
proxy = toClockProxy sn
in
feedback buffer $ proc ((time, tag, aMaybe), buf) -> do
(cMaybe, buf') <- case inTag proxy tag of
Nothing -> do
returnA -< (Nothing, buf)
Just tagIn -> do
timeInfo <- genTimeInfo (inProxy proxy) initialTime -< (time, tagIn)
Result buf' c <- arrM $ uncurry get -< (timeInfo, buf)
returnA -< (Just c, buf')
bdMaybe <- eraseClockSN initialTime sn -< (time, tag, (,) <$> aMaybe <*> cMaybe)
case (,) <$> outTag proxy tag <*> bdMaybe of
Nothing -> do
returnA -< (Nothing, buf')
Just (tagOut, (b, d)) -> do
timeInfo <- genTimeInfo (outProxy proxy) initialTime -< (time, tagOut)
buf'' <- arrM $ uncurry $ uncurry put -< ((timeInfo, d), buf')
returnA -< (Just b, buf'')
{-# INLINE feedbackSN #-}
-- | Bypass the signal network by forwarding data in parallel through a 'ResamplingBuffer'.
firstResampling sn buf = SN $ reader $ \initialTime ->
let
proxy = toClockProxy sn
in
proc (time, tag, acMaybe) -> do
bMaybe <- eraseClockSN initialTime sn -< (time, tag, fst <$> acMaybe)
let
resBufInput = case (inTag proxy tag, outTag proxy tag, snd <$> acMaybe) of
(Just tagIn, _, Just c) -> Just $ Left (time, tagIn, c)
(_, Just tagOut, _) -> Just $ Right (time, tagOut)
_ -> Nothing
dMaybe <- mapMaybeS $ eraseClockResBuf (inProxy proxy) (outProxy proxy) initialTime buf -< resBufInput
returnA -< (,) <$> bMaybe <*> join dMaybe
{-# INLINE firstResampling #-}