-
Notifications
You must be signed in to change notification settings - Fork 0
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
Reimplement canvas using view classes #80
Conversation
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/mlh-fellowship/scheduling-profiler-prototype/gs678bh9d |
@jevakallio this is ready for an approach/architecture review :) I've described the high level goals, broken down the classes and described the key behaviors. At this point, This PR is roughly half complete, where completion = feature/functional parity with the current profiler + #71. I'd love to get your thoughts on this, especially regarding these questions:
|
Thanks @jevakallio for the call! Key points brought up during the call:
|
Hey @bvaughn, I'd like to get your thoughts on the approach of this PR. Essentially, I'm thinking of replacing the current
My goals are:
My concerns:
Although I think the benefits of this view approach outweigh these cons, I'd like to get your feedback on this before I proceed further in this direction :) We can talk about this more during our call on Monday and I can walk you through this then, but for now the PR description will hopefully describe this in greater detail. Specifically, I'd like to know if you think this view-based system is a good approach, and whether you have any requirements/suggestions. |
Hi @taneliang, I'm likely not going to have a chance to review this code today because my schedule is pretty full. Happy to talk through it with you on Monday morning though. At a high level, the approach you're describing makes sense- memoizing/limiting the re-rendering work- although I have some concern when reading through the classes section of the PR. Using inheritance for different behaviors feels like it might be problematic. The classes designed seemed overly narrow. For instance, I imagine that I'm also not sure what an example of We can talk more on Monday 😄 Thanks for the ping and the detailed description! |
Thanks @bvaughn!
Ah, I think I should've clarified this better. The behaviors are currently composed by nesting views, and so our content views (e.g. The Vercel deploy preview currently has our content views in a vertical stack This is definitely not as clean a design as I'd hoped; I'd have preferred to inject behaviors into a I'm reasonably confident that this design works and may be good enough™️ since behaviors are also composed in a similar way in UIKit.
Nope, this architecture will be able to do everything that we can do now. On top of that, this design is also partially intended to also make it easier to support more complex UIs and interactions, including having the lanes area scroll separately from the flamechart area. There are some implementation details to be sorted out to ensure that the horizontal pan and zooms remain synced between the two views (even though they're in the same canvas, the views maintain their own pan and zoom state), but that should be relatively easy to solve. Let's talk more about this on Monday; it'll probably be easier to talk through these ideas then. Thanks for looking at this despite your packed schedule! Have a great weekend 😄 |
setNeedsDisplay used to propagate upwards, marking every recursive superview as needing display. This was necessary to allow every render to efficiently find views that need rendering. However, this prevent setNeedsDisplay from being used to propagate view invalidation. This commit changes setNeedsDisplay to propagate downwards in the view heirarchy, following UIView's setNeedsDisplay. A new setSubviewNeedsDisplay method is added to fulfill the need for efficiently locating views that need redrawing. Fixes the bug where canvas resizing leaves black areas where views did not redraw.
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.
Rubberstamp approved, as per discussion on Discord
Woo hoo! 🥳 |
Summary This PR begins a stack of PRs that improves the base `View` class implemented in #80. This PR adds subview management to `View`, as there was a lot of duplicated subview handling code present in almost all our `View` subclasses. This also brings us closer to UIKit's `UIView`, which also handles its own subviews. Implements #95. Test Plan * `yarn start`: nothing broken * `yarn lint` * `yarn flow`: no errors in affected code * `yarn test`
Summary This PR begins a stack of PRs that improves the base `View` class implemented in #80. This PR adds subview management to `View`, as there was a lot of duplicated subview handling code present in almost all our `View` subclasses. This also brings us closer to UIKit's `UIView`, which also handles its own subviews. Implements #95. Test Plan * `yarn start`: nothing broken * `yarn lint` * `yarn flow`: no errors in affected code * `yarn test`
Summary This PR begins a stack of PRs that improves the base `View` class implemented in #80. This PR adds subview management to `View`, as there was a lot of duplicated subview handling code present in almost all our `View` subclasses. This also brings us closer to UIKit's `UIView`, which also handles its own subviews. Implements #95. Test Plan * `yarn start`: nothing broken * `yarn lint` * `yarn flow`: no errors in affected code * `yarn test`
Summary This PR begins a stack of PRs that improves the base `View` class implemented in #80. This PR adds subview management to `View`, as there was a lot of duplicated subview handling code present in almost all our `View` subclasses. This also brings us closer to UIKit's `UIView`, which also handles its own subviews. Implements #95. Test Plan * `yarn start`: nothing broken * `yarn lint` * `yarn flow`: no errors in affected code * `yarn test`
Summary
TL;DR
Replaces Brian's original procedural code with view classes. The views are rudimentary implementations of iOS UIKit-style
UIView
s.Originally discussed at #50 (comment).
Related to #50. Resolves #71, resolves #39, resolves #83. Unblocks others (#70, #72)
This PR is enormous but I can't really split it, as I'm fixing conceptual issues as I go.
Problems this solves
renderCanvas
andgetHoveredEvent
) as we aren't able to share layout code between them.usePanAndZoom
. This is a problem as Add vertical resizer between canvas #19 requires resizing and independent scrolling of 2 areas of the same canvas (or 2 separate canvasses). This view architecture will allow us to implement independent scrolling within 1 canvas by storing state in individual classes.CanvasPage
React component, which in practice meant that moving the cursor across the canvas triggered a full-canvas render. This view architecture solves this with:CanvasPage
does not need will be stored in mutable refs or as variables in the view instances.setNeedsDisplay
orsetSubviewsNeedDisplay
has been called on them. If a view needs display, it'll lay out its subviews and draw them. This allows us to easily redraw parts of the canvas that have changed, improving hovering performance. Note that this does not improve scroll/zoom operations that require a near-full-canvas redraw.CanvasPage
state needs to be modified.Pros
Cons
Classes
All views inherit from the
View
class. The main classes and key purposes are listed below:Surface
: Represents the canvas surface. Also the main point of entry for all incoming interactions.View
: Base class that can be subclassed to implement custom content views (e.g. our flamegraph, React data, etc.). This is the equivalent of UIKit'sUIView
.StaticLayoutView
: A simpleView
subclass that can display a list of subviews laid out by an injectedlayouter
function. This is used for horizontal and vertical stacking as well as layering.HorizontalPanAndZoomView
: AView
subclass that handles horizontal pan, scroll and zoom interactions. This contains a singlecontentView
subview. When panning and zooming, thecontentView
'sframe
is mutated. Known issue: thecontentView
'sframe
is not clamped and you can scroll offscreen.More envisioned layout views:
VerticalScrollView
: AView
subclass that handles vertical pan and scroll interactions. This will be similar toHorizontalPanAndZoomView
except that it does not zoom.ResizableSplitView
: AView
subclass that has 2 subviews divided by a resize bar. For (Add vertical resizer between canvas #19).This design is not the cleanest as we complect a bunch of behaviors into the same class, and it's strange that only one class has an injected layouter. This can be cleaned up in the future if needed.
Content views:
TimeAxisMarkersView
ReactEventsView
ReactMeasuresView
(includes modifications to address Hide empty lanes #71)FlamegraphView
LaneLabelView
(envisioned, for Show React version #73)UserTimingMarksView
(envisioned, for Show remaining User Timing marks in the UI #72)These are instantiated in
CanvasPage
. Their code is a light-ish adaptation of the existing code inrenderCanvas
, but in the future we may want to turnReactMeasuresView
andFlamegraphView
into a vertically stackedStaticLayoutView
to reuse moreView
logic.Key flows
Rendering
displayIfNeeded
method is invoked, it calls its root view'sdisplayIfNeeded
method.needsDisplay
orsubviewsNeedDisplay
flags are true and the view is visible onscreen, it will layout its subviews and draw.displayIfNeeded
methods.Event handling
useCanvasInteraction
.useCanvasInteraction
is given a callback that invokes aSurface
'shandleInteraction
method.useCanvasInteraction
translates browser events into higher-level interactions (e.g. mouse wheel event -> zoom or scroll interactions). Note: The current implementation is a WIP and is neither complete nor correct.useCanvasInteraction
calls itsinteractor
callback. Our interactor is set inCanvasPage
, which calls the surface'shandleInteraction
method.handleInteractionAndPropagateToSubviews
method. A view may handle the interaction, but it must propagate the interaction to its subviews.State updates
setNeedsDisplay
method should be called. This is often done in superviews'layoutSubviews
or in event handlers.setNeedsDisplay
sets the view'sneedsDisplay
to true, and recursively invokes all its superclasses'setSubviewsNeedDisplay
. This ensures that the next render will be able to efficiently find all views that need to be redrawn.Test Plan
1.4k lines of new code and no tests 🙃
yarn lint
yarn flow
(has errors in untouched code, but none in the new code)yarn test
(tests untouched, but still passing)Manually tested on:
60 FPS on the Facebook.com profile 😍
TODO
To be addressed in this PR
HorizontalPanAndZoom
:frame
/visibleArea
don't get painted and leave a black area (canvas resizes should forceneedsDisplay
on all views)To be fixed in future PRs (will be convert most to issues)
View
(Views: Support subviews in View class #95)ResizableSplitView
(Add vertical resizer between canvas #19)