Force Curve UI Optimization#176
Force Curve UI Optimization#176DXCanas wants to merge 10 commits intoJaapvanEkris:0.9.7-(under-construction)from
Conversation
…thod for better performance and to follow Lit best practices.
…y for more efficient change detection with array values
|
Not a substitute for a state refactor @Abasz @JaapvanEkris Just an incremental step |
Commited
516bdc5 to
f8d6975
Compare
|
A little hard to follow, and I'm using the simulated row commented out in Sampling with changes, from first |
|
Dumb question: why compare the force curve to begin with? The rowing engine will only update the force curve at the end of the drive (so the metricscontext's isRecoveryStart is true). Wouldn't that be much simpler? |
|
I am also reading the docs: https://lit.dev/docs/components/lifecycle/#willupdate and some tutorials on best practices: https://open-wc.org/blog/some-things-to-know-when-developing-with-lit/ Based on the lifecycle hook I think It was a bead practice to put the chart data update in to render (though from a practical perspective the result is the same) and should have been put be moved to the willUpdate() method. I think it is recommended that render does generally create side effects. Though strictly speaking I dont see much difference but semantically it may be relevant as it better communicates intent? willUpdate() does not return a value and does not require super() call which tells me that it is specifically for component internal side effects existing outside its life and change detection flow.
I would leave that in just for defence against future changes (lets be honest on rpi this is rather a micro optimisation as it is not too expensive on a 50-100 element array, with full honestly I realize now that when I made this comment I was more in a micro controller mindset...) for now. Actually i have been looking at Lit "Data Management" and that will solve many of these problems. Once I gathered enough information on best practices I will sketch up something but the main idea is that with Lit Context we can inject data service directly into a component without the need for passing down everything from parent to child. And I am hoping that the individual metric component from there can take the array reference (via exposed readonly signal) held by the data service which will have proper scheduling for updating these properties based on metrcis context (which was the original intention anyway). This will properly encapsulate things, protect the data from outside changes as well as allocate the update scheduling to one place and we have full control. I dont like the idea of the server doing this, unless we create separate messages for the web socket (which would be similar to the PM5 specification) so for instance we fire a message recoveryStart and we include everything that changes, we fire a separate event for continuous metrics (distance for instance) etc. But in all honesty this will not change the overall complexity in a good way as we would need to have logic in web server to make decision what WS message to fire, and then another logic in the client how to handle the different messages. If we keep webserver as is and we have the logic in client it will not require doubling anything (client will need this anyway). Furthermore, I generally like those APIs where you expose a set of data that is very very stable and then leave clients to deal with them how ever they want. Its read only, webserver is still single source of truth, its relatively light weight (I mean we can of course save some bandwidth if we split the broadcast on the server as we would not send handle force array with every packet, but this is again a micro optimisation for the RPi, when I was doing ESPRM with web socket I did this because I wanted to limit the packet sizes as much as possible for the 240Mhz processor - and at the time I have not dared to use the second core for this :) so wifi communication was on the same core as the main ISRs and that messed with interrupts in rare cases... looking back to that it was not so mature implementation, but any way). |
That actually how the initial implementation; the array was always different because the parents' object was always "different" (A shallow copy might actually be like. nice. in this case.)? So the initial performance issue was that It was important to me that we start adhering to the few patterns that So I implemented … I thought it was a kind of a pre-optimzation, where And this makes sense of it to me now:
The only benefit I can think of for keeping it is the freakish case where an individual somehow draws the exact same curve 🤷 After looking more closely at the logs, I agree with this sentiment:
^ I don't think Context is a bad idea. But I do think there are smaller changes partways through. Whether they're worth it is something worth talking about, but uh. Probably not here. Some discussion of this over in #175. I agree with the comment after that point, but would rather have the discussion elsewhere for the same reason 😄 After the review over in #170, I have some other ideas, though! Will update both! |
…operty instead of separate updateForceCurve and value properties, removing module-level flag JSDoc documentation added for forceCurveData property
f106bae to
34a822d
Compare
Funnily enough, after throwing in more logs and letting it run, it looks like it's the array length comparison that gets evaluated most often 🤷 idk if this will be the case in real user practice, though: But yeah, alternative which I think is most closely aligned to @Abasz's original concept: split the props back up and then basically ignore all changes to the force curve array (hasChanged always returns false). I think that'd basically be the most performant. Initially, I wondered if a single |
…cycle method in DashboardForceCurve `firstUpdated` still needed to calculate height.
c48e1a6 to
123735f
Compare
But this is one of the key elements of the current architecture: the collection of flags is there by design to help data consumers understand the data and trigger updates. And it guarantees that everybody displays the same data regardless of channel (so that the force curve on your monitor will be the same as displayed in EXR or ErgZone and recorded by the RowsAndAll recorder). No need for local comparisons and half-baked guesses: the metricsContext will tell what state the rowing machine is. All recorders and peripherals use the exact same logic (the RowsAndAll recorder even uses it to trigger recording of strokes with the same force curve) and report the same data. |
Uses `updateForceCurve` as the determinent for updates. More performant/simple. This reverts commit 34a822d.
543925a to
f67ed76
Compare
Data preparation happens in willUpdate. DOM manipulation happens in updated after render completes.
Makes sense to me, and aligns with Abasz's original rationale for including the change 🙂; I've reverted the previous change. Both the curve array and the flag bool are taken in, but the flag is the only thing that triggers updates. I think this is the most pattern-compliant way to do it in lit with the way the data layer currently is.
|
|
Actually, something interesting @JaapvanEkris While I agree with your point architecturally, the former approach (object with array and bool) is resulting in less renders according to the logging I've got going In its current state, using only the flag and no deep checks, it's actually exhibiting and odd "staggered"/"double-tap" render every change. The bool flipping from true -> false is one render The last approach, where both the bool and the array were checked made it so that renders happened half as often, with a pretty performant array length check "winning out" most of the time. Update: I've found another way, that I think y'all will like better! Saw it while going through lifecycle docs, very aptly named ( |
95bdf9d to
b508f1a
Compare
|
Marking as draft until we merge #170 , as there are merge conflicts now 🫠 |
…to force-curve-optimizations
|
Merge conflicts resolved! |
Problem
appState = {...appState, newData}) invalidates all object referencesSolution
Single-property optimization using Lit's native features:
Usage:
Tradeoffs
Benefits
Testing
See comments for detailed logs
Commits pulled out from #170 to unblock it.
Shoutout to @Abasz for the
updateForceCurveproperty. Cherry-picked and integrated the commit from his lit upgrade branch (which I believe is 0.9.8 targetted)Discussion:
#173 (reply in thread)
#173 (comment)
99bf915
#170 (comment)
#168 (reply in thread)
#168 (reply in thread)
#168 (reply in thread)