-
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
Changes from all commits
f8b61f1
1c9431e
ee85b24
1e5e457
f1aab72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
import { useCallback, useEffect, useReducer, useRef, Dispatch, RefObject } from "react"; | ||
import { useCallback, useEffect, useReducer, useRef, Dispatch, RefObject, SetStateAction } from "react"; | ||
|
||
import { Vector3 } from "three"; | ||
|
||
import { PointCanvas } from "@/lib/PointCanvas"; | ||
import { PointsCollection } from "@/lib/PointSelectionBox"; | ||
import { PointSelectionMode } from "@/lib/PointSelector"; | ||
import { PointSelection, PointSelectionMode } from "@/lib/PointSelector"; | ||
import { ViewerState } from "@/lib/ViewerState"; | ||
import { TrackManager } from "@/lib/TrackManager"; | ||
|
||
enum ActionType { | ||
SYNC_TRACKS = "SYNC_TRACKS", | ||
AUTO_ROTATE = "AUTO_ROTATE", | ||
CAMERA_PROPERTIES = "CAMERA_PROPERTIES", | ||
CUR_TIME = "CUR_TIME", | ||
|
@@ -17,13 +18,23 @@ enum ActionType { | |
POINTS_POSITIONS = "POINTS_POSITIONS", | ||
REFRESH = "REFRESH", | ||
REMOVE_ALL_TRACKS = "REMOVE_ALL_TRACKS", | ||
SELECTION = "SELECTION", | ||
SELECTION_MODE = "SELECTION_MODE", | ||
SHOW_TRACKS = "SHOW_TRACKS", | ||
SHOW_TRACK_HIGHLIGHTS = "SHOW_TRACK_HIGHLIGHTS", | ||
SIZE = "SIZE", | ||
MIN_MAX_TIME = "MIN_MAX_TIME", | ||
} | ||
|
||
interface SyncTracks { | ||
type: ActionType.SYNC_TRACKS; | ||
trackManager: TrackManager; | ||
pointId: number; | ||
adding: Set<number>; | ||
// callback to decrement the number of loading tracks | ||
setNumLoadingTracks: Dispatch<SetStateAction<number>>; | ||
} | ||
|
||
interface AutoRotate { | ||
type: ActionType.AUTO_ROTATE; | ||
autoRotate: boolean; | ||
|
@@ -68,6 +79,11 @@ interface RemoveAllTracks { | |
type: ActionType.REMOVE_ALL_TRACKS; | ||
} | ||
|
||
interface Selection { | ||
type: ActionType.SELECTION; | ||
selection: PointSelection; | ||
} | ||
|
||
interface SelectionMode { | ||
type: ActionType.SELECTION_MODE; | ||
selectionMode: PointSelectionMode; | ||
|
@@ -97,6 +113,7 @@ interface MinMaxTime { | |
|
||
// setting up a tagged union for the actions | ||
type PointCanvasAction = | ||
| SyncTracks | ||
| AutoRotate | ||
| CameraProperties | ||
| CurTime | ||
|
@@ -106,6 +123,7 @@ type PointCanvasAction = | |
| PointsPositions | ||
| Refresh | ||
| RemoveAllTracks | ||
| Selection | ||
| SelectionMode | ||
| ShowTracks | ||
| ShowTrackHighlights | ||
|
@@ -136,7 +154,7 @@ function reducer(canvas: PointCanvas, action: PointCanvasAction): PointCanvas { | |
newCanvas.controls.autoRotate = action.autoRotate; | ||
break; | ||
case ActionType.HIGHLIGHT_POINTS: | ||
newCanvas.highlightPoints(action.points); | ||
newCanvas.highlightPoints([...action.points].map((p) => p % newCanvas.maxPointsPerTimepoint)); | ||
break; | ||
case ActionType.INIT_POINTS_GEOMETRY: | ||
newCanvas.initPointsGeometry(action.maxPointsPerTimepoint); | ||
|
@@ -154,6 +172,9 @@ function reducer(canvas: PointCanvas, action: PointCanvasAction): PointCanvas { | |
newCanvas.pointBrightness = 1.0; | ||
newCanvas.resetPointColors(); | ||
break; | ||
case ActionType.SELECTION: | ||
newCanvas.selector.selection = action.selection; | ||
break; | ||
case ActionType.SELECTION_MODE: | ||
newCanvas.setSelectionMode(action.selectionMode); | ||
break; | ||
|
@@ -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) => { | ||
setNumLoadingTracks((n) => n + 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 Another idea is maybe we do want an effect that runs whenever |
||
const tracks = await trackManager.fetchTrackIDsForPoint(p); | ||
// TODO: points actually only belong to one track, so can get rid of the outer loop | ||
for (const t of tracks) { | ||
// skip this if we already fetched the lineage for this track | ||
if (newCanvas.rootTracks.has(t)) continue; | ||
newCanvas.rootTracks.add(t); | ||
const lineage = await trackManager.fetchLineageForTrack(t); | ||
for (const l of lineage) { | ||
if (adding.has(l) || newCanvas.tracks.has(l)) continue; | ||
adding.add(l); | ||
const [pos, ids] = await trackManager.fetchPointsForTrack(l); | ||
newCanvas.addTrack(l, pos, ids); | ||
} | ||
} | ||
}; | ||
fetchAndAddTrack(pointId).then(() => { | ||
setNumLoadingTracks((n) => n - 1); | ||
}); | ||
break; | ||
} | ||
case ActionType.MIN_MAX_TIME: | ||
newCanvas.minTime = action.minTime; | ||
newCanvas.maxTime = action.maxTime; | ||
|
@@ -203,10 +248,14 @@ function usePointCanvas( | |
|
||
// When the selection changes internally due to the user interacting with the canvas, | ||
// we need to trigger a react re-render. | ||
canvas.selector.selectionChanged = useCallback((_selection: PointsCollection) => { | ||
console.debug("selectionChanged: refresh"); | ||
dispatchCanvas({ type: ActionType.REFRESH }); | ||
}, []); | ||
canvas.selector.selectionChanged = useCallback( | ||
(selection: PointSelection) => { | ||
console.debug("selectionChanged:", selection); | ||
const newSelection = new Set([...selection].map((p) => canvas.curTime * canvas.maxPointsPerTimepoint + p)); | ||
dispatchCanvas({ type: ActionType.SELECTION, selection: newSelection }); | ||
}, | ||
Comment on lines
+254
to
+255
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
[canvas.curTime, canvas.maxPointsPerTimepoint], | ||
); | ||
|
||
// set up the canvas when the div is available | ||
// this is an effect because: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,8 +22,7 @@ import { OutputPass } from "three/addons/postprocessing/OutputPass.js"; | |
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js"; | ||
|
||
import { Track } from "@/lib/three/Track"; | ||
import { PointSelector, PointSelectionMode } from "@/lib/PointSelector"; | ||
import { PointsCollection } from "@/lib/PointSelectionBox"; | ||
import { PointSelection, PointSelector, PointSelectionMode } from "@/lib/PointSelector"; | ||
|
||
type Tracks = Map<number, Track>; | ||
|
||
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
showTracks = true; | ||
showTrackHighlights = true; | ||
|
@@ -48,8 +49,7 @@ export class PointCanvas { | |
|
||
// this is used to initialize the points geometry, and kept to initialize the | ||
// tracks but could be pulled from the points geometry when adding tracks | ||
// private here to consolidate external access via `TrackManager` instead | ||
private maxPointsPerTimepoint = 0; | ||
maxPointsPerTimepoint = 0; | ||
|
||
constructor(width: number, height: number) { | ||
this.scene = new Scene(); | ||
|
@@ -107,7 +107,7 @@ export class PointCanvas { | |
return newCanvas as PointCanvas; | ||
} | ||
|
||
get selectedPoints(): PointsCollection { | ||
get selectedPoints(): PointSelection { | ||
return this.selector.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.