Skip to content

Commit d42846e

Browse files
Kwash67Copilot
andauthored
775 preload specific messages in fla
* send large numeric data as typed arrays to reduce ipc serialization overhead * removing redundant state and caching data * fiuring off preload with setTimeout, works so far * formatting * removing unused import * formatting * clean up * Update gcs/src/fla.jsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update gcs/src/fla.jsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fixing lint error * Applying copilot's suggestions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * formatting --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 4e403e3 commit d42846e

4 files changed

Lines changed: 139 additions & 34 deletions

File tree

gcs/electron/fla.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -453,15 +453,15 @@ export default async function openFile(event, filePath) {
453453
}
454454

455455
// on-demand retrieval of messages
456-
export async function retrieveMessages(_event, requestedMessages) {
456+
export async function getMessages(_event, requestedMessages) {
457457
// each requestedMessage should be of the form `${requestedMessageName}/${requestedFieldName}`
458458
// like ['ARM/ArmState', 'ARSP/Airspeed']
459459

460460
// for large log files, we need to consider decimation.
461461

462462
if (!logData) {
463463
console.error(
464-
"retrieveMessages: logData is null or undefined. Unable to retrieve messages.",
464+
"getMessages: logData is null or undefined. Unable to retrieve messages.",
465465
)
466466
return {
467467
success: false,
@@ -490,14 +490,35 @@ export async function retrieveMessages(_event, requestedMessages) {
490490
const categoryName = label.slice(0, slash)
491491
const fieldName = label.slice(slash + 1)
492492

493+
// Validate existence of requestedMessage in our log
494+
const fmt = formatMessages[categoryName]
495+
const hasField = fmt?.fields.includes(fieldName)
496+
497+
const series = logData[categoryName]
498+
499+
if (!hasField || !Array.isArray(series) || series.length === 0) {
500+
// Skip unknown or unavailable labels
501+
continue
502+
}
503+
504+
const len = series.length
505+
// use typed arrays to reduce IPC serialization overhead
506+
// Time as Float64, values as Float32 for size efficiency
507+
const x = new Float64Array(len)
508+
const y = new Float32Array(len)
509+
510+
for (let j = 0; j < len; j++) {
511+
const point = series[j]
512+
// making sure all the entries are numbers
513+
x[j] = typeof point.TimeUS === "number" ? point.TimeUS : 0
514+
y[j] = typeof point[fieldName] === "number" ? point[fieldName] : 0
515+
}
516+
493517
datasets.push({
494-
label: label,
518+
label,
495519
yAxisID: getUnit(categoryName, fieldName, formatMessages, units),
496-
// I guess this is the expensive part. We're looping through every data point
497-
data: logData[categoryName].map((d) => ({
498-
x: d.TimeUS,
499-
y: d[fieldName],
500-
})),
520+
x,
521+
y,
501522
})
502523
}
503524

gcs/electron/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import packageInfo from "../package.json"
1919
import openFile, {
2020
clearRecentFiles,
2121
getRecentFiles,
22-
retrieveMessages,
22+
getMessages,
2323
// @ts-expect-error - no types available
2424
} from "./fla"
2525
import registerAboutIPC, {
@@ -475,7 +475,7 @@ app.whenReady().then(() => {
475475
ipcMain.handle("fla:clear-recent-logs", clearRecentFiles)
476476

477477
// Load Messages on demand
478-
ipcMain.handle("fla:get-messages", retrieveMessages)
478+
ipcMain.handle("fla:get-messages", getMessages)
479479

480480
// Open native save dialog
481481
ipcMain.handle("app:get-save-file-path", async (event, options) => {

gcs/src/components/fla/constants.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,74 @@ export const colorInputSwatch = [
4949
"#fab005",
5050
"#fd7e14",
5151
]
52+
53+
// Fixed list of message labels to preload for FLA
54+
export const PRELOAD_LABELS = {
55+
dataflash: [
56+
// Attitude
57+
"ATT/DesRoll",
58+
"ATT/Roll",
59+
"ATT/DesPitch",
60+
"ATT/Pitch",
61+
"ATT/DesYaw",
62+
"ATT/Yaw",
63+
64+
// Speed
65+
"GPS/Spd",
66+
"ARSP/Airspeed",
67+
68+
// Vibration
69+
"VIBE/VibeX",
70+
"VIBE/VibeY",
71+
"VIBE/VibeZ",
72+
73+
// Battery
74+
"BAT1/Volt",
75+
"BAT1/Curr",
76+
77+
// Control tuning (copter/plane)
78+
"CTUN/DAlt",
79+
"CTUN/Alt",
80+
"CTUN/BAlt",
81+
"CTUN/DCRt",
82+
"CTUN/CRt",
83+
"CTUN/ThI",
84+
"CTUN/ThO",
85+
86+
// Control tuning (quadplane)
87+
"QTUN/DAlt",
88+
"QTUN/Alt",
89+
"QTUN/BAlt",
90+
"QTUN/DCRt",
91+
"QTUN/CRt",
92+
"QTUN/ThI",
93+
"QTUN/ThO",
94+
95+
// RC Inputs (first four)
96+
"RCIN/C1",
97+
"RCIN/C2",
98+
"RCIN/C3",
99+
"RCIN/C4",
100+
],
101+
102+
fgcs_telemetry: [
103+
// Attitude
104+
"ATTITUDE/roll",
105+
"ATTITUDE/pitch",
106+
107+
// Speed
108+
"VFR_HUD/groundspeed",
109+
"VFR_HUD/airspeed",
110+
111+
// Vibration
112+
"VIBRATION/vibration_x",
113+
"VIBRATION/vibration_y",
114+
"VIBRATION/vibration_z",
115+
116+
// RC Inputs (first four)
117+
"RC_CHANNELS/chan1_raw",
118+
"RC_CHANNELS/chan2_raw",
119+
"RC_CHANNELS/chan3_raw",
120+
"RC_CHANNELS/chan4_raw",
121+
],
122+
}

gcs/src/fla.jsx

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
// Base imports
8-
import { useEffect, useMemo, useState } from "react"
8+
import { useEffect, useMemo } from "react"
99

1010
// 3rd Party Imports
1111
import { useDispatch, useSelector } from "react-redux"
@@ -14,6 +14,7 @@ import { useDispatch, useSelector } from "react-redux"
1414
import { hexToRgba } from "./components/fla/utils"
1515

1616
// Custom components and helpers
17+
import { PRELOAD_LABELS } from "./components/fla/constants.js"
1718
import { logEventIds } from "./components/fla/logEventIds.js"
1819

1920
import SelectFlightLog from "./components/fla/SelectFlightLog.jsx"
@@ -48,9 +49,6 @@ export default function FLA() {
4849
const customColors = useSelector(selectCustomColors)
4950
const baseChartData = useSelector(selectBaseChartData)
5051

51-
// Local states
52-
const [chartData, setLocalChartData] = useState({ datasets: [] })
53-
5452
/**
5553
* Dispatch the lightweight summary info to Redux
5654
*/
@@ -76,13 +74,17 @@ export default function FLA() {
7674
),
7775
)
7876
dispatch(setBaseChartData([]))
77+
// Fire off preload in the background without blocking
78+
const labelsToPreload = PRELOAD_LABELS[summary.logType]
79+
if (labelsToPreload && labelsToPreload.length > 0) {
80+
setTimeout(() => fetchData(labelsToPreload), 0)
81+
}
7982
}
8083

8184
// Close file
8285
function closeLogFile() {
8386
dispatch(setFile(null))
8487
dispatch(setLogMessages(null))
85-
setLocalChartData({ datasets: [] })
8688
dispatch(setMessageFilters(null))
8789
dispatch(setCustomColors({}))
8890
dispatch(setUtcAvailable(false))
@@ -93,6 +95,30 @@ export default function FLA() {
9395
dispatch(setBaseChartData([]))
9496
}
9597

98+
async function fetchData(labelsToFetch) {
99+
const newDatasets = await window.ipcRenderer.invoke(
100+
"fla:get-messages",
101+
labelsToFetch,
102+
)
103+
// Unpack and Cache
104+
if (Array.isArray(newDatasets) && newDatasets.length > 0) {
105+
const transformed = newDatasets.map((ds) => {
106+
if (Array.isArray(ds?.data)) return ds
107+
const len = Math.min(ds.x.length, ds.y.length)
108+
const points = new Array(len)
109+
for (let i = 0; i < len; i++) {
110+
points[i] = { x: ds.x[i], y: ds.y[i] }
111+
}
112+
return { label: ds.label, yAxisID: ds.yAxisID, data: points }
113+
})
114+
// Deduplicate by label: new datasets override old ones
115+
const existing = baseChartData || []
116+
const newLabels = new Set(transformed.map((ds) => ds.label))
117+
const filteredExisting = existing.filter((ds) => !newLabels.has(ds.label))
118+
dispatch(setBaseChartData([...filteredExisting, ...transformed]))
119+
}
120+
}
121+
96122
// Step 1: Memoize the calculation of which labels are currently requested.
97123
// This loop only runs when `messageFilters` changes.
98124
const requestedLabels = useMemo(() => {
@@ -121,19 +147,9 @@ export default function FLA() {
121147

122148
if (labelsToFetch.length > 0) {
123149
console.log("Cache miss. Fetching:", labelsToFetch)
124-
const fetchMissingData = async () => {
125-
const newDatasets = await window.ipcRenderer.invoke(
126-
"fla:get-messages",
127-
labelsToFetch,
128-
)
129-
if (newDatasets) {
130-
// Dispatch to add the new data to our master cache in Redux
131-
dispatch(setBaseChartData([...(baseChartData || []), ...newDatasets]))
132-
}
133-
}
134-
fetchMissingData()
150+
fetchData(labelsToFetch)
135151
}
136-
}, [requestedLabels, baseChartData, dispatch])
152+
}, [requestedLabels, baseChartData])
137153

138154
// Step 3: Memoize the final chart data.
139155
// This filters the master cache and applies colors. It only re-runs if
@@ -153,18 +169,15 @@ export default function FLA() {
153169
})
154170
}, [baseChartData, customColors, requestedLabels])
155171

156-
// Step 4: Update the chart's state.
157-
// This is now very simple and just syncs the memoized data to the local state.
158-
useEffect(() => {
159-
setLocalChartData({ datasets: visibleDataWithColors })
160-
}, [visibleDataWithColors])
161-
162172
return (
163173
<Layout currentPage="fla">
164174
{messageFilters === null ? (
165175
<SelectFlightLog getLogSummary={getLogSummary} />
166176
) : (
167-
<MainDisplay closeLogFile={closeLogFile} chartData={chartData} />
177+
<MainDisplay
178+
closeLogFile={closeLogFile}
179+
chartData={{ datasets: visibleDataWithColors }}
180+
/>
168181
)}
169182
</Layout>
170183
)

0 commit comments

Comments
 (0)