Skip to content

Commit

Permalink
perf: indicator calculation sharding
Browse files Browse the repository at this point in the history
  • Loading branch information
liihuu committed Aug 20, 2024
1 parent 183f336 commit 4b3ab3b
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 48 deletions.
32 changes: 17 additions & 15 deletions src/Chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,8 @@ export default class ChartImp implements Chart {
if (isValid(callback)) {
logWarn('applyNewData', '', 'param `callback` has been deprecated since version 9.8.0, use `subscribeAction(\'onDataReady\')` instead.')
}
this._chartStore.addData(data, LoadDataType.Init, more).then(() => {}).catch(() => {}).finally(() => { callback?.() })
this._chartStore.addData(data, LoadDataType.Init, more)
callback?.()
}

/**
Expand All @@ -686,14 +687,16 @@ export default class ChartImp implements Chart {
*/
applyMoreData (data: KLineData[], more?: boolean, callback?: () => void): void {
logWarn('', '', 'Api `applyMoreData` has been deprecated since version 9.8.0.')
this._chartStore.addData(data, LoadDataType.Forward, more ?? true).then(() => {}).catch(() => {}).finally(() => { callback?.() })
this._chartStore.addData(data, LoadDataType.Forward, more ?? true)
callback?.()
}

updateData (data: KLineData, callback?: () => void): void {
if (isValid(callback)) {
logWarn('updateData', '', 'param `callback` has been deprecated since version 9.8.0, use `subscribeAction(\'onDataReady\')` instead.')
}
this._chartStore.addData(data).then(() => {}).catch(() => {}).finally(() => { callback?.() })
this._chartStore.addData(data)
callback?.()
}

/**
Expand All @@ -719,31 +722,30 @@ export default class ChartImp implements Chart {
let paneId = paneOptions?.id
const currentPane = this.getDrawPaneById(paneId ?? '')
if (currentPane !== null) {
this._chartStore.getIndicatorStore().addInstance(indicator, paneId ?? '', isStack ?? false).then(_ => {
const result = this._chartStore.getIndicatorStore().addInstance(indicator, paneId ?? '', isStack ?? false)
if (result) {
this._setPaneOptions(paneOptions ?? {}, currentPane.getAxisComponent().buildTicks(true) ?? false)
}).catch(_ => {})
}
} else {
paneId ??= createId(PaneIdConstants.INDICATOR)
const pane = this._createPane(IndicatorPane, paneId, paneOptions ?? {})
const height = paneOptions?.height ?? PANE_DEFAULT_HEIGHT
pane.setBounding({ height })
void this._chartStore.getIndicatorStore().addInstance(indicator, paneId, isStack ?? false).finally(() => {
const result = this._chartStore.getIndicatorStore().addInstance(indicator, paneId, isStack ?? false)
if (result) {
this.adjustPaneViewport(true, true, true, true, true)
callback?.()
})
}
}
return paneId ?? null
}

overrideIndicator (override: IndicatorCreate, paneId?: Nullable<string>, callback?: () => void): void {
this._chartStore.getIndicatorStore().override(override, paneId ?? null).then(
([onlyUpdateFlag, resizeFlag]) => {
if (onlyUpdateFlag || resizeFlag) {
this.adjustPaneViewport(false, resizeFlag, true, resizeFlag)
callback?.()
}
}
).catch(() => {})
const result = this._chartStore.getIndicatorStore().override(override, paneId ?? null)
if (result) {
this.adjustPaneViewport(false, false, true)
callback?.()
}
}

getIndicatorByPaneId (paneId?: string, name?: string): Nullable<Indicator> | Nullable<Map<string, Indicator>> | Map<string, Map<string, Indicator>> {
Expand Down
5 changes: 4 additions & 1 deletion src/common/TaskScheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
*/

import { requestIdleCallback, cancelIdleCallback, DEFAULT_REQUEST_ID } from './utils/compatible'

interface Task {
id: string
handler: () => void
}

export function generateTaskId (...params: string[]): string {
return params.join('_')
}

