Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 71 additions & 16 deletions src/reactviews/pages/QueryResult/table/asyncDataView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

// import * as vscode from 'vscode';
import { IDisposableDataProvider } from "./dataProvider";
import { v4 as uuid } from "uuid";

export interface IObservableCollection<T> {
getLength(): number;
Expand All @@ -22,6 +22,11 @@ class DataWindow<T> {
private _data: T[] | undefined;
private _length: number = 0;
private _offsetFromDataSource: number = -1;
private _currentRequestId: string = uuid();
private _debounceTimeout: NodeJS.Timeout | undefined;
private readonly _getRowsDebounceDelayMs: number = 50;
private _lastPositionTime: number = 0;
private _consecutivePositionCount: number = 0;

// private cancellationToken = new CancellationTokenSource();

Expand All @@ -33,6 +38,10 @@ class DataWindow<T> {

dispose() {
this._data = undefined;
if (this._debounceTimeout) {
clearTimeout(this._debounceTimeout);
this._debounceTimeout = undefined;
}
// this.cancellationToken.cancel();
}

Expand All @@ -55,28 +64,71 @@ class DataWindow<T> {
return this._data[index - this._offsetFromDataSource];
}

public positionWindow(offset: number, length: number): void {
public positionWindow(offset: number, length: number, totalItems: number): void {
offset = Math.max(0, offset); // Ensure offset is never negative
offset = Math.min(offset, totalItems); // Ensure offset is within total items

length = Math.max(0, length); // Ensure length is at least 0
length = Math.min(length, totalItems - offset); // Ensure length doesn't exceed total items

this._offsetFromDataSource = offset;
this._length = length;
this._data = undefined;

// this.cancellationToken.cancel();
// this.cancellationToken = new CancellationTokenSource();
// const currentCancellation = this.cancellationToken;
// Increment request ID to invalidate any pending requests
this._currentRequestId = uuid();
const currentRequestId = this._currentRequestId;

if (length === 0) {
return;
}

this.loadFunction(offset, length).then((data) => {
// if (!currentCancellation.token.isCancellationRequested) {
this._data = data;
this.loadCompleteCallback(
this._offsetFromDataSource,
this._offsetFromDataSource + this._length,
);
// }
});
// Detect if this is rapid continuous scrolling or a jump to position
const now = Date.now();
const timeSinceLastPosition = now - this._lastPositionTime;
this._lastPositionTime = now;

// If positions are happening very rapidly (< 100ms apart), it's continuous scrolling
if (timeSinceLastPosition < 100) {
this._consecutivePositionCount++;
} else {
this._consecutivePositionCount = 0;
}

// Clear any pending debounced requests
if (this._debounceTimeout) {
clearTimeout(this._debounceTimeout);
}

const executeLoad = () => {
// Double-check that this request is still current
if (currentRequestId !== this._currentRequestId) {
return; // Window was repositioned again, skip this request
}

this.loadFunction(offset, length).then((data) => {
// Only apply data if this request is still current (window hasn't been repositioned)
if (currentRequestId === this._currentRequestId) {
this._data = data;
this.loadCompleteCallback(
this._offsetFromDataSource,
this._offsetFromDataSource + this._length,
);
}
// Otherwise, ignore this outdated response to prevent flickering
});
};

// If rapid continuous scrolling (3+ rapid events), debounce to reduce load
if (this._consecutivePositionCount >= 3) {
this._debounceTimeout = setTimeout(() => {
this._debounceTimeout = undefined;
executeLoad();
}, this._getRowsDebounceDelayMs);
} else {
// Otherwise, load immediately (scrollbar drag, single scroll, or first few scrolls)
executeLoad();
}
}
}

Expand Down Expand Up @@ -158,6 +210,7 @@ export class VirtualizedCollection<T extends Slick.SlickData> implements IObserv
this._bufferWindowBefore.positionWindow(
newWindowOffset,
this._window.getStartIndex() - newWindowOffset,
this.length,
);
} else if (start >= this._bufferWindowAfter.getStartIndex()) {
// scroll down, shift down
Expand All @@ -171,7 +224,7 @@ export class VirtualizedCollection<T extends Slick.SlickData> implements IObserv
);
let newWindowLength = Math.min(this.length - newWindowOffset, this.windowSize);

this._bufferWindowAfter.positionWindow(newWindowOffset, newWindowLength);
this._bufferWindowAfter.positionWindow(newWindowOffset, newWindowLength, this.length);
}

return currentData;
Expand Down Expand Up @@ -204,17 +257,19 @@ export class VirtualizedCollection<T extends Slick.SlickData> implements IObserv
this._bufferWindowBefore.positionWindow(
bufferWindowBeforeStart,
bufferWindowBeforeEnd - bufferWindowBeforeStart,
this.length,
);

let mainWindowStart = bufferWindowBeforeEnd;
let mainWindowEnd = Math.min(mainWindowStart + this.windowSize, this.length);
this._window.positionWindow(mainWindowStart, mainWindowEnd - mainWindowStart);
this._window.positionWindow(mainWindowStart, mainWindowEnd - mainWindowStart, this.length);

let bufferWindowAfterStart = mainWindowEnd;
let bufferWindowAfterEnd = Math.min(bufferWindowAfterStart + this.windowSize, this.length);
this._bufferWindowAfter.positionWindow(
bufferWindowAfterStart,
bufferWindowAfterEnd - bufferWindowAfterStart,
this.length,
);
}
}
Expand Down