Initial revision.
[jlisp] / dep.lisp
CommitLineData
ee79a5f1
MW
1;;; -*-lisp-*-
2;;;
3;;; Maintenance and recalculation of dependent values
4;;;
5;;; (c) 2008 Mark Wooding
6;;;
7
8;;;----- Licensing notice ---------------------------------------------------
9;;;
10;;; This program is free software; you can redistribute it and/or modify
11;;; it under the terms of the GNU General Public License as published by
12;;; the Free Software Foundation; either version 2 of the License, or
13;;; (at your option) any later version.
14;;;
15;;; This program is distributed in the hope that it will be useful,
16;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;;; GNU General Public License for more details.
19;;;
20;;; You should have received a copy of the GNU General Public License
21;;; along with this program; if not, write to the Free Software Foundation,
22;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24(defpackage #:dep
25 (:use #:common-lisp #:queue)
26 (:export #:dep #:depp #:make-dep #:make-leaf-dep #:dep-goodp
27 #:dep-value #:dep-make-bad #:dep-bad #:dep-try
28 #:dep-add-listener))
29(in-package #:dep)
30
31;;;--------------------------------------------------------------------------
32;;; Dependencies.
33
34(defstruct (dep (:predicate depp)
35 (:constructor %make-dep))
36 "There are two kinds of `dep', though we use the same object type for both.
37 A leaf dep has no dependencies, and its value is set explicitly by the
38 programmer. A non-leaf dep has a value /function/, which computes the
39 dep's value as a function of other deps' values. The dependencies don't
40 need to be declared in advance, or remain constant over time.
41
42 When not during a recomputation phase (i.e., when `stable'), a dep is
43 either `good' (i.e., it has a value) or `bad'. An attempt to read the
44 value of a bad dep results in a throw of `bad-dep'. Badness propagates
45 automatically during recomputation phases."
46 (%value nil :type t)
47 (value-func nil :type (or function null))
48 (value-predicate #'eql :type function)
49 (goodp nil :type boolean)
50 (state :pending :type (member :stable :pending :recomputing))
51 (listeners nil :type list)
52 (dependents nil :type list))
53
54(defvar *evaluating-dep* nil
55 "The dep currently being evaluated. This is bound only during the call of
56 a value-func, and is used to track the dependencies implied during the
57 function's evaluation.")
58
59(defvar *pending-deps* nil
60 "A queue of deps pending recomputation. This is bound to a queue during
61 recomputation and restored afterwards, so it can also be used as a flag to
62 detect whether recomputation is happening.")
63
64(defun kick-dep (dep)
65 "Call when DEP's value (or good/bad state) has changed. Marks the
66 dependents of DEP as :pending, if they're currently :stable, and then
67 clears the dependent list. Also invokes DEP's listener functions."
68 (dolist (d (dep-dependents dep))
69 (when (eq (dep-state d) :stable)
70 (enqueue d *pending-deps*)
71 (setf (dep-state d) :pending)))
72 (setf (dep-dependents dep) nil)
73 (dolist (l (dep-listeners dep))
74 (funcall l)))
75
76(defun update-dep (dep value &optional (goodp t))
77 "Modify the value of DEP. If GOODP is t, then mark it as good and store
78 VALUE is its new value; otherwise mark it bad. If DEP's value is now
79 different (according to its value-predicate) then return true; otherwise
80 return false."
81 (setf (dep-state dep) :stable)
82 (cond ((not goodp)
83 (if (dep-goodp dep)
84 (progn (setf (dep-goodp dep) nil) t)
85 nil))
86 ((and (dep-goodp dep)
87 (funcall (dep-value-predicate dep) value (dep-%value dep)))
88 nil)
89 (t
90 (setf (dep-goodp dep) t
91 (dep-%value dep) value)
92 t)))
93
94(defun recompute-dep (dep)
95 "Recompute the value of DEP. This function is careful to trap nonlocal
96 transfers from the value-func."
97 (let ((winning nil))
98 (unwind-protect
99 (catch 'dep-bad
100 (setf (dep-state dep) :recomputing)
101 (when (update-dep dep (let ((*evaluating-dep* dep))
102 (funcall (dep-value-func dep))))
103 (kick-dep dep))
104 (setf winning t))
105 (unless winning
106 (when (update-dep dep nil nil)
107 (kick-dep dep))))))
108
109(defun recompute-deps ()
110 "Recompute all the pending deps, and any others that depend on them."
111 (unwind-protect
112 (loop (when (queue-emptyp *pending-deps*)
113 (return))
114 (let ((dep (dequeue *pending-deps*)))
115 (when (eq (dep-state dep) :pending)
116 (recompute-dep dep))))
117 (loop (when (queue-emptyp *pending-deps*)
118 (return))
119 (let ((d (dequeue *pending-deps*)))
120 (setf (dep-state d) :stable
121 (dep-goodp d) nil)))))
122
123(defun ensure-dep-has-value (dep)
124 "Ensure that DEP has a stable value. If DEP is currently computing,
125 signals an error."
126 (ecase (dep-state dep)
127 (:stable)
128 (:pending
129 (recompute-dep dep))
130 (:recomputing
131 (error "Ouch! Cyclic dependency."))))
132
133(defun pulse-dep (dep)
134 "Notifies DEP of a change in its value. If a recomputation phase is
135 currently under way, queue the dependents and leave fixing things up to
136 the outer loop; otherwise start up a recomputation phase."
137 (if *pending-deps*
138 (kick-dep dep)
139 (let ((*pending-deps* (make-queue)))
140 (kick-dep dep)
141 (recompute-deps))))
142
143(defun (setf dep-value) (value dep)
144 "Set DEP's value to be VALUE (and mark it as being good)."
145 (when (dep-value-func dep)
146 (error "Not a leaf dep."))
147 (when (update-dep dep value)
148 (pulse-dep dep))
149 value)
150
151(defun dep-make-bad (dep)
152 "Mark DEP as being bad."
153 (when (dep-value-func dep)
154 (error "Not a leaf dep."))
155 (when (update-dep dep nil nil)
156 (pulse-dep dep)))
157
158(defun dep-add-listener (dep func)
159 "Add a listener function FUNC to the DEP. The FUNC is called each time the
160 DEP's value (or good/bad state) changes. It is called with no arguments,
161 and its return value is ignored."
162 (push func (dep-listeners dep)))
163
164(defun dep-value (dep)
165 "Retrieve the current value from DEP."
166 (when *evaluating-dep*
167 (pushnew *evaluating-dep* (dep-dependents dep)))
168 (ensure-dep-has-value dep)
169 (if (dep-goodp dep) (dep-%value dep) (throw 'dep-bad nil)))
170
171(defun make-dep (value-func)
172 "Create a new DEP with the given VALUE-FUNC."
173 (let ((dep (%make-dep :value-func value-func)))
174 (let ((*pending-deps* (make-queue)))
175 (enqueue dep *pending-deps*)
176 (recompute-deps))
177 dep))
178
179(defun make-leaf-dep (&optional (value nil goodp))
180 "Creates a new DEP with the given VALUE, if any."
181 (%make-dep :%value value :goodp goodp :state :stable))
182
183(defmacro dep-try (expr &body body)
184 "Evaluate EXPR. If it throws dep-bad then evaluate BODY instead."
185 (let ((block-name (gensym "TRY")))
186 `(block ,block-name
187 (catch 'dep-bad
188 (return-from ,block-name ,expr))
189 ,@body)))
190
191(defun dep-bad ()
192 "Call from a value-func: indicates that the dep should marked as bad."
193 (throw 'dep-bad nil))
194
195#+ no
196(defmethod print-object ((dep dep) stream)
197 (print-unreadable-object (dep stream :type t :identity t)
198 (ensure-dep-has-value dep)
199 (if (dep-goodp dep)
200 (format stream ":GOOD ~W" (dep-%value dep))
201 (format stream ":BAD"))))
202
203#+ test
204(progn
205 (defparameter x (make-leaf-dep 1))
206 (defparameter y (make-leaf-dep 2))
207 (defparameter z (make-dep (lambda () (+ (dep-value x) (dep-value y)))))
208 (defparameter w (make-dep (lambda () (* (dep-value x) (dep-value z)))))
209 (dep-add-listener x (lambda () (format t "x now ~A~%" x)))
210 (dep-add-listener z (lambda () (format t "z now ~A~%" z)))
211 (dep-add-listener w (lambda () (format t "w now ~A~%" w))))
212
213;;;----- That's all, folks --------------------------------------------------