packages feed

Yampa-0.13.7: src/FRP/Yampa/EventS.hs

-- |
-- Module      :  FRP.Yampa.EventS
-- Copyright   :  (c) Ivan Perez, 2014-2022
--                (c) George Giorgidze, 2007-2012
--                (c) Henrik Nilsson, 2005-2006
--                (c) Antony Courtney and Henrik Nilsson, Yale University, 2003-2004
-- License     :  BSD-style (see the LICENSE file in the distribution)
--
-- Maintainer  :  ivan.perez@keera.co.uk
-- Stability   :  provisional
-- Portability :  non-portable (GHC extensions)
--
-- Event Signal Functions and SF combinators.
--
-- Events represent values that only exist instantaneously, at discrete points
-- in time. Examples include mouse clicks, zero-crosses of monotonic continuous
-- signals, and square waves.
--
-- For signals that carry events, there should be a limit in the number of
-- events we can observe in a time period, no matter how much we increase the
-- sampling frequency.
module FRP.Yampa.EventS
    (
      -- * Basic event sources
      never
    , now
    , after
    , repeatedly
    , afterEach
    , afterEachCat
    , delayEvent
    , delayEventCat
    , edge
    , iEdge
    , edgeTag
    , edgeJust
    , edgeBy

      -- * Stateful event suppression
    , notYet
    , once
    , takeEvents
    , dropEvents

      -- * Hybrid SF combinators
    , snap
    , snapAfter
    , sample
    , sampleWindow

      -- * Repetition and switching
    , recur
    , andThen
    )
  where

import Control.Arrow

