diff --git a/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Boarding.js b/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Boarding.js index aa1dfed8365..7805006cd30 100644 --- a/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Boarding.js +++ b/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_Boarding.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ function airplaneCanBoard() { const busDC2 = SimVar.GetSimVarValue("L:A32NX_ELEC_DC_2_BUS_IS_POWERED", "Bool"); const busDCHot1 = SimVar.GetSimVarValue("L:A32NX_ELEC_DC_HOT_1_BUS_IS_POWERED", "Bool"); @@ -28,6 +29,7 @@ class A32NX_Boarding { } async init() { + /* const inDeveloperState = SimVar.GetSimVarValue("L:A32NX_DEVELOPER_STATE", "Bool"); if (!inDeveloperState) { // Set default pax (0) @@ -36,12 +38,32 @@ class A32NX_Boarding { this.loadCargoZero(); this.loadCargoPayload(); } + */ } async fillPaxStation(station, paxToFill) { const pax = Math.min(paxToFill, station.seats); + const paxDiff = pax - station.pax; + const paxDelta = Math.abs(pax - station.pax); + + // bitwise SeatFlags implementation + if (paxDiff >= 0) { + console.log(`paxDiff positive, filling ${paxToFill} pax`); + // TODO FIXME: Fill from desired Flags + const toFill = station.desiredFlags.getFilledSeatIds(); + console.log('toFill', toFill.toString()); + station.activeFlags.fillSeats(paxDelta, toFill); + console.log('active flags', station.activeFlags.toString()); + } else { + console.log('paxDiff negative, emptying'); + station.activeFlags.emptyFilledSeats(paxDelta); + } + console.log(`setting L:${station.bitFlags} as ${station.activeFlags.toString()}`); + await SimVar.SetSimVarValue(`L:${station.bitFlags}`, "string", station.activeFlags.toString()); station.pax = pax; - await SimVar.SetSimVarValue(`L:${station.simVar}`, "Number", parseInt(pax)); + + // TODO REFACTOR: Change this + // await SimVar.SetSimVarValue(`L:${station.simVar}`, "Number", parseInt(pax)); } async fillCargoStation(station, loadToFill) { @@ -55,7 +77,10 @@ class A32NX_Boarding { async function fillStation(station, percent, paxToFill) { const pax = Math.min(Math.trunc(percent * paxToFill), station.seats); station.pax = pax; - await SimVar.SetSimVarValue(`L:${station.simVar}_DESIRED`, "Number", parseInt(pax)); + // SeatFlags implementation + station.activeFlags.setFlags(pax); + await SimVar.SetSimVarValue(`L:${station.bitFlags}_DESIRED`, "string", station.activeFlags.toString()); + // await SimVar.SetSimVarValue(`L:${station.simVar}_DESIRED`, "Number", parseInt(pax)); paxRemaining -= pax; } @@ -100,21 +125,31 @@ class A32NX_Boarding { return; } - const currentPax = Object.values(this.paxStations).map((station) => SimVar.GetSimVarValue(`L:${station.simVar}`, "Number")).reduce((acc, cur) => acc + cur); - const paxTarget = Object.values(this.paxStations).map((station) => SimVar.GetSimVarValue(`L:${station.simVar}_DESIRED`, "Number")).reduce((acc, cur) => acc + cur); - const currentLoad = Object.values(this.cargoStations).map((station) => SimVar.GetSimVarValue(`L:${station.simVar}`, "Number")).reduce((acc, cur) => acc + cur); - const loadTarget = Object.values(this.cargoStations).map((station) => SimVar.GetSimVarValue(`L:${station.simVar}_DESIRED`, "Number")).reduce((acc, cur) => acc + cur); - + // SeatFlags Implementation + let currentPax = 0; + let paxTarget = 0; let isAllPaxStationFilled = true; - for (const _station of Object.values(this.paxStations)) { - const stationCurrentPax = SimVar.GetSimVarValue(`L:${_station.simVar}`, "Number"); - const stationCurrentPaxTarget = SimVar.GetSimVarValue(`L:${_station.simVar}_DESIRED`, "Number"); + Object.values(this.paxStations).map((station) => { + station.activeFlags.setFlags(SimVar.GetSimVarValue(`L:${station.bitFlags}`, 'Number')); + const stationCurrentPax = station.activeFlags.getTotalFilledSeats(); + currentPax += stationCurrentPax; + + station.desiredFlags.setFlags(SimVar.GetSimVarValue(`L:${station.bitFlags}_DESIRED`, 'Number')); + const stationCurrentPaxTarget = station.desiredFlags.getTotalFilledSeats(); + paxTarget += stationCurrentPaxTarget; if (stationCurrentPax !== stationCurrentPaxTarget) { isAllPaxStationFilled = false; - break; + /* + console.log('not pax filled'); + console.log('station current passengers', station.name, stationCurrentPax); + console.log('station target passengers', station.name, 'target', stationCurrentPaxTarget); + */ } - } + }); + + const currentLoad = Object.values(this.cargoStations).map((station) => SimVar.GetSimVarValue(`L:${station.simVar}`, "Number")).reduce((acc, cur) => acc + cur); + const loadTarget = Object.values(this.cargoStations).map((station) => SimVar.GetSimVarValue(`L:${station.simVar}_DESIRED`, "Number")).reduce((acc, cur) => acc + cur); let isAllCargoStationFilled = true; for (const _station of Object.values(this.cargoStations)) { @@ -149,6 +184,13 @@ class A32NX_Boarding { if (currentPax === paxTarget && currentLoad === loadTarget && isAllPaxStationFilled && isAllCargoStationFilled) { // Finish boarding this.boardingState = "finished"; + + // SeatFlags: Balance pax flags + /* + station.desiredFlags.setFlags(station.activeFlags.toNumber()); + await SimVar.SetSimVarValue(`L:${station.bitFlags}_DESIRED`, "string", station.activeFlags.toString()); + */ + await SimVar.SetSimVarValue("L:A32NX_BOARDING_STARTED_BY_USR", "Bool", false); } else if ((currentPax < paxTarget) || (currentLoad < loadTarget)) { @@ -160,7 +202,12 @@ class A32NX_Boarding { if (boardingRate == 'INSTANT') { // Instant for (const paxStation of Object.values(this.paxStations)) { - const stationCurrentPaxTarget = SimVar.GetSimVarValue(`L:${paxStation.simVar}_DESIRED`, "Number"); + // const stationCurrentPaxTarget = SimVar.GetSimVarValue(`L:${paxStation.simVar}_DESIRED`, "Number"); + + // TODO: Replace with flags + paxStation.desiredFlags.setFlags(SimVar.GetSimVarValue(`L:${paxStation.bitFlags}_DESIRED`, "Number")); + const stationCurrentPaxTarget = paxStation.desiredFlags.getTotalFilledSeats(); + await this.fillPaxStation(paxStation, stationCurrentPaxTarget); } for (const loadStation of Object.values(this.cargoStations)) { @@ -187,8 +234,16 @@ class A32NX_Boarding { // Stations logic: for (const paxStation of Object.values(this.paxStations).reverse()) { - const stationCurrentPax = SimVar.GetSimVarValue(`L:${paxStation.simVar}`, "Number"); - const stationCurrentPaxTarget = SimVar.GetSimVarValue(`L:${paxStation.simVar}_DESIRED`, "Number"); + + // TODO REFACTOR: Replace with flags + // const stationCurrentPax = SimVar.GetSimVarValue(`L:${paxStation.simVar}`, "Number"); + // const stationCurrentPaxTarget = SimVar.GetSimVarValue(`L:${paxStation.simVar}_DESIRED`, "Number"); + + paxStation.activeFlags.setFlags(SimVar.GetSimVarValue(`L:${paxStation.bitFlags}`, "Number")); + const stationCurrentPax = paxStation.activeFlags.getTotalFilledSeats(); + + paxStation.desiredFlags.setFlags(SimVar.GetSimVarValue(`L:${paxStation.bitFlags}_DESIRED`, "Number")); + const stationCurrentPaxTarget = paxStation.desiredFlags.getTotalFilledSeats(); if (stationCurrentPax < stationCurrentPaxTarget) { this.fillPaxStation(paxStation, stationCurrentPax + 1); diff --git a/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_PayloadManager.js b/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_PayloadManager.js index 7218e095931..b718416567a 100644 --- a/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_PayloadManager.js +++ b/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_PayloadManager.js @@ -10,7 +10,11 @@ class A32NX_PayloadConstructor { stationIndex: 0 + 1, position: 21.98, seatsRange: [1, 36], - simVar: "A32NX_PAX_TOTAL_ROWS_1_6" + simVar: "A32NX_PAX_TOTAL_ROWS_1_6", + bitFlags: "A32NX_PAX_FLAGS_A", + activeFlags: new SeatFlags(SimVar.GetSimVarValue('L:A32NX_PAX_FLAGS_A', 'Number'), 36), + desiredFlags: new SeatFlags(SimVar.GetSimVarValue('L:A32NX_PAX_FLAGS_A_DESIRED', 'Number'), 36), + }, rows7_13: { name: 'ROWS [7-13]', @@ -21,7 +25,10 @@ class A32NX_PayloadConstructor { stationIndex: 1 + 1, position: 2.86, seatsRange: [37, 78], - simVar: "A32NX_PAX_TOTAL_ROWS_7_13" + simVar: "A32NX_PAX_TOTAL_ROWS_7_13", + bitFlags: "A32NX_PAX_FLAGS_B", + activeFlags: new SeatFlags(SimVar.GetSimVarValue('L:A32NX_PAX_FLAGS_B', 'Number'), 42), + desiredFlags: new SeatFlags(SimVar.GetSimVarValue('L:A32NX_PAX_FLAGS_B_DESIRED', 'Number'), 42), }, rows14_21: { name: 'ROWS [14-21]', @@ -32,7 +39,10 @@ class A32NX_PayloadConstructor { stationIndex: 2 + 1, position: -15.34, seatsRange: [79, 126], - simVar: "A32NX_PAX_TOTAL_ROWS_14_21" + simVar: "A32NX_PAX_TOTAL_ROWS_14_21", + bitFlags: "A32NX_PAX_FLAGS_C", + activeFlags: new SeatFlags(SimVar.GetSimVarValue('L:A32NX_PAX_FLAGS_C', 'Number'), 48), + desiredFlags: new SeatFlags(SimVar.GetSimVarValue('L:A32NX_PAX_FLAGS_C_DESIRED', 'Number'), 48), }, rows22_29: { name: 'ROWS [22-29]', @@ -43,7 +53,10 @@ class A32NX_PayloadConstructor { stationIndex: 3 + 1, position: -32.81, seatsRange: [127, 174], - simVar: "A32NX_PAX_TOTAL_ROWS_22_29" + simVar: "A32NX_PAX_TOTAL_ROWS_22_29", + bitFlags: "A32NX_PAX_FLAGS_D", + activeFlags: new SeatFlags(SimVar.GetSimVarValue('L:A32NX_PAX_FLAGS_D', 'Number'), 48), + desiredFlags: new SeatFlags(SimVar.GetSimVarValue('L:A32NX_PAX_FLAGS_D_DESIRED', 'Number'), 48), }, }; @@ -94,8 +107,8 @@ const cargoStations = payloadConstruct.cargoStations; const MAX_SEAT_AVAILABLE = 174; /** - * Calculate %MAC ZWFCG of all stations - */ + * Calculate %MAC ZWFCG of all stations + */ function getZfwcg() { const leMacZ = -5.386; // Accurate to 3 decimals, replaces debug weight values diff --git a/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Utils/bitFlags.js b/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Utils/bitFlags.js new file mode 100644 index 00000000000..5dad8bf1efa --- /dev/null +++ b/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Utils/bitFlags.js @@ -0,0 +1,151 @@ +class BitFlags { + constructor(number) { + this.f64View = new Float64Array(1); + this.u32View = new Uint32Array(this.f64View.buffer); + this.setFlags(number); + } + + setFlags(number) { + this.flags = Array.from(this.u32View); + const bigNumberAsBinaryStr = number.toString(2); + + let bigNumberAsBinaryStr2 = ''; + for (let i = 0; i < 64 - bigNumberAsBinaryStr.length; i++) { + bigNumberAsBinaryStr2 += '0'; + } + + bigNumberAsBinaryStr2 += bigNumberAsBinaryStr; + + this.flags[1] = parseInt(bigNumberAsBinaryStr2.substring(0, 32), 2); + this.flags[0] = parseInt(bigNumberAsBinaryStr2.substring(32), 2); + } + + getBitIndex(bit) { + if (bit > 63) { + return false; + } + const f = Math.floor(bit / 31); + const b = bit % 31; + + return ((this.flags[f] >> b) & 1) !== 0; + } + + toggleBitIndex(bit) { + if (bit > 63) { + return; + } + const f = Math.floor(bit / 31); + const b = bit % 31; + + this.flags[f] ^= (1 << b); + } + + toDouble() { + return (new Float64Array(Uint32Array.from(this.flags).buffer))[0]; + } + + toDebug() { + const debug = []; + this.flags.forEach((flag, index) => { + debug.push(flag.toString(2)); + const fL = 32 - flag.toString(2).length; + for (let i = 0; i < fL; i++) { + debug[index] = '0'.concat(debug[index]); + } + }); + return (`HIGH [ ${debug[1]} | ${debug[0]} ] LOW`); + } + + toNumber() { + return this.flags[1] * 2 ** 32 + this.flags[0]; + } + + toString() { + return this.toNumber().toString(); + } + + getTotalBits() { + let total = 0; + this.flags.forEach((flag) => { + const n = 32; + let i = 0; + while (i++ < n) { + if ((1 << i & flag) === (1 << i)) { + total++; + } + } + }); + return total; + } +} + +class SeatFlags extends BitFlags { + constructor(number, totalSeats) { + super(number); + // Limit bits utilisation to < totalSeats + this.totalSeats = totalSeats; + } + + getEmptySeatIds() { + const emptySeats = []; + for (let seatId = 0; seatId < this.totalSeats; seatId++) { + if (!this.getBitIndex(seatId)) { + emptySeats.push(seatId); + } + } + return emptySeats; + } + + getFilledSeatIds() { + const filledSeats = []; + for (let seatId = 0; seatId < this.totalSeats; seatId++) { + if (this.getBitIndex(seatId)) { + filledSeats.push(seatId); + } + } + return filledSeats; + } + + isSeatFilled(seatId) { + if (seatId > this.totalSeats) return false; + return this.getBitIndex(seatId); + } + + toggleSeatId(seatId) { + if (seatId > this.totalSeats) return; + this.toggleBitIndex(seatId); + } + + fillEmptySeats(numFill) { + this.fillSeats(numFill, this.getEmptySeatIds()); + } + + fillSeats(numFill, choices) { + for (let i = 0; i < numFill; i++) { + if (choices.length > 0) { + const chosen = ~~(Math.random() * choices.length); + this.toggleSeatId(choices[chosen]); + choices.splice(chosen, 1); + } + } + } + + emptyFilledSeats(numEmpty) { + const choices = this.getFilledSeatIds(); + for (let i = 0; i < numEmpty; i++) { + if (choices.length > 0) { + const chosen = ~~(Math.random() * choices.length); + this.toggleSeatId(choices[chosen]); + choices.splice(chosen, 1); + } + } + } + + getTotalFilledSeats() { + return this.getTotalBits(); + } + + getTotalEmptySeats() { + return this.totalSeats - this.getTotalBits(); + } +} diff --git a/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU.html b/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU.html index f0135777da1..8e320422c2f 100644 --- a/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU.html +++ b/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/Airliners/FlyByWire_A320_Neo/CDU/A320_Neo_CDU.html @@ -18,6 +18,7 @@ + diff --git a/src/instruments/src/Common/bitFlags.tsx b/src/instruments/src/Common/bitFlags.tsx index da45bce87bf..202b909c143 100644 --- a/src/instruments/src/Common/bitFlags.tsx +++ b/src/instruments/src/Common/bitFlags.tsx @@ -1,21 +1,85 @@ -import { useLocalStorage } from '@instruments/common/persistence'; -import { BitFlags } from '@shared/bitFlags'; -import { useCallback, useState } from 'react'; - -type BitFlagsSetter = (oldValue: T) => T; +/* eslint-disable function-paren-newline */ +import { useUpdate } from '@instruments/common/hooks'; +import { BitFlags, SeatFlags } from '@shared/bitFlags'; +import { useCallback, useRef, useState } from 'react'; export const useBitFlags = ( name: string, -): [BitFlags, (setter: BitFlags -) => void] => { - const [storage, setStorage] = useLocalStorage(name, ''); - const [stateValue, setValue] = useState(storage ? JSON.parse(storage) : 0); - - const setter = useCallback((valueOrSetter: BitFlags | BitFlagsSetter) => { - const executedValue: number = typeof valueOrSetter === 'function' ? valueOrSetter(stateValue) : valueOrSetter.toDouble(); - setValue(executedValue); - setStorage(JSON.stringify(executedValue)); + refreshInterval: number = 200, +): [ + BitFlags, + (setter: BitFlags) => void +] => { + const lastUpdate = useRef(Date.now() - refreshInterval - 1); + + const [stateValue, setStateValue] = useState(() => SimVar.GetSimVarValue(`L:${name}`, 'number')); + + const updateCallback = useCallback(() => { + const delta = Date.now() - lastUpdate.current; + + if (delta >= refreshInterval) { + lastUpdate.current = Date.now(); + + const newValue = SimVar.GetSimVarValue(`L:${name}`, 'number'); + if (newValue !== stateValue) { + setStateValue(newValue); + setter(new BitFlags(newValue)); + } + } + }, [name, stateValue, refreshInterval]); + + useUpdate(updateCallback); + + const setter = useCallback((value: BitFlags) => { + SimVar.SetSimVarValue(`L:${name}`, 'string', value.toString()).catch(console.error).then(); + setStateValue(value.toNumber()); }, [name, stateValue]); return [new BitFlags(stateValue), setter]; }; + +export const useSeatFlags = ( + name: string, + totalSeats: number, + refreshInterval: number = 200, +): [ + SeatFlags, + (setter: SeatFlags) => void +] => { + const lastUpdate = useRef(Date.now() - refreshInterval - 1); + + const [stateValue, setStateValue] = useState(() => SimVar.GetSimVarValue(name, 'number')); + // const [seatFlags] = useState(() => new SeatFlags(stateValue, totalSeats)); + + const updateCallback = useCallback(() => { + const delta = Date.now() - lastUpdate.current; + + if (delta >= refreshInterval) { + lastUpdate.current = Date.now(); + + const newValue = SimVar.GetSimVarValue(name, 'number'); + if (newValue !== stateValue) { + setStateValue(newValue); + // TODO: Refactor to recycle object instead of generating new object + setter(new SeatFlags(newValue, totalSeats)); + } + } + }, [name, stateValue, refreshInterval]); + + useUpdate(updateCallback); + + const setter = useCallback((value: SeatFlags) => { + lastUpdate.current = Date.now(); + // Note: as of SU XI 1.29.30.0 - Beyond (2^24) The BehaviourDebug window will incorrectly show this as its real value + 1. + // console.log(`[SetSimVarValue] ${name} => ${value.toString()}`); + SimVar.SetSimVarValue(name, 'string', value.toString()).catch(console.error).then(); + setStateValue(value.toNumber()); + // seatFlags.setFlags(value.toNumber()); + }, [name, stateValue]); + + return [ + // TODO: Refactor to recycle object instead of generating new object + new SeatFlags(stateValue, totalSeats), + setter, + ]; +}; diff --git a/src/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a20nv55.json b/src/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a20nv55.json index 7758da33e4f..f203599eb77 100644 --- a/src/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a20nv55.json +++ b/src/instruments/src/EFB/Ground/Pages/Payload/Loadsheet/a20nv55.json @@ -20,6 +20,7 @@ "seatMap": [ { "name": "ROWS [1-6]", + "capacity": 36, "rows": [ { "seats": [ @@ -291,10 +292,11 @@ "position": 21.98, "fill": 0.19, "simVar": "A32NX_PAX_TOTAL_ROWS_1_6", - "bitFlags": "PAX_FLAGS_A" + "bitFlags": "A32NX_PAX_FLAGS_A" }, { "name": "ROWS [7-13]", + "capacity": 42, "rows": [ { "seats": [ @@ -610,10 +612,11 @@ "position": 2.86, "fill": 0.25, "simVar": "A32NX_PAX_TOTAL_ROWS_7_13", - "bitFlags": "PAX_FLAGS_B" + "bitFlags": "A32NX_PAX_FLAGS_B" }, { "name": "ROWS [14-21]", + "capacity": 48, "rows": [ { "seats": [ @@ -973,10 +976,11 @@ "position": -15.34, "fill": 0.28, "simVar": "A32NX_PAX_TOTAL_ROWS_14_21", - "bitFlags": "PAX_FLAGS_C" + "bitFlags": "A32NX_PAX_FLAGS_C" }, { "name": "ROWS [22-29]", + "capacity": 48, "rows": [ { "seats": [ @@ -1336,7 +1340,7 @@ "stationIndex": 4, "position": -32.81, "simVar": "A32NX_PAX_TOTAL_ROWS_22_29", - "bitFlags": "PAX_FLAGS_D" + "bitFlags": "A32NX_PAX_FLAGS_D" } ], "cargoMap": [ diff --git a/src/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx b/src/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx index d9b2a430a4f..c3543acd801 100644 --- a/src/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx +++ b/src/instruments/src/EFB/Ground/Pages/Payload/Payload.tsx @@ -11,9 +11,9 @@ import { import { useSimVar } from '@instruments/common/simVars'; import { Units } from '@shared/units'; import { usePersistentProperty } from '@instruments/common/persistence'; -import { BitFlags } from '@shared/bitFlags'; -import { useBitFlags } from '@instruments/common/bitFlags'; import { round } from 'lodash'; +import { useSeatFlags } from '@instruments/common/bitFlags'; +import { SeatFlags } from '@shared/bitFlags'; import { CargoWidget } from './Seating/CargoWidget'; import { ChartWidget } from './Chart/ChartWidget'; import { PaxStationInfo, CargoStationInfo } from './Seating/Constants'; @@ -32,39 +32,33 @@ export const Payload = () => { const { usingMetric } = Units; const { showModal } = useModals(); - const pax: number[] = []; - const paxDesired: number[] = []; - const setPaxDesired: ((newValueOrSetter: any) => void)[] = []; - const activeFlags: BitFlags[] = []; - const setActiveFlags: ((newValueOrSetter: any) => void)[] = []; - const desiredFlags: BitFlags[] = []; - const setDesiredFlags: ((newValueOrSetter: any) => void)[] = []; - - const cargo: number[] = []; - const cargoDesired: number[] = []; - const setCargoDesired: ((newValueOrSetter: any) => void)[] = []; - - Loadsheet.seatMap.forEach((station) => { - const [stationPax] = useSimVar(`L:${station.simVar}`, 'Number', 300); - pax.push(stationPax); - const [stationPaxDesired, setStationPaxDesired] = useSimVar(`L:${station.simVar}_DESIRED`, 'Number', 300); - paxDesired.push(stationPaxDesired); - setPaxDesired.push(setStationPaxDesired); - const [pFlg, setPFlg] = useBitFlags(station.bitFlags); - activeFlags.push(pFlg); - setActiveFlags.push(setPFlg); - const [pFlgDesired, setPFlgDesired] = useBitFlags(`${station.bitFlags}_DESIRED`); - desiredFlags.push(pFlgDesired); - setDesiredFlags.push(setPFlgDesired); - }); - - Loadsheet.cargoMap.forEach((station) => { - const [stationWeight] = useSimVar(`L:${station.simVar}`, 'Number', 300); - cargo.push(stationWeight); - const [stationWeightDesired, setStationWeightDesired] = useSimVar(`L:${station.simVar}_DESIRED`, 'Number', 300); - cargoDesired.push(stationWeightDesired); - setCargoDesired.push(setStationWeightDesired); - }); + const [aFlags] = useSeatFlags(`L:${Loadsheet.seatMap[0].bitFlags}`, Loadsheet.seatMap[0].capacity); + const [bFlags] = useSeatFlags(`L:${Loadsheet.seatMap[1].bitFlags}`, Loadsheet.seatMap[1].capacity); + const [cFlags] = useSeatFlags(`L:${Loadsheet.seatMap[2].bitFlags}`, Loadsheet.seatMap[2].capacity); + const [dFlags] = useSeatFlags(`L:${Loadsheet.seatMap[3].bitFlags}`, Loadsheet.seatMap[3].capacity); + + const [aFlagsDesired, setAFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[0].bitFlags}_DESIRED`, Loadsheet.seatMap[0].capacity); + const [bFlagsDesired, setBFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[1].bitFlags}_DESIRED`, Loadsheet.seatMap[1].capacity); + const [cFlagsDesired, setCFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[2].bitFlags}_DESIRED`, Loadsheet.seatMap[2].capacity); + const [dFlagsDesired, setDFlagsDesired] = useSeatFlags(`L:${Loadsheet.seatMap[3].bitFlags}_DESIRED`, Loadsheet.seatMap[3].capacity); + + const activeFlags = useMemo(() => [aFlags, bFlags, cFlags, dFlags], [aFlags, bFlags, cFlags, dFlags]); + const desiredFlags = useMemo(() => [aFlagsDesired, bFlagsDesired, cFlagsDesired, dFlagsDesired], [aFlagsDesired, bFlagsDesired, cFlagsDesired, dFlagsDesired]); + const setDesiredFlags = useMemo(() => [setAFlagsDesired, setBFlagsDesired, setCFlagsDesired, setDFlagsDesired], []); + + const [fwdBag] = useSimVar(`L:${Loadsheet.cargoMap[0].name}`, 'Number', 200); + const [aftCont] = useSimVar(`L:${Loadsheet.cargoMap[1].name}`, 'Number', 200); + const [aftBag] = useSimVar(`L:${Loadsheet.cargoMap[2].name}`, 'Number', 200); + const [aftBulk] = useSimVar(`L:${Loadsheet.cargoMap[3].name}`, 'Number', 200); + + const [fwdBagDesired, setFwdBagDesired] = useSimVar(`L:${Loadsheet.cargoMap[0].name}_DESIRED`, 'Number', 200); + const [aftContDesired, setAftContDesired] = useSimVar(`L:${Loadsheet.cargoMap[1].name}_DESIRED`, 'Number', 200); + const [aftBagDesired, setAftBagDesired] = useSimVar(`L:${Loadsheet.cargoMap[2].name}_DESIRED`, 'Number', 200); + const [aftBulkDesired, setAftBulkDesired] = useSimVar(`L:${Loadsheet.cargoMap[3].name}_DESIRED`, 'Number', 200); + + const cargo = useMemo(() => [fwdBag, aftCont, aftBag, aftBulk], [fwdBag, aftCont, aftBag, aftBulk]); + const cargoDesired = useMemo(() => [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired], [fwdBagDesired, aftContDesired, aftBagDesired, aftBulkDesired]); + const setCargoDesired = useMemo(() => [setFwdBagDesired, setAftContDesired, setAftBagDesired, setAftBulkDesired], []); const massUnitForDisplay = usingMetric ? 'KGS' : 'LBS'; @@ -79,27 +73,30 @@ export const Payload = () => { const [emptyWeight] = useSimVar('A:EMPTY WEIGHT', usingMetric ? 'Kilograms' : 'Pounds', 2_000); const [stationSize, setStationLen] = useState([]); - const totalPax = useMemo(() => { - if (pax && pax.length > 0) { - return pax.reduce((a, b) => a + b); - } - return 0; - }, [...pax]); const maxPax = useMemo(() => ((stationSize && stationSize.length > 0) ? stationSize.reduce((a, b) => a + b) : -1), [stationSize]); + // Calculate Total Pax from Pax Flags + const totalPax = useMemo(() => { + let p = 0; + activeFlags.forEach((flag) => { + p += flag.getTotalFilledSeats(); + }); + return p; + }, [...activeFlags]); + const totalPaxDesired = useMemo(() => { - if (paxDesired && paxDesired.length > 0) { - return paxDesired.reduce((a, b) => a + b); - } - return 0; - }, [...paxDesired]); - const [clicked, setClicked] = useState(false); + let p = 0; + desiredFlags.forEach((flag) => { + p += flag.getTotalFilledSeats(); + }); + return p; + }, [...desiredFlags]); - const totalCargoDesired = useMemo(() => ((cargoDesired && cargoDesired.length > 0) ? cargoDesired.reduce((a, b) => parseInt(a) + parseInt(b)) : -1), [...cargoDesired, ...paxDesired]); + const totalCargoDesired = useMemo(() => ((cargoDesired && cargoDesired.length > 0) ? cargoDesired.reduce((a, b) => a + b) : -1), [...cargoDesired]); const [cargoStationSize, setCargoStationLen] = useState([]); - const totalCargo = useMemo(() => ((cargo && cargo.length > 0) ? cargo.reduce((a, b) => parseInt(a) + parseInt(b)) : -1), [...cargo, ...pax]); + const totalCargo = useMemo(() => ((cargo && cargo.length > 0) ? cargo.reduce((a, b) => a + b) : -1), [...cargo]); const maxCargo = useMemo(() => ((cargoStationSize && cargoStationSize.length > 0) ? cargoStationSize.reduce((a, b) => a + b) : -1), [cargoStationSize]); const [centerCurrent] = useSimVar('FUEL TANK CENTER QUANTITY', 'Gallons', 2_000); @@ -161,63 +158,16 @@ export const Payload = () => { const [eng2Running] = useSimVar('ENG COMBUSTION:2', 'Bool', 2_000); const [coldAndDark, setColdAndDark] = useState(true); - const returnSeats = useCallback((stationIndex: number, empty: boolean, flags: BitFlags[]): number[] => { - const seats: number[] = []; - const bitFlags: BitFlags = flags[stationIndex]; - for (let seatId = 0; seatId < stationSize[stationIndex]; seatId++) { - if (!empty && bitFlags.getBitIndex(seatId)) { - seats.push(seatId); - } else if (empty && !bitFlags.getBitIndex(seatId)) { - seats.push(seatId); - } - } - return seats; - }, [...desiredFlags, ...activeFlags]); - - const returnNumSeats = useCallback((stationIndex: number, empty: boolean, flags: BitFlags[]): number => { - let count = 0; - const bitFlags: BitFlags = flags[stationIndex]; - for (let seatId = 0; seatId < stationSize[stationIndex]; seatId++) { - if (!empty && bitFlags.getBitIndex(seatId)) { - count++; - } else if (empty && !bitFlags.getBitIndex(seatId)) { - count++; - } - } - return count; - }, [...desiredFlags, ...activeFlags]); - - const chooseSeats = useCallback((stationIndex: number, choices: number[], numChoose: number) => { - const bitFlags: BitFlags = activeFlags[stationIndex]; - for (let i = 0; i < numChoose; i++) { - if (choices.length > 0) { - const chosen = ~~(Math.random() * choices.length); - bitFlags.toggleBitIndex(choices[chosen]); - choices.splice(chosen, 1); - } - } - setActiveFlags[stationIndex](bitFlags); - }, [...pax, ...activeFlags]); - - const chooseDesiredSeats = useCallback((stationIndex: number, choices: number[], numChoose: number) => { - const bitFlags: BitFlags = desiredFlags[stationIndex]; - for (let i = 0; i < numChoose; i++) { - if (choices.length > 0) { - const chosen = ~~(Math.random() * choices.length); - bitFlags.toggleBitIndex(choices[chosen]); - choices.splice(chosen, 1); - } + const chooseDesiredSeats = useCallback((stationIndex: number, fillSeats: boolean = true, numChoose: number) => { + const seatFlags: SeatFlags = desiredFlags[stationIndex]; + if (fillSeats) { + seatFlags.fillEmptySeats(numChoose); + } else { + seatFlags.emptyFilledSeats(numChoose); } - setDesiredFlags[stationIndex](bitFlags); - }, [...paxDesired, ...desiredFlags]); - - const calculateSeatOptions = useCallback((stationIndex: number, increase: boolean): number[] => { - const plannedSeats = returnSeats(stationIndex, increase, desiredFlags); - const activeSeats = returnSeats(stationIndex, !increase, activeFlags); - const intersection = activeSeats.filter((element) => plannedSeats.includes(element)); - return intersection; - }, [...activeFlags, ...desiredFlags]); + setDesiredFlags[stationIndex](seatFlags); + }, [...desiredFlags]); const setTargetPax = useCallback((numOfPax: number) => { if (!stationSize || numOfPax === totalPaxDesired || numOfPax > maxPax || numOfPax < 0) return; @@ -225,20 +175,24 @@ export const Payload = () => { let paxRemaining = numOfPax; const fillStation = (stationIndex: number, percent: number, paxToFill: number) => { - const pax = Math.min(Math.trunc(percent * paxToFill), stationSize[stationIndex]); - setPaxDesired[stationIndex](pax); - paxRemaining -= pax; + const sFlags: SeatFlags = desiredFlags[stationIndex]; + const toBeFilled = Math.min(Math.trunc(percent * paxToFill), stationSize[stationIndex]); - const paxCount = returnNumSeats(stationIndex, false, activeFlags); - const seats: number[] = returnSeats(stationIndex, pax[stationIndex] > paxCount, activeFlags); - chooseDesiredSeats(stationIndex, seats, Math.abs(paxCount - pax[stationIndex])); + paxRemaining -= toBeFilled; + + const planSeatedPax = sFlags.getTotalFilledSeats(); + chooseDesiredSeats( + stationIndex, + (toBeFilled > planSeatedPax), + Math.abs(toBeFilled - planSeatedPax), + ); }; - for (let i = pax.length - 1; i > 0; i--) { + for (let i = seatMap.length - 1; i > 0; i--) { fillStation(i, seatMap[i].fill, numOfPax); } fillStation(0, 1, paxRemaining); - }, [...paxDesired, totalPaxDesired, maxPax, ...stationSize, ...seatMap]); + }, [maxPax, ...stationSize, ...seatMap, totalPaxDesired]); const setTargetCargo = useCallback((numberOfPax: number, freight: number, perBagWeight: number = paxBagWeight) => { const bagWeight = numberOfPax * perBagWeight; @@ -260,19 +214,20 @@ export const Payload = () => { const calculatePaxMoment = useCallback(() => { let paxMoment = 0; - pax.forEach((station, i) => { - paxMoment += station * paxWeight * seatMap[i].position; + activeFlags.forEach((stationFlag, i) => { + paxMoment += stationFlag.getTotalFilledSeats() * paxWeight * seatMap[i].position; }); return paxMoment; - }, [paxWeight, ...pax, seatMap]); + }, [paxWeight, seatMap, ...activeFlags]); const calculatePaxDesiredMoment = useCallback(() => { let paxMoment = 0; - paxDesired.forEach((station, i) => { - paxMoment += station * paxWeight * seatMap[i].position; + desiredFlags.forEach((stationFlag, i) => { + paxMoment += stationFlag.getTotalFilledSeats() * paxWeight * seatMap[i].position; }); + return paxMoment; - }, [paxWeight, ...paxDesired, seatMap]); + }, [paxWeight, seatMap, ...desiredFlags]); const calculateCargoMoment = useCallback(() => { let cargoMoment = 0; @@ -311,33 +266,30 @@ export const Payload = () => { setCargoDesired[cargoStation](Math.round(Units.kilogramToUser(cargoMap[cargoStation].weight) * cargoPercent)); }, [cargoMap]); - const onClickSeat = useCallback((station: number, seatId: number) => { - setClicked(true); - let newPax = totalPaxDesired; - // TODO FIXME: This calculation does not work correctly if user clicks on many seats in rapid succession - const freight = Math.max(totalCargoDesired - totalPaxDesired * paxBagWeight, 0); - const bitFlags: BitFlags = desiredFlags[station]; + const onClickSeat = useCallback((stationIndex: number, seatId: number) => { + const seatFlags: SeatFlags = desiredFlags[stationIndex]; + seatFlags.toggleSeatId(seatId); + setDesiredFlags[stationIndex](seatFlags); - if (bitFlags.getBitIndex(seatId)) { - newPax -= 1; - setPaxDesired[station](Math.max(paxDesired[station] - 1, 0)); - } else { - newPax += 1; - setPaxDesired[station](Math.min(paxDesired[station] + 1, stationSize[station])); - } + let newPaxDesired = 0; + desiredFlags.forEach((flag) => { + newPaxDesired += flag.getTotalFilledSeats(); + }); + // TODO FIXME: This calculation does not work correctly if user clicks on many seats in rapid succession + const newPaxBag = newPaxDesired * paxBagWeight; + const paxDelta = newPaxDesired - totalPaxDesired; + const freight = Math.max(totalCargoDesired - newPaxBag + paxDelta * paxBagWeight, 0); - setTargetCargo(newPax, freight); - bitFlags.toggleBitIndex(seatId); - setDesiredFlags[station](bitFlags); - setTimeout(() => setClicked(false), 500); + setTargetCargo(newPaxDesired, freight); }, [ - totalPaxDesired, paxBagWeight, - totalCargoDesired, totalPaxDesired, - ...cargoDesired, ...paxDesired, + paxBagWeight, + totalCargoDesired, + ...cargoDesired, ...desiredFlags, ...stationSize, + totalPaxDesired, ]); - const handleDeboarding = () => { + const handleDeboarding = useCallback(() => { if (!boardingStarted) { showModal( { ); } setBoardingStarted(false); - }; + }, [totalPaxDesired, totalPax, totalCargo, totalCargoDesired]); const boardingStatusClass = useMemo(() => { if (!boardingStarted) { return 'text-theme-highlight'; } return (totalPaxDesired * paxWeight + totalCargoDesired) >= (totalPax * paxWeight + totalCargo) ? 'text-green-500' : 'text-yellow-500'; - }, [boardingStarted, paxWeight, totalPaxDesired, totalCargoDesired, totalPax, totalCargo]); + }, [boardingStarted, paxWeight, totalCargoDesired, totalCargo, totalPaxDesired, totalPax]); // Init useEffect(() => { @@ -397,11 +349,7 @@ export const Payload = () => { stationSize.push(0); } seatMap.forEach((station, i) => { - station.rows.forEach((row) => { - row.seats.forEach(() => { - stationSize[i]++; - }); - }); + stationSize[i] = station.capacity; }); setStationLen(stationSize); }, [seatMap]); @@ -418,85 +366,6 @@ export const Payload = () => { setCargoStationLen(cargoSize); }, [cargoMap]); - // Check that pax data and bitflags are valid - useEffect(() => { - pax.forEach((stationPaxNum: number, stationIndex: number) => { - const paxCount = returnNumSeats(stationIndex, false, activeFlags); - if (stationPaxNum === 0 && paxCount !== stationPaxNum) { - setActiveFlags[stationIndex](new BitFlags(0)); - } - }); - - paxDesired.forEach((stationPaxNum, stationIndex) => { - const paxCount = returnNumSeats(stationIndex, false, desiredFlags); - if (stationPaxNum === 0 && paxCount !== stationPaxNum) { - setDesiredFlags[stationIndex](new BitFlags(0)); - } - }); - if (!boardingStarted) { - setTargetPax(totalPax); - setTargetCargo(0, totalCargo); - } - }, [stationSize]); - - // Adjusted desired passenger seating layout to match station passenger count on change - useEffect(() => { - paxDesired.forEach((stationNumPax, stationIndex) => { - const paxCount = returnNumSeats(stationIndex, false, desiredFlags); - if (!clicked && stationNumPax !== paxCount) { - const seatOptions = calculateSeatOptions(stationIndex, stationNumPax > paxCount); - const seatDelta = Math.abs(paxCount - stationNumPax); - - if (seatOptions.length >= seatDelta) { - chooseDesiredSeats(stationIndex, seatOptions, seatDelta); - } else if (seatOptions.length && seatOptions.length < seatDelta) { - // Fallback if we don't have enough seat options using desired as reference - const leftOver = seatDelta - seatOptions.length; - chooseDesiredSeats(stationIndex, seatOptions, seatOptions.length); - const seats: number[] = returnSeats(stationIndex, stationNumPax > paxCount, desiredFlags); - chooseDesiredSeats(stationIndex, seats, leftOver); - } else { - // Fallback if no seat options using desired as reference - const seats: number[] = returnSeats(stationIndex, stationNumPax > paxCount, desiredFlags); - chooseDesiredSeats(stationIndex, seats, seatDelta); - } - } - }); - }, [...paxDesired]); - - // Adjust actual passenger seating layout to match station passenger count on change - useEffect(() => { - pax.forEach((stationNumPax: number, stationIndex: number) => { - const paxCount = returnNumSeats(stationIndex, false, activeFlags); - if (!clicked && stationNumPax !== paxCount) { - const seatOptions = calculateSeatOptions(stationIndex, stationNumPax < paxCount); - const seatDelta = Math.abs(paxCount - stationNumPax); - if (seatOptions.length >= seatDelta) { - chooseSeats(stationIndex, seatOptions, seatDelta); - } else if (seatOptions.length && seatOptions.length < seatDelta) { - // Fallback if we don't have enough seat options using desired as reference - const leftOver = seatDelta - seatOptions.length; - chooseSeats(stationIndex, seatOptions, seatOptions.length); - const seats: number[] = returnSeats(stationIndex, stationNumPax > paxCount, activeFlags); - chooseSeats(stationIndex, seats, leftOver); - } else { - // Fallback if no seat options using desired as reference - const seats: number[] = returnSeats(stationIndex, stationNumPax > paxCount, activeFlags); - chooseSeats(stationIndex, seats, seatDelta); - } - } - }); - }, [...pax]); - - useEffect(() => { - pax.forEach((stationNumPax: number, stationIndex: number) => { - // Sync active to desired layout if pax is equal to desired - if (stationNumPax === paxDesired[stationIndex]) { - setActiveFlags[stationIndex](desiredFlags[stationIndex]); - } - }); - }, [boardingStarted]); - useEffect(() => { const centerTankMoment = -6; const innerTankMoment = -8; @@ -568,7 +437,7 @@ export const Payload = () => { setMlwDesiredCg(newDesiredCg); } }, [ - ...pax, ...paxDesired, + ...desiredFlags, ...activeFlags, ...cargo, ...cargoDesired, ...fuel, destEfob, paxWeight, paxBagWeight, @@ -753,7 +622,7 @@ export const Payload = () => {