Skip to content
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

feat(a380x/mfd): Add DATA/WAYPOINT page #9786

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
1 change: 1 addition & 0 deletions fbw-a32nx/src/systems/fmgc/src/FmsError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum FmsErrorType {
NotInDatabase,
NotYetImplemented,
FormatError,
FplnElementRetained,
EntryOutOfRange,
ListOf99InUse,
AwyWptMismatch,
Expand Down
8 changes: 8 additions & 0 deletions fbw-a32nx/src/systems/fmgc/src/flightplanning/DataManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,4 +366,12 @@ export class DataManager {
getStoredWaypointsByIdent(ident: string): PilotWaypoint[] {
return this.storedWaypoints.filter((wp) => wp && wp.waypoint.ident === ident);
}

getAllStoredWaypoints(): PilotWaypoint[] {
if (this.storedWaypoints === undefined) {
return [];
} else {
return this.storedWaypoints;
}
}
Comment on lines +370 to +376
Copy link
Member

@tracernz tracernz Feb 15, 2025

Choose a reason for hiding this comment

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

Suggested change
getAllStoredWaypoints(): PilotWaypoint[] {
if (this.storedWaypoints === undefined) {
return [];
} else {
return this.storedWaypoints;
}
}
getAllStoredWaypoints(): PilotWaypoint[] {
return this.storedWaypoints ?? [];
}

Could be better to just return undefined though rather than allocating an array every call when it's empty.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmmm, I dislike returning undefined for elements like this. This shouldn't be called that often so I feel like the allocation is not going to be that bad (this may be C/Rust brain thinking where an allocation for that slice would be tiny)

Copy link
Member

Choose a reason for hiding this comment

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

Then why not have this.storedWaypoints always an array instead? If this is being called every time the page updates it's allocating quite a lot of unneeded objects across a flight. It's death by a thousand cuts with GC pauses = microstutter.

Copy link
Contributor Author

@Slightlyclueless Slightlyclueless Feb 21, 2025

Choose a reason for hiding this comment

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

Then why not have this.storedWaypoints always an array instead

Just checked, it already is (Line 61). So this function doesn't need any nullish coaliescing at all, it can just return the array

}
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,9 @@ export class FlightManagementComputer implements FmcInterface {
case FmsErrorType.FormatError:
this.addMessageToQueue(NXSystemMessages.formatError, undefined, undefined);
break;
case FmsErrorType.FplnElementRetained:
this.addMessageToQueue(NXSystemMessages.fplnElementRetained, undefined, undefined);
break;
case FmsErrorType.NotInDatabase:
this.addMessageToQueue(NXSystemMessages.notInDatabase, undefined, undefined);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { MfdSurvControls } from 'instruments/src/MFD/pages/SURV/MfdSurvControls'
import { MfdFmsFplnFixInfo } from './pages/FMS/F-PLN/MfdFmsFplnFixInfo';
import { MfdSurvStatusSwitching } from 'instruments/src/MFD/pages/SURV/MfdSurvStatusSwitching';
import { MfdFmsDataAirport } from 'instruments/src/MFD/pages/FMS/DATA/MfdFmsDataAirport';
import { MfdFmsDataWaypoint } from 'instruments/src/MFD/pages/FMS/DATA/MfdFmsDataWaypoint';

export function pageForUrl(
url: string,
Expand Down Expand Up @@ -96,6 +97,8 @@ export function pageForUrl(
return <MfdFmsPositionNavaids pageTitle="NAVAIDS" bus={bus} mfd={mfd} fmcService={fmcService} />;
case 'fms/data/status':
return <MfdFmsDataStatus pageTitle="STATUS" bus={bus} mfd={mfd} fmcService={fmcService} />;
case 'fms/data/waypoint':
return <MfdFmsDataWaypoint pageTitle="WAYPOINT" bus={bus} mfd={mfd} fmcService={fmcService} />;
case 'fms/data/debug':
return <MfdFmsDataDebug pageTitle="DEBUG" bus={bus} mfd={mfd} fmcService={fmcService} />;
case 'fms/data/airport':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@import "../../../../MsfsAvionicsCommon/definitions";

.mfd-data-waypoint-input-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}

.mfd-data-waypoint-info {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 150px;
}

.mfd-data-latlong-text {
display: flex;
flex-direction: row;
}

.mfd-data-waypoint-stored-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 45px;
}

.mfd-data-waypoint-stored-ident-row {
display: flex;
flex-direction: row;
}

.mfd-data-waypoint-list-container {
display: flex;
direction: row;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { ArraySubject, FSComponent, Subject, VNode } from '@microsoft/msfs-sdk';
import { AbstractMfdPageProps } from 'instruments/src/MFD/MFD';
import { WaypointFormat } from 'instruments/src/MFD/pages/common/DataEntryFormats';
import { FmsPage } from 'instruments/src/MFD/pages/common/FmsPage';
import { InputField } from 'instruments/src/MsfsAvionicsCommon/UiWidgets/InputField';
import { TopTabNavigator, TopTabNavigatorPage } from 'instruments/src/MsfsAvionicsCommon/UiWidgets/TopTabNavigator';

import './MfdFmsDataWaypoint.scss';
import { ConditionalComponent } from 'instruments/src/MsfsAvionicsCommon/UiWidgets/ConditionalComponent';
import { coordinateToString } from '@flybywiresim/fbw-sdk';
import { Footer } from 'instruments/src/MFD/pages/common/Footer';
import { DropdownMenu } from 'instruments/src/MsfsAvionicsCommon/UiWidgets/DropdownMenu';
import { IconButton } from 'instruments/src/MsfsAvionicsCommon/UiWidgets/IconButton';
import { PilotWaypoint } from '@fmgc/flightplanning/DataManager';
import { NavigationDatabaseService } from '@fmgc/flightplanning/NavigationDatabaseService';

interface MfdFmsDataWaypointProps extends AbstractMfdPageProps {}

export class MfdFmsDataWaypoint extends FmsPage<MfdFmsDataWaypointProps> {
private readonly selectedPageIndex = Subject.create<number>(0);
private readonly pilotStoredWaypointsIndex = Subject.create<number | null>(0);

private readonly waypointIdent = Subject.create<string | null>(null);

private readonly waypointCoords = Subject.create('');

private readonly pilotStoredWaypointsList = ArraySubject.create<PilotWaypoint>([]);
private readonly pilotStoredWaypointNames = ArraySubject.create<string>([]);

private readonly disabledScrollLeft = Subject.create(true);
private readonly disabledScrollRight = Subject.create(false);

private readonly db = NavigationDatabaseService.activeDatabase;

private readonly isWaypointDataVisible = Subject.create<boolean>(false);
private readonly anyPilotStoredWaypoints = Subject.create<boolean>(false);

protected onNewData(): void {
const pilotStoredWaypoints = this.props.fmcService.master?.getDataManager()?.getAllStoredWaypoints() ?? [];

this.anyPilotStoredWaypoints.set(pilotStoredWaypoints.length > 0);
this.pilotStoredWaypointsList.set(pilotStoredWaypoints);
}

private async loadWaypoint(ident: string | null) {
if (ident) {
const waypoint = await this.db.searchWaypoint(ident);
const selectedWaypoint = await this.props.mfd.deduplicateFacilities(waypoint);

if (selectedWaypoint) {
const waypointCoords = coordinateToString(selectedWaypoint.location.lat, selectedWaypoint.location.long, false);
this.waypointCoords.set(waypointCoords);
}
}
}

private scrollStoredWaypointButtons(direction: 'left' | 'right'): void {
const currentIndex = this.pilotStoredWaypointsIndex.get() ?? 0;

this.pilotStoredWaypointsIndex.set(currentIndex + (direction === 'left' ? -1 : 1));
}

public onAfterRender(node: VNode): void {
super.onAfterRender(node);
this.waypointIdent.sub((ident: string | null) => {
if (ident) {
this.loadWaypoint(ident);
this.isWaypointDataVisible.set(true);
} else {
this.isWaypointDataVisible.set(false);
}
});

this.subs.push(
this.props.mfd.uiService.activeUri.sub((val) => {
if (val.extra === 'database') {
this.selectedPageIndex.set(0);
} else if (val.extra === 'pilot-stored') {
this.selectedPageIndex.set(1);
}
}, true),
);
}

public render(): VNode {
return (
<>
{super.render()}
<div class="mfd-page-container">
<TopTabNavigator
pageTitles={Subject.create(['DATABASE WPTs', 'PILOT STORED WPTs'])}
selectedPageIndex={this.selectedPageIndex}
pageChangeCallback={(val) => {
this.selectedPageIndex.set(val);
}}
selectedTabTextColor="white"
tabBarSlantedEdgeAngle={25}
>
<TopTabNavigatorPage containerStyle="height: 680px;">
<div class="mfd-data-waypoint-input-container">
<div class="mfd-label" style="position: relative; right: 65px">
WPT IDENT
</div>
<InputField<string>
dataEntryFormat={new WaypointFormat()}
dataHandlerDuringValidation={async (v) => this.waypointIdent.set(v)}
mandatory={Subject.create(true)}
canBeCleared={Subject.create(false)}
value={this.waypointIdent}
alignText="center"
errorHandler={(e) => this.props.fmcService.master?.showFmsErrorMessage(e)}
hEventConsumer={this.props.mfd.hEventConsumer}
interactionMode={this.props.mfd.interactionMode}
containerStyle="position: relative; right: 60px;"
/>
</div>

<div class="mfd-data-waypoint-info-container">
<ConditionalComponent
condition={this.isWaypointDataVisible}
componentIfTrue={
<div class="mfd-data-waypoint-info">
<div class="mfd-data-latlong-text">
<div class="mfd-label" style="position: relative; right: 75px;">
LAT
</div>
<div class="mfd-label" style="position: relative; left: 75px;">
LONG
</div>
</div>
<div class="mfd-value bigger mfd-data-airport-coords">{this.waypointCoords}</div>
</div>
}
componentIfFalse={<></>}
/>
</div>
</TopTabNavigatorPage>
<TopTabNavigatorPage containerStyle="height: 680px;">
<ConditionalComponent
condition={this.anyPilotStoredWaypoints}
componentIfTrue={
<div class="mfd-data-waypoint-list-container">
<div class="mfd-label" style="align-self:center; position: relative; top: 45px;">
WPT IDENT
</div>
<DropdownMenu
values={this.pilotStoredWaypointNames}
selectedIndex={this.pilotStoredWaypointsIndex}
freeTextAllowed={false}
idPrefix={`${this.props.mfd.uiService.captOrFo}_MFD_dataWaypointListDropdown`}
hEventConsumer={this.props.mfd.hEventConsumer}
interactionMode={this.props.mfd.interactionMode}
/>
<div class="mfd-data-waypoint-stored-list-scroll-buttons">
<IconButton
icon="double-left"
onClick={() => this.scrollStoredWaypointButtons('left')}
disabled={this.disabledScrollLeft}
containerStyle="width: 50px; height: 50px; margin-right: 5px"
/>
<IconButton
icon="double-right"
onClick={() => this.scrollStoredWaypointButtons('right')}
disabled={this.disabledScrollRight}
containerStyle="width: 50px; height: 50px;"
/>
</div>
<div class="mfd-label">{this.pilotStoredWaypointsList.length.toString().padStart(2, '0')}</div>
<div class="mfd-data-waypoint-stored-ll-display">
<div class="mfd-lable">LAT</div>
<div class="mfd-lable">LAT</div>
</div>
</div>
}
componentIfFalse={
<div class="mfd-data-waypoint-stored-container">
<div class="mfd-data-waypoint-stored-ident-row">
<div class="mfd-label" style="position: relative;">
NO PILOT STORED WPT
</div>
</div>
</div>
}
></ConditionalComponent>
</TopTabNavigatorPage>
</TopTabNavigator>
<div style="flex-grow: 1;" />
{/* fill space vertically */}
</div>
<Footer bus={this.props.bus} mfd={this.props.mfd} fmcService={this.props.fmcService} />
</>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class FmsHeader extends AbstractHeader {
menuItems={[
{ label: 'STATUS', action: () => this.props.uiService.navigateTo('fms/data/status') },
{ label: 'DEBUG', action: () => this.props.uiService.navigateTo('fms/data/debug') },
{ label: 'WAYPOINT', action: () => this.props.uiService.navigateTo('fms/data/waypoint'), disabled: true },
{ label: 'WAYPOINT', action: () => this.props.uiService.navigateTo('fms/data/waypoint') },
{ label: 'NAVAID', action: () => this.props.uiService.navigateTo('fms/data/navaid'), disabled: true },
{ label: 'ROUTE', action: () => this.props.uiService.navigateTo('fms/data/route'), disabled: true },
{ label: 'AIRPORT', action: () => this.props.uiService.navigateTo('fms/data/airport') },
Expand Down