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:

Normally, the programmer would supply both options. The unique-id would be some data structure associated with the corresponding part of output. The cache value would be something in that data structure that changes whenever the output changes.

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 5
After the sleep has terminated, the display will look like:

Element 1
  Element 2
  Element 17
  Element 4
  Element 5
CLIM 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]
Introduces a caching point for incremental redisplay.

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-outputstream continuation record-type unique-id id-test cache-value cache-test &key all-new parent-cache[Generic function]
Introduces a caching point for incremental redisplay. Calls the function continuation , which generates the output records to be redisplayed. continuation is a function of one argument, the stream; it has dynamic extent.

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.

redisplayrecord stream &key (check-overlapping t )[Function]
This function simply calls redisplay-output-record on the arguments record and stream .

redisplay-output-recordrecord stream &optional (check-overlapping t ) x y parent-x parent-y[Generic function]


Issue: SWM
The coordinate system stuff affected by the x/y and parent-x/y arguments is entirely bogus. The proposal to make ``stream relative'' coordinates for output records instead of ``parent relative'' coordinates will eliminate this completely.
(redisplay-output-record record stream ) causes the output of record to be recomputed. CLIM redisplays the changes ``incrementally'', that is, it only displays those parts that have been changed. record must already be part of the output history of the output recording stream stream , although it can be anywhere inside the hierarchy.

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


Issue: SWM
While the description of the API here is accurate, the description of the protocol is a disaster. This is no surprise, since the protocol for increment redisplay is itself a disaster.
updating-output-record[Protocol Class]
The protocol class corresponding to records that support incremental redisplay; a subclass of output-record . If you want to create a new class that behaves like an updating output record, it should be a subclass of updating-output-record. Subclasses of updating-output-record must obey the updating output record protocol.
updating-output-record-pobject[Predicate]
Returns true if object is an updating output record , otherwise returns false .

:unique-id[Init arg]
:id-test[Init arg]
:cache-value[Init arg]
:cache-test[Init arg]
:fixed-position[Init arg]
All subclasses of updating-output-record must handle these four initargs, which are used to specify, respectively, the unique id and id test, cache value and cache test, and the ``fixed position'' component of the output record.

standard-updating-output-record[Class]
The instantiable class of output record that supports incremental redisplay. This is a subclass of updating-output-record .

output-record-unique-idrecord[Generic function]
Returns the unique id associated with the updating output record record .

output-record-cache-valuerecord[Generic function]
Returns the cache value associated with the updating output record record .

output-record-fixed-positionrecord[Generic function]
Returns true if the updating output record record is at a fixed location on the output stream, otherwise returns false . Output records that are not at fixed location on the output stream will be moved by incremental redisplay when any of their siblings adjust their size or position.

output-record-displayerrecord[Generic function]
Returns the function that produces the output for this output record. This is the function that is called during redisplay to produce new output if the cache value mismatches.

compute-new-output-recordsrecord stream[Generic function]
compute-new-output-records modifies an output record tree to reflect new output done by the application. In addition to inserting the new output records into the output record tree, it must save enough information to be able to compute the difference set, such as the old bounding rectangle, old cursor positions, old children, and so forth.

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-setrecord &optional (check-overlapping t ) (offset-x 0 ) (offset-y 0 ) (old-offset-x 0 ) (old-offset-y 0 )[Generic function]
compute-difference-set compares the current state of the output record record with its previous state, and returns a ``difference set'' as five values. The difference set controls what needs to be done to the display device in order to accomplish the incremental redisplay.

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-setrecord erases moves draws erase-overlapping move-overlapping &optional x-offset y-offset old-x-offset old-y-offset[Generic function]

Issue: SWM
To be supplied.
note-output-record-child-changedrecord child mode old-position old-bounding-rectangle stream &optional erases moves draws erase-overlapping move-overlapping &key check-overlapping[Generic function]
note-output-record-child-changed is called after an output history has had changes made to it, but before any of the new output has been displayed. It will call propagate-output-record-changes-p to determine if the parent output record should be notified, and if so, will call propagate-output-record-changes to create an updated difference set. If no changes need to be propagated to the parent output record, then note-output-record-child-changed will call incremental-redisplay in order display the difference set.

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-precord child mode old-position old-bounding-rectangle[Generic function]
propagate-output-record-changes-p is a predicate that returns true if the change made to the child will cause record to be redisplayed in any way. Otherwise, it returns false . mode is one of :delete , :add , :change , :move , or :none .

propagate-output-record-changesrecord child mode &optional old-position old-bounding-rectangle erases moves draws erase-overlapping move-overlapping check-overlapping[Generic function]
Called when the changed child output record requires that its parent, record , be redisplayed as well. propagate-output-record-changes will update the difference set to reflect the additional changes.

check-overlapping is as for compute-difference-set .

match-output-recordsrecord &rest initargs[Generic function]
Returns true if record matches the supplied class initargs initargs , otherwise returns false .

find-child-output-recordrecord use-old-elements record-type &rest initargs &key unique-id unique-id-test[Generic function]
Finds a child of record matching the record-type and the supplied initargs initargs . unique-id and unique-id-test are used to match against the children as well. use-old-elements controls whether the desired record is to be found in the previous (before redisplay) contents of the record.

output-record-contents-okrecord[Generic function]
Returns true if the current state of record are up to date, otherwise returns false .

recompute-contents-okrecord[Generic function]
Compares the old (before redisplay) and new contents of record to determine whether or not this record changed in such a way so that the display needs updating.

cache-output-recordrecord child unique-id[Generic function]
record stores child such that it can be located later using unique-id .

decache-child-output-recordrecord child use-old-elements[Generic function]
Invalidates the redisplay state of record .

find-cached-output-recordrecord use-old-elements record-type &rest initargs &key unique-id unique-id-test &allow-other-keys [Generic function]
Finds a previously cached child matching record-type , initargs , unique-id , and unique-id-test . use-old-elements controls whether the desired record is to be found in the previous (before redisplay) contents of the record.

21.4 Incremental Redisplay Stream Protocol

redisplayable-stream-pstream[Generic function]
Returns true for any stream that maintains an output history and supports the incremental redisplay protocol, otherwise returns false .

stream-redisplaying-pstream[Generic function]
Returns true if the stream is currently doing redisplay (that is, is inside of a call to redisplay ), otherwise returns false .

incremental-redisplaystream position erases moves draws erase-overlapping move-overlapping[Generic function]
Performs the incremental update on stream according to the difference set comprised by erases , moves , draws , erase-overlapping , and move-overlapping , which are values returned by compute-difference-set . position is a point object that represents the start position of the topmost output record that will be redisplayed.

incremental-redisplay can be called on any extended output stream.



Text Formatting Contents Index Extended Stream Input