-
-
Notifications
You must be signed in to change notification settings - Fork 924
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make m.redraw() purely asynchronous, add m.redraw.sync() #1592
Conversation
de70d2b
to
521a4fc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the general idea. I just found a couple oddities
@@ -11,9 +11,14 @@ module.exports = function($window, redrawService) { | |||
var render, component, attrs, currentPath, lastUpdate | |||
var route = function(root, defaultRoute, routes) { | |||
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined") | |||
var run = function() { | |||
function run() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert this change. It's just diff noise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use function
declarations when the definition not intended to be modified, vs var
when it will be re-assigned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. I see.
test-utils/throttleMock.js
Outdated
module.exports = function() { | ||
var queue = [] | ||
return { | ||
throttle: function(fn) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a particular reason why the below closure is necessary (as opposed to a direct call)? It just seems odd to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean having a top level throttleMock()
factory?
It allows to have a per-test queue. If a test forgets to clear its queue, it will not have effects on a subsequent test.
I've also added afterEach
assertions to ensure that the queues are empty, but problems may arise when writing new tests/specs and the future author hasn't yet added such safeguards, it will not trigger errors in unrelated tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or do you mean the closure that's returned?
It is to emulate the throttle
API. Where a throttled function can be scheduled several times, but runs only once, on fire
(which emulates the next frame).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was referring to the closure.
@isiahmeadows I had missed your comments, thanks for the review |
Ping @lhorie (IIRC you had a few objections/concerns?) |
This should be put on hold until #1643 is merged... |
Another reason at least to redraw everything on I've added a temporary "table of contents" debug view to navigate an app more easily while it is being built (it is a form with a pre-defined user flow, I want to reach arbitrary pages easily during dev). I wanted to highlight the active route in the list using But the mounted component redraws on click before the params are updated, and when the route finishes, only the routed component is updated. As a consequence, the TOC is always one step behind the main view... Tacking the menu in a fragment at the top of the routed of the view is not an option because the app lives in a lightbox popup (defined outside Mithril) with little room left... An extra redraw isn't an option either So, for now, the current page isn't highligted. |
For one, @barneycarroll |
@pygy Apologies...I clicked "merge pull request" this time, so I have no clue what happened. 😖 If necessary, you may want to check this out from a while back on Gitter. That should update your local history without trashing later commits. |
2f29046
to
4218b32
Compare
@isiahmeadows the re-rebase was actually painless :-) @lhorie you actively opposed this proposal in the past do you still think there's a use case for a sometimes synchronous redraw API? Maybe we could have |
I'm A) in favor of always-async redraw and B) against If it's going to be provided it should be a more explicit API, like I don't remember & can't easily find the arguments against always-async redraw. 😕 |
I won't dig the post, but Leo mentioned a use case where you want to measure something in the DOM in He also documented the sometimes-async behavior as a deliberate optimization. He's been silent on the topic ever since. 👍 for |
IIRC my main concern was that some things only work if they are triggered from a user-created contexts (e.g. video autoplay in ios) |
Ok, the proposal is:
This will be a breaking change if so, and require bumping |
what is supposed to happen if one calls m.redraw.sync from oninit? |
🔥 🔥 🔥 🔥 I'd expect it to throw, that seems like an impossible request to process. |
Oh, yes, I had forgotten about video auto-play...
We can guard against nested Sync redraws from We may still enable these (by releasing the Edit: and sorry about the minified file, I'll remove it... @tivac I just gave my 👍 , but I'm not sure it is breaking. The documentation say:
|
Regarding sync redraws from hooks, we may detect nested |
@tivac I wouldn't necessarily call it a major breaking change, considering how it's documented. Oh, and regarding The logic might look like this: function isMountable(root, mounted, vnode) {
if (!root.contains(mounted)) return true
if (!vnode._inMutatingHook) return false
if (vnode.dom === mounted) return true
if (vnode.dom.contains(mounted)) return true
return false
} |
I've added No docs at this point. Edit: Before this is merged, we may want to cut v1.1.2 with the latest bug fixes. |
I hadn't run |
@pygy I'd be okay with cutting 1.1.2 first. (It's long overdue, anyways.) This is borderline breaking, so it's probably best to postpone. |
Agreed on a 1.1.2 first. I still think this should be 2.0.0 (because major version numbers are cheap) but would acquiesce to a 1.2.0 instead. |
I'd like to merge the #1734 fix before we cut 1.1.2 (the fix is easy, but the test suite only covers Re. 2.0, why not... There are other potentially breaking changes in the pipeline in peripheral APIs like m.request, the attrs changes, ... |
I only realize that I forgot to update the docs about this... I've started some work in |
Sorry for being late to the game. I think the I ran into a situation recently where I was very surprised that lifecycle events may be called out of order – and I think the What I came up with was this: import * as m from 'mithril'
import { model } from '../client-statics'
const queue: string[] = []
export function redraw(reason: string) {
if (model.isRedrawing) {
queue.push(reason)
} else {
model.isRedrawing = true
model.redrawReason = reason
m.redraw()
}
}
export function redrawDone() {
model.isRedrawing = false
if (queue.length) {
redraw(queue.shift())
}
} Then in ...
export class AppComp {
onbeforeupdate(v: Vnode, o: VnodeDOM) {
console.log(`--- Redraw: ${model.redrawReason} ---`)
model.redrawReason = undefined
}
view(v: Vnode) {
return (
<div>
...
<div
onupdate={() => {
console.log(`--- Redraw done ---`)
redrawDone()
}}
></div>
</div>
)
}
} This made sure that redraws follow each other immediately, without a flash, and yet they do not get nested. This takes care of what @lhorie was worried about, but it needs to be implemented in Mithril, or it won't work for multiple components mounted in a HTML. The thing that we need to be able to queue operations after the redraw is a |
I am putting a PR together to demonstrate how this can be achieved, but I am finding that promises are suboptimal for the task, as the |
The Promise version is here: https://github.com/andraaspar/mithril.js/tree/redraw-promise |
OK, here's the callback version. It's way less invasive and much more predictable than the Promise version. @lhorie @pygy @isiahmeadows @tivac Would you like to review it? |
Slightly modified my views on this, and submitted a PR: #1946 |
Not sure if it is welcome, but several people in the chat (recently) and elsewhere (less recently) mentioned that they'd prefer always-async redraws...
There they are.
Mounting is still synchronous for both routed and non-routed components (unless the user opts out of synchrony by using
onmatch
). Setting the route redraws the whole app (otherwise, the redraws when setting the route aren't throttled).I've introduced a
test-utils/throttleMock.js
that can be fired manually rather than waiting for the frame delay. It also allows to ensure that the delayed callbacks have fired (seeo.afterEach
).api/tests/test-mount.js
andapi/tests/test-mount.js
both depend on it.api/tests/test-redraw.js
is still mostly timeout-based in order to test the actual throttle code. I've also hardened a few tests.Edit: I had forgotten to add the throttleMock code...