13 February 2009

State mutators

I have been experimenting with the hip, cool, Clojure language recently. Today I stumbled upon a pattern that may be useful to others.

This is a variant on the idea of memoizing function calls. The assumption there is that if the function is pure (i.e. no side effects), then we can cache the return value for a given set of arguments, and in so doing, never have to compute the function for the same argument(s) twice. Subsequent invocations of the function with the same arguments that have been computed before becomes a simple table lookup.

But what about if your function's sole purpose is to effect a state change? Well, if the emitted state change is pure, and by that I mean that the state change effected by your function depends solely on the argument(s) to your function, then you can cache the arguments of the last invocation to your function and only emit the state change when the argument(s) change.

We are using a Lisp, so let's wrap this in a macro:

(defmacro defstatefn
"Defines a side-effecting function with cached argument.
When called, the body is executed only if the arguments
differ from the previous invocation."
[name & fdecl]

`(let [state# (atom nil)]
(defn ~name [& args#]
(let [f# (fn ~@fdecl)]
(if (not= (deref state#) args#)
(do
(apply f# args#)
(swap! state# (constantly args#))))))))

The idea is that we define a function which closes over a state atom and only executes the body (side-effect) if the value of the state atom is different from the previous invocation. If the cost of the state change dominates the function call, then we will save ourself some CPU time.

I came up with this idea while pondering how to minimize OpenGL state changes when rendering a complex scene. Using this primitive, we can create some wrappers around the OpenGL state mutators and be sure that if the state we are requesting is already current, we won't do it twice:

(defstatefn gl-clear-color [c]
(println "Calling .glClearColor")
(.glClearColor *gl* (c 0) (c 1) (c 2) (c 3)))


Upon closer inspection, these are loafers

After thinking about this some more, I am pretty convinced that any reasonable quality OpenGL driver (in other words, any OpenGL driver that anyone would care to use) would likely be doing this for me already, so I doubt this is really useful in this context.

We are only eliminating redundant state changes from reaching the OpenGL API, whereas what I really need to do is sort my data such that a minimum number of state changes are needed to correctly render the scene.

Ah well, perhaps this pattern may be useful in other contexts.

No comments: