| Text Formatting | Contents | Index | Extended Stream Input |
21 Incremental Redisplay
21.1 Overview of Incremental Redisplay
CLIM's incremental redisplay facility to allows the programmer to change the
output in an output history (and hence, on the screen or other output device) in
an incremental fashion. It allows the programmer to redisplay individual pieces
of the existing output differently, under program control. It is
``incremental'' in the sense that CLIM will try to minimize the changes to the
existing output on a display device when displaying new output.
There are two different ways to do incremental redisplay.
The first is to call redisplay on an output record. In essence, this tells CLIM to recompute the output of that output record over from scratch. CLIM compares the new results with the existing output and tries to do minimal redisplay. The updating-output form allows the programmer to assist CLIM by informing it that entire branches of the output history are known not to have changed. updating-output also allows the programmer to communicate the fact that a piece of the output record hierarchy has moved, either by having an output record change its parent, or by having an output record change its position.
The second way to do incremental redisplay is for the programmer to manually do the updates to the output history, and then call note-output-record-child-changed on an output record. This causes CLIM to propagate the changes up the output record tree and allows parent output records to readjust themselves to account for the changes.
Each style is appropriate under different circumstances. redisplay is often easier to use, especially when there might are large numbers of changes between two passes, or when the programmer has only a poor idea as to what the changes might be. note-output-record-child-changed can be more efficient for small changes at the bottom of the output record hierarchy, or in cases where the programmer is well informed as to the specific changes necessary and can help CLIM out.
21.1.1 Examples of Incremental Redisplay
The usual technique of incremental redisplay is to use updating-output to
inform CLIM what output has changed, and use redisplay to recompute and
redisplay that output.
The outermost call to updating-output identifies a program fragment that produces incrementally redisplayable output. A nested call to updating-output (that is, a call to updating-output that occurs during the execution of the body of the outermost updating-output and specifies the same stream) identifies an individually redisplayable piece of output, the program fragment that produces that output, and the circumstances under which that output needs to be redrawn. This nested calls to updating-output are just hints to incremental redisplay that can reduce the amount of work done by CLIM.
The outermost call to updating-output executes its body, producing the initial version of the output, and returns an updating-output-record that captures the body in a closure. Each nested call to updating-output stores its :unique-id and :cache-value arguments and the portion of the output produced by its body.
redisplay takes an updating-output-record and executes the captured body of updating-output over again. When a nested call to updating-output is executed during redisplay, updating-output decides whether the cached output can be reused or the output needs to be redrawn. This is controlled by the :cache-value argument to updating-output . If its value matches its previous value, the body would produce output identical to the previous output and thus it is unnecessary for CLIM to execute the body again. In this case the cached output is reused and updating-output does not execute its body. If the cache value does not match, the output needs to be recomputed, so updating-output executes its body and the new output drawn on the stream replaces the previous output. The :cache-value argument is only meaningful for nested calls to updating-output .
In order to compare the cache to the output record, two pieces of information are necessary:
It is valid to give the :unique-id and not the :cache-value . This is done to identify a parent in the hierarchy. By this means, the children essentially get a more complex unique id when they are matched for output. (In other words, it is like using a telephone area code.) The cache without a cache value is never valid. Its children always have to be checked.
It is also valid to give the :cache-value and not the :unique-id . In this case, unique ids are just assigned sequentially. So, if output associated with the same thing is done in the same order each time, it isn't necessary to invent new unique ids for each piece. This is especially true in the case of children of a cache with a unique id and no cache value of its own. In this case, the parent marks the particular data structure, whose components can change individually, and the children are always in the same order and properly identified by their parent and the order in which they are output.
A unique id need not be unique across the entire redisplay, only among the children of a given output cache; that is, among all possible (current and additional) uses made of updating-output that are dynamically (not lexically) within another.
To make incremental redisplay maximally efficient, the programmer should attempt to give as many caches with :cache-value as possible. For instance, if the thing being redisplayed is a deeply nested tree, it is better to be able to know when whole branches have not changed than to have to recurse to every single leaf and check it. So, if there is a modification tick in the leaves, it is better to also have one in their parent of the leaves and propagate the modification up when things change. While the simpler approach works, it requires CLIM to do more work than is necessary.
The following function illustrates the standard use of incremental redisplay:
(defun test (stream)
(let* ((list (list 1 2 3 4 5))
(record
(updating-output (stream)
(do* ((elements list (cdr elements))
(count 0 (1+ count)))
((null elements))
(let ((element (first elements)))
(updating-output (stream :unique-id count
:cache-value element)
(format stream "Element ~D" element)
(terpri stream)))))))
(sleep 10)
(setf (nth 2 list) 17)
(redisplay record stream)))
When this function is run on a window, the initial display will look like:
Element 1 Element 2 Element 3 Element 4 Element 5After the sleep has terminated, the display will look like:
Element 1 Element 2 Element 17 Element 4 Element 5CLIM takes care of ensuring that only the third line gets erased and redisplayed. In the case where items moved around (try the example substituting
(setq list (sort list #'(lambda (x y)
(declare (ignore x y))
(zerop (random 2)))))
for the form after the call to sleep ), CLIM would ensure that the minimum
amount of work would be done in updating the display, thereby minimizing
``flashiness'' while providing a powerful user interface.
See Chapter Application Frames for a discussion of how to use incremental redisplay automatically within the panes of an application frame.
21.2 Standard Programmer Interface
| updating-output | (stream &rest args &key unique-id (id-test #'eql ) cache-value (cache-test #'eql ) fixed-position all-new parent-cache record-type) &body body | [Macro] |
The stream argument is not evaluated, and must be a symbol that is bound to an output recording stream. If stream is t , *standard-output* is used. body may have zero or more declarations as its first forms.
record-type specifies the class of output record to create. The default is standard-updating-output-record . This argument should only be supplied by a programmer if there is a new class of output record that supports the updating output record protocol.
updating-output must be implemented by expanding into a call to invoke-updating-output , supplying a function that executes body as the continuation argument to invoke-updating-output . The exact behavior of this macro is described under invoke-updating-output .
| invoke-updating-output | stream continuation record-type unique-id id-test cache-value cache-test &key all-new parent-cache | [Generic function] |
If this is used outside the dynamic scope of an incremental redisplay, it has no particular effect. However, when incremental redisplay is occurring, the supplied cache-value is compared with the value stored in the cache identified by unique-id . If the values differ or the code in body has not been run before, the code in body runs, and cache-value is saved for next time. If the cache values are the same, the code in body is not run, because the current output is still valid.
unique-id provides a means to uniquely identify the output done by body . If unique-id is not supplied, CLIM will generate one that is guaranteed to be unique. unique-id may be any object as long as it is unique with respect to the id-test predicate among all such unique ids in the current incremental redisplay. id-test is a function of two arguments that is used for comparing unique ids; it has indefinite extent.
cache-value is a value that remains constant if and only if the output produced by body does not need to be recomputed. If the cache value is not supplied, CLIM will not use a cache for this piece of output. cache-test is a function of two arguments that is used for comparing cache values; it has indefinite extent.
If fixed-position is true , then the location of this output is fixed relative to its parent output record. When CLIM redisplays an output record that has a fixed position, then if the contents have not changed, the position of the output record will not change. If the contents have changed, CLIM assumes that the code will take care to preserve its position. The default for fixed-position is false .
If all-new is true , that indicates that all of the output done by body is new, and will never match output previously recorded. In this case, CLIM will discard the old output and do the redisplay from scratch. The default for all-new is false .
The output record tree created by updating-output defines a caching structure where mappings from a unique-id to an output record are maintained. If the programmer specifies an output record some output record P via the parent-cache argument, then CLIM will try to find a corresponding output record with the matching unique-id in the cache belonging to P. If neither parent-cache is not provided, then CLIM looks for the unique-id in the output record created by immediate dynamically enclosing call to updating-output . If that fails, CLIM use the unique-id to find an output record that is a child of the output history of stream . Once CLIM has found an output record that matches the unique-id, it uses the cache value and cache test to determine whether the output record has changed. If the output record has not changed, it may have moved, in which case CLIM will simply move the display of the output record on the display device.
| redisplay | record stream &key (check-overlapping t ) | [Function] |
| redisplay-output-record | record stream &optional (check-overlapping t ) x y parent-x parent-y | [Generic function] |
When check-overlapping is false , this means that CLIM can assume that no sibling output records overlap each other at any level in the output record tree. Supplying a false value for this argument can improve performance of redisplay.
Implementation note: redisplay-output-record is implemented by first binding stream-redisplaying-p of the stream to true , then creating the new output records by invoking compute-new-output-records . Once the new output records have been computed, compute-difference-set is called to compute the difference set, which is then passed to note-child-output-record-changed .
The other optional arguments can be used to specify where on the stream the output record should be redisplayed. x and y represent where the cursor should be, relative to (output-record-parent record), before we start redisplaying record . parent-x and parent-y can be supplied to say: do the output as if the parent started at positions parent-x and parent-y (which are in absolute coordinates). The default values for x and y are (output-record-start-position record ) . The default values for parent-x and parent-y are
(convert-from-relative-to-absolute-coordinates stream (output-record-parent record))record will usually be an output record created by updating-output . If it is not, then redisplay-output-record will be equivalent to replay-output-record .
21.3 Incremental Redisplay Protocol
| updating-output-record | [Protocol Class] |
| updating-output-record-p | object | [Predicate] |
| :unique-id | [Init arg] |
| :id-test | [Init arg] |
| :cache-value | [Init arg] |
| :cache-test | [Init arg] |
| :fixed-position | [Init arg] |
| standard-updating-output-record | [Class] |
| output-record-unique-id | record | [Generic function] |
| output-record-cache-value | record | [Generic function] |
| output-record-fixed-position | record | [Generic function] |
| output-record-displayer | record | [Generic function] |
| compute-new-output-records | record stream | [Generic function] |
compute-new-output-records recursively invokes itself on each child of record .
compute-new-output-records of an output record of type updating-output-record runs the displayer (output-record-displayer ), which gives the behavior of incremental redisplay. That is, it reruns the code (getting hints from updating-output ) and figures out the changes from there by comparing it to the old output history.
| compute-difference-set | record &optional (check-overlapping t ) (offset-x 0 ) (offset-y 0 ) (old-offset-x 0 ) (old-offset-y 0 ) | [Generic function] |
The values returned are erases (what areas of the display device need to be erased), moves (what output records need to be moved), draws (what output records need to be freshly replayed), erase-overlapping , and move-overlapping . Each is a list whose elements are lists of the form:
When check-overlapping is false , this means that CLIM can assume that no sibling output records overlap each other at any level. Supplying a false value for this argument can improve performance of redisplay.
| augment-draw-set | record erases moves draws erase-overlapping move-overlapping &optional x-offset y-offset old-x-offset old-y-offset | [Generic function] |
| note-output-record-child-changed | record child mode old-position old-bounding-rectangle stream &optional erases moves draws erase-overlapping move-overlapping &key check-overlapping | [Generic function] |
mode is one of :delete , :add , :change , :move , or :none old-position and old-bounding-rectangle describe where child was before it was moved.
check-overlapping is as for compute-difference-set .
| propagate-output-record-changes-p | record child mode old-position old-bounding-rectangle | [Generic function] |
| propagate-output-record-changes | record child mode &optional old-position old-bounding-rectangle erases moves draws erase-overlapping move-overlapping check-overlapping | [Generic function] |
check-overlapping is as for compute-difference-set .
| match-output-records | record &rest initargs | [Generic function] |
| find-child-output-record | record use-old-elements record-type &rest initargs &key unique-id unique-id-test | [Generic function] |
| output-record-contents-ok | record | [Generic function] |
| recompute-contents-ok | record | [Generic function] |
| cache-output-record | record child unique-id | [Generic function] |
| decache-child-output-record | record child use-old-elements | [Generic function] |
| find-cached-output-record | record use-old-elements record-type &rest initargs &key unique-id unique-id-test &allow-other-keys | [Generic function] |
21.4 Incremental Redisplay Stream Protocol
| redisplayable-stream-p | stream | [Generic function] |
| stream-redisplaying-p | stream | [Generic function] |
| incremental-redisplay | stream position erases moves draws erase-overlapping move-overlapping | [Generic function] |
incremental-redisplay can be called on any extended output stream.
| Text Formatting | Contents | Index | Extended Stream Input |