how to invoke a method for every second in ruby
Asked Answered
F

2

5

I wanted to create a stopwatch program in ruby so I googled it and found this SO Q.

But over there, the author calls the tick function with 1000xxx.times. I wanted to know how I can do it using something like (every second).times or for each increment of second do call the tick function.

Fronniah answered 20/9, 2012 at 1:17 Comment(3)
I'm not sure I understand exactly what you're asking. Can you try to clarify?Plotinus
In Ruby there is something like succ in Time class. t = Time.now #=> 2007-11-19 08:23:57 -0600 t.succ #=> 2007-11-19 08:23:58 -0600 This shows the next second of time. And I want to do it recursively.Fronniah
I want to show the time on screen for each increment of the seconds.Fronniah
D
13
Thread.new do
  while true do
    puts Time.now # or call tick function
    sleep 1
  end
end
Dustheap answered 20/9, 2012 at 2:2 Comment(6)
Thanks I also need to show the elapsed time finally and I should record the time somewhere so that it will be useful to resume the operation if the stopwatch is halted.Fronniah
may be before doing puts Time.now I should store it in some var t and then puts that t would do.. Am I right? :-/Fronniah
You are right. Also, if you are developing clock application, you might want to use lower sleep value (0.1 e.g.), store time from previous run in variable and output new value only if it's greater than previous (modulo second) otherwise your clock will be "jumpy" sometimes.Dustheap
I dont like the answer, as you inevitably will loose time; e.g. after 1M loops far more than 1M seconds will be spent.Bromberg
Technically, any other callback/timeout system will use thread with sleeps inside. Note that sleep doesn't consume cpu cycles while sleeping.Dustheap
Yepp, was more worrying about the non-sleep code execution time. What about integrating some sync-with-realtime functionality? Of course that will add code to the loop and increase the loop-delay; I guess it all depends how much time is lost during "call tick function" and how long the ticker is intended to run :-)Bromberg
A
17

This function:

def every_so_many_seconds(seconds)
  last_tick = Time.now
  loop do
    sleep 0.1
    if Time.now - last_tick >= seconds
      last_tick += seconds
      yield
    end
  end
end

When used like this:

every_so_many_seconds(1) do
  p Time.now
end

Results in this:

# => 2012-09-20 16:43:35 -0700
# => 2012-09-20 16:43:36 -0700
# => 2012-09-20 16:43:37 -0700

The trick is to sleep for less than a second. That helps to keep you from losing ticks. Note that you cannot guarantee you'll never lose a tick. That's because the operating system cannot guarantee that your unprivileged program gets processor time when it wants it.

Therefore, make sure your clock code does not depend on the block getting called every second. For example, this would be bad:

every_so_many_seconds(1) do
  @time += 1
  display_time(@time)
end

This would be fine:

every_so_many_seconds(1) do
  display_time(Time.now)
end
Apace answered 20/9, 2012 at 23:47 Comment(6)
if I use every_so_many_seconds(1) do p Time.now end it displays each second multiple times Why is it so? Can you explain?Fronniah
@clone, I introduced that bug during an edit (and like a fool, I didn't test it). It's fixed now.Apace
Why not last_tick += seconds instead of last_tick = Time.now? It would be more accurate.Exoskeleton
@Exoskeleton That sounds reasonable, but I don't see any change in how the code acts when I tried that change. BTW, I did just now notice that however last_tick is set, it should be set before the yield; I've edited the answer accordingly. This helps to prevent lost ticks if the block takes significant time.Apace
@WayneConrad The loop time is not exactly 100ms but 100ms + extra computing time. Each time the loop evaluates, the rounding error adds to itself and the time slowly drifts. When running this function to display tick every second on my machine, I had a 1-second difference after 10 minutes. 599 tick were displayed. There should have been 600. For a short-living process, this is perfectly fine but for a long-living server process it can be an issue. By doing last_tick += seconds, you ensure that the maximum drift will be the sleep time: 100ms.Exoskeleton
@Exoskeleton Ah, I understand. Thanks for explaining it so well, and for suggesting the improvement. I'll fold it into the answer in a moment.Apace
D
13
Thread.new do
  while true do
    puts Time.now # or call tick function
    sleep 1
  end
end
Dustheap answered 20/9, 2012 at 2:2 Comment(6)
Thanks I also need to show the elapsed time finally and I should record the time somewhere so that it will be useful to resume the operation if the stopwatch is halted.Fronniah
may be before doing puts Time.now I should store it in some var t and then puts that t would do.. Am I right? :-/Fronniah
You are right. Also, if you are developing clock application, you might want to use lower sleep value (0.1 e.g.), store time from previous run in variable and output new value only if it's greater than previous (modulo second) otherwise your clock will be "jumpy" sometimes.Dustheap
I dont like the answer, as you inevitably will loose time; e.g. after 1M loops far more than 1M seconds will be spent.Bromberg
Technically, any other callback/timeout system will use thread with sleeps inside. Note that sleep doesn't consume cpu cycles while sleeping.Dustheap
Yepp, was more worrying about the non-sleep code execution time. What about integrating some sync-with-realtime functionality? Of course that will add code to the loop and increase the loop-delay; I guess it all depends how much time is lost during "call tick function" and how long the ticker is intended to run :-)Bromberg

© 2022 - 2024 — McMap. All rights reserved.