Fork me on GitHub
#clojurescript
<
2024-06-02
>
puchacz18:06:38

hi, I have a resize observer that needs to access global state. I am migrating from plain reagent to re-frame and I am not supposed to read from app-db directly, and I am not allowed to subscribe outside of reactive content, like render function. so what's the recommended practice please?

p-himik18:06:13

What exactly does the observer do and what kind of state does it need?

puchacz18:06:11

I have a pdf viewer, component split in half. full pages on the right, thumbnails on the left.

puchacz18:06:09

I need to observe resizing (to update page widths) and scrolling to decide what page is the "current" one and render only a few pages after that.

puchacz18:06:03

I need to observe state like the "current page", or thumbnail width preference (they can organise themselves into 1 or 2 columns.

puchacz18:06:57

having looked at the code I think it is OK in general to read from @re-frame.db/app-db (but not to write to it, it would defeat the purpose of re-frame)

puchacz18:06:15

however, I expect there are "best practices"

p-himik18:06:41

Is it possible to make the observer dispatch some generic [:size-changed width height] and let the event handler do the rest?

puchacz18:06:08

it is. but I am assuming (without trying) it may be too slow

puchacz18:06:49

Eric Normand recommends using plain reagent atoms for situations with a huge number of events

puchacz18:06:18

in his video course

puchacz18:06:51

scrolling will generate a lot of events. imagine a PDF with 1000 pages.

puchacz18:06:22

obviously I cannot render them all at once, but still, the document body is huge, holding mostly empty page placeholders

puchacz18:06:18

I am also thinking about reading app-db in the observer callback, and call my "business logic" function with values, rather than read app-db in these functions. so only the callback itself will be impure

puchacz19:06:02

basically on scrolling I need to be very careful with performance

p-himik19:06:26

By itself, dispatching an event can hardly be slow. It doesn't do that much. "Huge number of events" is not an objective metric. :) It's what you do inside that even that can be slow. But that would also make handling a regular ratom slow. You can also debounce the actual computation so that it happens only when there's a pause in resizing. And if you consider the current size and/or separator position as something that's not a part of the app's state (i.e. nothing of value would be lost if that data got removed, it's not required to reproduce any issues, it's not required for orchestrating anything, etc.) then it would be better to not have it handled by re-frame at all, regardless of performance considerations.

puchacz19:06:57

debounce dispatching events or debounce action in reg-fx?

p-himik19:06:46

Either will probably be fine. Scrolling does not generate an obscene amount of events that would be enough to introduce any noticeable lag.

puchacz19:06:27

resizing is already "2 phases" in my reagent version. in the first phase, when the mouse is down, I only let user move an outline, and only on mouse up, I send "invalidate size" on all rendered pages.

p-himik19:06:11

Then you have 0 things to worry about.

puchacz19:06:50

I am trying to picture in my head what the extra load or lag I am introducing: calling dispatch puts the event vector on a queue, another loop will pick it up when the observer callback quits from the queue and it will update the db before quitting, right? no more delayed evaluations, only this queue between event dispatch and event handling?

p-himik19:06:23

another loop will pick it up when the observer callback quits from the queueThose are different queues.

puchacz19:06:28

so rf/dispatch -> queue1, then something internal queue1 -> queue2 -> fx ...?

p-himik19:06:38

When the observer callback is done, JS will get its control back. It can schedule anything it wants - including giving control to the re-frame's queue processor. When that processor (whatever it's actually called, I don't recall) starts working, it goes through the queue and runs the event handlers one by one while handling all the return events. So one queue is the one JS has internally. It's an implicit queue you have absolutely no control over. Re-frame has another queue that you can monitor and, I think, maybe even change. But don't do that. In any case, even if there are a thousand more steps to the overall pipeline here, it does not matter. What matter is not how many steps there are but how well it all performs, can a single step be fit inside a single frame as dictated by the refresh rate of the monitor. Typically 60 Hz, so you have 16 ms to do everything before the page should be updated according to what it should be displaying. 16 ms is a lot of time.

p-himik19:06:18

When in doubt - prototype and measure. Just don't use a synthetic microbenchmark, those are incredibly useless in JS.

puchacz19:06:21

ah, got you. yes. JS event processing loop and re-frame event processing loop are different indeed

puchacz19:06:12

"prototype and measure". I was thinking I should be trying my dev version on an android tablet. my PC is quite hefty and it can fool me

puchacz19:06:38

the server would still run on my PC, but the brower in the tablet

p-himik19:06:08

Right, the measurement should be done on target platforms. Of course, it's better when something is more performant. But there's also absolutely no need to target some ancient stuff that can't even display your page because the outdated and unsupported browser doesn't support some of the features that you're using in JS/HTML/CSS.

puchacz19:06:24

ok 🙂 a regular tablet, not the current flagship, but not very ancient either. does it make sense to get a feel of performance like this or you meant something more systematic using testing frameworks etc?

puchacz19:06:00

(I don't know of any testing frameworks)

p-himik19:06:29

I think it makes sense. Definitely better than a synthetic benchmark. And if it ends up being slower than you like, try doing the same but on a release build. The performance difference between a dev and a prod build can be drastic.

puchacz19:06:50

thanks, I did not realise. so to sum up re-frame extra load or lag: there should be no measurable extra load. it just goes through a vector of trivial interceptors when processing an event. as of lag, there is ONE discontinuity: the vector event is put on the single, central re-frame queue and then dispatch quits, and whatever called my dispatch must quit etc. and only then Javascript may wake up re-frame fx handlers. so ONE hop, right?

puchacz19:06:18

should be not much worse than setTimeout and similar?

puchacz19:06:09

setTimeout(0, something) I mean

puchacz19:06:13

anyway, thanks a lot 🙂

p-himik20:06:36

> there should be no measurable extra load In your case, yes. > whatever called my dispatch must quit etc. and only then Javascript may wake up re-frame fx handlers. so ONE hop, right? Yes. > should be not much worse than setTimeout and similar? In your case, yes. It's not hard to imagine someone dispatching a million of events in a loop that do nothing - of course, that will be much more noticeable than a million of calls to setTimeout. But I wouldn't consider such a thing reasonable at all.

puchacz20:06:23

cool. I am playing with the tablet now, it is harder than I expected to go from wifi to PC 🙂

puchacz20:06:45

but don't worry, stack overflow is for this 🙂

p-himik08:06:51

Oh, BTW - there's #C073DKH9P