Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Beginnings of tutorial/porting suite of demonstration/example/regress…
…ion test code. Also, a fix to core Cells so rules can happen to return multiple values (say by using ROUND as the last form) without tripping over Synapse-handling.
- Loading branch information
ktilton
committed
May 30, 2006
1 parent
6511515
commit 5aedb77
Showing
15 changed files
with
433 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
(defmacro cells::ct-assert (form &rest stuff) | ||
`(progn | ||
(print `(attempting ,',form)) | ||
(assert ,form () "Error with ~a >> ~a" ',form (list ,@stuff)))) | ||
|
||
(defpackage #:tu-selfinit (:use :cl :cells)) | ||
|
||
;; | ||
;; We will keep making new packages so we can incrementally develop the | ||
;; same class without newer versions stomping on earlier versions (by | ||
;; being in the same package and effectively redefining earlier versions). | ||
;; | ||
(in-package #:tu-selfinit) | ||
|
||
(defmodel rectangle () | ||
((len :initarg :len :accessor len | ||
:initform (c? (* 2 (width self)))) | ||
(width :initarg :width :initform nil :accessor width)) | ||
(:default-initargs | ||
:width (c? (/ (len self) 2)))) | ||
|
||
#+test | ||
(cells::ct-assert (eql 21 (width (make-instance 'rectangle :len 42)))) | ||
|
||
;;; The first thing we see is that we are not creating something new, we are | ||
;;; merely /extending/ CLOS. defmodel works like defclass in all ways, except for | ||
;;; extensions to provide the behavior of Cells. We see both :initform | ||
;;; and :default-initarg used to provide rules for a slot. We also see | ||
;;; the initarg :len used to override the default initform. | ||
;;; | ||
;;; By extending defclass we (a) retain its expressiveness, and (b) produce | ||
;;; something hopefully easier to learn by developers already familiar with CLOS. | ||
;;; | ||
;;; The first extension we see is that the len initform refers to the | ||
;;; Smalltalk-like anaphoric variable self, to which will be bound | ||
;;; the rectangle instance being initialized. Normally an initform is evaluated | ||
;;; without being able to see the instance, and any initialization requiring | ||
;;; that must be done in the class initializer. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
(defpackage #:tu-dataflow (:use :cl :cells)) | ||
(in-package #:tu-dataflow) | ||
|
||
(defmodel rectangle () | ||
((len :initarg :len :accessor len | ||
:initform (c? (* 2 (width self)))) | ||
(width :initarg :width :initform nil :accessor width)) | ||
(:default-initargs | ||
:width (c? (/ (len self) 2)))) | ||
|
||
#+test | ||
(let ((r (make-instance 'rectangle :len (c-in 42)))) | ||
(cells::ct-assert (eql 21 (width r))) | ||
(cells::ct-assert (= 1000 (setf (len r) 1000))) ;; make sure we did not break SETF, which must return the value set | ||
(cells::ct-assert (eql 500 (width r)))) ;; make sure new value propagated | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#| There is the fun part: automatic state management. Not only can a slot get its value from | ||
a self-aware rule, but that value will stay current with other values as they change. | ||
But often changes to a value must be reflected outside the automatic dataflow model. See next. | ||
|# | ||
|
||
(defpackage #:tu-change-handling (:use :cl :cells)) | ||
(in-package #:tu-change-handling) | ||
|
||
(defmodel rectangle () | ||
((len :initarg :len :accessor len | ||
:initform (c? (* 2 (width self)))) | ||
(width :initarg :width :initform nil :accessor width)) | ||
(:default-initargs | ||
:width (c? (/ (len self) 2)))) | ||
|
||
(defvar *gui-told*) | ||
|
||
(defobserver len ((self rectangle) new-value old-value old-value-bound-p) | ||
;; Where rectangle is a GUI element, we need to tell the GUI framework | ||
;; to update this area of the screen | ||
(setf *gui-told* t) | ||
(print (list "tell GUI about" self new-value old-value old-value-bound-p))) | ||
|
||
#+test | ||
(let* ((*gui-told* nil) | ||
(r (make-instance 'rectangle :len (c-in 42)))) | ||
(cells::ct-assert *gui-told*) | ||
(setf *gui-told* nil) | ||
(cells::ct-assert (eql 21 (width r))) | ||
|
||
(cells::ct-assert (= 1000 (setf (len r) 1000))) | ||
(cells::ct-assert *gui-told*) | ||
(cells::ct-assert (eql 500 (width r)))) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#| Now we have automatic state management (including change propagation) | ||
outside the Cells model as well as in. Now lets look at cascading change | ||
by adding another level of computation, so A->B->C. | ||
[Actually, I see I need to make this a little deeper, since area has | ||
a direct dependency on width. Not tonight. :)] | ||
|# | ||
|
||
(defpackage #:tu-depth (:use :cl :cells)) | ||
(in-package #:tu-depth) | ||
|
||
|
||
(defmodel rectangle () | ||
((area :initarg :area :accessor area | ||
:initform (c? (print :compue-area) | ||
(* (len self)(width self)))) | ||
(len :initarg :len :accessor len | ||
:initform (c? (print :compute-len) | ||
(* 2 (width self)))) | ||
(width :initarg :width :accessor width | ||
:initform (c? (print :compute-width) | ||
(floor (len self) 2))))) | ||
|
||
#+test | ||
(let ((r (make-instance 'rectangle :len (c-in 42)))) | ||
(cells::ct-assert (eql 21 (width r))) | ||
(cells::ct-assert (eql (* 21 42) (area r))) | ||
(cells::ct-assert (= 1000 (setf (len r) 1000))) | ||
(cells::ct-assert (eql 500000 (area r)))) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
#| A->B->C works. For efficiency, let's have propagation stop if some rule | ||
computes the same value as last time. | ||
|# | ||
|
||
(defpackage #:tu-smart-propagation (:use :cl :cells :utils-kt :tu-cells)) | ||
(in-package #:tu-smart-propagation) | ||
|
||
|
||
;;; ----------------------------------------------- | ||
|
||
(defmodel rectangle () | ||
((padded-width :initarg :padded-width :accessor padded-width | ||
:initform (c? (compute-log :padded-width) | ||
(+ 10 (width self)))) | ||
(len :initarg :len :accessor len | ||
:initform (c? (compute-log :len) | ||
(* 2 (width self)))) | ||
(width :initarg :width :accessor width | ||
:initform (c? (compute-log :width) | ||
(floor (len self) 2))))) | ||
|
||
(defobserver width () | ||
(assert (not (eql new-value old-value))) | ||
(TRC "observing width" new-value old-value) | ||
(compute-log :width-observer)) | ||
|
||
(defobserver len () | ||
(compute-log :len-observer)) | ||
|
||
#+test | ||
(let* ((r (progn | ||
(CELLS-RESET) | ||
(clear-computed) | ||
(make-instance 'rectangle :len (c-in 42))))) | ||
(cells::ct-assert (eql 21 (width r))) | ||
|
||
;; first check that setting an input cell does not | ||
;; propagate needlessly... | ||
|
||
(clear-computed) | ||
(verify-not-computed :len-observer :width :width-observer :padded-width) | ||
(setf (len r) 42) ;; n.b. same as existing value, no change | ||
(cells::ct-assert (eql 21 (width r))) ;; floor truncates | ||
(verify-not-computed :len-observer :width :width-observer :padded-width) | ||
|
||
;; now check that intermediate computations, when unchanged | ||
;; from the preceding computation, does not propagate needlessly... | ||
|
||
(clear-computed) | ||
(setf (len r) 43) | ||
(cells::ct-assert (eql 21 (width r))) ;; floor truncates | ||
(verify-computed :len-observer :width) | ||
(verify-not-computed :width-observer :padded-width) | ||
|
||
#| Ok, so the engine runs the width rule, sees that it computes | ||
the same value as before, so does not invoke either the width | ||
observer or recalculation of are. Very efficient. The sanity check | ||
reconfirms that the engine does do that work when necessary. | ||
|# | ||
|
||
(clear-computed) | ||
(setf (len r) 44) | ||
(verify-computed :len-observer :width :width-observer :padded-width)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
|
||
|
||
(defpackage #:tu-ephemeral (:use :cl :utils-kt :cells :tu-cells)) | ||
(in-package #:tu-ephemeral) | ||
|
||
|
||
#| | ||
Events present a problem for spreadsheet models. Suppose we have a clicked rule for a button | ||
which says: | ||
:clicked (c? (point-in-rect | ||
(screen-location (mouse-event *window*)) | ||
(bounding-box self))) | ||
Now suppose we get a mouse-event outside the bounding box of widget X, and then in the | ||
next application event something happens that makes the bounding box grow such that it | ||
includes the location of the old mouse event. We need the mouse-event not to be there any more, | ||
because, well, events are events. It is relevant only in the moment of its creation and propagation. | ||
Note, btw, that this must happen not as bang-bang: | ||
(setf (mouse-event *window*) (get-next-event) | ||
(setf (mouse-event *window*) nil) | ||
...because observers can kick off state change, and anyway SETF has interesting Cell semantics, | ||
including observers firing. So setf-nil is a kludge, better that the Cells engine acknowledge that | ||
events are different and accomodate them by silently reverting an event to nil as soon as it finishes | ||
propagating. | ||
Finally, so far this has worked out well as a slot attribute as defined at the class level, not | ||
instance by instance, by specifying :cell :ephemeral | ||
|# | ||
|
||
(defmodel rectangle () | ||
((click :cell :ephemeral :initform (c-in nil) :accessor click) | ||
(bbox :initarg :bbox :initform (c-in nil) :accessor bbox) | ||
(clicked :cell :ephemeral :accessor clicked | ||
:initform (c? (point-in-rect (^click)(^bbox)))))) | ||
|
||
(defun point-in-rect (p r) | ||
(when (and p r) | ||
(destructuring-bind (x y) p | ||
(destructuring-bind (l top r b) r | ||
(and (<= l x r) | ||
(<= b y top)))))) | ||
|
||
(defobserver click ((self rectangle) new-value old-value old-value-bound-p) | ||
(when new-value | ||
(with-integrity (:change) | ||
(TRC "setting bbox!!!") | ||
(setf (bbox self) (list -1000 1000 1000 -1000))))) | ||
|
||
(defobserver clicked ((self rectangle) new-value old-value old-value-bound-p) | ||
(when new-value | ||
(TRC "clicked!!!!" self new-value) | ||
(compute-log :clicked))) | ||
|
||
#+test | ||
(progn | ||
(cells-reset) | ||
(let* ((starting-bbox (list 10 10 20 20)) | ||
(r (make-instance 'rectangle | ||
:bbox (c-in (list 10 10 20 20))))) | ||
(clear-computed) | ||
(setf (click r) (list 0 0)) | ||
(assert (and (not (point-in-rect (list 0 0) starting-bbox)) | ||
(point-in-rect (list 0 0)(bbox r)) | ||
(verify-not-computed :clicked))))) | ||
|
||
#| | ||
The assertion demonstrates... well, it is complicated. Point 0-0 is | ||
in the current bbox, but the system correctly determines that it | ||
was not clicked. The click event at 0-0 happened when the bbox | ||
was elsewhwer. When the bbox moved, the Cells engine had already cleared | ||
the "ephemeral" click. | ||
Note that now we have less transparency: if one wants to perturb the data model | ||
from with an observer of some ongoing perturbation, one needs to arrange for | ||
that nested perturbation to wait until the ongoing one completes. That | ||
explains the "with-integrity" macro. | ||
|# | ||
|
Oops, something went wrong.