I'm writing a music player in Haskell with reactive-banana. One problem I have is fetching up-to-date values with fromPoll. I want to enable the user to optionally select a part of the track while playing. My code looks something like this:
makePlayNetworkDescr :: Player a => AddHandler Command -> a -> NetworkDescription t ()
makePlayNetworkDescr addCmdEvent player = do
bPosition <- fromPoll (getPosition player)
eCmds <- fromAddHandler addCmdEvent
let eSetStart = filterE (isJust) $ bPosition <@ filterE (==SetStart) eCmds
eSetEnd = filterE (isJust) $ bPosition <@ filterE (==SetEnd) eCmds
eClearRange = filterE (==ClearRange) eCmds
bStart = accumB Nothing ((const <$> eSetStart) `union` (const Nothing <$ eClearRange))
bEnd = accumB Nothing ((const <$> eSetEnd) `union` (const Nothing <$ eClearRange))
Above, getPosition is a partial function, returning Nothing before the playback actually starts. The problem is that once the addCmdEvent fires for the first time, bPosition will still hold a Nothing value. eSetStart/End calculate their values based on this. Only then does bPosition get updated, and this is the value which will be used next time that addCmdEvent fires. And so on, the value will always be "off by one", so to speak.
There is a related SO question, but in that case there exists a "trigger" event which can be used to calculate the new value of the behavior. Is anything like that possible with fromPoll?
As of reactive-banana-0.5 and 0.6, the fromPoll
function updates the behavior whenever an external event triggers the event network. You can access these updates as an event by using
eUpdate <- changes bSomeBehavior
However, note that behaviors represent continuous time-varying values which do not support a general notion of an "update event". The changes
function will try to return a useful approximation, but there are no formal guarantees.
Alternatively, you can change the external event to include the player position as part of the addCmdEvent
. In your case, this means to add more data to the SetStart
and SetEnd
constructors. Then, you can use
eSetStart = filterJust $ matchSetStart <$> eCmds
where
matchSetStart (SetStart pos) = Just pos
matchSetStart _ = Nothing
Both solutions require you to observe the most recent value as an event instead of a behavior. The reason is that behaviors created with stepper
will always return the old value at the moment they are updated (they "lag behind by one"), as this is very useful for recursive definitions.
In any case, the underlying issue is that the player position is updated externally long before the addCmdEvent
occurs, but the problem is that this is not what the event network sees. Rather, the network thinks that the behavior returned by fromPoll
updates simultaneously with the addCmdEvent
. In fact, unless you have access to the external event source that is responsible for updating the player position, that's the only thing it can think. (If you do have access, you can use the fromChanges
function.)
I realize that this behavior of fromPoll
is somewhat unsatisfactory for your common use case. I am undecided whether I should fix it in my library, though: there is a trade-off between fromPoll
returning the lastest value and the changes
function trying to do its best. If the latest value is returned, then the changes
will behave as if it has skipped one update (when the value was updated externally) and triggered a superfluous one (when the network updates the value to match the external one). If you have any opinion on this, please let me know.
Note that combining behaviors with the applicative operator <*>
will combine the most recent values just fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With