Skip to content

Commit

Permalink
feat: fms-v2 msfs sync (#8508)
Browse files Browse the repository at this point in the history
* add intial sync with all todos and hacks

* serialize dest and origin airports

* fix: guard origin and destination airport in serialization

* fix: waypoint insertion, add extra module for sync

* remove unncecessary change

* use more granular sync events

* some cleanup

* feat: sync flight plan from game

* fix some errors

* more log

* fix: export FlightPlanRpcServer

* fix: mapping

* fix: delay init until ingame

* use native fms2 sync

* fix: missing airport and runway in sync

* Revert "fix: missing airport and runway in sync"

This reverts commit 0d87e5a.

* Revert "use native fms2 sync"

This reverts commit b9f2f1f.

* fix: sync confusion

* small improvement

* enable basic load from sim

* fix: rpcclient creation

* only use rpc client for initial load

* fix: datastore subscription

* fix: enroute waypoint insertion
  • Loading branch information
Saschl authored Mar 18, 2024
1 parent 20c9d31 commit 5683499
Show file tree
Hide file tree
Showing 8 changed files with 404 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ class NavSystem extends BaseInstrument {

if (this.nodeName.includes('CDU')) {
this.currFlightPhaseManager = Fmgc.getFlightPhaseManager();
const bus = new Fmgc.EventBus();
this.currFlightPlanService = new Fmgc.FlightPlanService(bus, new Fmgc.A320FlightPlanPerformanceData());
this.rpcServer = new Fmgc.FlightPlanRpcServer(bus, this.currFlightPlanService);

this.currFlightPlanService = new Fmgc.FlightPlanService(new Fmgc.EventBus(), new Fmgc.A320FlightPlanPerformanceData());
this.currFlightPlanService.createFlightPlans();

this.currNavigationDatabaseService = Fmgc.NavigationDatabaseService;
Expand Down
8 changes: 7 additions & 1 deletion fbw-a32nx/src/systems/extras-host/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable no-await-in-loop */
// Copyright (c) 2022 FlyByWire Simulations
// SPDX-License-Identifier: GPL-3.0

import { EventBus, HEventPublisher } from '@microsoft/msfs-sdk';
import { NotificationManager } from '@flybywiresim/fbw-sdk';
import { ExtrasSimVarPublisher } from 'extras-host/modules/common/ExtrasSimVarPublisher';
import { PushbuttonCheck } from 'extras-host/modules/pushbutton_check/PushbuttonCheck';
import { FlightPlanAsoboSync } from 'extras-host/modules/flightplan_sync/FlightPlanAsoboSync';
import { KeyInterceptor } from './modules/key_interceptor/KeyInterceptor';
import { VersionCheck } from './modules/version_check/VersionCheck';
import { FlightPlanTest } from './modules/flight_plan_test/FlightPlanTest';
Expand Down Expand Up @@ -43,6 +45,8 @@ class ExtrasHost extends BaseInstrument {

private readonly flightPlanTest: FlightPlanTest;

private readonly flightPlanAsoboSync: FlightPlanAsoboSync;

/**
* "mainmenu" = 0
* "loading" = 1
Expand All @@ -63,7 +67,7 @@ class ExtrasHost extends BaseInstrument {
this.pushbuttonCheck = new PushbuttonCheck(this.bus, this.notificationManager);
this.versionCheck = new VersionCheck(this.bus);
this.keyInterceptor = new KeyInterceptor(this.bus, this.notificationManager);
this.flightPlanTest = new FlightPlanTest(this.bus);
this.flightPlanAsoboSync = new FlightPlanAsoboSync(this.bus);

console.log('A32NX_EXTRASHOST: Created');
}
Expand All @@ -86,6 +90,7 @@ class ExtrasHost extends BaseInstrument {
this.pushbuttonCheck.connectedCallback();
this.versionCheck.connectedCallback();
this.keyInterceptor.connectedCallback();
this.flightPlanAsoboSync.connectedCallback();
}

public Update(): void {
Expand All @@ -98,6 +103,7 @@ class ExtrasHost extends BaseInstrument {
this.versionCheck.startPublish();
this.keyInterceptor.startPublish();
this.simVarPublisher.startPublish();
this.flightPlanAsoboSync.init();
}
this.gameState = gs;
} else {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// SPDX-License-Identifier: GPL-3.0

import { FlightPlan } from '@fmgc/flightplanning/new/plans/FlightPlan';
import { EventBus, Publisher } from '@microsoft/msfs-sdk';
import { EventBus, Publisher, Subscribable, Subscription } from '@microsoft/msfs-sdk';
import {
FlightPlanEvents,
FlightPlanSyncResponsePacket, PerformanceDataFlightPlanSyncEvents,
Expand All @@ -25,16 +25,23 @@ export class FlightPlanManager<P extends FlightPlanPerformanceData> {

private ignoreSync = false;

private subs: Subscription[] = [];

public destroy() {
this.subs.forEach((sub) => sub.destroy());
}

constructor(
private readonly bus: EventBus,
private readonly performanceDataInit: P,
private readonly syncClientID: number,
private readonly master: boolean,
) {
const subs = bus.getSubscriber<FlightPlanEvents>();
const sub = bus.getSubscriber<FlightPlanEvents>();

subs.on('flightPlanManager.syncRequest').handle(() => {
if (!this.ignoreSync) {
this.subs.push(sub.on('flightPlanManager.syncRequest').handle(() => {
// TODO clarify, I guess only one instance should reply to this
if (!this.ignoreSync && this.master) {
console.log('[FpmSync] SyncRequest()');

const plansRecord: Record<number, SerializedFlightPlan> = {};
Expand All @@ -49,9 +56,9 @@ export class FlightPlanManager<P extends FlightPlanPerformanceData> {

this.sendEvent('flightPlanManager.syncResponse', response);
}
});
}));

subs.on('flightPlanManager.syncResponse').handle((event) => {
this.subs.push(sub.on('flightPlanManager.syncResponse').handle((event) => {
if (!this.ignoreSync) {
console.log('[FpmSync] SyncResponse()');

Expand All @@ -63,44 +70,44 @@ export class FlightPlanManager<P extends FlightPlanPerformanceData> {
this.set(intIndex, newPlan);
}
}
});
}));

subs.on('flightPlanManager.create').handle((event) => {
this.subs.push(sub.on('flightPlanManager.create').handle((event) => {
if (!this.ignoreSync) {
console.log(`[FpmSync] Create(${event.planIndex})`);
this.create(event.planIndex, false);
}
});
}));

subs.on('flightPlanManager.delete').handle((event) => {
this.subs.push(sub.on('flightPlanManager.delete').handle((event) => {
if (!this.ignoreSync) {
console.log(`[FpmSync] Delete(${event.planIndex})`);
this.delete(event.planIndex, false);
}
});
}));

subs.on('flightPlanManager.deleteAll').handle(() => {
this.subs.push(sub.on('flightPlanManager.deleteAll').handle(() => {
if (!this.ignoreSync) {
console.log('[FpmSync] DeleteAll');
this.deleteAll(false);
}
});
}));

subs.on('flightPlanManager.copy').handle((event) => {
this.subs.push(sub.on('flightPlanManager.copy').handle((event) => {
if (!this.ignoreSync) {
console.log(`[FpmSync] Copy(${event.planIndex}, ${event.targetPlanIndex})`);
this.copy(event.planIndex, event.targetPlanIndex, event.options, false);
}
});
}));

subs.on('flightPlanManager.swap').handle((event) => {
this.subs.push(sub.on('flightPlanManager.swap').handle((event) => {
if (!this.ignoreSync) {
console.log(`[FpmSync] Swap(${event.planIndex}, ${event.targetPlanIndex})`);
this.swap(event.planIndex, event.targetPlanIndex, false);
}
});
}));

if (!master) {
if (!this.master) {
setTimeout(() => this.sendEvent('flightPlanManager.syncRequest', undefined), 5_000);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2236,6 +2236,12 @@ export abstract class BaseFlightPlan<P extends FlightPlanPerformanceData = Fligh

performanceData: this instanceof FlightPlan ? this.performanceData.serialize() : undefined,

destinationAirport: this.destinationSegment?.destinationAirport?.ident ?? '',

originAirport: this.originSegment?.originAirport?.ident ?? '',
originRunway: this.originRunway?.ident ?? '',
destinationRunway: this.destinationRunway?.ident ?? '',

segments: {
originSegment: this.originSegment.serialize(),
departureRunwayTransitionSegment: this.departureRunwayTransitionSegment.serialize(),
Expand Down Expand Up @@ -2300,6 +2306,11 @@ export interface SerializedFlightPlan {

performanceData?: SerializedFlightPlanPerformanceData,

originAirport: string,
originRunway: string,
destinationAirport: string,
destinationRunway: string,

segments: {
originSegment: SerializedFlightPlanSegment,
departureRunwayTransitionSegment: SerializedFlightPlanSegment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
import { FlightPlanInterface } from '@fmgc/flightplanning/new/FlightPlanInterface';
import { Fix, Waypoint } from '@flybywiresim/fbw-sdk';
import { FlightPlanIndex, FlightPlanManager } from '@fmgc/flightplanning/new/FlightPlanManager';
import { EventBus } from '@microsoft/msfs-sdk';
import { EventBus, Subscription } from '@microsoft/msfs-sdk';
import { v4 } from 'uuid';
import { HoldData } from '@fmgc/flightplanning/data/flightplan';
import { Coordinates } from '@fmgc/flightplanning/data/geo';
import { AltitudeConstraint } from '@fmgc/flightplanning/data/constraint';
import { FlightPlanPerformanceData } from '@fmgc/flightplanning/new/plans/performance/FlightPlanPerformanceData';
import { FlightPlanServerRpcEvents } from '@fmgc/flightplanning/new/rpc/FlightPlanRpcServer';
import { FlightPlanLegDefinition } from '../legs/FlightPlanLegDefinition';
import { FixInfoEntry } from '../plans/FixInfo';
import { FlightPlan } from '../plans/FlightPlan';
Expand All @@ -25,26 +26,40 @@ export interface FlightPlanRemoteClientRpcEvents<P extends FlightPlanPerformance
}

export class FlightPlanRpcClient<P extends FlightPlanPerformanceData> implements FlightPlanInterface<P> {
constructor(private readonly bus: EventBus) {
private subs: Subscription[] = [];

constructor(private readonly bus: EventBus, private readonly performanceDataInit: P) {
this.subs.push(this.sub.on('flightPlanServer_rpcCommandResponse').handle(([responseId, response]) => {
if (this.rpcCommandsSent.has(responseId)) {
const [resolve] = this.rpcCommandsSent.get(responseId) ?? [];

if (resolve) {
resolve(response);
this.rpcCommandsSent.delete(responseId);
}
}
}));
}

private readonly flightPlanManager = new FlightPlanManager<P>(
this.bus,
{} as P /* This flight plan manager will never create plans, so this is fine */,
this.performanceDataInit as P /* This flight plan manager will never create plans, so this is fine */,
Math.round(Math.random() * 10_000),
false,
);

private readonly pub = this.bus.getPublisher<FlightPlanRemoteClientRpcEvents<P>>();

private readonly sub = this.bus.getSubscriber<FlightPlanServerRpcEvents>();

private rpcCommandsSent = new Map<string, [PromiseFn, PromiseFn]>();

private async callFunctionViaRpc<T extends keyof FunctionsOnlyAndUnwrapPromises<FlightPlanInterface<P>> & string>(
funcName: T, ...args: Parameters<FunctionsOnlyAndUnwrapPromises<FlightPlanInterface<P>>[T]>
): Promise<ReturnType<FunctionsOnlyAndUnwrapPromises<FlightPlanInterface<P>>[T]>> {
const id = v4();

this.pub.pub('flightPlanRemoteClient_rpcCommand', [funcName, id, ...args]);
this.pub.pub('flightPlanRemoteClient_rpcCommand', [funcName, id, ...args], true);

const result = await this.waitForRpcCommandResponse<ReturnType<FunctionsOnlyAndUnwrapPromises<FlightPlanInterface<P>>[T]>>(id);

Expand All @@ -54,9 +69,20 @@ export class FlightPlanRpcClient<P extends FlightPlanPerformanceData> implements
private waitForRpcCommandResponse<T>(id: string): Promise<T> {
return new Promise((resolve, reject) => {
this.rpcCommandsSent.set(id, [resolve, reject]);
setTimeout(() => {
if (this.rpcCommandsSent.has(id)) {
this.rpcCommandsSent.delete(id);
reject(new Error(`Timeout waiting for response from server for request ${id}`));
}
}, 5000);
});
}

public destroy() {
this.flightPlanManager.destroy();
this.subs.forEach((sub) => sub.destroy());
}

get(index: number): FlightPlan<P> {
return this.flightPlanManager.get(index);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ export class FlightPlanRpcServer<P extends FlightPlanPerformanceData = FlightPla
private readonly pub = this.bus.getPublisher<FlightPlanServerRpcEvents>();

private async handleRpcCommand(command: string, id: string, ...args: any): Promise<void> {
console.log('Handling RPC command', command, id, args);
const returnValue = await this.localFlightPlanService[command](...args as any[]);

await this.respondToRpcCommand(id, returnValue);
}

private async respondToRpcCommand(id: string, response: any): Promise<void> {
this.pub.pub('flightPlanServer_rpcCommandResponse', [id, response]);
this.pub.pub('flightPlanServer_rpcCommandResponse', [id, response], true);
}
}
2 changes: 2 additions & 0 deletions fbw-a32nx/src/systems/fmgc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { DataManager } from '@fmgc/flightplanning/new/DataManager';
import { CoRouteUplinkAdapter } from '@fmgc/flightplanning/new/uplink/CoRouteUplinkAdapter';
import { EfisInterface } from '@fmgc/efis/EfisInterface';
import { EventBus } from '@microsoft/msfs-sdk';
import { FlightPlanRpcServer } from '@fmgc/flightplanning/new/rpc/FlightPlanRpcServer';
import { FlightPlanService } from './flightplanning/new/FlightPlanService';
import { NavigationDatabase, NavigationDatabaseBackend } from './NavigationDatabase';
import { FlightPhaseManager, getFlightPhaseManager } from './flightphase';
Expand Down Expand Up @@ -35,6 +36,7 @@ export {
RunwayUtils,
ApproachType,
FlightPlanService,
FlightPlanRpcServer,
A320FlightPlanPerformanceData,
NavigationDatabase,
NavigationDatabaseBackend,
Expand Down

0 comments on commit 5683499

Please sign in to comment.