Mercurial > hg > indyvon
changeset 174:f4b82e358751
Reimplementation of async-view.
Previous version had known unfixed race conditions.
author | Mikhail Kryshen <mikhail@kryshen.net> |
---|---|
date | Wed, 10 Dec 2014 20:01:45 +0300 |
parents | 8769a7b50b4f |
children | eb1bedf22731 |
files | src/indyvon/async.clj |
diffstat | 1 files changed, 99 insertions(+), 105 deletions(-) [+] |
line wrap: on
line diff
--- a/src/indyvon/async.clj Wed Dec 10 19:00:17 2014 +0300 +++ b/src/indyvon/async.clj Wed Dec 10 20:01:45 2014 +0300 @@ -29,16 +29,19 @@ 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 -;; most recently updated -;; :free -;; not in use +(defrecord Buffers [;; BufferedImage for copying on screen. + front + ;; Number of processes using the front buffer. + front-readers + ;; BufferedImage used for asynchronous drawing. + back + ;; True after drawing to the back buffer is finished. + back-ready + ;; True after the buffers were flipped, + ;; indicates that AsyncView needs to be redrawn. + flipped + ;; Used to synchronize initialization of the buffers. + new]) (defn- create-image [async-view ^GraphicsConfiguration device-conf] ;; TODO: support different image types. @@ -47,103 +50,94 @@ (:height async-view) Transparency/TRANSLUCENT)) -(defn- create-buffer [async-view device-conf] - (->Buffer (Object.) (create-image async-view device-conf) 0 :free)) +(defn- create-buffers [async-view device-conf] + (->Buffers (create-image async-view device-conf) + 0 + (create-image async-view device-conf) + false + false + true)) -(defn- find-buffer - "Find a buffer with the one of the specified states given - in the order of preference." - [buffers & states] - (some identity - (for [state states] - (some #(if (= (:state %) state) % nil) buffers)))) - -(defn- replace-buffer [buffers buffer] - (conj (remove #(= (:id %) (:id buffer)) buffers) - buffer)) +(defn- maybe-flip [buffers] + (if (and (:back-ready buffers) + (zero? (:front-readers buffers))) + (assoc buffers + :front (:back buffers) + :back (:front buffers) + :back-ready false + :flipped true) + (assoc buffers + :flipped false))) -(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- apply-flip [buffers f & args] + (maybe-flip (apply f buffers args))) + +(defn- take-front! [av] + (-> (:buffers av) + (swap! update-in [:front-readers] inc) + :front)) + +(defn- release-front! [av] + (when (-> (:buffers av) + (swap! apply-flip update-in [:front-readers] dec) + :flipped) + (update av))) + +(defn- take-back! [av] + (-> (:buffers av) + (swap! assoc :back-ready false) + :back)) -(defn- release-buffer [al buffer] - (dosync - (let [state (:state buffer) - readers (if (= state :front) - (dec (:readers buffer)) - (:readers buffer)) - fresh (delay (find-buffer @(:buffers al) :fresh)) - state (cond - (pos? readers) :front - (= :back state) :fresh - @fresh :free - :default :fresh)] - (when (and (= state :fresh) @fresh) - ;; Change state of the previously fresh buffer to :free. - (alter (:buffers al) - replace-buffer (assoc @fresh - :state :free))) - (alter (:buffers al) - replace-buffer (assoc buffer - :state state - :readers readers))))) +(defn- release-back! [av] + (when (-> (:buffers av) + (swap! apply-flip assoc :back-ready true) + :flipped) + (update av))) -(defmacro with-buffer - {:private true} - [al type [name] & body] - `(let [al# ~al - ~name (take-buffer al# ~type)] - (try - ~@body - (finally - (release-buffer al# ~name))))) - -(defn- draw-offscreen [async-view] +(defn- draw-offscreen! [async-view] ;;(Thread/sleep 1000) - (with-buffer async-view :back [b] - (let [g (.createGraphics ^BufferedImage (:image b))] - ;; Clear the buffer. - (.setComposite g AlphaComposite/Clear) - (.fillRect g 0 0 (:width async-view) (:height async-view)) - (.setComposite g AlphaComposite/Src) - (draw-scene! (:scene async-view) - g - (:width async-view) - (:height async-view)))) - (update async-view)) + (let [b (take-back! async-view) + g (.createGraphics ^BufferedImage b)] + ;; Clear the buffer. + (.setComposite g AlphaComposite/Clear) + (.fillRect g 0 0 (:width async-view) (:height async-view)) + (.setComposite g AlphaComposite/Src) + (draw-scene! (:scene async-view) + g + (:width async-view) + (:height async-view)) + ;; Will not be called if an exception is thrown in the code above. + (release-back! async-view))) -(defn- draw-offscreen-async [async-view] - (.execute ^ThreadPoolExecutor (:executor async-view) - (bound-fn* #(draw-offscreen async-view)))) +(defn- pool-execute! [async-view f] + (.execute ^ThreadPoolExecutor (:executor async-view) f)) + +(defn- ensure-buffers! [async-view device-conf] + (let [buffers (:buffers async-view)] + (when (and (not @buffers) + (:new (swap! buffers + #(if % + (assoc % :new false) + (create-buffers async-view device-conf))))) + (let [bound-draw! (bound-fn* draw-offscreen!) + async-draw! #(pool-execute! % (partial bound-draw! %))] + (add-observer async-view + (:scene async-view) + ;; Must not hold onto async-view. + (fn [a _] (async-draw! a))) + (async-draw! async-view))))) (defrecord AsyncView [scene width height executor buffers] View (render! [view] - (repaint-on-update view) - (add-context-observer scene (bound-fn [_ _] - (draw-offscreen-async view))) - (when-not @buffers - ;; TODO: dynamic size, recreate buffers when size increases. - (let [device-conf (.getDeviceConfiguration *graphics*) - new-buffers (repeatedly 2 - (partial create-buffer view device-conf))] - (dosync - (ref-set buffers new-buffers))) - (draw-offscreen-async view)) - (with-buffer view :front [b] - (.drawImage *graphics* ^Image (:image b) 0 0 nil))) + (let [g *graphics*] + (ensure-buffers! view (.getDeviceConfiguration g)) + (let [^Image b (take-front! view)] + (try + (.drawImage g b 0 0 nil) + (finally + (release-front! view)))) + (repaint-on-update view))) (geometry [view] (->Size width height))) @@ -167,13 +161,13 @@ (defn async-view "Creates a View that draws the content asynchronously using an - offscreen buffer." + offscreen buffer." ([width height content] - (async-view width height nil content)) + (async-view width height nil content)) ([width height priority content] - ;; TODO: use operational event dispatcher. - (->AsyncView (make-scene content) - width - height - (create-executor priority) - (ref nil)))) + ;; TODO: use operational event dispatcher. + (->AsyncView (make-scene content) + width + height + (create-executor priority) + (atom nil))))