feat(orderbook): improve data handling performance (#605)

* feat(orderbook): improve data handling performance

* feat(orderbook): fix scrolling out of range
This commit is contained in:
Bartłomiej Głownia 2022-06-27 12:05:05 +02:00 committed by GitHub
parent f36d3af286
commit 98d3c47808
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 335 additions and 259 deletions

View File

@ -1,3 +1,4 @@
import produce from 'immer';
import { gql } from '@apollo/client';
import { makeDataProvider } from '@vegaprotocol/react-helpers';
import type {
@ -87,15 +88,16 @@ export const FILTERS_QUERY = gql`
`;
const update = (
draft: SimpleMarkets_markets[],
data: SimpleMarkets_markets[],
delta: SimpleMarketDataSub_marketData
) => {
const index = draft.findIndex((m) => m.id === delta.market.id);
if (index !== -1) {
draft[index].data = delta;
}
// @TODO - else push new market to draft
};
) =>
produce(data, (draft) => {
const index = draft.findIndex((m) => m.id === delta.market.id);
if (index !== -1) {
draft[index].data = delta;
}
// @TODO - else push new market to draft
});
const getData = (responseData: SimpleMarkets) => responseData.markets;
const getDelta = (

View File

@ -1,3 +1,4 @@
import produce from 'immer';
import { gql } from '@apollo/client';
import type {
Accounts,
@ -52,17 +53,18 @@ export const getId = (
) => `${data.type}-${data.asset.symbol}-${data.market?.id ?? 'null'}`;
const update = (
draft: Accounts_party_accounts[],
data: Accounts_party_accounts[],
delta: AccountSubscribe_accounts
) => {
const id = getId(delta);
const index = draft.findIndex((a) => getId(a) === id);
if (index !== -1) {
draft[index] = delta;
} else {
draft.push(delta);
}
};
) =>
produce(data, (draft) => {
const id = getId(delta);
const index = draft.findIndex((a) => getId(a) === id);
if (index !== -1) {
draft[index] = delta;
} else {
draft.push(delta);
}
});
const getData = (responseData: Accounts): Accounts_party_accounts[] | null =>
responseData.party ? responseData.party.accounts : null;
const getDelta = (

View File

@ -1,5 +1,4 @@
import { DepthChart } from 'pennant';
import { produce } from 'immer';
import throttle from 'lodash/throttle';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import {
@ -92,28 +91,31 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
if (!dataRef.current) {
return false;
}
dataRef.current = produce(dataRef.current, (draft) => {
if (delta.buy) {
draft.data.buy = updateLevels(
draft.data.buy,
delta.buy,
decimalPlacesRef.current
);
}
if (delta.sell) {
draft.data.sell = updateLevels(
draft.data.sell,
delta.sell,
decimalPlacesRef.current
);
}
draft.midPrice = delta.market.data?.staticMidPrice
dataRef.current = {
...dataRef.current,
midPrice: delta.market.data?.staticMidPrice
? formatMidPrice(
delta.market.data?.staticMidPrice,
decimalPlacesRef.current
)
: undefined;
});
: undefined,
data: {
buy: delta.buy
? updateLevels(
dataRef.current.data.buy,
delta.buy,
decimalPlacesRef.current
)
: dataRef.current.data.buy,
sell: delta.sell
? updateLevels(
dataRef.current.data.sell,
delta.sell,
decimalPlacesRef.current
)
: dataRef.current.data.sell,
},
};
setDepthDataThrottledRef.current(dataRef.current);
return true;
},

View File

@ -86,27 +86,31 @@ const sequenceNumbers: Record<string, number> = {};
const update: Update<
MarketDepth_market,
MarketDepthSubscription_marketDepthUpdate
> = (draft, delta, reload) => {
if (delta.market.id !== draft.id) {
return;
> = (data, delta, reload) => {
if (delta.market.id !== data.id) {
return data;
}
const sequenceNumber = Number(delta.sequenceNumber);
if (sequenceNumber <= sequenceNumbers[delta.market.id]) {
return;
return data;
}
/*
if (sequenceNumber - 1 !== sequenceNumbers[delta.market.id]) {
sequenceNumbers[delta.market.id] = 0;
reload();
return;
}
*/
sequenceNumbers[delta.market.id] = sequenceNumber;
Object.assign(draft.data, delta.market.data);
const updatedData = { ...data };
data.data = delta.market.data;
if (delta.buy) {
draft.depth.buy = updateLevels(draft.depth.buy ?? [], delta.buy);
updatedData.depth.buy = updateLevels(data.depth.buy ?? [], delta.buy);
}
if (delta.sell) {
draft.depth.sell = updateLevels(draft.depth.sell ?? [], delta.sell);
updatedData.depth.sell = updateLevels(data.depth.sell ?? [], delta.sell);
}
return updatedData;
};
const getData = (responseData: MarketDepth) => {

View File

@ -55,10 +55,8 @@ describe('compactRows', () => {
'1097': 3,
'1098': 2,
'1099': 1,
'1100': 0,
});
expect(orderbookRows[orderbookRows.length - 1].bidByLevel).toEqual({
'901': 0,
'902': 1,
'903': 2,
'904': 3,
@ -81,7 +79,7 @@ describe('compactRows', () => {
});
describe('updateLevels', () => {
const levels: MarketDepth_market_depth_sell[] = new Array(10)
let levels: MarketDepth_market_depth_sell[] = new Array(10)
.fill(null)
.map((n, i) => ({
__typename: 'PriceLevel',
@ -96,9 +94,9 @@ describe('updateLevels', () => {
volume: '0',
numberOfOrders: '0',
};
updateLevels(levels, [removeFirstRow]);
levels = updateLevels(levels, [removeFirstRow]);
expect(levels[0].price).toEqual('20');
updateLevels(levels, [removeFirstRow]);
levels = updateLevels(levels, [removeFirstRow]);
expect(levels[0].price).toEqual('20');
expect(updateLevels([], [removeFirstRow])).toEqual([]);
const addFirstRow: MarketDepthSubscription_marketDepthUpdate_sell = {
@ -107,7 +105,7 @@ describe('updateLevels', () => {
volume: '10',
numberOfOrders: '10',
};
updateLevels(levels, [addFirstRow]);
levels = updateLevels(levels, [addFirstRow]);
expect(levels[0].price).toEqual('10');
const addBeforeLastRow: MarketDepthSubscription_marketDepthUpdate_sell = {
__typename: 'PriceLevel',
@ -115,7 +113,7 @@ describe('updateLevels', () => {
volume: '95',
numberOfOrders: '95',
};
updateLevels(levels, [addBeforeLastRow]);
levels = updateLevels(levels, [addBeforeLastRow]);
expect(levels[levels.length - 2].price).toEqual('95');
const addAtTheEnd: MarketDepthSubscription_marketDepthUpdate_sell = {
__typename: 'PriceLevel',
@ -123,7 +121,7 @@ describe('updateLevels', () => {
volume: '115',
numberOfOrders: '115',
};
updateLevels(levels, [addAtTheEnd]);
levels = updateLevels(levels, [addAtTheEnd]);
expect(levels[levels.length - 1].price).toEqual('115');
const updateLastRow: MarketDepthSubscription_marketDepthUpdate_sell = {
__typename: 'PriceLevel',
@ -131,7 +129,7 @@ describe('updateLevels', () => {
volume: '116',
numberOfOrders: '115',
};
updateLevels(levels, [updateLastRow]);
levels = updateLevels(levels, [updateLastRow]);
expect(levels[levels.length - 1]).toEqual(updateLastRow);
expect(updateLevels([], [updateLastRow])).toEqual([updateLastRow]);
});

View File

@ -1,4 +1,3 @@
import produce from 'immer';
import groupBy from 'lodash/groupBy';
import { VolumeType } from '@vegaprotocol/react-helpers';
import { MarketTradingMode } from '@vegaprotocol/types';
@ -31,6 +30,8 @@ export interface OrderbookRowData {
cumulativeVol: CumulativeVol;
}
type PartialOrderbookRowData = Pick<OrderbookRowData, 'price' | 'ask' | 'bid'>;
export type OrderbookData = Partial<
Omit<MarketDepth_market_data, '__typename' | 'market'>
> & { rows: OrderbookRowData[] | null };
@ -75,21 +76,31 @@ const updateRelativeData = (data: OrderbookRowData[]) => {
});
};
export const createPartialRow = (
price: string,
volume = 0,
dataType?: VolumeType
): PartialOrderbookRowData => ({
price,
ask: dataType === VolumeType.ask ? volume : 0,
bid: dataType === VolumeType.bid ? volume : 0,
});
export const extendRow = (row: PartialOrderbookRowData): OrderbookRowData =>
Object.assign(row, {
cumulativeVol: {
ask: 0,
bid: 0,
},
askByLevel: row.ask ? { [row.price]: row.ask } : {},
bidByLevel: row.bid ? { [row.price]: row.bid } : {},
});
export const createRow = (
price: string,
volume = 0,
dataType?: VolumeType
): OrderbookRowData => ({
price,
ask: dataType === VolumeType.ask ? volume : 0,
bid: dataType === VolumeType.bid ? volume : 0,
cumulativeVol: {
ask: dataType === VolumeType.ask ? volume : 0,
bid: dataType === VolumeType.bid ? volume : 0,
},
askByLevel: dataType === VolumeType.ask ? { [price]: volume } : {},
bidByLevel: dataType === VolumeType.bid ? { [price]: volume } : {},
});
): OrderbookRowData => extendRow(createPartialRow(price, volume, dataType));
const mapRawData =
(dataType: VolumeType.ask | VolumeType.bid) =>
@ -99,8 +110,8 @@ const mapRawData =
| MarketDepthSubscription_marketDepthUpdate_sell
| MarketDepth_market_depth_buy
| MarketDepthSubscription_marketDepthUpdate_buy
): OrderbookRowData =>
createRow(data.price, Number(data.volume), dataType);
): PartialOrderbookRowData =>
createPartialRow(data.price, Number(data.volume), dataType);
/**
* @summary merges sell amd buy data, orders by price desc, group by price level, counts cumulative and relative values
@ -121,37 +132,38 @@ export const compactRows = (
resolution: number
) => {
// map raw sell data to OrderbookData
const askOrderbookData = [...(sell ?? [])].map<OrderbookRowData>(
const askOrderbookData = [...(sell ?? [])].map<PartialOrderbookRowData>(
mapRawData(VolumeType.ask)
);
// map raw buy data to OrderbookData
const bidOrderbookData = [...(buy ?? [])].map<OrderbookRowData>(
const bidOrderbookData = [...(buy ?? [])].map<PartialOrderbookRowData>(
mapRawData(VolumeType.bid)
);
// group by price level
const groupedByLevel = groupBy<OrderbookRowData>(
const groupedByLevel = groupBy<PartialOrderbookRowData>(
[...askOrderbookData, ...bidOrderbookData],
(row) => getPriceLevel(row.price, resolution)
);
// create single OrderbookData from grouped OrderbookData[], sum volumes and atore volume by level
const orderbookData = Object.keys(groupedByLevel).reduce<OrderbookRowData[]>(
(rows, price) =>
rows.concat(
groupedByLevel[price].reduce<OrderbookRowData>(
(a, c) => ({
...a,
ask: a.ask + c.ask,
askByLevel: Object.assign(a.askByLevel, c.askByLevel),
bid: (a.bid ?? 0) + (c.bid ?? 0),
bidByLevel: Object.assign(a.bidByLevel, c.bidByLevel),
}),
createRow(price)
)
),
[]
);
const orderbookData: OrderbookRowData[] = [];
Object.keys(groupedByLevel).forEach((price) => {
const row = extendRow(
groupedByLevel[price].pop() as PartialOrderbookRowData
);
row.price = price;
let subRow: PartialOrderbookRowData | undefined;
// eslint-disable-next-line no-cond-assign
while ((subRow = groupedByLevel[price].pop())) {
row.ask += subRow.ask;
row.bid += subRow.bid;
if (subRow.ask) {
row.askByLevel[subRow.price] = subRow.ask;
}
if (subRow.bid) {
row.bidByLevel[subRow.price] = subRow.bid;
}
}
orderbookData.push(row);
});
// order by price, it's safe to cast to number price diff should not exceed Number.MAX_SAFE_INTEGER
orderbookData.sort((a, b) => Number(BigInt(b.price) - BigInt(a.price)));
// count cumulative volumes
@ -163,11 +175,9 @@ export const compactRows = (
(i !== 0 ? orderbookData[i - 1].cumulativeVol.bid : 0);
}
for (let i = maxIndex; i >= 0; i--) {
if (!orderbookData[i].cumulativeVol.ask) {
orderbookData[i].cumulativeVol.ask =
orderbookData[i].ask +
(i !== maxIndex ? orderbookData[i + 1].cumulativeVol.ask : 0);
}
orderbookData[i].cumulativeVol.ask =
orderbookData[i].ask +
(i !== maxIndex ? orderbookData[i + 1].cumulativeVol.ask : 0);
}
}
// count relative volumes
@ -186,13 +196,13 @@ export const compactRows = (
*/
const partiallyUpdateCompactedRows = (
dataType: VolumeType,
draft: OrderbookRowData[],
data: OrderbookRowData[],
delta:
| MarketDepthSubscription_marketDepthUpdate_sell
| MarketDepthSubscription_marketDepthUpdate_buy,
resolution: number,
modifiedIndex: number
) => {
): [number, OrderbookRowData[]] => {
const { price } = delta;
const volume = Number(delta.volume);
const priceLevel = getPriceLevel(price, resolution);
@ -201,28 +211,36 @@ const partiallyUpdateCompactedRows = (
const oppositeVolKey = isAskDataType ? 'bid' : 'ask';
const volByLevelKey = isAskDataType ? 'askByLevel' : 'bidByLevel';
const resolveModifiedIndex = isAskDataType ? Math.max : Math.min;
let index = draft.findIndex((data) => data.price === priceLevel);
let index = data.findIndex((row) => row.price === priceLevel);
if (index !== -1) {
modifiedIndex = resolveModifiedIndex(modifiedIndex, index);
draft[index][volKey] =
draft[index][volKey] - (draft[index][volByLevelKey][price] || 0) + volume;
draft[index][volByLevelKey][price] = volume;
data[index] = {
...data[index],
[volKey]:
data[index][volKey] - (data[index][volByLevelKey][price] || 0) + volume,
[volByLevelKey]: {
...data[index][volByLevelKey],
[price]: volume,
},
};
return [modifiedIndex, [...data]];
} else {
const newData: OrderbookRowData = createRow(priceLevel, volume, dataType);
index = draft.findIndex((data) => BigInt(data.price) < BigInt(priceLevel));
index = data.findIndex((row) => BigInt(row.price) < BigInt(priceLevel));
if (index !== -1) {
draft.splice(index, 0, newData);
newData.cumulativeVol[oppositeVolKey] =
draft[index + (isAskDataType ? -1 : 1)]?.cumulativeVol[
oppositeVolKey
] ?? 0;
data[index + (isAskDataType ? 0 : 1)]?.cumulativeVol[oppositeVolKey] ??
0;
modifiedIndex = resolveModifiedIndex(modifiedIndex, index);
return [
modifiedIndex,
[...data.slice(0, index), newData, ...data.slice(index)],
];
} else {
draft.push(newData);
modifiedIndex = draft.length - 1;
modifiedIndex = data.length - 1;
return [modifiedIndex, [...data, newData]];
}
}
return modifiedIndex;
};
/**
@ -239,59 +257,66 @@ export const updateCompactedRows = (
sell: MarketDepthSubscription_marketDepthUpdate_sell[] | null,
buy: MarketDepthSubscription_marketDepthUpdate_buy[] | null,
resolution: number
) =>
produce(rows, (draft) => {
let sellModifiedIndex = -1;
sell?.forEach((delta) => {
sellModifiedIndex = partiallyUpdateCompactedRows(
VolumeType.ask,
draft,
delta,
resolution,
sellModifiedIndex
);
});
let buyModifiedIndex = draft.length;
buy?.forEach((delta) => {
buyModifiedIndex = partiallyUpdateCompactedRows(
VolumeType.bid,
draft,
delta,
resolution,
buyModifiedIndex
);
});
// update cummulative ask only below hihgest modified price level
if (sellModifiedIndex !== -1) {
for (let i = Math.min(sellModifiedIndex, draft.length - 2); i >= 0; i--) {
draft[i].cumulativeVol.ask =
draft[i + 1].cumulativeVol.ask + draft[i].ask;
}
}
// update cummulative bid only above lowest modified price level
if (buyModifiedIndex !== draft.length) {
for (
let i = Math.max(buyModifiedIndex, 1), l = draft.length;
i < l;
i++
) {
draft[i].cumulativeVol.bid =
draft[i - 1].cumulativeVol.bid + draft[i].bid;
}
}
let index = 0;
// remove levels that do not have any volume
while (index < draft.length) {
if (!draft[index].ask && !draft[index].bid) {
draft.splice(index, 1);
} else {
index += 1;
}
}
// count relative volumes
updateRelativeData(draft);
) => {
let sellModifiedIndex = -1;
let data = [...rows];
sell?.forEach((delta) => {
[sellModifiedIndex, data] = partiallyUpdateCompactedRows(
VolumeType.ask,
data,
delta,
resolution,
sellModifiedIndex
);
});
let buyModifiedIndex = data.length;
buy?.forEach((delta) => {
[buyModifiedIndex, data] = partiallyUpdateCompactedRows(
VolumeType.bid,
data,
delta,
resolution,
buyModifiedIndex
);
});
// update cummulative ask only below hihgest modified price level
if (sellModifiedIndex !== -1) {
for (let i = Math.min(sellModifiedIndex, data.length - 2); i >= 0; i--) {
data[i] = {
...data[i],
cumulativeVol: {
...data[i].cumulativeVol,
ask: data[i + 1].cumulativeVol.ask + data[i].ask,
},
};
}
}
// update cummulative bid only above lowest modified price level
if (buyModifiedIndex !== data.length) {
for (let i = Math.max(buyModifiedIndex, 1), l = data.length; i < l; i++) {
data[i] = {
...data[i],
cumulativeVol: {
...data[i].cumulativeVol,
bid: data[i - 1].cumulativeVol.bid + data[i].bid,
},
};
}
}
let index = 0;
// remove levels that do not have any volume
while (index < data.length) {
if (!data[index].ask && !data[index].bid) {
data.splice(index, 1);
} else {
index += 1;
}
}
// count relative volumes
updateRelativeData(data);
return data;
};
export const mapMarketData = (
data:
@ -319,23 +344,24 @@ export const mapMarketData = (
* @returns
*/
export const updateLevels = (
levels: (MarketDepth_market_depth_buy | MarketDepth_market_depth_sell)[],
draft: (MarketDepth_market_depth_buy | MarketDepth_market_depth_sell)[],
updates: (
| MarketDepthSubscription_marketDepthUpdate_buy
| MarketDepthSubscription_marketDepthUpdate_sell
)[]
) => {
const levels = [...draft];
updates.forEach((update) => {
let index = levels.findIndex((level) => level.price === update.price);
if (index !== -1) {
if (update.volume === '0') {
levels.splice(index, 1);
} else {
Object.assign(levels[index], update);
levels[index] = update;
}
} else if (update.volume !== '0') {
index = levels.findIndex(
(level) => Number(level.price) > Number(update.price)
(level) => BigInt(level.price) > BigInt(update.price)
);
if (index !== -1) {
levels.splice(index, 0, update);

View File

@ -1,5 +1,4 @@
import throttle from 'lodash/throttle';
import produce from 'immer';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { Orderbook } from './orderbook';
import { useDataProvider } from '@vegaprotocol/react-helpers';
@ -25,27 +24,52 @@ export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
rows: null,
});
const dataRef = useRef<OrderbookData>({ rows: null });
const setOrderbookDataThrottled = useRef(throttle(setOrderbookData, 1000));
const deltaRef = useRef<MarketDepthSubscription_marketDepthUpdate>();
const updateOrderbookData = useRef(
throttle(() => {
if (!deltaRef.current) {
return;
}
dataRef.current = {
...deltaRef.current.market.data,
...mapMarketData(deltaRef.current.market.data, resolutionRef.current),
rows: updateCompactedRows(
dataRef.current.rows ?? [],
deltaRef.current.sell,
deltaRef.current.buy,
resolutionRef.current
),
};
deltaRef.current = undefined;
setOrderbookData(dataRef.current);
}, 1000)
);
const update = useCallback(
(delta: MarketDepthSubscription_marketDepthUpdate) => {
if (!dataRef.current.rows) {
return false;
}
dataRef.current = produce(dataRef.current, (draft) => {
Object.assign(draft, delta.market.data);
draft.rows = updateCompactedRows(
draft.rows ?? [],
delta.sell,
delta.buy,
resolutionRef.current
);
Object.assign(
draft,
mapMarketData(delta.market.data, resolutionRef.current)
);
});
setOrderbookDataThrottled.current(dataRef.current);
if (deltaRef.current) {
deltaRef.current.market = delta.market;
if (delta.sell) {
if (deltaRef.current.sell) {
deltaRef.current.sell.push(...delta.sell);
} else {
deltaRef.current.sell = delta.sell;
}
}
if (delta.buy) {
if (deltaRef.current.buy) {
deltaRef.current.buy.push(...delta.buy);
} else {
deltaRef.current.buy = delta.buy;
}
}
} else {
deltaRef.current = delta;
}
updateOrderbookData.current();
return true;
},
// using resolutionRef.current to avoid using resolution as a dependency - it will cause data provider restart on resolution change

View File

@ -176,7 +176,10 @@ export const Orderbook = ({
// adjust to current rows position
scrollTop +=
(scrollTopRef.current % rowHeight) - (scrollTop % rowHeight);
const priceCenterScrollOffset = Math.max(0, Math.min(scrollTop));
const priceCenterScrollOffset = Math.max(
0,
Math.min(scrollTop, numberOfRows * rowHeight - viewportHeight)
);
if (scrollTopRef.current !== priceCenterScrollOffset) {
updateScrollOffset(priceCenterScrollOffset);
scrollTopRef.current = priceCenterScrollOffset;
@ -184,7 +187,13 @@ export const Orderbook = ({
}
}
},
[maxPriceLevel, resolution, viewportHeight, updateScrollOffset]
[
maxPriceLevel,
resolution,
viewportHeight,
numberOfRows,
updateScrollOffset,
]
);
useEffect(() => {
@ -199,23 +208,36 @@ export const Orderbook = ({
return;
}
priceInCenter.current = undefined;
setLockOnMidPrice(true);
scrollToPrice(
getPriceLevel(
BigInt(bestStaticOfferPrice) +
(BigInt(bestStaticBidPrice) - BigInt(bestStaticOfferPrice)) /
BigInt(2),
resolution
)
let midPrice = getPriceLevel(
BigInt(bestStaticOfferPrice) +
(BigInt(bestStaticBidPrice) - BigInt(bestStaticOfferPrice)) / BigInt(2),
resolution
);
}, [bestStaticOfferPrice, bestStaticBidPrice, scrollToPrice, resolution]);
if (BigInt(midPrice) > BigInt(maxPriceLevel)) {
midPrice = maxPriceLevel;
} else {
const minPriceLevel =
BigInt(maxPriceLevel) - BigInt(Math.floor(numberOfRows * resolution));
if (BigInt(midPrice) < minPriceLevel) {
midPrice = minPriceLevel.toString();
}
}
scrollToPrice(midPrice);
setLockOnMidPrice(true);
}, [
bestStaticOfferPrice,
bestStaticBidPrice,
scrollToPrice,
resolution,
maxPriceLevel,
numberOfRows,
]);
// adjust scroll position to keep selected price in center
useLayoutEffect(() => {
if (resolutionRef.current !== resolution) {
priceInCenter.current = undefined;
resolutionRef.current = resolution;
setLockOnMidPrice(true);
}
if (priceInCenter.current) {
scrollToPrice(priceInCenter.current);
@ -238,21 +260,19 @@ export const Orderbook = ({
return () => window.removeEventListener('resize', handleResize);
}, []);
const renderedRows = useMemo(() => {
let offset = Math.max(0, Math.round(scrollOffset / rowHeight));
const prependingBufferSize = Math.min(bufferSize, offset);
offset -= prependingBufferSize;
const viewportSize = Math.round(viewportHeight / rowHeight);
const limit = Math.min(
prependingBufferSize + viewportSize + bufferSize,
numberOfRows - offset
);
return {
offset,
limit,
data: getRowsToRender(rows, resolution, offset, limit),
};
}, [rows, scrollOffset, resolution, viewportHeight, numberOfRows]);
let offset = Math.max(0, Math.round(scrollOffset / rowHeight));
const prependingBufferSize = Math.min(bufferSize, offset);
offset -= prependingBufferSize;
const viewportSize = Math.round(viewportHeight / rowHeight);
const limit = Math.min(
prependingBufferSize + viewportSize + bufferSize,
numberOfRows - offset
);
const renderedRows = {
offset,
limit,
data: getRowsToRender(rows, resolution, offset, limit),
};
const paddingTop = renderedRows.offset * rowHeight;
const paddingBottom =

View File

@ -1,3 +1,4 @@
import produce from 'immer';
import { gql } from '@apollo/client';
import type {
Markets,
@ -90,13 +91,14 @@ const MARKET_DATA_SUB = gql`
}
`;
const update = (draft: Markets_markets[], delta: MarketDataSub_marketData) => {
const index = draft.findIndex((m) => m.id === delta.market.id);
if (index !== -1) {
draft[index].data = delta;
}
// @TODO - else push new market to draft
};
const update = (data: Markets_markets[], delta: MarketDataSub_marketData) =>
produce(data, (draft) => {
const index = draft.findIndex((m) => m.id === delta.market.id);
if (index !== -1) {
draft[index].data = delta;
}
// @TODO - else push new market to draft
});
const getData = (responseData: Markets): Markets_markets[] | null =>
responseData.markets;
const getDelta = (subscriptionData: MarketDataSub): MarketDataSub_marketData =>

View File

@ -1,3 +1,4 @@
import produce from 'immer';
import { gql } from '@apollo/client';
import { makeDataProvider } from '@vegaprotocol/react-helpers';
import type { OrderFields } from './__generated__/OrderFields';
@ -78,19 +79,20 @@ export const prepareIncomingOrders = (delta: OrderFields[]) => {
return incoming;
};
const update = (draft: OrderFields[], delta: OrderFields[]) => {
const incoming = prepareIncomingOrders(delta);
const update = (data: OrderFields[], delta: OrderFields[]) =>
produce(data, (draft) => {
const incoming = prepareIncomingOrders(delta);
// Add or update incoming orders
incoming.forEach((order) => {
const index = draft.findIndex((o) => o.id === order.id);
if (index === -1) {
draft.unshift(order);
} else {
draft[index] = order;
}
// Add or update incoming orders
incoming.forEach((order) => {
const index = draft.findIndex((o) => o.id === order.id);
if (index === -1) {
draft.unshift(order);
} else {
draft[index] = order;
}
});
});
};
const getData = (responseData: Orders): Orders_party_orders[] | null =>
responseData?.party?.orders || null;

View File

@ -1,3 +1,4 @@
import produce from 'immer';
import { gql } from '@apollo/client';
import type {
Positions,
@ -75,16 +76,17 @@ export const POSITIONS_SUB = gql`
`;
const update = (
draft: Positions_party_positions[],
data: Positions_party_positions[],
delta: PositionSubscribe_positions
) => {
const index = draft.findIndex((m) => m.market.id === delta.market.id);
if (index !== -1) {
draft[index] = delta;
} else {
draft.push(delta);
}
};
) =>
produce(data, (draft) => {
const index = draft.findIndex((m) => m.market.id === delta.market.id);
if (index !== -1) {
draft[index] = delta;
} else {
draft.push(delta);
}
});
const getData = (responseData: Positions): Positions_party_positions[] | null =>
responseData.party ? responseData.party.positions : null;
const getDelta = (

View File

@ -1,5 +1,3 @@
import { produce } from 'immer';
import type { Draft } from 'immer';
import type {
ApolloClient,
DocumentNode,
@ -34,11 +32,7 @@ export interface Subscribe<Data, Delta> {
type Query<Result> = DocumentNode | TypedDocumentNode<Result, any>;
export interface Update<Data, Delta> {
(
draft: Draft<Data>,
delta: Delta,
reload: (forceReset?: boolean) => void
): void;
(data: Data, delta: Delta, reload: (forceReset?: boolean) => void): Data;
}
interface GetData<QueryData, Data> {
@ -105,14 +99,12 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
data = getData(res.data);
// if there was some updates received from subscription during initial query loading apply them on just received data
if (data && updateQueue && updateQueue.length > 0) {
data = produce(data, (draft) => {
while (updateQueue.length) {
const delta = updateQueue.shift();
if (delta) {
update(draft, delta, reload);
}
while (updateQueue.length) {
const delta = updateQueue.shift();
if (delta) {
data = update(data, delta, reload);
}
});
}
}
} catch (e) {
// if error will occur data provider stops subscription
@ -168,9 +160,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
if (loading || !data) {
updateQueue.push(delta);
} else {
const newData = produce(data, (draft) => {
update(draft, delta, reload);
});
const newData = update(data, delta, reload);
if (newData === data) {
return;
}

View File

@ -4,6 +4,7 @@ import type { TradeFields } from './__generated__/TradeFields';
import type { Trades } from './__generated__/Trades';
import type { TradesSub } from './__generated__/TradesSub';
import orderBy from 'lodash/orderBy';
import produce from 'immer';
export const MAX_TRADES = 50;
@ -52,17 +53,18 @@ export const sortTrades = (trades: TradeFields[]) => {
);
};
const update = (draft: TradeFields[], delta: TradeFields[]) => {
const incoming = sortTrades(delta);
const update = (data: TradeFields[], delta: TradeFields[]) =>
produce(data, (draft) => {
const incoming = sortTrades(delta);
// Add new trades to the top
draft.unshift(...incoming);
// Add new trades to the top
draft.unshift(...incoming);
// Remove old trades from the bottom
if (draft.length > MAX_TRADES) {
draft.splice(MAX_TRADES, draft.length - MAX_TRADES);
}
};
// Remove old trades from the bottom
if (draft.length > MAX_TRADES) {
draft.splice(MAX_TRADES, draft.length - MAX_TRADES);
}
});
const getData = (responseData: Trades): TradeFields[] | null =>
responseData.market ? responseData.market.trades : null;