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.

Community
  • 1
  • 1
Clone
  • 919
  • 4
  • 11
  • 22
  • 2
    I'm not sure I understand exactly what you're asking. Can you try to clarify? – sunnyrjuneja Sep 20 '12 at 01:26
  • 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. – Clone Sep 20 '12 at 01:37
  • I want to show the time on screen for each increment of the seconds. – Clone Sep 20 '12 at 01:38

2 Answers2

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
Wayne Conrad
  • 103,207
  • 26
  • 155
  • 191
  • 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? – Clone Sep 22 '12 at 18:31
  • @clone, I introduced that bug during an edit (and like a fool, I didn't test it). It's fixed now. – Wayne Conrad Sep 22 '12 at 18:55
  • 1
    Why not `last_tick += seconds` instead of `last_tick = Time.now`? It would be more accurate. – cbliard May 12 '14 at 13:34
  • @cbliard 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. – Wayne Conrad May 12 '14 at 16:05
  • 2
    @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. – cbliard May 13 '14 at 07:19
  • @cbliard Ah, I understand. Thanks for explaining it so well, and for suggesting the improvement. I'll fold it into the answer in a moment. – Wayne Conrad May 13 '14 at 12:10
13
Thread.new do
  while true do
    puts Time.now # or call tick function
    sleep 1
  end
end
iced
  • 1,562
  • 8
  • 10
  • 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. – Clone Sep 20 '12 at 02:38
  • 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? :-/ – Clone Sep 20 '12 at 02:39
  • 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. – iced Sep 20 '12 at 03:15
  • I dont like the answer, as you inevitably will loose time; e.g. after 1M loops far more than 1M seconds will be spent. – Atastor Sep 20 '12 at 08:15
  • Technically, any other callback/timeout system will use thread with sleeps inside. Note that sleep doesn't consume cpu cycles while sleeping. – iced Sep 20 '12 at 08:37
  • 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 :-) – Atastor Sep 20 '12 at 09:05