import FRP.Yampa.Arrow
import FRP.Yampa.Basic
import FRP.Yampa.Diagnostics
import FRP.Yampa.Event
import FRP.Yampa.Hybrid
import FRP.Yampa.InternalCore (SF (..), SF' (..), Time, sfConst)
import FRP.Yampa.Scan
import FRP.Yampa.Switches

infixr 5 `andThen`

-- * Basic event sources

-- | Event source that never occurs.
{-# ANN never "HLint: ignore Use const" #-}
never :: SF a (Event b)
never = SF {sfTF = \_ -> (sfNever, NoEvent)}

sfNever :: SF' a (Event b)
sfNever = sfConst NoEvent

-- | Event source with a single occurrence at time 0. The value of the event
-- is given by the function argument.
now :: b -> SF a (Event b)
now b0 = Event b0 --> never

-- | Event source with a single occurrence at or as soon after (local) time /q/
-- as possible.
after :: Time -- ^ The time /q/ after which the event should be produced
      -> b    -- ^ Value to produce at that time
      -> SF a (Event b)
after q x = afterEach [(q,x)]

-- | Event source with repeated occurrences with interval q.
-- Note: If the interval is too short w.r.t. the sampling intervals,
-- the result will be that events occur at every sample. However, no more
-- than one event results from any sampling interval, thus avoiding an
-- "event backlog" should sampling become more frequent at some later
-- point in time.
repeatedly :: Time -> b -> SF a (Event b)
repeatedly q x | q > 0 = afterEach qxs
               | otherwise = usrErr "Yampa" "repeatedly" "Non-positive period."
  where
    qxs = (q,x):qxs

-- | Event source with consecutive occurrences at the given intervals.
-- Should more than one event be scheduled to occur in any sampling interval,
-- only the first will in fact occur to avoid an event backlog.
afterEach :: [(Time,b)] -> SF a (Event b)
afterEach qxs = afterEachCat qxs >>> arr (fmap head)

-- | Event source with consecutive occurrences at the given intervals.
-- Should more than one event be scheduled to occur in any sampling interval,
-- the output list will contain all events produced during that interval.
afterEachCat :: [(Time,b)] -> SF a (Event [b])
afterEachCat [] = never
afterEachCat ((q,x):qxs)
    | q < 0     = usrErr "Yampa" "afterEachCat" "Negative period."
    | otherwise = SF {sfTF = tf0}
  where
    tf0 _ = if q <= 0
              then emitEventsScheduleNext 0.0 [x] qxs
              else (awaitNextEvent (-q) x qxs, NoEvent)

    emitEventsScheduleNext _ xs [] = (sfNever, Event (reverse xs))
    emitEventsScheduleNext t xs ((q,x):qxs)
        | q < 0     = usrErr "Yampa" "afterEachCat" "Negative period."
        | t' >= 0   = emitEventsScheduleNext t' (x:xs) qxs
        | otherwise = (awaitNextEvent t' x qxs, Event (reverse xs))
      where
        t' = t - q
    awaitNextEvent t x qxs = SF' tf -- False
      where
        tf dt _ | t' >= 0   = emitEventsScheduleNext t' [x] qxs
                | otherwise = (awaitNextEvent t' x qxs, NoEvent)
          where
            t' = t + dt

-- | Delay for events. (Consider it a triggered after, hence /basic/.)
delayEvent :: Time -> SF (Event a) (Event a)
delayEvent q | q < 0     = usrErr "Yampa" "delayEvent" "Negative delay."
             | q == 0    = identity
             | otherwise = delayEventCat q >>> arr (fmap head)

-- | Delay an event by a given delta and catenate events that occur so closely
-- so as to be /inseparable/.
delayEventCat :: Time -> SF (Event a) (Event [a])
delayEventCat q | q < 0     = usrErr "Yampa" "delayEventCat" "Negative delay."
                | q == 0    = arr (fmap (:[]))
                | otherwise = SF {sfTF = tf0}
  where
    tf0 e = ( case e of
                NoEvent -> noPendingEvent
                Event x -> pendingEvents (-q) [] [] (-q) x
            , NoEvent
            )

    noPendingEvent = SF' tf -- True
      where
        tf _ e = ( case e of
                     NoEvent -> noPendingEvent
                     Event x -> pendingEvents (-q) [] [] (-q) x
                 , NoEvent
                 )

    -- t_next is the present time w.r.t. the next scheduled event.
    -- t_last is the present time w.r.t. the last scheduled event.
    -- In the event queues, events are associated with their time
    -- w.r.t. to preceding event (positive).
    pendingEvents t_last rqxs qxs t_next x = SF' tf -- True
      where
        tf dt e
            | t_next' >= 0
            = emitEventsScheduleNext e t_last' rqxs qxs t_next' [x]
            | otherwise
            = (pendingEvents t_last'' rqxs' qxs t_next' x, NoEvent)
          where
            t_next' = t_next  + dt
            t_last' = t_last  + dt
            (t_last'', rqxs') =
              case e of
                NoEvent  -> (t_last', rqxs)
                Event x' -> (-q, (t_last'+q,x') : rqxs)

    -- t_next is the present time w.r.t. the *scheduled* time of the
    -- event that is about to be emitted (i.e. >= 0).
    -- The time associated with any event at the head of the event
    -- queue is also given w.r.t. the event that is about to be emitted.
    -- Thus, t_next - q' is the present time w.r.t. the event at the head
    -- of the event queue.
    emitEventsScheduleNext e _ [] [] _ rxs =
      ( case e of
          NoEvent -> noPendingEvent
          Event x -> pendingEvents (-q) [] [] (-q) x
      , Event (reverse rxs)
      )
    emitEventsScheduleNext e t_last rqxs [] t_next rxs =
      emitEventsScheduleNext e t_last [] (reverse rqxs) t_next rxs
    emitEventsScheduleNext e t_last rqxs ((q', x') : qxs') t_next rxs
      | q' > t_next = ( case e of
                          NoEvent ->
                            pendingEvents t_last
                                          rqxs
                                          qxs'
                                          (t_next - q')
                                          x'
                          Event x'' ->
                            pendingEvents (-q)
                                          ((t_last+q, x'') : rqxs)
                                          qxs'
                                          (t_next - q')
                                          x'
                      , Event (reverse rxs)
                      )
      | otherwise   = emitEventsScheduleNext e
                                             t_last
                                             rqxs
                                             qxs'
                                             (t_next - q')
                                             (x' : rxs)

-- | A rising edge detector. Useful for things like detecting key presses.
-- It is initialised as /up/, meaning that events occurring at time 0 will
-- not be detected.
edge :: SF Bool (Event ())
edge = iEdge True

-- | A rising edge detector that can be initialized as up ('True', meaning
--   that events occurring at time 0 will not be detected) or down
--   ('False', meaning that events occurring at time 0 will be detected).
iEdge :: Bool -> SF Bool (Event ())
iEdge b = sscanPrim f (if b then 2 else 0) NoEvent
  where
    f :: Int -> Bool -> Maybe (Int, Event ())
    f 0 False = Nothing
    f 0 True  = Just (1, Event ())
    f 1 False = Just (0, NoEvent)
    f 1 True  = Just (2, NoEvent)
    f 2 False = Just (0, NoEvent)
    f 2 True  = Nothing
    f _ _     = undefined

-- | Like 'edge', but parameterized on the tag value.
edgeTag :: a -> SF Bool (Event a)
edgeTag a = edge >>> arr (`tag` a)

-- | Edge detector particularized for detecting transitions
--   on a 'Maybe' signal from 'Nothing' to 'Just'.
edgeJust :: SF (Maybe a) (Event a)
edgeJust = edgeBy isJustEdge (Just undefined)
  where
    isJustEdge Nothing  Nothing     = Nothing
    isJustEdge Nothing  ma@(Just _) = ma
    isJustEdge (Just _) (Just _)    = Nothing
    isJustEdge (Just _) Nothing     = Nothing

-- | Edge detector parameterized on the edge detection function and initial
-- state, i.e., the previous input sample. The first argument to the
-- edge detection function is the previous sample, the second the current one.
edgeBy :: (a -> a -> Maybe b) -> a -> SF a (Event b)
edgeBy isEdge a_init = SF {sfTF = tf0}
  where
    tf0 a0 = (ebAux a0, maybeToEvent (isEdge a_init a0))

    ebAux a_prev = SF' tf -- True
      where
        tf _ a = (ebAux a, maybeToEvent (isEdge a_prev a))

-- * Stateful event suppression

-- | Suppression of initial (at local time 0) event.
notYet :: SF (Event a) (Event a)
notYet = initially NoEvent

-- | Suppress all but the first event.
once :: SF (Event a) (Event a)
once = takeEvents 1

-- | Suppress all but the first n events.
takeEvents :: Int -> SF (Event a) (Event a)
takeEvents n | n <= 0 = never
takeEvents n = dSwitch (arr dup) (const (NoEvent >-- takeEvents (n - 1)))

-- | Suppress first n events.
dropEvents :: Int -> SF (Event a) (Event a)
dropEvents n | n <= 0  = identity
dropEvents n =
  -- Here dSwitch or switch does not really matter.
  dSwitch (never &&& identity)
          (const (NoEvent >-- dropEvents (n - 1)))

-- ** Hybrid continuous-to-discrete SF combinators.

-- | Event source with a single occurrence at time 0. The value of the event is
-- obtained by sampling the input at that time.
snap :: SF a (Event a)
snap =
  -- switch ensures that the entire signal function will become just
  -- "constant" once the sample has been taken.
  switch (never &&& (identity &&& now () >>^ \(a, e) -> e `tag` a)) now

-- | Event source with a single occurrence at or as soon after (local) time
-- @t_ev@ as possible. The value of the event is obtained by sampling the input
-- a that time.
snapAfter :: Time -> SF a (Event a)
snapAfter t_ev =
  switch (never &&& (identity &&& after t_ev () >>^ \(a, e) -> e `tag` a)) now

-- | Sample a signal at regular intervals.
sample :: Time -> SF a (Event a)
sample p_ev = identity &&& repeatedly p_ev () >>^ \(a, e) -> e `tag` a

-- | Window sampling
--
-- First argument is the window length wl, second is the sampling interval t.
-- The output list should contain (min (truncate (T/t) wl)) samples, where
-- T is the time the signal function has been running. This requires some
-- care in case of sparse sampling. In case of sparse sampling, the
-- current input value is assumed to have been present at all points where
-- sampling was missed.
sampleWindow :: Int -> Time -> SF a (Event [a])
sampleWindow wl q =
    identity &&& afterEachCat (repeat (q, ()))
    >>> arr (\(a, e) -> fmap (map (const a)) e)
    >>> accumBy updateWindow []
  where
    updateWindow w as = drop (max (length w' - wl) 0) w'
      where w' = w ++ as

-- * Repetition and switching

-- | Makes an event source recurring by restarting it as soon as it has an
-- occurrence.
recur :: SF a (Event b) -> SF a (Event b)
recur sfe = switch (never &&& sfe) $ \b -> Event b --> (recur (NoEvent-->sfe))

-- | Apply the first SF until it produces an event, and, afterwards, switch to
-- the second SF. This is just a convenience function, used to write what
-- sometimes is more understandable switch-based code.
andThen :: SF a (Event b) -> SF a (Event b) -> SF a (Event b)
sfe1 `andThen` sfe2 = dSwitch (sfe1 >>^ dup) (const sfe2)