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]]
  • [[#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 sc as a local nickname for stopclock. *** Tests You can run tests with asdf. Test system depends on fiveam library. #+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 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][Local nickname]]). ** Simple usage To create a clock use make-clock function. #+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 with stop function and run it with run function. #+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 use time function. 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 with reset function. #+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 T or :run T key 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 as stop and run, act destructively on the clock and return itself for convenience. You can copy the clock with copy-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, 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: #+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, and paused. #+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 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. #+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 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. #+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 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. #+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 the zero-clock-speed-error will 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 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. #+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 :paused arguments 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 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][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. #+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_SRC

    For 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 clocks up and down will 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 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. #+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 macro with-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 from up and down clocks 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.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.