Skip to content

Commit

Permalink
Fix race condition between removing a flight and updating it
Browse files Browse the repository at this point in the history
  • Loading branch information
eidge committed Jul 4, 2022
1 parent 16b0dd0 commit ee0d7c1
Show file tree
Hide file tree
Showing 23 changed files with 141 additions and 104 deletions.
12 changes: 6 additions & 6 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import LoadingScreen from "../src/flight_analysis/components/loading_screen";
import FullScreenWithDrawer from "../src/ui/components/layout/full_screen_with_drawer";
import BGALoader from "../src/flight_analysis/url_flight_loaders/bga_loader";
import IGCLoader from "../src/flight_analysis/url_flight_loaders/igc_loader";
import FlightGroup from "glana/src/analysis/flight_group";
import { FlightDatum } from "../src/flight_analysis/store/models/flight_datum";

const LOADER_CLASSES = [BGALoader, IGCLoader];

interface State {
isLoading: boolean;
flightGroup?: FlightGroup;
flightData?: FlightDatum[];
}

export default function Index() {
Expand All @@ -24,7 +24,7 @@ export default function Index() {
return state.isLoading ? (
<Loading />
) : (
<FlightAnalysis flightGroup={state.flightGroup} />
<FlightAnalysis flightData={state.flightData} />
);
}

Expand All @@ -36,14 +36,14 @@ async function maybeLoadFlightsFromURL(setState: any) {
const loader = loaders.find(l => l.willHandle());

if (loader) {
const flightGroup = await loader.loadFlightGroup();
if (flightGroup.flights.length < 1) {
const flightData = await loader.loadFlightGroup();
if (flightData.length < 1) {
window.location.href = "/failed_to_load_flight";
}
setState((s: State) => ({
...s,
isLoading: false,
flightGroup: flightGroup
flightData: flightData
}));
} else {
setState((s: State) => ({ ...s, isLoading: false }));
Expand Down
4 changes: 2 additions & 2 deletions src/flight_analysis/components/analysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export default function Analysis() {

try {
const igcBlob = new IGCBlob(files);
const flightGroup = await igcBlob.toFlightGroup();
dispatch(actions.setFlightGroup(flightGroup));
const flightData = await igcBlob.toFlightData();
dispatch(actions.setFlightData(flightData));
} catch (e) {
await errorTracker.report(e);
}
Expand Down
2 changes: 1 addition & 1 deletion src/flight_analysis/components/flights_screen/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SummaryTab from "./summary_tab";
import PhasesTab from "./phases_tab";
import { useFlightAnalysisDispatch, useFlightAnalysisState } from "../../store";
import { actions } from "../../store/actions";
import { FlightDatum } from "../../store/reducer";
import { FlightDatum } from "../../store/models/flight_datum";
import { units } from "../../settings";

type Tab = "Summary" | "Phases" | "More";
Expand Down
3 changes: 2 additions & 1 deletion src/flight_analysis/components/flights_screen/phases_tab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ButtonGroup from "../../../ui/components/button_group";
import { FlightDataById, FlightDatum } from "../../store/reducer";
import { FlightDataById } from "../../store/reducer";
import FlightLabel from "../../../ui/components/flight_label";
import { GliderState } from "glana/src/flight_computer/state_machine";
import { useEffect, useMemo, useState } from "react";
Expand All @@ -8,6 +8,7 @@ import PhasesTable from "./phases_table";
import React from "react";
import { UnitSettings } from "../../settings";
import Phase from "glana/src/analysis/phases/phase";
import { FlightDatum } from "../../store/models/flight_datum";

interface Props {
flightData: FlightDatum[];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FlightDatum } from "../../store/reducer";
import { FlightDatum } from "../../store/models/flight_datum";
import FlightLabel from "../../../ui/components/flight_label";
import Icon from "../../../ui/components/icon";
import Button from "../../../ui/components/button";
Expand Down
6 changes: 3 additions & 3 deletions src/flight_analysis/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import Analysis from "./analysis";
import React from "react";
import { StoreProvider } from "../store";
import FlightGroup from "glana/src/analysis/flight_group";
import { FlightDatum } from "../store/models/flight_datum";

interface Props {
flightGroup?: FlightGroup;
flightData?: FlightDatum[];
}

export default function FlightAnalysis(props: Props) {
return (
<StoreProvider flightGroup={props.flightGroup}>
<StoreProvider flightData={props.flightData}>
<Analysis />
</StoreProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import SavedFlight from "glana/src/saved_flight";
import React from "react";
import { useMemo } from "react";
import { splitWhen } from "../../../utils/arrays";
import { FlightDatum } from "../../store/reducer";
import { FlightDatum } from "../../store/models/flight_datum";

const MAX_POINTS = 500;

Expand Down
2 changes: 1 addition & 1 deletion src/flight_analysis/components/main_screen/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import AnimationTicker from "../../animation_ticker";
import { useFlightAnalysisDispatch, useFlightAnalysisState } from "../../store";
import { Action, actions } from "../../store/actions";
import { Settings, units } from "../../settings";
import { FlightDatum } from "../../store/reducer";
import { FlightDatum } from "../../store/models/flight_datum";
import TimelineDetails from "./timeline_details";
import analytics from "../../../analytics";

Expand Down
3 changes: 2 additions & 1 deletion src/flight_analysis/components/main_screen/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ function useRenderFlights(
useEffect(() => {
if (!mapRenderer || !flightData) return;
flightData.forEach(f => mapRenderer.addFlight(f));
mapRenderer.zoomToFit();
// Give the UI time to hide the sidebar if it's open.
setTimeout(() => mapRenderer.zoomToFit(), 1000);

return () => flightData.forEach(f => mapRenderer.removeFlight(f));
}, [mapRenderer, flightData]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Task, { TaskTurnpoint } from "glana/src/flight_computer/tasks/task";
import React from "react";
import { FlightDatum } from "../../store/reducer";
import { FlightDatum } from "../../store/models/flight_datum";
import { relativeTo } from "../../utils/time";

interface Props {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UnitSettings } from "../../settings";
import { FlightDatum } from "../../store/reducer";
import { FlightDatum } from "../../store/models/flight_datum";
import FlightLabel from "../../../ui/components/flight_label";
import { ReactNode } from "react";

Expand Down
14 changes: 7 additions & 7 deletions src/flight_analysis/components/upload_screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export default function UploadScreen() {
const onFileInput = async (event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();
setIsLoading(true);
const flightGroup = await handleFileInput(event);
if (flightGroup) {
const flightData = await handleFileInput(event);
if (flightData.length > 0) {
dispatch(actions.closeDrawer());
dispatch(actions.setFlightGroup(flightGroup));
dispatch(actions.setFlightData(flightData));
} else {
setIsLoading(false);
}
Expand Down Expand Up @@ -79,16 +79,16 @@ export default function UploadScreen() {
}

async function handleFileInput(event: React.ChangeEvent<HTMLInputElement>) {
if (!event.target.files) return;
if (!event.target.files) return [];

const files = Array.from(event.target.files) as Blob[];
if (files.length < 1) return;
if (files.length < 1) return [];

try {
const igcBlob = new IGCBlob(files);
return await igcBlob.toFlightGroup();
return await igcBlob.toFlightData();
} catch (e) {
await errorTracker.report(e);
return null;
return [];
}
}
10 changes: 4 additions & 6 deletions src/flight_analysis/igc_blob.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import IGCParser from "glana/src/igc/parser";
import FlightGroup from "glana/src/analysis/flight_group";
import { FlightDatum } from "./store/models/flight_datum";

export default class IGCBlob {
private blobs: Blob[];
Expand All @@ -8,7 +8,7 @@ export default class IGCBlob {
this.blobs = blobs;
}

async toFlightGroup() {
async toFlightData() {
let blobContents = await this.readBlobs(this.blobs);
return this.parseIGCs(blobContents);
}
Expand All @@ -29,12 +29,10 @@ export default class IGCBlob {
}

private parseIGCs(blobContents: string[]) {
let savedFlights = blobContents.map(contents => {
return blobContents.map(contents => {
let parser = new IGCParser();
const flight = parser.parse(contents);
return flight;
return new FlightDatum(flight);
});

return new FlightGroup(savedFlights);
}
}
19 changes: 14 additions & 5 deletions src/flight_analysis/maps/flight_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Map, LngLatBounds, GeoJSONSource } from "mapbox-gl";
import ReactDOMServer from "react-dom/server";
import { glider } from "../../ui/components/icon/icons";
import { chunk, splitWhen } from "../../utils/arrays";
import { FlightDatum } from "../store/reducer";
import { FlightDatum } from "../store/models/flight_datum";
import { Z_INDEX_2, Z_INDEX_3 } from "./renderer";

const MAX_SEGMENT_SIZE = 200;
Expand Down Expand Up @@ -38,6 +38,7 @@ export default class FlightRenderer {
private timestamp: Date;
private marker: mapboxgl.Marker;
private trackSegments: TrackSegment[];
private isDestroyed: boolean = false;

flightDatum: FlightDatum;
currentPosition: LngLatLike;
Expand All @@ -46,8 +47,8 @@ export default class FlightRenderer {
this.map = map;
this.flightDatum = flightDatum;
this.timestamp = flightDatum.flight.getRecordingStartedAt();
this.sourceId = `source-${this.flightDatum.id}`;
this.layerId = `layer-${this.flightDatum.id}`;
this.sourceId = `source-flight-${this.flightDatum.id}`;
this.layerId = `layer-flight-${this.flightDatum.id}`;
this.trackSegments = this.buildTrackSegments(flightDatum);
this.geoJSON = this.buildGeoJSON(this.trackSegments);
this.bounds = this.calculateBounds(this.geoJSON);
Expand Down Expand Up @@ -168,6 +169,10 @@ export default class FlightRenderer {
}

destroy() {
if (this.isDestroyed) return;

this.isDestroyed = true;

this.marker.remove();
this.map.removeLayer(this.layerId);
this.map.removeSource(this.sourceId);
Expand All @@ -178,6 +183,8 @@ export default class FlightRenderer {
}

setActive(isActive: boolean) {
if (this.isDestroyed) return;

this.isActive = isActive;
this.setSourceOpacity();
this.setLayerZIndex();
Expand All @@ -188,7 +195,7 @@ export default class FlightRenderer {
this.geoJSON.features.forEach(f => {
f.properties.opacity = this.opacity;
});
source.setData(this.geoJSON);
source && source.setData(this.geoJSON);
}

private setLayerZIndex() {
Expand All @@ -209,6 +216,8 @@ export default class FlightRenderer {
}

setTime(timestamp: Date) {
if (this.isDestroyed) return;

const datumIdx = this.flightDatum.flight.datumIndexAt(timestamp);
const datum = this.flightDatum.flight.datums[datumIdx];
this.currentPosition = this.positionToGeoJSON(datum.position);
Expand Down Expand Up @@ -244,7 +253,7 @@ export default class FlightRenderer {
});

const source = this.map.getSource(this.sourceId) as GeoJSONSource;
source.setData(this.geoJSON);
source && source.setData(this.geoJSON);
}

private updateMarker(datum: Datum) {
Expand Down
3 changes: 2 additions & 1 deletion src/flight_analysis/maps/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Task from "glana/src/flight_computer/tasks/task";
import mapboxgl, { Map, LngLatBounds } from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import { isProduction } from "../../utils/environment";
import { FlightDatum } from "../store/reducer";
import { FlightDatum } from "../store/models/flight_datum";
import FlightRenderer from "./flight_renderer";
import { AIRSPACE_LAYERS, AIRSPACE_SOURCE } from "./opeanaip_layers";
import TaskRenderer from "./task_renderer";
Expand Down Expand Up @@ -296,6 +296,7 @@ export default class Renderer {
const flightRenderer = this.flightRenderers[flightDatum.id];
if (!flightRenderer) return;
flightRenderer.destroy();
delete this.flightRenderers[flightDatum.id];

console.log(`Flight removed id=${flightDatum.id}`);
}
Expand Down
15 changes: 7 additions & 8 deletions src/flight_analysis/store/actions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import FlightGroup from "glana/src/analysis/flight_group";
import { Duration, milliseconds } from "glana/src/units/duration";
import Quantity from "glana/src/units/quantity";
import analytics from "../../analytics";
import { Settings } from "../settings";
import { FlightDatum } from "./reducer";
import { FlightDatum } from "./models/flight_datum";

export enum ActionType {
SetFlightGroup = "SET_FLIGHT_GROUP",
SetFlightData = "SET_FLIGHT_DATA",
SetActiveTimestamp = "SET_ACTIVE_TIMESTAMP",
TogglePlay = "TOGGLE_PLAY",
ToggleFlights = "TOGGLE_FLIGHTS",
Expand All @@ -20,7 +19,7 @@ export enum ActionType {
}

export const actions = {
setFlightGroup,
setFlightData,
setActiveTimestamp,
advanceActiveTimestamp,
togglePlay,
Expand All @@ -43,13 +42,13 @@ type ElementType<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<

export type Action = ReturnType<ElementType<typeof actionFns>>;

function setFlightGroup(flightGroup: FlightGroup) {
function setFlightData(flightData: FlightDatum[]) {
analytics.trackEvent("loaded_flights", {
count: flightGroup.flights.length
count: flightData.length
});
return {
type: ActionType.SetFlightGroup as ActionType.SetFlightGroup,
flightGroup: flightGroup
type: ActionType.SetFlightData as ActionType.SetFlightData,
flightData: flightData
};
}

Expand Down
11 changes: 6 additions & 5 deletions src/flight_analysis/store/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ import React, { ReactNode, useContext, useEffect, useReducer } from "react";
import { initialState, reducer } from "./reducer";
import { Action, actions } from "./actions";
import FlightGroup from "glana/src/analysis/flight_group";
import { FlightDatum } from "./models/flight_datum";

const StateContext = React.createContext(initialState());
const DispatchContent = React.createContext<React.Dispatch<Action>>(() => {});

interface ProviderProps {
flightGroup?: FlightGroup;
flightData?: FlightDatum[];
children: ReactNode;
}

export function StoreProvider(props: ProviderProps) {
const { flightGroup } = props;
const { flightData } = props;
const [state, dispatch] = useReducer(reducer, initialState());
useEffect(() => {
if (flightGroup) {
dispatch(actions.setFlightGroup(flightGroup));
if (flightData) {
dispatch(actions.setFlightData(flightData));
} else {
dispatch(actions.showFlightUploader());
}
Expand All @@ -27,7 +28,7 @@ export function StoreProvider(props: ProviderProps) {
setDebug: (isDebug: boolean) => dispatch(actions.setDebug(isDebug))
};
}
}, [flightGroup]);
}, [flightData]);
return (
<DispatchContent.Provider value={dispatch}>
<StateContext.Provider value={state}>
Expand Down
Loading

1 comment on commit ee0d7c1

@vercel
Copy link

@vercel vercel bot commented on ee0d7c1 Jul 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.