7

For some experiments I need some basic timer function. I tried various methods, and neither of them are fully satisfactory. What I need is a button, that starts/stops a timer, resets timer with each restart, updates some other variables as well besides the timer and has a trigger effect when started/stopped too. It should also be customizable for having any text on the button.

Let's see how Trigger behaves. First, define a switch that tests whether the counter is active or not, a total time of 50, and continuous variables for the running time and timeLeft:

active = False;
time = 0;
total = timeLeft = 50;

The Trigger should switch active and update time and timeLeft:

Trigger[Dynamic[time, (time = #; timeLeft = total - #; 
    active = 0 < # < total) &], {0, total}]

Dynamic@Column@{time, timeLeft, active}

This however has the following issues:

  • There seems to be no way to stop the timer other than the Trigger's own pause button.
  • The way active is set is wasteful, it is unnecessary to test for it every timestep. Sadly, no other way I could figure out how to call a function only once when the Trigger is started or stopped. I've tried to play with the start/end arguments (as in Dynamic[var, {start, update, end}]) with no success. Perhaps layering a button above the trigger? See next issue.
  • No label can be given for the button. The option Appearance does not seem to have any effect here, and even if I remove all buttons via AppearanceElements->{}, I cannot overlay a normal button over it and wrap it in an EventHandler that passes events down to the trigger, as the Trigger object cannot be set to have the same ImageSize as the Button:

.

EventHandler[Overlay[{
   Button[Dynamic@If[active, "Stop", "Start"]],
   Trigger[
    Dynamic[time, (time = #; timeLeft = total - #) &], {0, total}, 
    AppearanceElements -> {"TriggerButton"(*,"PauseButton",
      "ResetButton"*)}]
   }],
 {"MouseClicked" :> (active = ! active)}, PassEventsDown -> True]
Dynamic@Column@{time, timeLeft, active}

Mathematica graphics

I also tried to construct my own clock function, as follows. It uses a global clock, that starts immediately when the cell is generated, thus it always runs in the background, and the actual stopwatch is created by subtracting the start time (the moment the button is pushed) from the global clock.

active = False;
total = 50;
start = end = time = 0;

Column@{
  Dynamic[globalTime = Clock[{0, total}]; time = globalTime - start; 
   globalTime],
  Dynamic@If[active, time, end - start],
  Dynamic@Button[If[active, "Stop", "Start"], 
    If[active, end = globalTime, start = globalTime]; 
    active = ! active]
  }

Mathematica graphics

(Note that the actual dynamic value of globalTime (upper value) is not copied correctly via Szabolcs's otherwise magnificent image upload palette, and it shows less than time (lower value).)

However, It has the annoying feature that if globalTime does not appear on screen, no dynamic updating is done. Thus if I want to hide it, I have to wrap it into Style[..., Opacity[0]]. Also, time is constantly (and unnecessarily) updated, even if the button was not pushed.

Question

Is there any reliable built-in way to provide such a functionality? How would you solve this problem?

I am interested in any idea, as for the last 3 months, I had to construct 3 guis with three different timing functions, and there is always something new (request/issue/feature) I have to deal with.

István Zachar
  • 47,032
  • 20
  • 143
  • 291

1 Answers1

7

Here's my first stab at a solution based on the idea of a closure. Maybe it has some elements that you can draw on for inspiration.

Function to create a timer function

makeTimer[] := Module[{start = AbsoluteTime[]}, Switch[#,
    "now", AbsoluteTime[] - start,
    "lap", AbsoluteTime[] - start,
    "reset", (start = AbsoluteTime[]; 0)]
   &]

Timers can now be created with timer = makeTimer[] and utilised with actions such as timer["lap"] to get the current lap time. Timers are quiescent unless asked to do something.

You could extend this to handle a pause function if that's required.

Timer with buttons

Create a timer and some buttons to access it, associate the timer with a dynamic variable time.

Grid[{{Button["Start", timer = makeTimer[]; time = 0]}, 
      {Button["Lap",   time = timer["lap"]]}, 
      {Button["Reset", time = timer["reset"]]}, 
      {Button[Dynamic[time]]}}]

Schedule a task to update the timer/clock

Create a scheduled task to run every 2 seconds to update the displayed elapsed time or clock.

RunScheduledTask[time = timer["now"], 2]

Scheduled tasks can be removed thus:

RemoveScheduledTask[ScheduledTasks[]]
image_doctor
  • 10,234
  • 23
  • 40
  • My timer has to operate at a very fine resolution level. Could you please tell me something about how scheduled tasks cope with that in general? Are they fast enough to be called like a 100 times per second for updating, e.g. RunScheduledTask[time = timer["now"], 0.01]? – István Zachar Apr 13 '12 at 13:07
  • @IstvánZachar I only included scheduled tasks to demonstrate how you could get a displayed time updated without the need for an expensive timing loop and to address your comment about no updates being displayed unless it was on screen. The timer is not associated in any way with the scheduled task. The timer does nothing unless accessed. It can be accessed by expressions such as timer["now"]. These have AbsoluteTime resolution and can be called at normal function speeds. So you can call it hundreds or thousands of time each second. – image_doctor Apr 13 '12 at 13:13
  • Thanks image_doctor, and sorry: I didn't want to sound discontent, just that I never used scheduled tasks before. Using the system time via AbsoluteTime is indeed a clever idea, though this approach has one major problem. It does not return time in the expected resolution: if I define the scheduled task to be updated at each second, it returns the following times: {1.0110578, 2.0121151, 3.0131723, 4.0142296, 5.0152868, 6.0163441}, so there is a lag. – István Zachar Apr 13 '12 at 13:28
  • 1
    @IstvánZachar I didn't feel you were discontent :) Sorry I was perhaps too brusque in my reply. What delay accuracy do you need? Is it vital that periods be 1 sec, for instance, to n decimal places. I'm not an expert on this but Mathematica is not designed as a real time system. It runs on top of Linux/Windows/OS X, none of these are real time operating systems and are multitasking, so the response you will get to any timing related function may well be subject to delay/inaccuracies dependent upon what else is going on. – image_doctor Apr 13 '12 at 13:38
  • Ok, we have passed the necessary routines of politeness :) It is not really important to run it in exact real time (i.e. 50 sec does not really have to be exactly 50 sec), but it is important to divide the total time to equal piecese, so e.g. 50 into 5000 fragments of length 0.01 sec. While Clock (and related) seems to be good in that, it has all the issues I described in my Q. – István Zachar Apr 13 '12 at 13:44
  • @IstvánZachar Nice to meet you :) To explore a bit, I tried this. Do[Print@(AbsoluteTime[] ); Pause[1], {10}]. This seems to be accurate to about 3 decimal places, so effectively with error in the order of 100 of microseconds. I tried the same based on makeTimer, it seemed to reflect the same degree of accuracy. My feeling is that makeTimer is of the same order of accuracy as AbsoluteTime[] or perhaps Pause. Is error of the order or about 0.0003 seconds per sample period within your criteria? Maybe there are other ways of approaching interval timing in Mathematica. – image_doctor Apr 13 '12 at 13:59
  • Looking back I didn't make it clear that "Timer with buttons" is the effective basis for a trigger control. You can stop the timer either with the button , or via the timer interface. You can put whatever text you like on the buttons in the grid. And any actions you want to trigger can be linked to the buttons. – image_doctor Apr 13 '12 at 14:28
  • Fiddling with schedules, here is my negative example: list = {}; start = AbsoluteTime[]; RunScheduledTask[time = AbsoluteTime[] - start; AppendTo[list, time], {.1, 5}]; Differences[list] evaluating at every .1 sec for 5 times. It returns the following successive time intervals: {0.1248002, 0.0936001, 0.0936002, 0.1248002}. Clearly, it is rather not accurate. – István Zachar Apr 13 '12 at 17:49
  • No quibble with that, I wasn't necessarily suggesting using schedule for precise timing, just as a solution to the issue you raised with updating display elements in the latter part of your question which avoids the need for a tight timing loop. For short time intervals it you would want to call the timer interface directly. Something has to measure the time interval. I gave an example using Pause, this seems to be accurate to a few hundred microseconds (~0.0002s), I suspect that if you investigate it Clock[] will have a similar degree of error/accuracy, depending on how you view it. – image_doctor Apr 13 '12 at 18:11
  • Out of curiosity and not because I think schedule is per se the best way to do this, I ran your schedule code locally to see what statistics it gave on a sample of a thousand tasks spaced at 0.1 seconds. – image_doctor Apr 13 '12 at 18:31
  • In the end I ran ten thousand tasks spaced at 0.1 seconds. The results were, {Mean[Differences[ts]], Variance[Differences[ts]]^(1/2)} -> {0.100517, 0.005003}. This seems to be at the accuracy level of several hundred microseconds, which matches the results for Pause and probably for Clock. Which I suspect might be the limit of Mathematica's capabilities in this respect. – image_doctor Apr 13 '12 at 18:50
  • 1
    @IstvánZachar This answer deals with the accuracy of the windows task scheduler. It seems to be around 20 ms. – Sjoerd C. de Vries Apr 14 '12 at 08:45
  • Ok, it turned out that scheduled tasks are very reliable things compared to the method relying on Trigger or Clock objects. Thus I ended up using your approach, and I'm quite satisfied with it! Thanks. – István Zachar Apr 14 '12 at 23:34
  • Great, glad you found a workable solution to your problem. :) – image_doctor Apr 15 '12 at 09:42