+++ /dev/null
-;;; -*-lisp-*-
-;;;
-;;; Maintenance and recalculation of dependent values
-;;;
-;;; (c) 2008 Mark Wooding
-;;;
-
-;;;----- Licensing notice ---------------------------------------------------
-;;;
-;;; This program is free software; you can redistribute it and/or modify
-;;; it under the terms of the GNU General Public License as published by
-;;; the Free Software Foundation; either version 2 of the License, or
-;;; (at your option) any later version.
-;;;
-;;; This program is distributed in the hope that it will be useful,
-;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-;;; GNU General Public License for more details.
-;;;
-;;; You should have received a copy of the GNU General Public License
-;;; along with this program; if not, write to the Free Software Foundation,
-;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-
-(defpackage #:dep
- (:use #:common-lisp #:queue)
- (:export #:dep #:depp #:make-dep #:make-leaf-dep #:dep-goodp
- #:dep-value #:dep-make-bad #:dep-bad #:dep-try
- #:dep-add-listener))
-(in-package #:dep)
-
-;;;--------------------------------------------------------------------------
-;;; Dependencies.
-
-(defstruct (dep (:predicate depp)
- (:constructor %make-dep))
- "There are two kinds of `dep', though we use the same object type for both.
- A leaf dep has no dependencies, and its value is set explicitly by the
- programmer. A non-leaf dep has a value /function/, which computes the
- dep's value as a function of other deps' values. The dependencies don't
- need to be declared in advance, or remain constant over time.
-
- When not during a recomputation phase (i.e., when `stable'), a dep is
- either `good' (i.e., it has a value) or `bad'. An attempt to read the
- value of a bad dep results in a throw of `bad-dep'. Badness propagates
- automatically during recomputation phases."
- (%value nil :type t)
- (value-func nil :type (or function null))
- (value-predicate #'eql :type function)
- (goodp nil :type boolean)
- (state :pending :type (member :stable :pending :recomputing))
- (listeners nil :type list)
- (dependents nil :type list))
-
-(defvar *evaluating-dep* nil
- "The dep currently being evaluated. This is bound only during the call of
- a value-func, and is used to track the dependencies implied during the
- function's evaluation.")
-
-(defvar *pending-deps* nil
- "A queue of deps pending recomputation. This is bound to a queue during
- recomputation and restored afterwards, so it can also be used as a flag to
- detect whether recomputation is happening.")
-
-(defun kick-dep (dep)
- "Call when DEP's value (or good/bad state) has changed. Marks the
- dependents of DEP as :pending, if they're currently :stable, and then
- clears the dependent list. Also invokes DEP's listener functions."
- (dolist (d (dep-dependents dep))
- (when (eq (dep-state d) :stable)
- (enqueue d *pending-deps*)
- (setf (dep-state d) :pending)))
- (setf (dep-dependents dep) nil)
- (dolist (l (dep-listeners dep))
- (funcall l)))
-
-(defun update-dep (dep value &optional (goodp t))
- "Modify the value of DEP. If GOODP is t, then mark it as good and store
- VALUE is its new value; otherwise mark it bad. If DEP's value is now
- different (according to its value-predicate) then return true; otherwise
- return false."
- (setf (dep-state dep) :stable)
- (cond ((not goodp)
- (if (dep-goodp dep)
- (progn (setf (dep-goodp dep) nil) t)
- nil))
- ((and (dep-goodp dep)
- (funcall (dep-value-predicate dep) value (dep-%value dep)))
- nil)
- (t
- (setf (dep-goodp dep) t
- (dep-%value dep) value)
- t)))
-
-(defun recompute-dep (dep)
- "Recompute the value of DEP. This function is careful to trap nonlocal
- transfers from the value-func."
- (let ((winning nil))
- (unwind-protect
- (catch 'dep-bad
- (setf (dep-state dep) :recomputing)
- (when (update-dep dep (let ((*evaluating-dep* dep))
- (funcall (dep-value-func dep))))
- (kick-dep dep))
- (setf winning t))
- (unless winning
- (when (update-dep dep nil nil)
- (kick-dep dep))))))
-
-(defun recompute-deps ()
- "Recompute all the pending deps, and any others that depend on them."
- (unwind-protect
- (loop (when (queue-emptyp *pending-deps*)
- (return))
- (let ((dep (dequeue *pending-deps*)))
- (when (eq (dep-state dep) :pending)
- (recompute-dep dep))))
- (loop (when (queue-emptyp *pending-deps*)
- (return))
- (let ((d (dequeue *pending-deps*)))
- (setf (dep-state d) :stable
- (dep-goodp d) nil)))))
-
-(defun ensure-dep-has-value (dep)
- "Ensure that DEP has a stable value. If DEP is currently computing,
- signals an error."
- (ecase (dep-state dep)
- (:stable)
- (:pending
- (recompute-dep dep))
- (:recomputing
- (error "Ouch! Cyclic dependency."))))
-
-(defun pulse-dep (dep)
- "Notifies DEP of a change in its value. If a recomputation phase is
- currently under way, queue the dependents and leave fixing things up to
- the outer loop; otherwise start up a recomputation phase."
- (if *pending-deps*
- (kick-dep dep)
- (let ((*pending-deps* (make-queue)))
- (kick-dep dep)
- (recompute-deps))))
-
-(defun (setf dep-value) (value dep)
- "Set DEP's value to be VALUE (and mark it as being good)."
- (when (dep-value-func dep)
- (error "Not a leaf dep."))
- (when (update-dep dep value)
- (pulse-dep dep))
- value)
-
-(defun dep-make-bad (dep)
- "Mark DEP as being bad."
- (when (dep-value-func dep)
- (error "Not a leaf dep."))
- (when (update-dep dep nil nil)
- (pulse-dep dep)))
-
-(defun dep-add-listener (dep func)
- "Add a listener function FUNC to the DEP. The FUNC is called each time the
- DEP's value (or good/bad state) changes. It is called with no arguments,
- and its return value is ignored."
- (push func (dep-listeners dep)))
-
-(defun dep-value (dep)
- "Retrieve the current value from DEP."
- (when *evaluating-dep*
- (pushnew *evaluating-dep* (dep-dependents dep)))
- (ensure-dep-has-value dep)
- (if (dep-goodp dep) (dep-%value dep) (throw 'dep-bad nil)))
-
-(defun make-dep (value-func)
- "Create a new DEP with the given VALUE-FUNC."
- (let ((dep (%make-dep :value-func value-func)))
- (let ((*pending-deps* (make-queue)))
- (enqueue dep *pending-deps*)
- (recompute-deps))
- dep))
-
-(defun make-leaf-dep (&optional (value nil goodp))
- "Creates a new DEP with the given VALUE, if any."
- (%make-dep :%value value :goodp goodp :state :stable))
-
-(defmacro dep-try (expr &body body)
- "Evaluate EXPR. If it throws dep-bad then evaluate BODY instead."
- (let ((block-name (gensym "TRY")))
- `(block ,block-name
- (catch 'dep-bad
- (return-from ,block-name ,expr))
- ,@body)))
-
-(defun dep-bad ()
- "Call from a value-func: indicates that the dep should marked as bad."
- (throw 'dep-bad nil))
-
-#+ no
-(defmethod print-object ((dep dep) stream)
- (print-unreadable-object (dep stream :type t :identity t)
- (ensure-dep-has-value dep)
- (if (dep-goodp dep)
- (format stream ":GOOD ~W" (dep-%value dep))
- (format stream ":BAD"))))
-
-#+ test
-(progn
- (defparameter x (make-leaf-dep 1))
- (defparameter y (make-leaf-dep 2))
- (defparameter z (make-dep (lambda () (+ (dep-value x) (dep-value y)))))
- (defparameter w (make-dep (lambda () (* (dep-value x) (dep-value z)))))
- (dep-add-listener x (lambda () (format t "x now ~A~%" x)))
- (dep-add-listener z (lambda () (format t "z now ~A~%" z)))
- (dep-add-listener w (lambda () (format t "w now ~A~%" w))))
-
-;;;----- That's all, folks --------------------------------------------------