Skip to content

stopclock is a library for measuring time using (stop)clocks.

License

Notifications You must be signed in to change notification settings

Gleefre/stopclock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

63 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

stopclock

stopclock is a library for measuring time using (stop)clocks.

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

Tutorial

Load the stopclock library

You can load this library from quicklisp:

(ql:quickload :stopclock)

Local nickname

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.

Tests

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%)
; => T

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).

Simple usage

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.260098

Finally, 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 now

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.

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>)

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:

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>

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.

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>

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.

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>

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.

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>

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.

CL-USER> (sc:make-clock :speed 0)
; Evaluation aborted on #<SC:ZERO-CLOCK-SPEED-ERROR {1006E41983}>.

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.

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>

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.) 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>)

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:

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

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:

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

For 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>
; => T

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.

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))))))
; => T

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.

About

stopclock is a library for measuring time using (stop)clocks.

Resources

License

Stars

Watchers

Forks

Packages

No packages published