humbleui

JAtkins 2024-08-07T22:43:14.457739Z

Forgot to post this on saturday - I spent a few hours trying to learn the humble primitives. Very nice! This is a spotify-ish ui. I did run into a couple questions along the way though: • First, performance. After around 200 elements paints were really costly. The main structure of the page is a stack with the top/bottom/list items, and the list is a column with two spacers at the top and bottom. I guess there isn't special handling for "offscreen-ness" in columns? I'm not sure. • Second, paint artifacts. You can see at the end of the video when I'm scrolling slowly that theres a "shimmer" at the top and bottom of the blurred areas. I'm not sure what's causing that? Seems to be related to a partially rendered text line. • Third (unfilmed), the top and bottom bars don’t capture the mouse. I see the hover effect when the mouse is on those bars. How can I turn those into a mouse sink?

JAtkins 2024-08-08T07:27:50.870729Z

Nevermind my assumption on #1, I looked at the virtualized rendering example again and I get the same behavior modifying that code - really long lists take a long time to render paint, even if only a small subset are actually drawn.

fricze 2024-08-08T10:01:44.188419Z

Do you have some repo for that?

fricze 2024-08-08T10:02:25.011529Z

Super nice stuff

fricze 2024-08-08T10:03:04.919609Z

I’d be eager to play around and investigate those performance issues

JAtkins 2024-08-08T10:11:32.325069Z

It's just a clone of the humble repo with an extra example. I'd be happy to publish it if you wanna mess around with it. I didn't (still don't) plan to take this shell to completion, so it's quite messy code 😂

fricze 2024-08-08T10:51:17.801619Z

Yeah, if you can publish, that’d be great. I’m also just messing around with Humble lastly so I’m just curious to check out what you’ve done :)

😎 1
fricze 2024-08-08T10:51:42.803639Z

Don’t really care if it’s messy, just hacking around checking what works and what doesn’t

JAtkins 2024-08-08T11:06:56.595199Z

Gimme ~2h

👍 1
Niki 2024-08-08T11:19:27.150479Z

1. Performance is what I’m working right now. There’s some virtualization (stuff not in viewport is not drawn, but still measured & laid out), can always have more. And there’s a terrible O² behavior in measure that I’m working on fixing 2. Blur like this requires some tricky compositing. I plan to explore that eventually but haven’t yet. There’s Blur demo in Humble repo, it has same problem 3.

👀 1
Niki 2024-08-08T11:21:05.004859Z

Not sure about 3, you want it to prevent hover from happening on stuff underneath?

JAtkins 2024-08-08T11:21:26.666869Z

Yeah, basically in a stack how do I get the higher element to sink mouse events

Niki 2024-08-08T12:15:14.621119Z

I’ve had something like this somewhere recently, I’ll look into it again

Ben Sless 2024-08-08T12:18:37.237559Z

Any performance bottlenecks besides algorithmic?

Niki 2024-08-08T12:59:37.622939Z

Lots, but so far what I see if I cache layout properly (even go from O(n²) to O(n)) most times collapse to under a ms. Should be fine

Niki 2024-08-08T13:00:46.739609Z

But I do see stuff like map lookups, dynamic bindings and arithmetic ops on flame graph, which is a bit worrying. Hope they don’t add up to too much

Ben Sless 2024-08-08T13:05:45.459119Z

Using dunamic bindings is very wasteful

Ben Sless 2024-08-08T13:08:06.418929Z

There are some tricks you can pull of map lookups' cost becomes noticeable

Ben Sless 2024-08-08T13:10:01.537359Z

I can look at the graphs if you'd like

Niki 2024-08-08T13:13:06.486659Z

Can you run Humble UI demo app? It’s just clone & ./script/repl.py Then select any demo, scroll left panel all the way down and click Profile button. After about ~10 seconds it should generate a flame graph and print path to it in console

👍 1
JAtkins 2024-08-08T13:24:02.356739Z

Oh, that’s how that’s supposed to work lol. That button crashes the app for me on Linux 🤣

Niki 2024-08-08T13:24:52.288489Z

Check if Linux instructions here help https://github.com/clojure-goes-fast/clj-async-profiler

