Technique for a custom event counter that varies in speed

23 views
Skip to first unread message

Samuel Tay

unread,
Mar 10, 2017, 8:50:29 PM3/10/17
to Brick Users
Hey, just started using this for a pet project and this library is awesome, great job on the docs.

I'm basically displaying a simulation that has discrete steps. Really the state will be a bit more complicated, having play/pause and step on Enter-key, etc., but the basic setup shouldn't be all that different from the CustomEventDemo.hs example.

One complication however, is that I want to have speed setting. It will look like a progress bar from 0 - 100. For a simple example, let's say I want speed 50 == 1 second intervals and speed 100 == 100ms second intervals. The speed is held in my application state, since it depends on user input. Then I want to initially write to the counter channel with threadDelay 1000000, then later on depending on state, change it to threadDelay 100000.

Currently, this doens't work in the simple structure of CustomEventDemo.hs since that forked process is not in scope when handling events, it just sits in main:

main = do
    chan
<- newBChan 10

    forkIO $ forever $
do
        writeBChan chan
Counter
        threadDelay
1000000

   
void $ customMain (V.mkVty V.defaultConfig) (Just chan) theApp initialState



Is this possible? That channel is passed to customMain so I'm wondering if we can acess it and perform actions with it from an event/app handler.

I'm thinking one (maybe messier) solution would be to have the counter looped with a thread delayed at the highest speed (smallest interval) that I offer the user, and then manage counter + speed in state. Then, given a 100ms thread delay, my event handler would do some arithmetic and make sure that if speed == 50 and I should be updating simulation every 1 second, then I need to wait until I've accumulated 10 counter events in state.

But, thought I'd ask if there was a way to manage channels from event handlers. Let me know what you think.

Thanks again for brick, cool library.

Jonathan Daugherty

unread,
Mar 11, 2017, 4:55:36 PM3/11/17
to Samuel Tay, Brick Users
Hi,

> Hey, just started using this for a pet project and this library is
> awesome, great job on the docs.

Thanks!

> One complication however, is that I want to have speed setting.
>
> ...
>
> Is this possible? That channel is passed to customMain so I'm
> wondering if we can acess it and perform actions with it from an
> event/app handler.

This is definitely possible. One way to do it is to set up a
communication mechanism from the main thread (which ultimately runs your
application event handler) to the custom event thread. I tried this out
by modifying CustomEventDemo.hs to use an STM TVar as follows:

import Control.Monad.IO.Class (liftIO)
import Control.Concurrent.STM

Add a TVar to the application state:

data St =
St { ...
, _stAmount :: TVar Int
}

In main, create a TVar with the initial "speed" setting (in my case the
number of seconds to sleep):

tv <- atomically $ newTVar 1

In the counter thread, start by reading from the TVar. Use the read
value to determine how long to sleep.

forkIO $ forever $ do
amt <- atomically $ readTVar tv
writeBChan chan Counter
threadDelay $ amt * 1000000

In the application event handler, on a speed change event, update the
TVar with the new speed.

appEvent st e =
case e of
VtyEvent (V.EvKey (V.KChar '+') []) -> do
let tv = st^.stAmount
liftIO $ atomically $ modifyTVar tv succ
continue st
VtyEvent (V.EvKey (V.KChar '-') []) -> do
let tv = st^.stAmount
liftIO $ atomically $ modifyTVar tv (max 0 . pred)
continue st

This way, whenever the counter thread wakes up it will get the new
setting. Whenever the user changes the speed setting, the transactional
variable used by the counter thread will be updated.

--
Jonathan Daugherty

Samuel Tay

unread,
Mar 11, 2017, 8:06:51 PM3/11/17
to Brick Users
Ah! Nice. Would have never thought of that... I'm a beginner Haskeller and haven't used TVAr yet. After I sent that post I started implementing the arithmetic way I described where the forked counter stream stays constant the whole time, but I like your idea better. It's a little less "pure" but I think it's better to keep that state within TVar instead of muddying up all my App state. Thanks for the feedback!

Samuel Tay

unread,
Mar 11, 2017, 9:17:18 PM3/11/17
to Brick Users
Actually.. I may have spoke too soon. If I'm displaying the current speed, then I need to render it from the drawing function of type St -> [Widget ()]. And there's no access to IO in the drawing function (we're not in any type of MonadIO as far as I'm aware). Does this break your approach? In your example above, how would you draw st^.stAmount ?

--
You received this message because you are subscribed to a topic in the Google Groups "Brick Users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/brick-users/k0py-VbUKx0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to brick-users+unsubscribe@googlegroups.com.
To post to this group, send email to brick...@googlegroups.com.
Visit this group at https://groups.google.com/group/brick-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/brick-users/4703f593-2e27-4b8f-bc37-a9742a436628%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Jonathan Daugherty

unread,
Mar 11, 2017, 10:04:18 PM3/11/17
to Samuel Tay, Brick Users
> It's a little less "pure" but I think it's better to keep that state
> within TVar instead of muddying up all my App state.

Bear in mind that in Haskell, while pure functions are desirable for a
lot of reasons, almost all useful programs have state. So the goal is
not to get *rid* of state and "make everything pure" but to keep state
as well-contained as possible.

--
Jonathan Daugherty

Jonathan Daugherty

unread,
Mar 11, 2017, 10:05:47 PM3/11/17
to Samuel Tay, Brick Users
> Actually.. I may have spoke too soon. If I'm displaying the current
> speed, then I need to render it from the drawing function of type St
> -> [Widget ()]. And there's no access to IO in the drawing function
> (we're not in any type of MonadIO as far as I'm aware). Does this
> break your approach? In your example above, how would you draw
> st^.stAmount ?

If you need to render it, then the speed value has to be kept in the
application state and just written to the TVar when it changes:

St = St { ...
, _stAmount :: Int
, _stAmountVar :: TVar Int
}

In that case I'd look at the TVar merely as a way of communicating
changes in _stAmount rather than "storing" the amount.

--
Jonathan Daugherty
Reply all
Reply to author
Forward
0 new messages