21.1 Overview of Incremental Redisplay

  • 21.1.1 Examples 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. [annotate]

    There are two different ways to do incremental redisplay. [annotate]

    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. [annotate]

    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. [annotate]

    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. [annotate]

    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. [annotate]

    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. [annotate]

    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. [annotate]

    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. [annotate]

    In order to compare the cache to the output record, two pieces of information are necessary: [annotate]

    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. [annotate]

    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. [annotate]

    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. [annotate]

    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. [annotate]

    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. [annotate]

    The following function illustrates the standard use of incremental redisplay: [annotate]

    (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: [annotate]

      Element 1
      Element 2
      Element 3
      Element 4
      Element 5
    

    After the sleep has terminated, the display will look like: [annotate]

      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 [annotate]

    (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. [annotate]

    See Chapter 28 for a discussion of how to use incremental redisplay automatically within the panes of an application frame. [annotate]