Dealing with the current time in reactive-banana

前端 未结 1 825
无人及你
无人及你 2021-01-12 23:48

How do you deal with the current time in reactive-banana?

Ideally I\'d like to have a Behaviour which I can \"poll\" to get the current time. Howeve

1条回答
  •  再見小時候
    2021-01-13 00:30

    Given your example use case, I think you should be fine if you stay away from fromPoll. To explain why, a few clarifications are needed. (Note: in what follows, "stream" refers to an Event t a, and "occurrence" to one of the firings which compose them.)

    However, polling Behaviours with Events (via <@ etc.) gives me the value of the Behaviour from the previous Event, not the current value.

    I suppose you are alluding to explanations such as this one, from the docs for stepper:

    Note that the smaller-than-sign in the comparision timex < time means that the value of the behavior changes "slightly after" the event occurrences. This allows for recursive definitions.

    That delay, however, is only with respect to the stream used to define the behaviour (i.e. the one you pass to stepper/accumB) and any streams that are synchronised with it. For instance, suppose you have two independent streams, eTick and eTock, and the following network snippet:

    eIncrement = (+1) <$ eTick
    bCount = accumB 0 eIncrement
    eCountTick = bCount <@ eTick
    
    eCountTock = bCount <@ eTock
    

    eIncrement and eCountTick are in sync with eTick, and so the value observed through eCountTick is the "old" value; that is, the value before the synchronised update. From the point of view given by eCountTock, however, none of that matters. To an observer using eCountTock, there is no delay to speak of, and the value is always the current one.

    Behaviours that are observed from fromPoll cannot depend on themselves, thus no cycles can be introduced by observing the behaviour just before this Event is fired rather than just after the previous Event fired.

    We are only concerned with streams synchronised with the one which updates the behaviour. Thus, as far as observed values go "just before the next occurrence" and "just after the previous occurrence" boil down to the same thing. fromPoll, however, muddles things quite a bit. It creates a behaviour which is updated whenever any occurrence happens in the event network; and so the updates are synchronised with the union of all streams. There is no such thing as a stream independent from a fromPoll event, and therefore the observed value will be affected by the delay however we observe it. That being so, fromPoll won't work for an application-driving clock, which requires tracking continuous change with some accuracy.

    Implicit in all of the above is that reactive-banana has no built-in notion of time. There are only the "logical" time lines within each stream, which can be interwoven by merging streams. So if we want a current time behaviour our best bet is building one from an independent stream. Here is a demo of that approach, which will produce fresh and timely results as far as the precision of threadDelay allows:

    {-# LANGUAGE RankNTypes #-}
    module Main where
    
    import Control.Concurrent
    import Control.Monad
    import Data.Time
    import Reactive.Banana
    import Reactive.Banana.Frameworks
    
    main = do
        let netDesc :: forall t. Frameworks t => Moment t ()
            netDesc = do
                (eTime, fireTime) <- newEvent
                liftIO . forkIO . forever $
                    threadDelay (50 * 1000) >> getCurrentTime >>= fireTime
                bTime <- flip stepper eTime <$> liftIO getCurrentTime
                (eTick, fireTick) <- newEvent
                liftIO . forkIO . forever $
                    threadDelay (5000 * 1000) >> fireTick ()
                reactimate $ print <$> bTime <@ eTick
        network <- compile netDesc
        actuate network >> threadDelay (52000 * 1000) >> pause network
    

    bTime is updated through eTime each 0.05s; it is observed through eTick, a stream independent from eTime with occurrences every 5s. You can then use eTick and streams derived from it to observe and update your entities. Alternatively, you can combine bTime and the entity behaviours in applicative style to get, e.g. behaviours for the latest pings, to be observed with eTick.

    Having a canonical time behaviour looks like a sound approach in your case; it is conceptually clear and readily generalisable for multiple ticks. In any case, other approaches that you can play with include getting rid of bTime and using eTick as a low-resolution current time stream (though that seems to make the threadDelay innacurcies build up faster), and getting rid of eTick by using changes to get a stream of freshly updated values from the behaviour (through that comes with its own quirks and annoyances, as the documentation hints at).

    0 讨论(0)
提交回复
热议问题