Manage cascading state updates efficiently
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Manage state dirtiness dependency cascades.

Get it

  1. Leiningen dependency: [org.timmc/cascade "1.0.0"]
  2. Add this to your ns block: (:require [org.timmc.cascade :as cascade])


Built with Leiningen 1.x; should be buildable with 2.x as well.

Purpose and usage

Let's say you have a GUI with an interactive canvas and a collection of related stateful control widgets, such as a rotation slider and a zoom slider. The user can drag objects around on the canvas, which requires redrawing the canvas image, but a redraw will also be required when the canvas is dragged or the rotate/zoom sliders are altered. The sliders may be altered by a "best fit" button, and the user's data objects (shown on the canvas) may be manipulated by the undo/redo menu items. Resizing the window and hovering over canvas elements will have further effects. Perhaps your dependency lists look like this:

  • :pose [] - zoom / rotate / translate values (based on controls)
  • :dim [] - window dimensions
  • :xform [:pose :dim] - graphics transform matrix
  • :udata [] - user data (objects on canvas)
  • :hover [] - what the user is hovering over on the canvas
  • :painting [:udata :xform :hover] - image data buffer
  • :mode [:udata] - interaction mode based on what existing data
  • et cetera...

Chaining the various dependent state updates is a bug-prone exercise in frustration. org.timmc/cascade allows you to define your directed acyclic graph of dependencies as a persistent "cascade" data structure, storing a "dirty" or "clean" indicator for each node. The example below is taken from the Bézier curve editor that originally spawned this library.

  :canvas-shape nil false ;; shape and size of canvas component
  :center update-canvas-shape! [:canvas-shape]
  :pose-spinners nil false ;; data in JSpinners
  :pose update-pose! [:pose-spinners]
  :mouse-pos nil false ;; mouse-pos in @state
  :udata nil false ;; pretty much anything in @udata
  :hover update-hover! [:mouse-pos :udata]
  :xform update-xform! [:pose :center]
  :mode update-mode! [:udata]
  :menustate update-menus! [:mode]
  :history-gui update-history-gui! true ;; undo/redo was committed
  ;; top-level states
  :painting ask-redraw [:udata :xform :hover]
  :gui nil [:menustate :history-gui]
  ;; collector
  :all nil [:painting :gui])

Each node declares a side-effecting cleaner thunk (or nil) and either a list of dependency nodes or a boolean indicating whether the node is already clean. If the resulting data structure is stored in an atom c, then a drag event handler can call (swap! c cascade/dirty :center) to indicate that the canvas has been dragged, and then (swap! c cascade/clean :painting) to recalculate all (and only) the relying nodes from :painting down to :center that actually need to be updated.

(Note that if a cleaner thunk causes an error, the swap! will fail, and the cascade value will remain the same. Plan accordingly.)

See the main namespace docstring for more info: org.timmc.cascade



  • Initial version


  • Bump to v1 with no code changes to signify that the API is stable
  • Now tested with Clojure 1.4.0


Copyright Tim McCormack 2011-2013.

Free-licensed under the Eclipse Public License v1.0 (see ./epl-v10.html).