Skip to content

Commit

Permalink
perf: batch gcode file metadata requests (mainsail-crew#1737)
Browse files Browse the repository at this point in the history
this results in fewer re-renders of the table, significantly reducing
load time

Signed-off-by: Nathan Regner <[email protected]>
  • Loading branch information
nathanregner authored Jan 26, 2024
1 parent d34aa64 commit 65a62cb
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 66 deletions.
9 changes: 5 additions & 4 deletions src/components/panels/GcodefilesPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1235,11 +1235,12 @@ export default class GcodefilesPanel extends Mixins(BaseMixin, ControlMixin) {
refreshMetadata(data: FileStateGcodefile[]) {
const items = data.filter((file) => !file.isDirectory && !file.metadataRequested && !file.metadataPulled)
items.forEach((file: FileStateGcodefile) => {
this.$store.dispatch('files/requestMetadata', {
this.$store.dispatch(
'files/requestMetadata',
items.map((file: FileStateGcodefile) => ({
filename: 'gcodes' + this.currentPath + '/' + file.filename,
})
})
}))
)
}
clickRow(item: FileStateGcodefile, force = false) {
Expand Down
10 changes: 5 additions & 5 deletions src/components/panels/Status/Gcodefiles.vue
Original file line number Diff line number Diff line change
Expand Up @@ -343,12 +343,12 @@ export default class StatusPanelGcodefiles extends Mixins(BaseMixin, ControlMixi
const requestItems = gcodes.filter(
(file: FileStateGcodefile) => !file.metadataRequested && !file.metadataPulled
)
requestItems.forEach((file: FileStateGcodefile) => {
this.$store.dispatch('files/requestMetadata', {
this.$store.dispatch(
'files/requestMetadata',
requestItems.map((file: FileStateGcodefile) => ({
filename: 'gcodes/' + file.filename,
})
})
}))
)
return gcodes
}
Expand Down
137 changes: 89 additions & 48 deletions src/plugins/webSocketClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class WebSocketClient {
reconnectInterval = 1000
reconnects = 0
keepAliveTimeout = 1000
messageId: number = 0
timerId: number | null = null
store: Store<RootState> | null = null
waits: Wait[] = []
Expand All @@ -25,6 +26,54 @@ export class WebSocketClient {
this.url = url
}

handleMessage(data: any) {
const wait = this.getWaitById(data.id)

// report error messages
if (data.error?.message) {
// only report errors, if not disconnected and no init component
if (data.error?.message !== 'Klippy Disconnected' && !wait?.action?.startsWith('server/')) {
window.console.error(`Response Error: ${data.error.message} (${wait?.action ?? 'no action'})`)
}

if (wait?.id) {
const modulename = wait.action?.split('/')[1] ?? null

if (modulename && wait.action?.startsWith('server/') && initableServerComponents.includes(modulename)) {
const component = wait.action.replace('server/', '').split('/')[0]
window.console.error(`init server component ${component} failed`)
this.store?.dispatch('server/addFailedInitComponent', component)
this.store?.dispatch('socket/removeInitComponent', `server/${component}/`)
}

this.removeWaitById(wait.id)
}

return
}

// pass it to socket/onMessage, if no wait exists
if (!wait) {
this.store?.dispatch('socket/onMessage', data)
return
}

// pass result to action
if (wait?.action) {
let result = data.result
if (result === 'ok') result = { result: result }
if (typeof result === 'string') result = { result: result }

const preload = {}
if (wait.actionPayload) Object.assign(preload, wait.actionPayload)
Object.assign(preload, { requestParams: wait.params })
Object.assign(preload, result)
this.store?.dispatch(wait.action, preload)
}

this.removeWaitById(wait.id)
}

async connect() {
this.store?.dispatch('socket/setData', {
isConnecting: true,
Expand Down Expand Up @@ -58,55 +107,13 @@ export class WebSocketClient {
if (this.store === null) return

const data = JSON.parse(msg.data)
const wait = this.getWaitById(data.id)

// report error messages
if (data.error?.message) {
// only report errors, if not disconnected and no init component
if (data.error?.message !== 'Klippy Disconnected' && !wait?.action?.startsWith('server/')) {
window.console.error(`Response Error: ${data.error.message} (${wait?.action ?? 'no action'})`)
}

if (wait?.id) {
const modulename = wait.action?.split('/')[1] ?? null

if (
modulename &&
wait.action?.startsWith('server/') &&
initableServerComponents.includes(modulename)
) {
const component = wait.action.replace('server/', '').split('/')[0]
window.console.error(`init server component ${component} failed`)
this.store?.dispatch('server/addFailedInitComponent', component)
this.store?.dispatch('socket/removeInitComponent', `server/${component}/`)
}

this.removeWaitById(wait.id)
if (Array.isArray(data)) {
for (const message of data) {
this.handleMessage(message)
}

return
} else {
this.handleMessage(data)
}

// pass it to socket/onMessage, if no wait exists
if (!wait) {
this.store?.dispatch('socket/onMessage', data)
return
}

// pass result to action
if (wait?.action) {
let result = data.result
if (result === 'ok') result = { result: result }
if (typeof result === 'string') result = { result: result }

const preload = {}
if (wait.actionPayload) Object.assign(preload, wait.actionPayload)
Object.assign(preload, { requestParams: wait.params })
Object.assign(preload, result)
this.store?.dispatch(wait.action, preload)
}

this.removeWaitById(wait.id)
}
}

Expand All @@ -130,7 +137,7 @@ export class WebSocketClient {
emit(method: string, params: Params, options: emitOptions = {}): void {
if (this.instance?.readyState !== WebSocket.OPEN) return

const id = Math.floor(Math.random() * 10000) + 1
const id = this.messageId++
this.waits.push({
id: id,
params: params,
Expand All @@ -150,6 +157,33 @@ export class WebSocketClient {
})
)
}

emitBatch(messages: BatchMessage[]): void {
if (messages.length === 0) return
if (this.instance?.readyState !== WebSocket.OPEN) return

const body = []
for (const { method, params, emitOptions = {} } of messages) {
const id = this.messageId++
this.waits.push({
id: id,
params: params,
action: emitOptions.action ?? null,
actionPayload: emitOptions.actionPayload ?? {},
loading: emitOptions.loading ?? null,
})

if (emitOptions.loading) this.store?.dispatch('socket/addLoading', { name: emitOptions.loading })
body.push({
jsonrpc: '2.0',
method,
params,
id,
})
}

this.instance.send(JSON.stringify(body))
}
}

export function WebSocketPlugin(Vue: typeof _Vue, options: WebSocketPluginOptions): void {
Expand All @@ -169,6 +203,13 @@ export interface WebSocketClient {
connect(): void
close(): void
emit(method: string, params: Params, emitOptions: emitOptions): void
emitBatch(messages: BatchMessage[]): void
}

export interface BatchMessage {
method: string
params: Params
emitOptions: emitOptions
}

export interface Wait {
Expand Down
34 changes: 25 additions & 9 deletions src/store/files/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { RootState } from '@/store/types'
import i18n from '@/plugins/i18n'
import { hiddenDirectories, validGcodeExtensions } from '@/store/variables'
import axios from 'axios'
import { BatchMessage } from '@/plugins/webSocketClient'

export const actions: ActionTree<FileState, RootState> = {
reset({ commit }) {
Expand Down Expand Up @@ -159,13 +160,26 @@ export const actions: ActionTree<FileState, RootState> = {
}
},

requestMetadata({ commit }, payload: { filename: string }) {
const rootPath = payload.filename.slice(0, payload.filename.indexOf('/'))
if (rootPath === 'gcodes') {
const requestFilename = payload.filename.slice(7)
commit('setMetadataRequested', { filename: requestFilename })
Vue.$socket.emit('server.files.metadata', { filename: requestFilename }, { action: 'files/getMetadata' })
requestMetadata({ commit }, payload: { filename: string }[]) {
// request file metadata in batches to reduce the number of table re-renders when responses are received
let messages: BatchMessage[] = []
for (const { filename } of payload) {
if (messages.length >= 100) {
Vue.$socket.emitBatch(messages)
messages = []
}
const rootPath = filename.slice(0, filename.indexOf('/'))
if (rootPath === 'gcodes') {
const requestFilename = filename.slice(7)
commit('setMetadataRequested', { filename: requestFilename })
messages.push({
method: 'server.files.metadata',
params: { filename: requestFilename },
emitOptions: { action: 'files/getMetadata' },
})
}
}
Vue.$socket.emitBatch(messages)
},

getMetadata({ commit, rootState }, payload) {
Expand Down Expand Up @@ -203,9 +217,11 @@ export const actions: ActionTree<FileState, RootState> = {
payload.item.root === 'gcodes' &&
validGcodeExtensions.includes(payload.item.path.slice(payload.item.path.lastIndexOf('.')))
) {
await dispatch('requestMetadata', {
filename: 'gcodes/' + payload.item.path,
})
await dispatch('requestMetadata', [
{
filename: 'gcodes/' + payload.item.path,
},
])
}
break

Expand Down

0 comments on commit 65a62cb

Please sign in to comment.