changeset 85:e718a69f7d99

Observers: use weak keys, renamed some fns.
author Mikhail Kryshen <mikhail@kryshen.net>
date Fri, 01 Oct 2010 18:45:17 +0400
parents b04bdeec5700
children 069ea63803a2
files project.clj src/net/kryshen/indyvon/async.clj src/net/kryshen/indyvon/component.clj src/net/kryshen/indyvon/core.clj
diffstat 4 files changed, 41 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- a/project.clj	Wed Sep 08 04:40:05 2010 +0400
+++ b/project.clj	Fri Oct 01 18:45:17 2010 +0400
@@ -2,7 +2,8 @@
   :description "INteractive DYnamic VisualizatiON library"
   ;;:warn-on-reflection true
   :dependencies [[org.clojure/clojure "1.2.0"]
-                 [org.clojure/clojure-contrib "1.2.0"]]
+                 [org.clojure/clojure-contrib "1.2.0"]
+                 [com.google.guava/guava "r07"]]
   :dev-dependencies [[swank-clojure/swank-clojure "1.2.1"]]
   :namespaces [net.kryshen.indyvon.core
                net.kryshen.indyvon.async
--- a/src/net/kryshen/indyvon/async.clj	Wed Sep 08 04:40:05 2010 +0400
+++ b/src/net/kryshen/indyvon/async.clj	Fri Oct 01 18:45:17 2010 +0400
@@ -127,7 +127,7 @@
   Layer
   (render! [layer]
     (repaint-on-update layer)
-    (add-context-observer content (fn [_] (draw-offscreen-async layer)))
+    (add-context-observer content (fn [_ _] (draw-offscreen-async layer)))
     (when-not @buffers
       ;; TODO: dynamic size, recreate buffers when size increases.
       (let [new-buffers (repeatedly 2 (partial create-buffer layer))]
--- a/src/net/kryshen/indyvon/component.clj	Wed Sep 08 04:40:05 2010 +0400
+++ b/src/net/kryshen/indyvon/component.clj	Fri Oct 01 18:45:17 2010 +0400
@@ -44,7 +44,12 @@
                (let [s (root-size layer (font-context this) this)]
                  (Dimension. (:width s) (:height s)))))]
        (.setBackground panel (:back-color *theme*))
-       (add-observer panel layer (fn [_] (.repaint panel)))
+       (add-observer panel layer (fn [w _]
+                                   ;; Use the first observer argument
+                                   ;; instead of closing over panel to
+                                   ;; allow the panel and associated
+                                   ;; observer to be gc'd.
+                                   (.repaint ^Component w)))
        (listen! event-dispatcher panel)
        panel)))
 
--- a/src/net/kryshen/indyvon/core.clj	Wed Sep 08 04:40:05 2010 +0400
+++ b/src/net/kryshen/indyvon/core.clj	Fri Oct 01 18:45:17 2010 +0400
@@ -22,7 +22,8 @@
    (java.awt Graphics2D RenderingHints Component Color Font Shape)
    (java.awt.geom AffineTransform Point2D$Double Rectangle2D$Double Area)
    (java.awt.event MouseListener MouseMotionListener)
-   (java.awt.font FontRenderContext)))
+   (java.awt.font FontRenderContext)
+   (com.google.common.collect MapMaker)))
 
 ;;
 ;; Layer context
@@ -135,43 +136,43 @@
 (defn- assoc-cons [m key val]
   (->> (get m key) (cons val) (assoc m key)))
 
-(defn- assoc-in-cons [m keys val]
-  (->> (get-in m keys) (cons val) (assoc-in m keys)))
-
 ;;
 ;; Observers
+;; The mechanism used by layers to request repaints
 ;;
 
-(def observers (atom nil))
+(def ^java.util.Map observers
+     (-> (MapMaker.) (.weakKeys) (.makeMap)))
 
-;; TODO: groups should be weakly referenced.
 (defn add-observer
-  "Add observer fn for the target to the specified group."
-  [group target f]
-  (swap! observers assoc-in-cons [group target] f)
+  "Add observer fn for the target. Watcher identifies the group of
+  observers and could be used to remove the group. Watcher is weakly
+  referenced, all associated observers will be removed when the
+  wathcer is removed by gc. The observer fn will be called with
+  watcher and target arguments and any additional arguments specified
+  in update call."
+  [watcher target f]
+  (.put observers watcher (assoc-cons (.get observers watcher) target f))
   nil)
 
-(defn remove-observer-group
-  "Remove group of observers."
-  [group]
-  (swap! observers dissoc group)
+(defn remove-observers
+  "Remove group of observers associated with the specified watcher."
+  [watcher]
+  (.remove observers watcher)
   nil)
 
-(defn- replace-observer-group*
-  [observers old-id new-id]
-  (let [group (get observers old-id)]
-    (assoc (dissoc observers old-id)
-      new-id group)))
-
-(defn- replace-observer-group
-  [old-id new-id]
-  (swap! observers replace-observer-group* old-id new-id))
+(defn- replace-observers-watcher
+  [old-watcher new-watcher]
+  (if-let [old (.remove observers old-watcher)]
+    (.put observers new-watcher old))
+  nil)
 
 (defn update
   "Notify observers."
   [target & args]
-  (doseq [f (reduce #(concat %1 (get %2 target)) nil (vals @observers))]
-    (apply f target args)))
+  (doseq [entry observers
+          f (get (val entry) target)]
+    (apply f (key entry) target args)))
 
 (defn add-context-observer
   "Observer registered with this function will be automatically
@@ -185,7 +186,7 @@
   [target]
   (let [root *root*]
     (if (not= root target)
-      (add-observer root target (fn [_] (update root))))))
+      (add-observer root target (fn [w _] (update w))))))
 
 (defn repaint
   "Repaint the current scene."
@@ -347,15 +348,15 @@
        (apply-theme)
        (with-color (:back-color *theme*)
          (.fillRect graphics 0 0 width height))
-       (let [tmp-group (Object.)]
-         ;; Keep current context observers until the rendering is complete.
-         ;; Some observers may be invoked twice if they appear in both
-         ;; groups until tmp-group is removed.
-         (replace-observer-group layer tmp-group)
+       (let [tmp-watcher (Object.)]
+         ;; Keep current context observers until the rendering is
+         ;; complete. Some observers may be invoked twice if they
+         ;; appear in both groups until tmp-watcher is removed.
+         (replace-observers-watcher layer tmp-watcher)
          (try
            (render! layer)
            (finally
-            (remove-observer-group tmp-group)
+            (remove-observers tmp-watcher)
             (commit event-dispatcher)))))))
 
 (defn root-size