Skip to content

Commit

Permalink
Hook up to UserSettings API
Browse files Browse the repository at this point in the history
  • Loading branch information
BlueberryKing committed Feb 24, 2025
1 parent 4081e0f commit 2da1e48
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 129 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ import { FlightPlanIndex } from '@fmgc/flightplanning/FlightPlanManager';
import { initComponents, updateComponents } from '@fmgc/components';
import { CoRouteUplinkAdapter } from '@fmgc/flightplanning/uplink/CoRouteUplinkAdapter';
import { WaypointEntryUtils } from '@fmgc/flightplanning/WaypointEntryUtils';
import { AmiDatabase } from '@shared/a32nx_ami';

export abstract class FMCMainDisplay implements FmsDataInterface, FmsDisplayInterface, Fmgc {
private static DEBUG_INSTANCE: FMCMainDisplay;
Expand Down Expand Up @@ -313,8 +312,6 @@ export abstract class FMCMainDisplay implements FmsDataInterface, FmsDisplayInte

this.currFlightPlanService.createFlightPlans();
this.currNavigationDatabaseService.activeDatabase = this.navigationDatabase;

AmiDatabase.pubToBus(this.bus);
}

public get flightPhaseManager() {
Expand Down
22 changes: 20 additions & 2 deletions fbw-a32nx/src/systems/shared/src/a32nx_ami.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { AirlineModifiableInformation, AirlineModifiableInformationDatabase } from '@flybywiresim/fbw-sdk';
import {
AirlineModifiableInformation,
AirlineModifiableInformationDatabase,
DefaultValuesAmiDatabaseLayer,
LiveryAmiDatabaseLayer,
UserSettingsAmiDatabaseLayer,
} from '@flybywiresim/fbw-sdk';
import { DefaultUserSettingManager, EventBus } from '@microsoft/msfs-sdk';

const defaultAmiData: AirlineModifiableInformation = {
perfFactor: 0, // %
Expand All @@ -17,4 +24,15 @@ const defaultAmiData: AirlineModifiableInformation = {
finalDest: 'A', // P or A
};

export const AmiDatabase = new AirlineModifiableInformationDatabase(defaultAmiData);
/** This will probably come from persistence.ts when NXDataStore is gone */
const bus = new EventBus();
const userSettings = new DefaultUserSettingManager<AirlineModifiableInformation>(
bus,
Object.entries(defaultAmiData).map(([key, value]) => ({ name: key, defaultValue: value })),
);

export const AmiDatabase = new AirlineModifiableInformationDatabase(
new UserSettingsAmiDatabaseLayer(userSettings),
new LiveryAmiDatabaseLayer(),
new DefaultValuesAmiDatabaseLayer(defaultAmiData),
);
152 changes: 28 additions & 124 deletions fbw-common/src/systems/shared/src/ami.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EventBus, ObjectSubject, ObjectSubjectHandler, Subscription } from '@microsoft/msfs-sdk';
import { UserSettingManager } from '@microsoft/msfs-sdk';

export interface AirlineModifiableInformation {
export type AirlineModifiableInformation = {
/**
* %, performance factor
*/
Expand Down Expand Up @@ -57,150 +57,54 @@ export interface AirlineModifiableInformation {
* P or A, Final holding pattern is flown at Primary or Alternate destination
*/
finalDest: 'A' | 'P';
}
};

type AmiKey = keyof AirlineModifiableInformation;
type AmiValue<T extends AmiKey> = AirlineModifiableInformation[T];

interface AmiReadAccess {
get<T extends AmiKey>(key: T): AmiValue<T>;
getAll(): Readonly<AirlineModifiableInformation>;
sub(
handler: ObjectSubjectHandler<AirlineModifiableInformation>,
initialNotify?: boolean,
paused?: boolean,
): Subscription;
interface OptionalAmiDatabaseLayer {
get<T extends AmiKey>(key: T): AmiValue<T> | undefined;
}

interface AmiReadWriteAccess extends AmiReadAccess {
set<T extends AmiKey>(key: T, value: AmiValue<T>): void;
interface AmiDatabaseLayer {
get<T extends AmiKey>(key: T): AmiValue<T>;
}

/**
* Represents the AMI database for an aircraft. Values can be fetched from the database directly, or subscribed to.
* Values can only be set from the livery, or through the EFB. In this case, the access has to be provided by the DB.
*
* @example
* // Create a new database
* const defaultAmiData: AirlineModifiableInformation = {
* perfFactor: 0,
* // ... more properties
* };
* const db = new AirlineModifiableInformationDatabase(defaultAmiData);
*
* // Fetch a value
* db.get('perfFactor');
*
* // Connect to an event bus within an instrument
* const bus = new EventBus()
* db.pubToBus(bus);
*
* const sub = bus.getSubscriber<AirlineModifiableInformation>();
* sub.on('perfCode').handle((value) => console.log('Received new perfCode:', value));
*
* // Setting values
*
* // Pass this to the EFB
* const efbAmi = db.getEfbAccess();
* efbAmi.set('perfFactor', 0.5);
*
* // Create a hook for the EFB
* const efbAmi = db.getEfbAccess();
*
* const useAmiValue = <T extends AmiKey>(key: T) => {
* return [
* efbAmi.get(key),
* (value: AmiValue<T>) => efbAmi.set(key, value),
* ] as const;
*
* const [perfFactor, setPerfFactor] = useAmiValue('perfFactor');
*/
export class AirlineModifiableInformationDatabase implements AmiReadAccess {
private readonly liveryRecords: AmiReadWriteAccess;

private readonly userRecords: AmiReadWriteAccess;

constructor(defaultRecords: AirlineModifiableInformation) {
const defaultDb = new AmiDatabaseDefaultValues(defaultRecords);

this.liveryRecords = new BaseAmiDatabaseReadWriteAccess(defaultDb);
this.userRecords = new BaseAmiDatabaseReadWriteAccess(this.liveryRecords);
}
export class AirlineModifiableInformationDatabase implements AmiDatabaseLayer {
constructor(
private readonly settings: AmiDatabaseLayer,
private readonly liveryRecords: OptionalAmiDatabaseLayer,
private readonly defaultRecods: AmiDatabaseLayer,
) {}

get<T extends AmiKey>(key: T): AmiValue<T> {
return this.userRecords.get(key);
}

getAll() {
return this.userRecords.getAll();
}

sub(handler: ObjectSubjectHandler<AirlineModifiableInformation>, initialNotify?: boolean, paused?: boolean) {
return this.userRecords.sub(handler, initialNotify, paused);
}

pubToBus(bus: EventBus) {
const pub = bus.getPublisher<AirlineModifiableInformation>();
return this.userRecords.sub((_, key, value) => pub.pub(key, value));
}

getLiveryAccess(): AmiReadWriteAccess {
return this.liveryRecords;
}

getEfbAccess(): AmiReadWriteAccess {
return this.userRecords;
return this.settings.get(key) ?? this.liveryRecords.get(key) ?? this.defaultRecods.get(key);
}
}

class AmiDatabaseDefaultValues implements AmiReadAccess {
private readonly records: ObjectSubject<AirlineModifiableInformation>;

constructor(private readonly defaultData: AirlineModifiableInformation) {
this.records = ObjectSubject.create(defaultData);
}

sub(handler: ObjectSubjectHandler<AirlineModifiableInformation>, initialNotify?: boolean, paused?: boolean) {
return this.records.sub(handler, initialNotify, paused);
}
export class UserSettingsAmiDatabaseLayer implements AmiDatabaseLayer {
constructor(private readonly userSettings: UserSettingManager<AirlineModifiableInformation>) {}

get<T extends AmiKey>(key: T): AmiValue<T> {
return this.defaultData[key];
return this.userSettings.getSetting(key).value;
}

getAll(): Readonly<AirlineModifiableInformation> {
return this.defaultData;
whenChanged(key: AmiKey) {
return this.userSettings.whenSettingChanged(key);
}
}

class BaseAmiDatabaseReadWriteAccess implements AmiReadWriteAccess {
private readonly records = ObjectSubject.create<Partial<AirlineModifiableInformation>>({});

private readonly output: ObjectSubject<AirlineModifiableInformation>;

private onRecordChanged: ObjectSubjectHandler<AirlineModifiableInformation> = (_, key) =>
this.output.set(key, this.records.get()[key] ?? this.fallback.get(key));

constructor(private readonly fallback: AmiReadAccess) {
this.output = ObjectSubject.create<AirlineModifiableInformation>({ ...fallback.getAll() });

this.records.sub(this.onRecordChanged);
this.fallback.sub(this.onRecordChanged);
}

get<T extends AmiKey>(key: T): AmiValue<T> {
return this.output.get()[key];
}

getAll() {
return this.output.get();
export class LiveryAmiDatabaseLayer implements OptionalAmiDatabaseLayer {
get<T extends AmiKey>(_key: T): AmiValue<T> {
// TODO Fetch from livery
return undefined;
}
}

set<T extends AmiKey>(key: T, value: AmiValue<T>) {
this.records.set(key, value);
}
export class DefaultValuesAmiDatabaseLayer implements OptionalAmiDatabaseLayer {
constructor(private readonly defaultValues: AirlineModifiableInformation) {}

sub(handler: ObjectSubjectHandler<AirlineModifiableInformation>, initialNotify?: boolean, paused?: boolean) {
return this.output.sub(handler, initialNotify, paused);
get<T extends AmiKey>(key: T): AmiValue<T> {
return this.defaultValues[key];
}
}

0 comments on commit 2da1e48

Please sign in to comment.