stopclock is a library for measuring time using (stop)clocks.
It allows you to create a clock, pause it and resume it. You can change its speed and adjust its time at any moment. For example you can have a clock that runs backwards 2 times faster than an ordinary clock.
You can load this library from quicklisp:
(ql:quickload :stopclock)You can define local nickname for this package. (It is possible with almost all implementations, see Cookbook - packages - PLN). You can do it in your package definition:
(defpackage #:my-package
(:use #:cl)
(:local-nicknames (#:sc #:stopclock)))Or during your REPL session:
CL-USER> (add-package-local-nickname '#:sc '#:stopclock)
; => #<PACKAGE "COMMON-LISP-USER">The rest of this tutorial uses sc as a local nickname for stopclock.
You can run tests with asdf. Test system depends on fiveam library.
(asdf:test-system :stopclock) ; depends on fiveam
; ...
; Did 564 checks.
; Pass: 564 (100%)
; Skip: 0 ( 0%)
; Fail: 0 ( 0%)
; => TNote that you can’t (use-package :stopclock) because it causes name-conflicts with common-lisp package.
There are two function you will need to shadow: sc:time and sc:speed.
It is recommended to use package-local-nicknames instead (see Local nickname).
To create a clock use make-clock function.
CL-USER> (defparameter *c* (sc:make-clock))
; => *C*
CL-USER> *c*
; => #<CLOCK :TIME 1.98 SECONDS :RUNNING :SPEED x1>You can stop the clock with stop function and run it with run function.
CL-USER> (sc:stop *c*)
; => #<CLOCK :TIME 11.08 SECONDS :PAUSED :SPEED x1>
CL-USER> (sc:run *c*)
; => #<CLOCK :TIME 11.08 SECONDS :RUNNING :SPEED x1>
CL-USER> *c*
; => #<CLOCK :TIME 15.70 SECONDS :RUNNING :SPEED x1> ; the clock is running again.To get current clock time use time function. It returns the time in seconds.
CL-USER> (sc:time *c*)
; => 12388023/500000 ; usually time is a rational number
CL-USER> (float (sc:time *c*))
; => 35.260098Finally, you can reset the current time on the clock with reset function.
CL-USER> (sc:reset *c*)
; => #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x1>It does not pause or run of the clock by default, but you can
specify it with :paused T or :run T key arguments.
CL-USER> (sc:reset *c* :paused T)
; => #<CLOCK :TIME 0.00 SECONDS :PAUSED :SPEED x1> ; clock is paused now
CL-USER> (sc:reset *c*)
; => #<CLOCK :TIME 0.00 SECONDS :PAUSED :SPEED x1> ; clock is still paused
CL-USER> (sc:reset *c* :run T)
; => #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x1> ; clock is running nowMost functions, such as stop and run, act destructively on the clock
and return itself for convenience. You can copy the clock with copy-clock.
CL-USER> (defparameter *c* (sc:make-clock))
; => *C*
CL-USER> (defparameter *d* (sc:copy-clock *c*))
; => *D*
CL-USER> (list *c* *d*)
; => (#<CLOCK :TIME 16.93 SECONDS :RUNNING :SPEED x1>
; #<CLOCK :TIME 16.93 SECONDS :RUNNING :SPEED x1>)
CL-USER> (list *c* (sc:stop *d*))
; => (#<CLOCK :TIME 28.90 SECONDS :RUNNING :SPEED x1>
; #<CLOCK :TIME 28.90 SECONDS :PAUSED :SPEED x1>)
CL-USER> (list *c* *d*)
; => (#<CLOCK :TIME 31.64 SECONDS :RUNNING :SPEED x1>
; #<CLOCK :TIME 28.90 SECONDS :PAUSED :SPEED x1>)A clock has three parameters: time, speed and whether it is paused or is running.
(speed refers to the speed with which the time on the clock changes.)
You can pass these parameters to the initialization function. For example you can create a paused clock that runs backwards with 5 seconds in the beginning:
CL-USER> (sc:make-clock :paused t :time 5 :speed -1)
; => #<CLOCK :TIME 5.00 SECONDS :PAUSED :SPEED -x1>
CL-USER> (sc:run *)
; => #<CLOCK :TIME 5.00 SECONDS :RUNNING :SPEED -x1>
CL-USER> *
; => #<CLOCK :TIME 3.03 SECONDS :RUNNING :SPEED -x1>For each of these parameters, a corresponding accessor is defined: time, speed, and paused.
CL-USER> (setf (sc:paused *c*) t)
; => T
CL-USER> (setf (sc:speed *c*) -10)
; => -10
CL-USER> (list (float (sc:time *c*))
(sc:speed *c*)
(sc:paused *c*))
; => (322.43793 -10 T)
CL-USER> (setf (sc:time *c*) 100)
; => 100
CL-USER> *c*
; => #<CLOCK :TIME 100.00 SECONDS :PAUSED :SPEED -x10>The paused / running state of the clock can be accessed with function paused.
The state can be set by combining paused with setf.
It also can be set by functions run (or a synonymous start),
pause (or a synonymous stop) and toggle.
These function return the clock itself.
CL-USER> (sc:make-clock :paused t)
; => #<CLOCK :TIME 0.00 SECONDS :PAUSED :SPEED x1>
CL-USER> (sc:run *) ; or (sc:start *)
; => #<CLOCK :TIME 0.04 SECONDS :RUNNING :SPEED x1>
CL-USER> (sc:stop *) ; or (sc:pause *)
; => #<CLOCK :TIME 4.47 SECONDS :PAUSED :SPEED x1>
CL-USER> (setf (sc:paused *) t)
; => T
CL-USER> **
; => #<CLOCK :TIME 4.47 SECONDS :PAUSED :SPEED x1>The time on the clock can accessed with function time.
You can set the time by combining time with setf.
There is also an adjust function that adds a given number of seconds to the current clock time.
It is more efficient than using combination of incf and time.
Unlike setf or incf it returns the clock itself.
CL-USER> (sc:make-clock)
; => #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x1>
CL-USER> (setf (sc:time (sc:stop *)) 0) ; stop returns the clock itself which allows chaining like that.
; => 0
CL-USER> **
; => #<CLOCK :TIME 0.00 SECONDS :PAUSED :SPEED x1>
CL-USER> (incf (sc:time *) 10)
; => 10
CL-USER> **
; => #<CLOCK :TIME 10.00 SECONDS :PAUSED :SPEED x1>
CL-USER> (sc:adjust * 20)
; => #<CLOCK :TIME 30.00 SECONDS :PAUSED :SPEED x1>The speed of the clock can accessed with speed.
You can set it by combining speed with setf.
There is also an accelerate function that will multiply the speed by a given factor.
Unlike setf or incf it returns the clock itself.
CL-USER> (sc:make-clock)
; => #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x1>
CL-USER> (setf (sc:speed *) 10)
; => 10
CL-USER> **
; => #<CLOCK :TIME 26.72 SECONDS :RUNNING :SPEED x10>
CL-USER> (setf (sc:speed *) -100)
; => -100
CL-USER> **
; => #<CLOCK :TIME -39.91 SECONDS :RUNNING :SPEED -x100>
CL-USER> (sc:accelerate * -2)
; => #<CLOCK :TIME -1020.11 SECONDS :RUNNING :SPEED x200>
CL-USER> *
; => #<CLOCK :TIME 1995.27 SECONDS :RUNNING :SPEED x200>The speed of the clock cannot be equal to zero.
If you try to set it to zero the zero-clock-speed-error will be signalled.
CL-USER> (sc:make-clock :speed 0)
; Evaluation aborted on #<SC:ZERO-CLOCK-SPEED-ERROR {1006E41983}>.To reset the clock you can use reset function.
By default it only resets the time to 0.
You can pass one of :paused or :run key arguments to
set the clock’s state to the corresponding value.
You can also specify :speed and :time to be set.
The function returns the clock itself.
CL-USER> (sc:make-clock)
; => #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x1>
CL-USER> (sc:reset * :paused t)
; => #<CLOCK :TIME 0.00 SECONDS :PAUSED :SPEED x1>
CL-USER> (sc:reset * :run t)
; => #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x1>
CL-USER> (sc:reset * :speed 10)
; => #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x10>
CL-USER> (sc:reset * :time -10)
; => #<CLOCK :TIME -10.00 SECONDS :RUNNING :SPEED x10>The :paused arguments takes precedence over :run:
CL-USER> (sc:reset *c* :paused t :run t)
; => #<CLOCK :TIME 0.00 SECONDS :PAUSED :SPEED x10>By default the clock will get current time with get-internal-real-time function.
This behaviour can be changed by passing :time-source parameter to the make-clock function.
This must be a function that returns the current time in seconds.
(It also can be another clock, see Synchronized clocks.)
stopclock defines two possible time-sources:
real-time that uses get-internal-real-time is used by default,
and run-time that uses get-internal-run-time instead.
CL-USER> (let ((real-clock (sc:make-clock :paused nil :time-source 'sc:real-time)) ; default time source
(run-clock (sc:make-clock :paused nil :time-source 'sc:run-time)))
(sleep 5)
(list real-clock run-clock))
; => (#<CLOCK :TIME 5.00 SECONDS :RUNNING :SPEED x1>
; #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x1>)It is impossible to start or stop two clocks at the same time, since they may have different time sources. However, synchronized clocks can be obtained by using a third clock as the time source. Consider this example:
CL-USER> (let ((1x (sc:make-clock))
(latency (sleep 0.01))
(5x (sc:make-clock :speed 5)))
(declare (ignore latency))
(sleep 1)
(= (* 5 (sc:time 1x))
(sc:time 5x)))
; => NILWe create two clocks, one running 5 times faster than another. We also introduce an artificial latency between their creation. As a result they are out of sync. If we use the third clock as the time source paused during the creation of clocks, then the clocks are synchronized:
CL-USER> (let* ((clock (sc:make-clock :paused t))
(1x (sc:make-clock :time-source (lambda () (sc:time clock))))
(latency (sleep 0.01))
(5x (sc:make-clock :time-source (lambda () (sc:time clock))
:speed 5)))
(declare (ignore latency))
(sc:run clock)
(sleep 1)
(sc:stop clock)
(= (* 5 (sc:time 1x))
(sc:time 5x)))
; => TFor convenience you can directly pass another clock as the time source. Here is another example:
CL-USER> (let* ((source-clock (sc:make-clock :paused t))
(up (sc:make-clock :time-source source-clock))
(down (sc:make-clock :time-source source-clock
:speed -1 :time 50)))
(sc:run source-clock)
(format t "; up: ~a~%; down: ~a~%" up down)
(sleep 1)
(format t "; up: ~a~%; down: ~a~%" up down)
(sc:stop source-clock)
(= 50 (+ (sc:time up) (sc:time down))))
; up: #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x1>
; down: #<CLOCK :TIME 50.00 SECONDS :RUNNING :SPEED -x1>
; up: #<CLOCK :TIME 1.00 SECONDS :RUNNING :SPEED x1>
; down: #<CLOCK :TIME 49.00 SECONDS :RUNNING :SPEED -x1>
; => TTime on the clocks up and down will always add up to 50.
If you want to read the time on synchronized clocks you need to pause the common source clock first.
That means that the time spent on processing time values will not be tracked.
Clock freeze solves this problem.
When you freeze the clock it freezes the time on the clock, which is almost identical to pausing it.
However, when you unfreeze it, the clock behaves as if it had not been frozen.
CL-USER> (sc:make-clock)
; => #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x1>
CL-USER> (sc:freeze *)
; => #<CLOCK :TIME 4.19 SECONDS :FREEZED :SPEED x1>
CL-USER> *
; => #<CLOCK :TIME 4.19 SECONDS :FREEZED :SPEED x1>
CL-USER> (sc:unfreeze *)
; => #<CLOCK :TIME 10.36 SECONDS :RUNNING :SPEED x1> ; about 6 seconds elapsed during the freeze.It also means that the paused clock will remain paused after the freeze.
CL-USER> (sc:make-clock :time 3 :paused t)
; => #<CLOCK :TIME 3.00 SECONDS :PAUSED :SPEED x1>
CL-USER> (sc:freeze *)
; => #<CLOCK :TIME 3.00 SECONDS :PAUSED :SPEED x1>
CL-USER> (sc:unfreeze *)
; => #<CLOCK :TIME 3.00 SECONDS :PAUSED :SPEED x1>
CL-USER> *
; => #<CLOCK :TIME 3.00 SECONDS :PAUSED :SPEED x1>stopclock also provides a macro with-freeze. Consider the previous example:
CL-USER> (let* ((source-clock (sc:make-clock :paused t))
(up (sc:make-clock :time-source source-clock))
(down (sc:make-clock :time-source source-clock
:speed -1 :time 50)))
(sc:run source-clock)
(loop repeat 5
do (sleep 0.1)
always (= 50 (sc:with-freeze source-clock
(+ (sc:time up) (sc:time down))))))
; => TTo keep the time read from up and down clocks in sync,
we freeze their common source each time we need to read them.
Feel free to report bugs or make suggestions by filing an issue on github.
Feel free to submit pull requests on github as well.
Copyright 2023 Gleefre
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.