Monorepo for Aesthetic.Computer
aesthetic.computer
1;;; Utility functions — logging, timing
2
3(in-package :ac-native.util)
4
5(defvar *log-stream* *error-output*
6 "Where ac-log writes. Rebound to a file stream when USB is mounted.")
7
8(defvar *log-file* nil "Open file stream for USB log, or nil.")
9
10(defun ac-log (fmt &rest args)
11 "Log a message to stderr and optionally to USB log file."
12 (let ((msg (apply #'format nil fmt args)))
13 (write-string msg *error-output*)
14 (force-output *error-output*)
15 (when *log-file*
16 (write-string msg *log-file*)
17 (force-output *log-file*))))
18
19(defun open-usb-log (path)
20 "Open a log file on the USB drive (append mode)."
21 (setf *log-file* (open path :direction :output
22 :if-exists :append
23 :if-does-not-exist :create)))
24
25(defun close-usb-log ()
26 (when *log-file*
27 (close *log-file*)
28 (setf *log-file* nil)))
29
30;;; Monotonic clock
31
32(cffi:defcstruct timespec
33 (tv-sec :long)
34 (tv-nsec :long))
35
36(cffi:defcfun ("clock_gettime" %clock-gettime) :int
37 (clk-id :int)
38 (tp :pointer))
39
40(defconstant +clock-monotonic+ 1)
41
42(defun monotonic-time-ms ()
43 "Return monotonic time in milliseconds as a double-float."
44 (cffi:with-foreign-object (ts '(:struct timespec))
45 (%clock-gettime +clock-monotonic+ ts)
46 (let ((sec (cffi:foreign-slot-value ts '(:struct timespec) 'tv-sec))
47 (nsec (cffi:foreign-slot-value ts '(:struct timespec) 'tv-nsec)))
48 (+ (* sec 1000.0d0) (/ nsec 1000000.0d0)))))
49
50(cffi:defcfun ("clock_nanosleep" %clock-nanosleep) :int
51 (clk-id :int)
52 (flags :int)
53 (request :pointer)
54 (remain :pointer))
55
56(defconstant +timer-abstime+ 1)
57
58(defvar *frame-target-ns* (floor 1000000000 60)
59 "Nanoseconds per frame at 60fps.")
60
61(defvar *next-frame-sec* 0)
62(defvar *next-frame-nsec* 0)
63
64(defun frame-sync-60fps ()
65 "Sleep until the next 60fps frame boundary. Call once per frame."
66 (incf *next-frame-nsec* *frame-target-ns*)
67 (when (>= *next-frame-nsec* 1000000000)
68 (decf *next-frame-nsec* 1000000000)
69 (incf *next-frame-sec*))
70 ;; Initialize on first call
71 (when (zerop *next-frame-sec*)
72 (cffi:with-foreign-object (ts '(:struct timespec))
73 (%clock-gettime +clock-monotonic+ ts)
74 (setf *next-frame-sec*
75 (cffi:foreign-slot-value ts '(:struct timespec) 'tv-sec))
76 (setf *next-frame-nsec*
77 (cffi:foreign-slot-value ts '(:struct timespec) 'tv-nsec))
78 (return-from frame-sync-60fps)))
79 ;; Sleep until target time
80 (cffi:with-foreign-object (ts '(:struct timespec))
81 (setf (cffi:foreign-slot-value ts '(:struct timespec) 'tv-sec) *next-frame-sec*)
82 (setf (cffi:foreign-slot-value ts '(:struct timespec) 'tv-nsec) *next-frame-nsec*)
83 (%clock-nanosleep +clock-monotonic+ +timer-abstime+ ts (cffi:null-pointer))))