pack .start .count .count2 -side left
set value 0
proc count {} {incr ::value; after 100 count}
set value2 0
proc count2 {} {incr ::value2; after 10 count2}
count2 is slower than count
is there's a way to keep the same speed rate for both?
nicolas
in fact, i intend to have 100 on value while i get 1000 on value2
or i get 100 on value and 850 on value2
as count2 is 10* faster than count,
i don't understand why thereis this difference?
do you understand
count2 was slower on my mac box, in the sense they were different more
than just a factor of ten. One would assume that one value would hit
1000 as the other value hit 10000, but on my box one hit 10000 but the
other was at 980 or so.
As I've let it run for a while, the values are now 6000 and 55100.
Interesting result.
You establish the time the program starts, T0. Then you want count called
at T0+(i*100ms). Each time you call count, compute the next call time,
T0+(value*100), and the number of milliseconds until then,
$T0 + ($value*100) - [clock clicks -milliseconds]
(ignoring the effect of clock overflow) and after by this amount. It will
often be 100, but can be a little smaller if count proc was called late.
Individual calls can be delayed, so this will not appear at exactly 100ms
everytime, but over the long term it should self correct back to this.
SImilarly for count2, except with multiples of 10 instead of 100.
On clock overflow, the after amount will possibly be about 100-2^w, where
w=31 for 32 bit integers, but you need to check the details of integer and
clock representation on your machine.
--
SM Ryan http://www.rawbw.com/~wyrmwif/
Mention something out of a Charleton Heston movie, and suddenly
everybody's a theology scholar.
> #
> # is there's a way to keep the same speed rate for both?
>
> You establish the time the program starts, T0. Then you want count called
> at T0+(i*100ms). Each time you call count, compute the next call time,
...
> Individual calls can be delayed, so this will not appear at exactly 100ms
> everytime, but over the long term it should self correct back to this.
No.
This is a well-known problem with timing in control applications. If
you have different timing sources (and "after" obviously starts a new
source with each invocation), they will drift apart, and can't be
reconciled.
For applications needing synchronized timing, the only solution is to
use a single clock source, starting all invocations at a single time,
and using the _total_ count since the start to calculate your intervals.
Otherwise, your errors accumulate over time, as Nicolas and Bryan have
found. There is no solution using "after", or any other incremental
timing command.
After 10 is "slower" than after 100 because it has 10X the exposure to
this fundamental problem, and, in this case, appears to start late more
often than early (personally, without digging to the tcl source, I think
it's the usual case of being always either late or on time).
This is actually an elementary comtrols problem, whose source and
solution have been known at least since the '70s :-).
>
> On clock overflow, the after amount will possibly be about 100-2^w, where
> w=31 for 32 bit integers, but you need to check the details of integer and
> clock representation on your machine.
Utterly irrelevant to Nicolas's problem.
John Perry
where can i find element to deal with this problem?
many thanks
nicolas
Hm. There are a number of answers. of increasing complexity and/or
difficulty, depending on your needs.
1. Do you want accurate time on average? How accurate, and what
dispersion is acceptable over what time period?
2. Do you want accurate synchronization, without worrying too much
about time accuracy, or is synchronization alone important?
3. Do you want precise time as well as accurate time? How precise, how
accurate?
1. is the easiest; In each activity's proc, just read the time of day
(TOD), and when you've passed the deadline, add the interval to the
current deadline (NOT to the current reading -- that's why resources
like after drift off), and trigger your action. Adjust the parameter to
after to expire at the new TOD deadline. Time accuracy depends upon
your computer's date-time accuracy. You'll always be pretty close to
the right time, depending upon the speed of your computer, operating
system, and language implementation.
2. Start a counter in a single process using something like after, and
use variables to keep deadlines for each activity. Use a single proc,
calling "after" only once at each increment, using the time increment
for the shortest interval for resolution. As each "after" adds its
increment to the counter, check all the deadlines. If any deadline has
passed, update it and trigger that activity. Your single counter will
drift with respect to absolute time, but your activities will be
perfectly synchronized.
3a. For less demanding requirements, combine the solutions to 1. and 2.
3b. If you need really tight accuracy and precision, get a real-time
operating system. You won't do this with windows or umix, unless you
use one of the real-time add-ons. RTOS's (or kernels, if you have the
skills to use one effectively) were invented for this type of problem,
and most handle it quite well.
As you can see, none of these are simple enough to present in a few
lines of newsgroup discussion. I've given the basics; you can fill in
the details. Or come back with some indication of what exactly you
need, and we can work on it, if the group will indulge us.
A quick Google on "real-time periodic", real-time delay" and "real-time
wait" came up with millions of hits on everything but what we're
interested in, so unless you can think of something simmilar to search
for, the principles above will have to do. Or, you could read some
real-time texts and articles :-).
John Perry
# This is a well-known problem with timing in control applications. If
# you have different timing sources (and "after" obviously starts a new
# source with each invocation), they will drift apart, and can't be
# reconciled.
Silly me. I thought Tcl library used the one system clock.
--
SM Ryan http://www.rawbw.com/~wyrmwif/
The little stoner's got a point.
I'm sure it does. I'm also pretty sure that it does its thing by adding
the increment to the current time, then returning when it detects that
time's expiration.
The point is that the "source" I referred to was the current tick time.
If the tick has incremented between the time "after" expired and the
time it's called again, that's a new source origin. So you've added an
extra tick, and your time is off. Now add in other calls to "after"
from other procs, give them different increments, and they will all add
different spurious ticks at each invocation, so, not only do they drift
away from the TOD clock, they drift away from each other.
That's why all incremental delays fail to maintain a correct running
time, and why systems that need a true periodic invocation have a
separate "periodic" system call. If there's not a system call, the
designer has to cobble it together himself.
John Perry
namespace eval dd {
variable z 0
}
pack [label .count -textvar value -width 5]
pack [button .b -text time -command time]
set value 0
proc time {} {
set t [clock clicks -milliseconds ]
go $t
}
proc go {x} {
after [expr ($x-100)] { incr dd::z 1 ; time}
}
proc disp {x} {
trace add variable $dd::z write incr ::value
}
+++
nicolas
I'm not quite sure what that's trying to do. But whatever it is doing,
it's not doing what you expect! Firstly, the disp procedure is wrong. At
a guess, you'd be happier with:
proc disp {} {
trace add variable ::dd::z write {incr ::value ;#}
}
Secondly, the time procedure is using [clock clicks -milli] and feeding
the value which comes out (generally very large) into [after] is not
going to be right. You probably want to take it all relative to some
absolute time standard, perhaps like this:
proc go {{interval 100}} {
variable ::dd::z 0 ;# Init the counter
global t0 ;# Init the time base
set t0 [clock clicks -milliseconds]
doTime $interval ;# Start the ticker!
}
proc doTime {interval} {
global t0
variable ::dd::z
# Compute when the next tick should be, updating the counter
# in the process.
set t [expr {$t0 + $interval*[incr z]}]
# Invoke ourselves again in $t-"now" milliseconds...
after [expr {$t-[clock clicks -milliseconds]}] doTime $interval
}
On my machine, that keeps fairly good time (comparing against a timer
that's external to the computer) when not heavily loaded. The timer is
started by calling [go] and optionally passing the time-interval between
'ticks', in milliseconds.
I'll leave reworking that into code that can support multiple
simultaneous timers (hint: multiple t0's and counters are required) as
an exercise to the interested reader.
Donal.
but i guess there's a all-in-one solution
many thanks
nicolas
nicolas, Donal,
I'm just leaving town and will be out of touch for a few days. I'll try
to put together something that works well to post when I get back.
nicolas, you should look hard at my second message and give some kind of
guidance on just what you are looking for.
jp
this point is important for me.
i need all the timers to be synchronised :in my 1st post, value= 100
while value2=1000 is important
donal's code work pretty well in this sense, but i've multipiled 20
time the code to get 20 timers (wich is not too much, i expect to get
160 at least), and tcl fail.
perhaps the add trace variable is not a good idea
in my app, values are displayed in a canvas text, so i only have -tags
option avalaible (or maybe i can change that......)
have good time
nicolas
There is only one timer. Tcl adds the current time to the given interval
to get the earliest time for that after event. All these events go into
the same queue. Tcl checks the beginnings of the queue ever and anon;
events after the system clock are removed and evaluated. If Tcl is going
to sleep because no events are ready, it uses the time of the first
after event in the queue to get the maximum sleep time.
All after events are queued together and reference the same system clock.
--
SM Ryan http://www.rawbw.com/~wyrmwif/
OOOOOOOOOO! NAVY SEALS!
so i've done that:
here's the starter:
set ::compteur::ti 0
set ::compteur::to 200
set ::init::init1 [clock click -milliseconds]
set ::init::init2 [clock click -milliseconds]
doTimeto 400
doTimeti 400
and here's the proc
namespace eval init {
variable init1 0
variable init2 0
}
namespace eval compteur {
variable ti
variable to
}
proc doTimeti {z} {
variable ::compteur::ti
set t0 [ set ::init::init1]
if {[ set ::compteur::ti] == 200 } {
return
}
set t [ expr {$t0 + $z*[ incr ti]}]
after [ expr {$t-[ clock clicks -milliseconds ]} ] doTimeti $z
vui
}
proc doTimeto {z} {
variable ::compteur::to
set t0 [ set ::init::init2]
if {[ set ::compteur::to] == 0 } {
return
}
set t [ expr {$t0 + $z*[ incr to -1 ]}]
after [ expr {$t-[ clock clicks -milliseconds ]} ] doTimeto $z
vuo
}
vui and vuo update a "bargraph" with [ set ::compteur::to] and [ set
::compteur::ti] value
am i wrong?
I don't know how Tcl implements [after] timers at the lowest level, and
I'm almost certain it's going to be somehow platform and processor
dependent to some degree.
From my experience working with different languages and environments,
multiple timers are a bitch.
The worst case I've seen is one (this is NOT in Tcl) where you have
events scheduled to sleep until the next clock multiple of X. If you
scheduled TWO events for the same multiple, they would TAKE TURNS
firing. Thus, only execute only half as often as desired. Add a third,
event, and they would round-robin in threes. I didn't have access to
the low-level code, but presumably, there was some form of "interrupt
masking" (either real or virtual) going on, or else there was no
checking to see that X amount of time had lapsed and that a timer
multiple had been missed.
You get the picture.
My recommendation is to use ONE single timer at the smallest resolution
required to be the least common denominator for all your timers. If you
have timers that require 1000, 100, and 20ms resolution, use a 20ms
timer. Then evaluate the current time and see which tasks need to be
run.
However, you will experience some drift as compared to absolute time,
i.e.:
proc foo {} {
after 20 foo
# evaluate scheduler tasks as appropriate...
# if multiple of 100, do 100ms task
# if multiple of 1000, do 1000ms task
# always do 20ms task
}
The time it takes to reschedule the after event will wind up costing you
not only 20ms and change. Eventually, you'll find you've executed not
every 20ms, but perhaps every 20.001 ms. Over time, this could lead to,
for example, one fewer execution than you had intended every 20000ms.
To avoid this drift, you could modify this to run every 1ms, even if you
only need a 20ms loop. Then check [clock clicks -mil] and, if more than
20ms has elapsed, perform the 20ms task, etc. This gives you LESS
theoretical accuracy per execution (that is, your 20ms task may execute
every 20-21ms), but it will ensure that you have exactly 1000 executions
in 20000ms (plus or minus one), rather than some indeterminate number
always less than 20000 executions.
If absolute time in terms of both number of executions and drift is
crucial, use an RTOS.
--
MKS
At the lowest level, Tcl computes how long it is until the first timer
event to fire and waits that long for an external event (using select()
on Unix, and something similar on Windows). When that returns, it fires
(in sequence) all timer events whose deadlines have expired. When no
more ready events are available, the time to the next event is computed
and the code goes back to waiting. (If you want to know more, read Tcl's
notifier implementation. I must warn that that code has the distinction
of being the sort of thing that I usually only understand fully for a
few weeks after reading it before forgetting again.)
With the code I did previously in this thread, what this means is that
the events will fire properly at regular intervals as long as the system
clock is reasonably accurate, and as long as it takes less time for one
event to be processed than the average interval between events (i.e. you
can be occasionally a little late, but shouldn't normally be).
Donal.
While the processor clock resolution will vary from platform to platform, I
think you'll find the Tcl event queue mechanism consistent. It will always
work the same way. (Donal gives an excellent explanation of how it works.)
> My recommendation is to use ONE single timer at the smallest
> resolution required to be the least common denominator for all your
> timers. If you have timers that require 1000, 100, and 20ms
> resolution, use a 20ms timer. Then evaluate the current time and
> see which tasks need to be run.
>
> However, you will experience some drift as compared to absolute
> time,
...
> To avoid this drift, you could modify this to run every 1ms, even if
> you only need a 20ms loop. Then check [clock clicks -mil] and, if
> more than 20ms has elapsed, perform the 20ms task, etc.
Iiiicccckkk! You're essentially writing your own event queue. That's a lot
of work.
But you can compensate for "drift" by checking against [clock
clicks -milliseconds]. I wrote a test proc which computed a target time for
the next event like:
set nextTime [expr {$start + ($interval * $count)}]
set afterDelay [expr {$nextTime - $now}]
after $afterDelay {doMoreWork}
It was rock solid on hitting 20ms or 100ms timing. I'm not sure how often
[clock clicks -milliseconds] rolls over, but that might cause some problems.
I also tried a relative timing, where the work proc saves [clock
clicks -milliseconds] every time, and computes afterDelay from actual
elapsed time and desired interval. But I don't think I got the math right.
Maybe a successful implementation would be a good wiki page.
Bob
--
Bob Techentin techenti...@NOSPAMmayo.edu
Mayo Foundation (507) 538-5495
200 First St. SW FAX (507) 284-9171
Rochester MN, 55901 USA http://www.mayo.edu/sppdg/
proc doTime {a z g} {
if {[ set ::compteur::ti] == 0 && [ set ::compteur::to] == 200 } {
return
}
variable ::compteur::ti
variable ::compteur::to
set t0 [ set ::init::init1]
set t [ expr {$t0 + $a}]
set t2 [ expr {$t0 + $g*[ incr to]}]
set t1 [ expr {$t0 + $z*[ incr ti -1]}]
after [ expr {$t1-[ clock clicks -milliseconds ]} ] vui
after [ expr {$t2-[ clock clicks -milliseconds ]} ] vuo
after [ expr {$t-[ clock clicks -milliseconds ]} ] [ list doTime $a $z
$g ]
}
proc doTimei {z g} {
if {$compteur::ti == $g } {
return
}
variable compteur::ti
set t0 [ set ::init::init ]
set t [ expr {$t0 + $z*[ incr ti ]}]
after [ expr {$t-[ clock clicks -milliseconds ]} ] [ list doTimei $z $g
] ; vui
}
proc doTimeo {z g} {
if {$compteur::to == $g } {
return
}
variable compteur::to
set t0 [ set ::init::init ]
set t [ expr {$t0 + $z*[ incr to ]}]
after [ expr {$t-[ clock clicks -milliseconds ]} ] [ list doTimeo $z $g
] ; vuo
}
> > To avoid this drift, you could modify this to run every 1ms, even if
> > you only need a 20ms loop. Then check [clock clicks -mil] and, if
> > more than 20ms has elapsed, perform the 20ms task, etc.
> Iiiicccckkk! You're essentially writing your own event queue. That's a lot
> of work.
You might say "ick." I personally don't think it's much work at all.
It's just a task list.
I suppose it's overkill, though. Since [after]-scheduled events are
queued in order, the Tcl event queue should be quite determinate.
As I explained, I've just gotten burned by screwy implementations in
other languages, and tend to revert to the "safest" method of generating
one's own clock pulse within the language environment, rather than
relying on knowing everything about every language.
--
MKS
> At the lowest level, Tcl computes how long it is until the first timer
> event to fire and waits that long for an external event (using select()
> on Unix, and something similar on Windows). When that returns, it fires
> (in sequence) all timer events whose deadlines have expired.
Good info. Thanks!
> With the code I did previously in this thread, what this means is that
> the events will fire properly at regular intervals as long as the system
> clock is reasonably accurate, and as long as it takes less time for one
> event to be processed than the average interval between events (i.e. you
> can be occasionally a little late, but shouldn't normally be).
IOW, if you schedule 25ms of code to run every 20ms, you're screwed. I
think that's fairly universal to any language. :-)
It wreaks havoc on discrete controls -- it makes them think that time is
running slower than it really is, which isn't good when physical
properties like spring constants do not also change. I've seen it
happen. It's not pretty.
--
MKS
other question:
with this proc :
proc doTimei {z g} {
if {$compteur::ti == $g } {
return
}
variable compteur::ti
set t0 [ set ::init::init ]
set t [ expr {$t0 + $z*[ incr ti ]}]
after [ expr {$t-[ clock clicks -milliseconds ]} ] [ list doTimei $z $g
] ; vui
}
vui is a parsing proc.
the counter wait for vui is finished to count again.
this make the calcul false.
how to avoid this?
best,
nicolas