 (defmacro defn-memoized [fn-name & body]
   "Def's a memoized fn. Same semantics as defn."
   `(def ~fn-name (memoize (fn ~@body))))
+
+(defmacro loop-with-index [idx-name bindings & body]
+  "bindings => binding-form seq-expression
+
+  Repeatedly executes body (presumably for side-effects) with
+  binding form and idx-name in scope.  `idx-name` is repeatedly bound to the index of the item being
+  evaluated ex: to each successive value in `(range (count (second bindings)))`. Does not retain
+      the head of the sequence. Returns nil.
+
+   (loop-with-index idx [[k v] {:a 1 :b 2 :c 3}] (println \"idx\" idx \"k\" k \"v\" v))
+   idx 0 k :a v 1
+   idx 1 k :b v 2
+   idx 2 k :c v 3
+   nil"
+  (let [form (bindings 0) coll (bindings 1)]
+     `(loop [coll# ~coll
+             ~idx-name 0]
+        (when coll#
+          (let [~form (first coll#)]
+            ~@body
+            (recur (next coll#) (inc ~idx-name)))))))
 
 (defn convert-key-code [event-char event-key on-key-fn]
   ;; Cond instead of case. For an unknown reason, case does not match event-key to Keyboard/* constants.

        (.clear fg-image-data)
        (.clear bg-image-data)
        (doseq [[layer-id character-map] layers-character-map]
          (doseq [[row line] (map-indexed vector (reverse @character-map))
                  [col c]    (map-indexed vector line)]
            ;;(log/info "row" row "col" col "c" c)
            (let [chr        (char (or (get c :fx-character) (get c :character)))
                  chr        (if (and (= layer-id base-layer-id)
                                      (= chr (char 0)))

             (recur)))))
     ;; Wait for Display to be created
     (.await latch)))

(defn- load-shader
  [^String shader-str ^Integer shader-type]
