-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Delay loading and rendering gui #4800
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
Conversation
src/containers/gui.jsx
Outdated
// Use setTimeout. Do not use requestAnimationFrame or a resolved | ||
// Promise. We want this work delayed until after the data request is | ||
// made. | ||
setTimeout(() => { |
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.
@mzgoddard I think this introduces a non-deterministic race condition where it is possible for the assets to be loaded before the VM has a renderer. (You can confirm this by putting in a long timeout time at the end of this). I noticed because I was getting errors like "no rendering module present" sometimes when testing.
I implemented the load time testing you used (thanks for showing how to do that!) and it at first look it doesn't seem to make a difference (still looks like approximately 2000ms -> ~1500ms), so maybe for getting this in we could just inline the renderer creation before the project load?
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.
Crud. I fixed this yesterday and forgot to push the change.
The setTimeout doesn't create a race condition. The race condition is effectively in render() since its what blocks the existing renderer creation. We need to test for this in the if block before rendering the GUI.
Here's the fixed logic.
if (
!fontsLoaded ||
fetchingProject ||
this.props.vm.renderer && isLoading
) {
// TODO: Render a loading screen.
return null;
}
I'm going to take this a step further and ensure the renderer by this point.
src/containers/gui.jsx
Outdated
// as with "Load from your computer"). | ||
if (!fontsLoaded || fetchingProject || isLoading) { | ||
// TODO: Render a loading screen. | ||
return null; |
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.
@mzgoddard Returning a <Loader />
component here seems like a reasonable thing to do.
As a side effect, I think this gets us to a "first meaningful paint" faster, because we would have a loader being rendered without all the rest of the GUI, so not only does the time to "seeing the full GUI" go down, but so does "time to seeing the loading screen instead of a blank page".
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.
Cool.
This will cause a screen flash. When the <Loader />
is replaced by the Loader in GUI the animation will restart because it is a new DOM element, and the CSS animation will be a different instance. Fixing this flash will be a next step perhaps.
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. Hmm. I'm not sure that will happen. It will probably normally not happen since that be the same render when loading is false.
Uhm.
src/containers/gui.jsx
Outdated
@@ -102,6 +139,17 @@ class GUI extends React.Component { | |||
loadingStateVisible, | |||
...componentProps | |||
} = this.props; | |||
|
|||
// TODO: Determine if we finished loading previously. In which case we |
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 would be fine with adding a flag to this container for the previous load (could set it in componentDidUpdate above?). In theory it is actually fine to just re-mount the whole GUI (we do that sometimes for other things, like changing languages), but it would probably be a wasted render.
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.
Cool. Uh I think it has to be in both Update and Mount. At least to be safe, we could in theory be done fetching before we render this component.
@mzgoddard this is looking great! A few comments. I think the tests are probably failing due to the lack of loading screen, might be fixed after that? |
- Create the scratch renderer earlier in the GUI container/HOC stack - Delay loading and rendering GUI component until after project assets have been fetched
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.
Code LGTM, do you think this is ready to merge?
Resolves
Faster project load time when loading a project by id.
Proposed Changes
Related #4722.
Reason for Changes
When loading a project by id scratch needs to load a data file and then load assets referenced by that data file. Since these requests work asynchronously outside the javascript event loop, they make events to announce to javascript when they have completed some work. GUI does a lot of work that first blocks the data request from being sent and then blocks the response events from being emitted. Delaying this GUI work until the data and assets have loaded significantly improves load time.
This PR follows #4722 by delaying some work. In this case it creates the scratch renderer earlier and delays all other GUI work until after the project loads. #4722 by comparison optionally breaks up GUI into smaller
Further Work
This idea can be taken further. A future change could move creating the scratch storage out of the react + redux stack. Loading the project data only requires the Storage instance. Loading the project assets requires the Renderer, AudioEngine and VirtualMachine.
TODO
The loading screens are currently embedded in the components that are in a sense being loaded. Needing to render the component to render its loading screen makes it happen too late when combined with this PR. How should we solve this?
Currently it is best that most of the GUI not be evaluated or rendered until after the asset requests are made. Currently there is no state available know when this has happened. To best know when the GUI can be rendered we need this state. Alternatively we can render the GUI when the project has finished loading (as this PR currently does).
How does the container know that we have already rendered the GUI before?
I think the best answer would be two "loading screens" wrapping the container or a higher component. One that renders while there is a renderer and isLoading is true and then never render again. And a second that always renders while isLoading is true.
The easy solution here would probably be to unmount the GUI and render a loading screen here.
This question is "too big" for this PR. But I find myself asking it since its moving Renderer's creation point. Since the VM is in the redux store, maybe Renderer and AudioEngine should be initialized by a redux middleware.
Benchmarks
Benchmark methodology source
Performance is a little better in this PR, delay-gui, when compared to the dynamic-stage PR #4722. Primarily this is because delay-gui performs its delay at an earlier time than the other PR. Optimistically that will load faster by making the async requests earlier. A similar change could be made for #4722 to add an earlier delay breakpoint.
Detailed Chart
Browser Coverage
Check the OS/browser combinations tested (At least 2)
Mac
Windows
Chromebook
iPad
Android Tablet