README.org
June 20, 2023 ยท View on GitHub
#+title: stopclock #+author: Gleefre #+email: varedif.a.s@gmail.com
#+language: en #+options: toc:nil
=stopclock= is a library for measuring time using (stop)clocks.
[[http://quickdocs.org/stopclock/][file:http://quickdocs.org/badge/sketch.svg]]
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.
- Table of contents
- [[#stopclock][stopclock]]
- [[#table-of-contents][Table of contents]]
- [[#tutorial][Tutorial]]
- [[#load-the-stopclock-library][Load the stopclock library]]
- [[#local-nickname][Local nickname]]
- [[#tests][Tests]]
- [[#dont-use-package][Don't :use package]]
- [[#simple-usage][Simple usage]]
- [[#copy-clock][Copy clock]]
- [[#clock-parameters][Clock parameters]]
- [[#state-of-the-clock][State of the clock]]
- [[#time-on-the-clock][Time on the clock]]
- [[#speed-of-the-clock][Speed of the clock]]
- [[#zero-clock-speed-error][zero-clock-speed-error]]
- [[#reset-the-clock][Reset the clock]]
- [[#time-source][Time source]]
- [[#synchronized-clocks][Synchronized clocks]]
- [[#clock-freeze][Clock freeze]]
- [[#load-the-stopclock-library][Load the stopclock library]]
- [[#bugs--contributions][Bugs & Contributions]]
- [[#license][License]]
-
Tutorial ** Load the stopclock library You can load this library from quicklisp: #+BEGIN_SRC lisp (ql:quickload :stopclock) #+END_SRC *** Local nickname You can define local nickname for this package. (It is possible with almost all implementations, see [[https://lispcookbook.github.io/cl-cookbook/packages.html#package-local-nicknames-pln][Cookbook - packages - PLN]]). You can do it in your package definition: #+BEGIN_SRC lisp (defpackage #:my-package (:use #:cl) (:local-nicknames (#:sc #:stopclock))) #+END_SRC Or during your REPL session: #+BEGIN_SRC lisp CL-USER> (add-package-local-nickname '#:sc '#:stopclock) ; => #<PACKAGE "COMMON-LISP-USER"> #+END_SRC The rest of this tutorial uses
scas a local nickname forstopclock. *** Tests You can run tests with asdf. Test system depends onfiveamlibrary. #+BEGIN_SRC lisp (asdf:test-system :stopclock) ; depends on fiveam ; ... ; Did 564 checks. ; Pass: 564 (100%) ; Skip: 0 ( 0%) ; Fail: 0 ( 0%) ; => T #+END_SRC *** Don't :use package Note that you can't(use-package :stopclock)because it causes name-conflicts withcommon-lisppackage. There are two function you will need to shadow:sc:timeandsc:speed.It is recommended to use
package-local-nicknamesinstead (see [[#local-nickname][Local nickname]]). ** Simple usage To create a clock usemake-clockfunction. #+BEGIN_SRC lisp CL-USER> (defparameter c (sc:make-clock)) ; => C CL-USER> c ; => #<CLOCK :TIME 1.98 SECONDS :RUNNING :SPEED x1> #+END_SRC You can stop the clock withstopfunction and run it withrunfunction. #+BEGIN_SRC lisp 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. #+END_SRC To get current clock time usetimefunction. It returns the time in seconds. #+BEGIN_SRC lisp CL-USER> (sc:time c) ; => 12388023/500000 ; usually time is a rational number CL-USER> (float (sc:time c)) ; => 35.260098 #+END_SRC Finally, you can reset the current time on the clock withresetfunction. #+BEGIN_SRC lisp CL-USER> (sc:reset c) ; => #<CLOCK :TIME 0.00 SECONDS :RUNNING :SPEED x1> #+END_SRC It does not pause or run of the clock by default, but you can specify it with:paused Tor:run Tkey arguments. #+BEGIN_SRC lisp 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 now #+END_SRC ** Copy clock Most functions, such asstopandrun, act destructively on the clock and return itself for convenience. You can copy the clock withcopy-clock. #+BEGIN_SRC lisp 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>) #+END_SRC ** Clock parameters A clock has three parameters:time,speedand whether it ispausedor is running. (speedrefers 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: #+BEGIN_SRC lisp 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> #+END_SRC
For each of these parameters, a corresponding accessor is defined:
time,speed, andpaused. #+BEGIN_SRC lisp 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> #+END_SRC ** State of the clock The paused / running state of the clock can be accessed with functionpaused. The state can be set by combiningpausedwithsetf. It also can be set by functionsrun(or a synonymousstart),pause(or a synonymousstop) andtoggle. These function return the clock itself. #+BEGIN_SRC lisp 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> #+END_SRC ** Time on the clock The time on the clock can accessed with functiontime. You can set the time by combiningtimewithsetf. There is also anadjustfunction that adds a given number of seconds to the current clock time. It is more efficient than using combination ofincfandtime. Unlikesetforincfit returns the clock itself. #+BEGIN_SRC lisp 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> #+END_SRC ** Speed of the clock The speed of the clock can accessed withspeed. You can set it by combiningspeedwithsetf. There is also anacceleratefunction that will multiply the speed by a given factor. Unlikesetforincfit returns the clock itself. #+BEGIN_SRC lisp 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> #+END_SRC *** zero-clock-speed-error The speed of the clock cannot be equal to zero. If you try to set it to zero thezero-clock-speed-errorwill be signalled. #+BEGIN_SRC lisp CL-USER> (sc:make-clock :speed 0) ; Evaluation aborted on #<SC:ZERO-CLOCK-SPEED-ERROR {1006E41983}>. #+END_SRC ** Reset the clock To reset the clock you can useresetfunction. By default it only resets the time to 0. You can pass one of:pausedor:runkey arguments to set the clock's state to the corresponding value. You can also specify:speedand:timeto be set. The function returns the clock itself. #+BEGIN_SRC lisp 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> #+END_SRC The:pausedarguments takes precedence over:run: #+BEGIN_SRC lisp CL-USER> (sc:reset c :paused t :run t) ; => #<CLOCK :TIME 0.00 SECONDS :PAUSED :SPEED x10> #+END_SRC ** Time source By default the clock will get current time withget-internal-real-timefunction. This behaviour can be changed by passing:time-sourceparameter to themake-clockfunction. This must be a function that returns the current time in seconds. (It also can be another clock, see [[#synchronized-clocks][Synchronized clocks]].) =stopclock= defines two possible time-sources:real-timethat usesget-internal-real-timeis used by default, andrun-timethat usesget-internal-run-timeinstead. #+BEGIN_SRC lisp 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>) #+END_SRC ** Synchronized clocks 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: #+BEGIN_SRC lisp 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))) ; => NIL #+END_SRC We 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: #+BEGIN_SRC lisp 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))) ; => T #+END_SRCFor convenience you can directly pass another clock as the time source. Here is another example: #+BEGIN_SRC lisp 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> ; => T #+END_SRC Time on the clocksupanddownwill always add up to 50. ** Clock freeze 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 youfreezethe clock it freezes the time on the clock, which is almost identical to pausing it. However, when youunfreezeit, the clock behaves as if it had not been frozen. #+BEGIN_SRC lisp 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. #+END_SRC It also means that the paused clock will remain paused after the freeze. #+BEGIN_SRC lisp 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> #+END_SRC =stopclock= also provides a macrowith-freeze. Consider the previous example: #+BEGIN_SRC lisp 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)))))) ; => T #+END_SRC To keep the time read fromupanddownclocks in sync, we freeze their common source each time we need to read them. -
Bugs & Contributions Feel free to report bugs or make suggestions by filing an issue on github.
Feel free to submit pull requests on github as well.
-
License 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.0Unless 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.