changeset 67:a19cf5007d14

Asynchronous drawing.
author Mikhail Kryshen <mikhail@kryshen.net>
date Sat, 28 Aug 2010 02:37:30 +0400
parents a1999c1f7289
children 9b511fe09867
files project.clj src/net/kryshen/indyvon/async.clj src/net/kryshen/indyvon/core.clj src/net/kryshen/indyvon/demo.clj src/net/kryshen/indyvon/layers.clj
diffstat 5 files changed, 155 insertions(+), 79 deletions(-) [+]
line wrap: on
line diff
--- a/project.clj	Fri Aug 27 19:47:28 2010 +0400
+++ b/project.clj	Sat Aug 28 02:37:30 2010 +0400
@@ -4,6 +4,7 @@
                  [org.clojure/clojure-contrib "1.2.0"]]
   :dev-dependencies [[swank-clojure/swank-clojure "1.2.1"]]
   :namespaces [net.kryshen.indyvon.core
+               net.kryshen.indyvon.async
                net.kryshen.indyvon.layers
                net.kryshen.indyvon.component
                net.kryshen.indyvon.demo])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/kryshen/indyvon/async.clj	Sat Aug 28 02:37:30 2010 +0400
@@ -0,0 +1,138 @@
+;;
+;; Copyright (C) 2010 Mikhail Kryshen <mikhail@kryshen.net>
+;;
+;; This file is part of Indyvon.
+;;
+
+(ns net.kryshen.indyvon.async
+  "Asynchronous drawing."
+  (:use
+   net.kryshen.indyvon.core)
+  (:import
+   (net.kryshen.indyvon.core Size Location)
+   (java.awt Image)
+   (java.awt.image BufferedImage)
+   (java.util.concurrent ThreadPoolExecutor
+                         ThreadPoolExecutor$DiscardOldestPolicy
+                         ArrayBlockingQueue TimeUnit)))
+
+(defrecord Buffer [id image readers state])
+;; Buffer states:
+;;   :front, readers > 0
+;;      being copied on screen
+;;   :back
+;;      being rendered to (offscreen)
+;;   :fresh
+;;      offscreen rendering finished
+;;   :free
+;;      not in use
+
+(defn- create-image [async-layer]
+  (BufferedImage. (:width async-layer) (:height async-layer)
+                  BufferedImage/TYPE_INT_ARGB))
+
+(defn- create-buffer [async-layer id]
+  (Buffer. id (create-image async-layer) 0 :free))
+
+(defn- find-buffer [buffers & states]
+  "Find a buffer with the one of the specified states given
+   in the order of preference."
+  (some identity
+    (for [state states]
+      (some #(if (= (:state %) state) % nil) buffers))))
+
+(defn- replace-buffer
+  [buffers buffer]
+  (conj (remove #(= (:id %) (:id buffer)) buffers)
+        buffer))
+
+(defn- take-buffer [al type]
+  (dosync
+   (let [buffers @(:buffers al)
+         b (case type
+             :front (find-buffer buffers :front :fresh :free)
+             :back (find-buffer buffers :free :fresh)
+             (throw (IllegalArgumentException.)))
+         readers (if (= type :front)
+                   (inc (:readers b))
+                   (:readers b))
+         b (assoc b
+             :state type
+             :readers readers)]
+     (alter (:buffers al) replace-buffer b)
+     b)))
+
+(defn- release-buffer
+  [al buffer]
+  (dosync
+   (let [buffers @(:buffers al)
+         state (:state buffer)
+         readers (if (= state :front)
+                   (dec (:readers buffer))
+                   (:readers buffer))
+         state (cond
+                (pos? readers) :front
+                (= :back (:state buffer)) :fresh
+                :default :free)
+         buffers
+          (if (= state :fresh)
+            ;; Change state of all the other buffers from :fresh to :free.
+            (reduce replace-buffer buffers 
+                    (for [b buffers :when (= (:state b) :fresh)]
+                      (assoc b :state :free)))
+            buffers)
+         buffers (replace-buffer buffers (assoc buffer
+                                           :state state
+                                           :readers readers))]
+     (ref-set (:buffers al) buffers))))
+  
+(defmacro with-buffer [al type [name] & body]
+  `(let [al# ~al
+         ~name (take-buffer al# ~type)]
+     (try
+       ~@body
+       (finally
+        (release-buffer al# ~name)))))
+
+(defn- draw-offscreen [async-layer]
+  ;;(Thread/sleep 1000)
+  (with-buffer async-layer :back [b]
+    (draw-root! (:content async-layer)
+                (.getGraphics (:image b))
+                (:width async-layer)
+                (:height async-layer)
+                dummy-event-dispatcher))
+  (update async-layer))
+
+(defn- draw-offscreen-async [async-layer]
+  (.execute ^ThreadPoolExecutor (:executor async-layer)
+            #(draw-offscreen async-layer)))
+
+(defrecord AsyncLayer [content width height executor buffers]
+  Layer
+  (render! [layer]
+     (repaint-on-update layer)
+     (add-context-observer content (fn [_] (draw-offscreen-async layer)))
+     (when-not @buffers
+       ;; TODO: dynamic size, recreate buffers when size increases.
+       (let [new-buffers (list (create-buffer layer 1)
+                               (create-buffer layer 2))]
+         (dosync
+          (ref-set buffers new-buffers)))
+       (draw-offscreen-async layer))
+     (with-buffer layer :front [b]
+       (.drawImage *graphics* ^Image (:image b) 0 0 nil)))
+  (layer-size [layer]
+     (Size. width height)))
+
+(defn async-layer 
+  "Creates layer that draws the content asynchronously in an
+   offscreen buffer."
+  [content width height]
+  (AsyncLayer. content width height
+               (ThreadPoolExecutor.
+                (int 1) (int 1)
+                (long 0) TimeUnit/SECONDS
+                (ArrayBlockingQueue. 1)
+                (ThreadPoolExecutor$DiscardOldestPolicy.))
+               (ref nil)))
--- a/src/net/kryshen/indyvon/core.clj	Fri Aug 27 19:47:28 2010 +0400
+++ b/src/net/kryshen/indyvon/core.clj	Sat Aug 28 02:37:30 2010 +0400
@@ -6,7 +6,7 @@
 
 (ns net.kryshen.indyvon.core
   (:import
-   (java.awt Graphics2D Component Color Font AWTEvent Shape)
+   (java.awt Graphics2D RenderingHints Component Color Font AWTEvent Shape)
    (java.awt.geom AffineTransform Point2D$Double Rectangle2D$Double Area)
    (java.awt.event MouseListener MouseMotionListener)
    (java.awt.font FontRenderContext)))
@@ -328,6 +328,15 @@
                *width* width
                *height* height
                *clip* (Rectangle2D$Double. 0 0 width height)]
+       ;; (.setRenderingHint graphics
+       ;;                    RenderingHints/KEY_INTERPOLATION
+       ;;                    RenderingHints/VALUE_INTERPOLATION_BILINEAR)
+       ;; (.setRenderingHint graphics
+       ;;                    RenderingHints/KEY_ALPHA_INTERPOLATION
+       ;;                    RenderingHints/VALUE_ALPHA_INTERPOLATION_QUALITY)
+       ;; (.setRenderingHint graphics
+       ;;                    RenderingHints/KEY_ANTIALIASING
+       ;;                    RenderingHints/VALUE_ANTIALIAS_ON)
        (apply-theme)
        (with-color (:back-color *theme*)
          (.fillRect graphics 0 0 width height))
--- a/src/net/kryshen/indyvon/demo.clj	Fri Aug 27 19:47:28 2010 +0400
+++ b/src/net/kryshen/indyvon/demo.clj	Sat Aug 28 02:37:30 2010 +0400
@@ -88,9 +88,7 @@
 
 (defn show-frame [layer]
   (doto (JFrame. "Test")
-    (.addWindowListener
-     (proxy [java.awt.event.WindowAdapter] []
-       (windowClosing [event] (.dispose frame))))
+    (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
     (.. (getContentPane) (add (make-jpanel layer)))
     (.pack)
     (.setVisible true)))
--- a/src/net/kryshen/indyvon/layers.clj	Fri Aug 27 19:47:28 2010 +0400
+++ b/src/net/kryshen/indyvon/layers.clj	Sat Aug 28 02:37:30 2010 +0400
@@ -7,17 +7,14 @@
 (ns net.kryshen.indyvon.layers
   "Implementations of Layer protocol."
   (:use
-   net.kryshen.indyvon.core)
+   (net.kryshen.indyvon core async))
   (:import
    (net.kryshen.indyvon.core Size Location)
    (java.lang.ref SoftReference)
    (java.awt Font Cursor Image Toolkit)
-   (java.awt.image ImageObserver BufferedImage)
-   (java.awt.font FontRenderContext TextLayout)
-   (java.util.concurrent ThreadPoolExecutor
-                         ThreadPoolExecutor$DiscardOldestPolicy
-                         ArrayBlockingQueue TimeUnit)))
-
+   (java.awt.image ImageObserver)
+   (java.awt.font FontRenderContext TextLayout)))
+  
 ;; Define as macro to avoid unnecessary calculation of inner and outer
 ;; sizes in the first case.
 (defmacro align-xy [inner outer align first center last]
@@ -173,76 +170,9 @@
               width (if (pos? width) width 1)
               height (if (pos? height) height 1)]
           (Size. width height))))))
-
-(defn- create-buffer [async-layer]
-  (BufferedImage. (:width async-layer) (:height async-layer)
-                  BufferedImage/TYPE_INT_ARGB))
-
-(defn- draw-offscreen [async-layer]
-  ;;(Thread/sleep 3000)
-  (let [buffers (:buffers async-layer)
-        ^Image b (dosync
-                  (let [b (peek @buffers)]
-                    (alter buffers pop)
-                    b))]
-    (try
-      ;; TODO: use operational event dispatcher.
-      (draw-root! (:content async-layer)
-                  (.getGraphics b)
-                  (:width async-layer)
-                  (:height async-layer)
-                  dummy-event-dispatcher)
-      (finally
-       (dosync
-        (alter buffers conj b)
-        (ref-set (:updated async-layer) true))))
-    (update async-layer)))
-
-(defn- draw-offscreen-async [async-layer]
-  (.execute ^ThreadPoolExecutor (:executor async-layer)
-            #(draw-offscreen async-layer)))
-
-(defrecord AsyncLayer [content width height executor buffers updated]
-  Layer
-  (render! [layer]
-     (repaint-on-update layer)
-     (add-context-observer content (fn [_] (draw-offscreen-async layer)))
-     (when-not @buffers
-       ;; TODO: dynamic size, recreate buffers when size increases.
-       (let [new-buffers [(create-buffer layer) (create-buffer layer)]]
-         (dosync
-          (ref-set buffers new-buffers)))
-       (draw-offscreen-async layer))
-     (let [buffer (dosync
-            (if @updated
-              (let [b (peek @buffers)]
-                (alter buffers pop)
-                (ref-set updated false)
-                b)
-              (let [b (first @buffers)]
-                (alter buffers subvec 1)
-                b)))]
-       (.drawImage *graphics* ^Image buffer 0 0 nil)
-       (dosync
-        (alter buffers #(vec (cons buffer %))))))
-  (layer-size [layer]
-     (Size. width height)))
-
-(defn async-layer 
-  "Creates layer that draws the content asynchroniously in an
-   offscreen buffer."
-  [content width height]
-  (AsyncLayer. content width height
-               (ThreadPoolExecutor.
-                (int 1) (int 1)
-                (long 0) TimeUnit/SECONDS
-                (ArrayBlockingQueue. 1)
-                (ThreadPoolExecutor$DiscardOldestPolicy.))
-               (ref nil)
-               (ref false)))
     
 (defn miniature
-  "Creates layer that asynchroniously renders view of the content
+  "Creates layer that asynchronously renders view of the content
   scaled to the specified size."
   [content width height]
   (async-layer