-
Notifications
You must be signed in to change notification settings - Fork 1
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
[WIP] Fix dependency issues in effect-selection #93
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
src/components/App.tsx
Outdated
// pass in the dispatcher to trigger a refresh, is this a hack? | ||
dispatcher: dispatchCanvas, |
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.
This is pretty weird. What this is doing is passing in the dispatcher, so it can be called from the dispatcher (yo, dog).
This is because the dispatch (here) is instantaneous, but the code in the reducer is async/await. We want to refresh when tracks are actually fetched so the cell counter goes up.
We don't need a separate effect for that because the cell counter uses canvas.tracks.size
in props
, so if it changes it will trigger a re-render there.
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.
This issue is gone now, right?
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.
Yeah I took this out because the loading updates cause enough rerendering to get the counter updated.
@@ -154,6 +192,9 @@ function reducer(canvas: PointCanvas, action: PointCanvasAction): PointCanvas { | |||
newCanvas.pointBrightness = 1.0; | |||
newCanvas.resetPointColors(); | |||
break; | |||
case ActionType.SELECTION: | |||
newCanvas.selector.selection = action.selection; |
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.
This might need a new Map(action.selection)
sometimes? Otherwise we just need to be careful (that's a big "just") not to merely modify the selection and then dispatch this.
This still has issues with |
I thought 1e5e457 would fix this, but it doesn't quite. I can't cause an issue anymore by selecting a bunch of points and moving the slider, but it still causes issues if you select while playing. |
import { BoxPointSelector } from "./BoxPointSelector"; | ||
import { SpherePointSelector } from "./SpherePointSelector"; | ||
|
||
export type PointSelection = Set<number>; |
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.
This is bad naming - now there is PointSelection
and PointsCollection
. That can definitely be cleaned up.
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.
This code is mostly vendored, so I think it's fine. Plus we might even consider getting rid of this code given that it's fairly complicated for something that is probably not too useful.
const newSelection = new Set([...selection].map((p) => canvas.curTime * canvas.maxPointsPerTimepoint + p)); | ||
dispatchCanvas({ type: ActionType.SELECTION, selection: newSelection }); |
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.
This changes the IDs of the points from indices within the timeframe to the calculated pointID
. As noted there are still possible mismatch problems, so maybe we need to do this conversion even earlier?
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'm thinking about this issue now. What are the remaining possible mismatch problems? This will be executed immediately after the selection is made, so curTime
and maxPointsPerTimepoint
will not change. Are their values assumed not to change in a later side-effect related to this?
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 think this may be sufficient. I need to re-test systematically, my above comment may be inaccurate.
@@ -38,6 +37,8 @@ export class PointCanvas { | |||
readonly selector: PointSelector; | |||
|
|||
readonly tracks: Tracks = new Map(); | |||
// set of track IDs that have had their lineage fetched | |||
readonly rootTracks: Set<number> = new Set(); |
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.
@@ -168,6 +189,30 @@ function reducer(canvas: PointCanvas, action: PointCanvasAction): PointCanvas { | |||
case ActionType.SIZE: | |||
newCanvas.setSize(action.width, action.height); | |||
break; | |||
case ActionType.SYNC_TRACKS: { | |||
const { trackManager, pointId, adding, setNumLoadingTracks } = action; | |||
const fetchAndAddTrack = async (p: number) => { |
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 can't find much official documentation on whether calling an async function in a react reducer is recommended. I found some posts on SO/Medium, but nothing too authoritative.
Given that reducers should be pure, my guess is that it's probably not recommended.
I guess the reduction of all dispatched actions will be the same even with async because those will not have started yet. But the instance will be modified in the future when the async functions run. And each dispatch will mutate different instances, which makes this feel fragile.
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.
Yeah, you're definitely right. Fragile is a kind word for it.
I'm trying to think of how we can do this without having an effect depend on canvas
. One idea is to split the canvas back into state + ref, but that's another pretty big change at this point.
Another idea is maybe we do want an effect that runs whenever canvas
changes to sync the state. Maybe that's fine as long as it's somewhat smart about skipping unnecessary work?
Closing as #95 covers the desired behavior changes, and I think this is the wrong direction for refactoring. |
Closes #49 Closes #62 (by making it no longer relevant) This stores selection state in the URL hash. In order to make this work, we need some state that reflects the selected points/cells/tracks independent of `PointCanvas.curTime` because the ordering of the React effects when re-hydrating from the entire state can be different compared to when the user is interacting with the application. Previously, the canvas stored the last selected point indices, which is transient state that depends on `PointCanvas.curTime`. Here we stored the all the point IDs that were selected by the user (inspired from #93). Alternatively, we considered storing the track IDs that were selected in #91 , but that doesn't work as well with the rest of the application logic. This PR also updates the URL hash to store some simpler state that not been added like point brightness. It also refactors that code a little to make it easier to keep new state in sync. Though there is still a lot of duplication that could be improved. It does not fix #30 , which is related, but can be solved independently.
This is some code for discussion on dealing with selection in the effects.
Some ideas here may be useful, but I don't love passing
trackmanager
to the reducer. I'll probably take another crack at this.