🫡 1
1
Ben Sless 2024-08-08T13:47:09.867439Z

It's been a while since I profiled it, I'll take a look later today

🙏 1
Ben Sless 2024-08-08T15:51:54.099339Z

Lots of overhead from binding

Ben Sless 2024-08-08T15:56:54.206199Z

you'll pay a lot less for it if you hand craft some macro which ends up emitting a call to RT.mapUniqueKeys(Object... init)

Ben Sless 2024-08-08T15:57:33.791319Z

That's just two constructor calls which wrap the array and voila

Niki 2024-08-08T16:00:43.735389Z

Yeah, binding I plan to get rid of. I recently realized it’s all on the same thread, so can even do simple with-redefs

Niki 2024-08-08T16:02:50.727299Z

Also planning to extract scale to a separate top-level var, since 90% of use of ctx are for it, getting it through map lookup every time might not be the best idea

Ben Sless 2024-08-08T16:03:06.360549Z

(defn evil-mapunique [& xs]
  (clojure.lang.RT/mapUniqueKeys (.array ^clojure.lang.ArraySeq xs)))

(defmacro evil-binding [bs & body]
  (assert (vector? bs))
  (assert (even? (count bs)))
  (assert (apply distinct? (take-nth 2 bs)))
  `(let []
     (push-thread-bindings (evil-mapunique ~@(mapcat (fn [[v e]] [`(var ~v) e]) (partition 2 bs))))
     (try
       ~@body
       (finally
         (pop-thread-bindings)))))

Niki 2024-08-08T16:03:31.998319Z

Nice!

Ben Sless 2024-08-08T16:17:48.952279Z

btw, profiling borked on linux because of

# "-Dclj-async-profiler.output-dir=/ws/humbleui",

Niki 2024-08-08T16:23:11.774569Z

Right! I was wondering where did I put it but couldn’t find it right away

Niki 2024-08-08T16:23:21.871629Z

Wonder if can be made relative

Ben Sless 2024-08-08T16:25:44.193319Z

Relative or under /tmp

Ben Sless 2024-08-08T16:26:28.382599Z

Also, checked with evil binding and eliminated all calls related to creating the hash map and iterating over the keys

Niki 2024-08-08T16:26:39.837589Z

/tmp is the default, but kinda hard to find

Niki 2024-08-08T16:27:14.848489Z

push-thread-bindings still has to merge maps though, so probably still overhead

Ben Sless 2024-08-08T16:27:33.747449Z

/tmp/ws?

Ben Sless 2024-08-08T16:27:43.196359Z

I don't see the merge

Ben Sless 2024-08-08T16:28:07.658429Z

I think it's a chained env

Niki 2024-08-08T16:28:18.755189Z

oh

Ben Sless 2024-08-08T16:51:17.761889Z

I didn't get increased throughput, trying to figure out why

Ben Sless 2024-08-08T18:48:15.958159Z

Rough estimate of binding's overhead:

(def ^:dynamic *a*)
(def ^:dynamic *b*)
(def ^:dynamic *c*)

(defn foo1 [] (binding [*a* 1] 1))
(defn foo1' [] (evil-binding [*a* 1] 1))

(cc/quick-bench (foo1)) ; 204.706525 ns
(cc/quick-bench (foo1')) ; 84.057809 ns

(defn foo2 [] (binding [*a* 1 *b* 2] 1))
(defn foo2' [] (evil-binding [*a* 1 *b* 2] 1))

(cc/quick-bench (foo2)) ; 371.001624 ns
(cc/quick-bench (foo2')) ; 178.677062 ns

(defn foo3 [] (binding [*a* 1 *b* 2 *c* 3] 1))
(defn foo3' [] (evil-binding [*a* 1 *b* 2 *c* 3] 1))

(cc/quick-bench (foo3)) ; 531.225786 ns
(cc/quick-bench (foo3')) ; 242.553081 ns

JAtkins 2024-08-09T11:28:43.368849Z

@andrzej.fricze https://github.com/JJ-Atkinson/HumbleUI btw

👍 1
fricze 2024-08-09T15:22:17.331029Z

Thanks