export default class TaskScheduler {
private readonly _tasks: Task[]

Expand Down
21 changes: 9 additions & 12 deletions src/store/ChartStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export default class ChartStore {
return this._visibleDataList
}

async addData (data: KLineData | KLineData[], type?: LoadDataType, more?: boolean): Promise<void> {
addData (data: KLineData | KLineData[], type?: LoadDataType, more?: boolean): void {
let success = false
let adjustFlag = false
let dataLengthChange = 0
Expand Down Expand Up @@ -284,16 +284,13 @@ export default class ChartStore {
}
}
if (success) {
try {
this._overlayStore.updatePointPosition(dataLengthChange, type)
if (adjustFlag) {
this._timeScaleStore.adjustVisibleRange()
this._tooltipStore.recalculateCrosshair(true)
await this._indicatorStore.calcInstance()
this._chart.adjustPaneViewport(false, true, true, true)
}
this._actionStore.execute(ActionType.OnDataReady)
} catch {}
this._overlayStore.updatePointPosition(dataLengthChange, type)
if (adjustFlag) {
this._timeScaleStore.adjustVisibleRange()
this._tooltipStore.recalculateCrosshair(true)
this._indicatorStore.calcInstance()
}
this._actionStore.execute(ActionType.OnDataReady)
}
}

Expand Down Expand Up @@ -322,7 +319,7 @@ export default class ChartStore {
)
) {
const cb: ((data: KLineData[], more?: boolean) => void) = (data: KLineData[], more?: boolean) => {
this.addData(data, params.type, more).then(() => {}).catch(() => {})
this.addData(data, params.type, more)
}
this._loading = true
this._loadDataCallback({ ...params, callback: cb })
Expand Down
57 changes: 37 additions & 20 deletions src/store/IndicatorStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ import { type IndicatorCreate, type Indicator } from '../component/Indicator'
import type IndicatorImp from '../component/Indicator'
import { IndicatorSeries } from '../component/Indicator'
import { getIndicatorClass } from '../extension/indicator/index'
import TaskScheduler, { generateTaskId } from '../common/TaskScheduler'

export default class IndicatorStore {
private readonly _chartStore: ChartStore
private readonly _instances = new Map<string, IndicatorImp[]>()

private readonly _scheduler = new TaskScheduler()

constructor (chartStore: ChartStore) {
this._chartStore = chartStore
}
Expand All @@ -40,13 +43,26 @@ export default class IndicatorStore {
}
}

async addInstance (indicator: IndicatorCreate, paneId: string, isStack: boolean): Promise<boolean> {
private _addTask (paneId: string, indicator: IndicatorImp): void {
this._scheduler.addTask({
id: generateTaskId(paneId, indicator.name),
handler: () => {
indicator.calcImp(this._chartStore.getDataList()).then(result => {
if (result) {
this._chartStore.getChart().adjustPaneViewport(false, true, true, true)
}
}).catch(() => {})
}
})
}

addInstance (indicator: IndicatorCreate, paneId: string, isStack: boolean): boolean {
const { name } = indicator
let paneInstances = this._instances.get(paneId)
if (isValid(paneInstances)) {
const instance = paneInstances.find(ins => ins.name === name)
if (isValid(instance)) {
return await Promise.reject(new Error('Duplicate indicators.'))
return false
}
}
if (!isValid(paneInstances)) {
Expand All @@ -58,12 +74,14 @@ export default class IndicatorStore {
this.synchronizeSeriesPrecision(instance)
instance.override(indicator)
if (!isStack) {
this.removeInstance(paneId)
paneInstances = []
}
paneInstances.push(instance)
this._instances.set(paneId, paneInstances)
this._sort(paneId)
return await instance.calcImp(this._chartStore.getDataList())
this._addTask(paneId, instance)
return true
}

getInstances (paneId: string): IndicatorImp[] {
Expand All @@ -77,10 +95,14 @@ export default class IndicatorStore {
if (isString(name)) {
const index = paneInstances.findIndex(ins => ins.name === name)
if (index > -1) {
this._scheduler.removeTask(generateTaskId(paneId, name))
paneInstances.splice(index, 1)
removed = true
}
} else {
paneInstances.forEach(instance => {
this._scheduler.removeTask(generateTaskId(paneId, instance.name))
})
this._instances.set(paneId, [])
removed = true
}
Expand All @@ -95,34 +117,31 @@ export default class IndicatorStore {
return this._instances.has(paneId)
}

async calcInstance (name?: string, paneId?: string): Promise<boolean> {
const tasks: Array<Promise<boolean>> = []
calcInstance (name?: string, paneId?: string): void {
if (isString(name)) {
if (isString(paneId)) {
const paneInstances = this._instances.get(paneId)
if (isValid(paneInstances)) {
const instance = paneInstances.find(ins => ins.name === name)
if (isValid(instance)) {
tasks.push(instance.calcImp(this._chartStore.getDataList()))
this._addTask(paneId, instance)
}
}
} else {
this._instances.forEach(paneInstances => {
this._instances.forEach((paneInstances, paneId) => {
const instance = paneInstances.find(ins => ins.name === name)
if (isValid(instance)) {
tasks.push(instance.calcImp(this._chartStore.getDataList()))
this._addTask(paneId, instance)
}
})
}
} else {
this._instances.forEach(paneInstances => {
this._instances.forEach((paneInstances, paneId) => {
paneInstances.forEach(instance => {
tasks.push(instance.calcImp(this._chartStore.getDataList()))
this._addTask(paneId, instance)
})
})
}
const result = await Promise.all(tasks)
return result.includes(true)
}

getInstanceByPaneId (paneId?: string, name?: string): Nullable<Indicator> | Nullable<Map<string, Indicator>> | Map<string, Map<string, Indicator>> {
Expand Down Expand Up @@ -175,7 +194,7 @@ export default class IndicatorStore {
}
}

async override (indicator: IndicatorCreate, paneId: Nullable<string>): Promise<[boolean, boolean]> {
override (indicator: IndicatorCreate, paneId: Nullable<string>): boolean {
const { name } = indicator
let instances = new Map<string, IndicatorImp[]>()
if (paneId !== null) {
Expand All @@ -186,10 +205,9 @@ export default class IndicatorStore {
} else {
instances = this._instances
}
let onlyUpdateFlag = false
const tasks: Array<Promise<boolean>> = []
let updateFlag = false
let sortFlag = false
instances.forEach(paneInstances => {
instances.forEach((paneInstances, paneId) => {
const instance = paneInstances.find(ins => ins.name === name)
if (isValid(instance)) {
instance.override(indicator)
Expand All @@ -198,18 +216,17 @@ export default class IndicatorStore {
sortFlag = true
}
if (calc) {
tasks.push(instance.calcImp(this._chartStore.getDataList()))
this._addTask(paneId, instance)
} else {
if (draw) {
onlyUpdateFlag = true
updateFlag = true
}
}
}
})
if (sortFlag) {
this._sort()
}
const result = await Promise.all(tasks)
return [onlyUpdateFlag, result.includes(true)]
return updateFlag
}
}

0 comments on commit 4b3ab3b

Please sign in to comment.