phronmophobic/membrane

`Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "clojure.lang.Var.get()" because "x" is null`

rgkirch opened this issue · 3 comments

Hey!

I have some symbols in a map somewhere. I want to lookup a symbol out of the map, resolve the symbol to a function in my namespace, call it to get a ui component, and then use that as the ui component. I'm getting an exception.

(ns examples.error
  (:require
   [membrane.ui
    :as
    ui
    :refer
    [horizontal-layout
     label
     vertical-layout]]
   [membrane.java2d :as backend]))

(defn help []
  (ui/label "help"))

(defn what
  []
  (let [component (var-get (resolve 'help))]
   (ui/vertical-layout
    (component)
    (ui/label "me"))))

(backend/run
  (var what)
  {:window-start-width 400
   :window-start-height 400})

produces

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "clojure.lang.Var.get()" because "x" is null
	at clojure.core$var_get.invokeStatic(core.clj:4319)
	at clojure.core$var_get.invoke(core.clj:4315)
	at examples.error$what.invokeStatic(error.clj:17)
	at examples.error$what.invoke(error.clj:15)
	at clojure.lang.Var.invoke(Var.java:380)
	at membrane.java2d$make_panel$fn__17653$fn__17654.invoke(java2d.clj:858)
	at clojure.lang.Atom.swap(Atom.java:37)
	at clojure.core$swap_BANG_.invokeStatic(core.clj:2356)
	at clojure.core$swap_BANG_.invoke(core.clj:2349)
	at membrane.java2d$make_panel$fn__17653.invoke(java2d.clj:856)
	at membrane.java2d.proxy$java.awt.Component$ff19274a.paint(Unknown Source)
	at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:966)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1128)
	at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:952)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1128)
	at java.desktop/javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
	at java.desktop/javax.swing.JComponent.paintChildren(JComponent.java:952)
	at java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5318)
	at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1644)
	at java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1619)
	at java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1557)
	at java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1324)
	at java.desktop/javax.swing.JComponent.paint(JComponent.java:1105)
	at java.desktop/java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
	at java.desktop/sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:75)
	at java.desktop/sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:112)
	at java.desktop/java.awt.Container.paint(Container.java:2006)
	at java.desktop/java.awt.Window.paint(Window.java:3954)
	at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:877)
	at java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:849)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:849)
	at java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:824)
	at java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:773)
	at java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1885)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

calling (what) produces:

[#membrane.ui.Label{:text "help", :font #membrane.ui.Font{:name nil, :size 14, :weight nil, :width nil, :slant nil}} #membrane.ui.Translate{:x 0, :y 17.609375, :drawable #membrane.ui.Label{:text "me", :font #membrane.ui.Font{:name nil, :size 14, :weight nil, :width nil, :slant nil}}}]

Thanks!

@rgkirch , I made it work with the following updated version. The issue is that the view is running in another thread and the *ns* binding is different than the examples.error namespace. Generally, you can't assume that *ns* matches the current namespace when a function is called (which is something to watch out for regardless of membrane). The fix is to use ns-resolve to explicitly lookup help in the right namespace.

(ns example.error
  (:require
   [membrane.ui
    :as
    ui
    :refer
    [horizontal-layout
     label
     vertical-layout]]
   [membrane.java2d :as backend]))

(def my-ns *ns*)
(defn help []
  (ui/label "help"))

(defn what
  []
  (let [component (var-get (ns-resolve my-ns 'help))]
    (ui/vertical-layout
     (component)
     (ui/label "me"))))

(comment
  (backend/run
    #'what
    {:window-start-width 400
     :window-start-height 400})
  ,)

Thank you! I was guessing it had something to do with resolve not working on another thread.

(defn what
  []
  (let [component (var-get (resolve 'help))]
    (fn []
      (ui/vertical-layout
       (component)
       (ui/label "me")))))

I tried to close over the result of var-get in a new lambda and hoped that the lambda would run in the ui thread but the var-get would run in the "main" thread. That ... didn't work before but it's working now... Ok, the minimal example seems to work but my actual code doesn't work.

Actual code:

(defn make-component [tree]
  (let [component (var-get (resolve (:component/component tree)))
        incidental-state (:component/state tree)
        subcomponents (:component/subcomponents tree)]
    (fn [state]
      (apply
       component
       (merge incidental-state state)
       (map make-component subcomponents)))))

I don't know why one example works and the other doesn't but it's not a big deal; that code is not as clear as your solution with ns-resolve.

Thank you!

@rgkirch , Anything inside a function isn't guaranteed to have ns set to the enclosing namespace when the function is called. You would have to call resolve from a top level statement.