Skip to content

Commit

Permalink
refactor: improve selection and misc. performance (#545)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidfig committed Jun 19, 2023
1 parent 9d87209 commit 5835e35
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 81 deletions.
4 changes: 2 additions & 2 deletions src/grid/controller/sheetController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export class SheetController {

if (this.app) {
if (transaction.cursor) {
this.app.settings.setInteractionState?.(transaction.cursor);
this.app.settings.setInteractionState(transaction.cursor);
}

// TODO: The transaction should keep track of everything that becomes dirty while executing and then just sets the correct flags on app.
Expand Down Expand Up @@ -195,7 +195,7 @@ export class SheetController {

if (this.app) {
if (transaction.cursor) {
this.app.settings.setInteractionState?.(transaction.cursor);
this.app.settings.setInteractionState(transaction.cursor);
}

// TODO: The transaction should keep track of everything that becomes dirty while executing and then just sets the correct flags on app.
Expand Down
16 changes: 16 additions & 0 deletions src/grid/sheet/Bounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ export class Bounds {
this.empty = false;
}

addX(x: number): void {
this.minX = Math.min(this.minX, x);
this.maxX = Math.max(this.maxX, x);
if (this.minY !== Infinity) {
this.empty = false;
}
}

addY(y: number): void {
this.minY = Math.min(this.minY, y);
this.maxY = Math.max(this.maxY, y);
if (this.minX !== Infinity) {
this.empty = false;
}
}

addRectangle(rectangle: Rectangle): void {
this.minX = Math.min(rectangle.left, this.minX);
this.maxX = Math.max(rectangle.right, this.maxX);
Expand Down
33 changes: 26 additions & 7 deletions src/grid/sheet/GridOffsetsCache.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Bounds } from './Bounds';
import { GridOffsets } from './GridOffsets';

const GRID_OFFSETS_CACHE_SIZE = 1000;
Expand All @@ -8,17 +9,21 @@ export class GridOffsetsCache {
private rowCache: number[] = [];
private columnNegativeCache: number[] = [];
private rowNegativeCache: number[] = [];
private bounds: Bounds;

constructor(gridOffsets: GridOffsets) {
this.gridOffsets = gridOffsets;
this.bounds = new Bounds();
this.clear();
}

clear() {
this.bounds.clear();
this.columnCache = [0];
this.rowCache = [0];
this.columnNegativeCache = [0];
this.rowNegativeCache = [0];
this.bounds.add(0, 0);
}

reset(direction: 'column' | 'row', negative: boolean) {
Expand All @@ -39,7 +44,7 @@ export class GridOffsetsCache {

getColumnPlacement(column: number): { x: number; width: number } {
let position = 0;
if (column === 0) {
if (Math.abs(column) === 0) {
return { x: 0, width: this.gridOffsets.getColumnWidth(0) };
}

Expand All @@ -60,10 +65,12 @@ export class GridOffsetsCache {

// otherwise calculate the cache as you iterate
else {
for (let x = 0; x < column; x++) {
position = this.columnCache[this.bounds.maxX];
for (let x = this.bounds.maxX * GRID_OFFSETS_CACHE_SIZE; x < column; x++) {
// add to cache when needed
if (x % GRID_OFFSETS_CACHE_SIZE === 0) {
this.columnCache[x / GRID_OFFSETS_CACHE_SIZE] = position;
this.bounds.addX(x / GRID_OFFSETS_CACHE_SIZE);
}

position += this.gridOffsets.getColumnWidth(x);
Expand All @@ -83,6 +90,7 @@ export class GridOffsetsCache {
// add to cache when needed
if (-x % GRID_OFFSETS_CACHE_SIZE === 0) {
this.columnNegativeCache[-x / GRID_OFFSETS_CACHE_SIZE] = position;
this.bounds.addX(x / GRID_OFFSETS_CACHE_SIZE);
}
if (x !== 0) {
position -= this.gridOffsets.getColumnWidth(x);
Expand All @@ -92,10 +100,12 @@ export class GridOffsetsCache {

// otherwise calculate the cache as you iterate
else {
for (let x = -1; x >= column; x--) {
position = this.columnNegativeCache[-this.bounds.minX];
for (let x = this.bounds.minX * GRID_OFFSETS_CACHE_SIZE; x >= column; x--) {
// add to cache when needed
if (-x % GRID_OFFSETS_CACHE_SIZE === 0) {
this.columnNegativeCache[-x / GRID_OFFSETS_CACHE_SIZE] = position;
this.bounds.addX(x / GRID_OFFSETS_CACHE_SIZE);
}

if (x !== 0) {
Expand Down Expand Up @@ -135,7 +145,7 @@ export class GridOffsetsCache {

getRowPlacement(row: number): { y: number; height: number } {
let position = 0;
if (row === 0) {
if (Math.abs(row) === 0) {
return { y: 0, height: this.gridOffsets.getRowHeight(0) };
}

Expand All @@ -148,6 +158,7 @@ export class GridOffsetsCache {
// add to cache when needed
if (y % GRID_OFFSETS_CACHE_SIZE === 0) {
this.rowCache[y / GRID_OFFSETS_CACHE_SIZE] = position;
this.bounds.addY(y / GRID_OFFSETS_CACHE_SIZE);
}

position += this.gridOffsets.getRowHeight(y);
Expand All @@ -156,10 +167,12 @@ export class GridOffsetsCache {

// otherwise calculate the cache as you iterate
else {
for (let y = 0; y < row; y++) {
position = this.rowCache[this.bounds.maxY];
for (let y = this.bounds.maxY * GRID_OFFSETS_CACHE_SIZE; y < row; y++) {
// add to cache when needed
if (y % GRID_OFFSETS_CACHE_SIZE === 0) {
this.rowCache[y / GRID_OFFSETS_CACHE_SIZE] = position;
this.bounds.addY(y / GRID_OFFSETS_CACHE_SIZE);
}

position += this.gridOffsets.getRowHeight(y);
Expand All @@ -174,11 +187,13 @@ export class GridOffsetsCache {
// use cache if available
const closestIndex = Math.floor(-row / GRID_OFFSETS_CACHE_SIZE);
if (this.rowNegativeCache.length > closestIndex) {
position = this.columnNegativeCache[closestIndex];
position = this.rowNegativeCache[closestIndex];
for (let y = -closestIndex * GRID_OFFSETS_CACHE_SIZE; y >= row; y--) {
// add to cache when needed
if (-y % GRID_OFFSETS_CACHE_SIZE === 0) {
if (position === undefined) debugger;
this.rowNegativeCache[-y / GRID_OFFSETS_CACHE_SIZE] = position;
this.bounds.addY(y / GRID_OFFSETS_CACHE_SIZE);
}
if (y !== 0) {
position -= this.gridOffsets.getRowHeight(y);
Expand All @@ -188,17 +203,21 @@ export class GridOffsetsCache {

// otherwise calculate the cache as you iterate
else {
for (let y = -1; y >= row; y--) {
position = this.rowNegativeCache[-this.bounds.minY];
for (let y = this.bounds.minY * GRID_OFFSETS_CACHE_SIZE; y >= row; y--) {
// add to cache when needed
if (-y % GRID_OFFSETS_CACHE_SIZE === 0) {
if (position === undefined) debugger;
this.rowNegativeCache[-y / GRID_OFFSETS_CACHE_SIZE] = position;
this.bounds.addY(y / GRID_OFFSETS_CACHE_SIZE);
}

if (y !== 0) {
position -= this.gridOffsets.getRowHeight(y);
}
}
}
if (isNaN(position)) debugger;
return { y: position, height: this.gridOffsets.getRowHeight(row) };
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/gridGL/dashedTextures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ interface DashedTexture {

// fallback to a white texture
export const dashedTextures: DashedTexture = {
dottedVertical: Texture.WHITE,
dottedHorizontal: Texture.WHITE,
dashedVertical: Texture.WHITE,
dashedHorizontal: Texture.WHITE,
dottedVertical: Texture.EMPTY,
dottedHorizontal: Texture.EMPTY,
dashedVertical: Texture.EMPTY,
dashedHorizontal: Texture.EMPTY,
};

function createDashedLine(horizontal: boolean): Texture {
Expand Down
53 changes: 14 additions & 39 deletions src/gridGL/interaction/pointer/PointerDown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export class PointerDown {
if (IS_READONLY_MODE) return;
if (this.app.settings.interactionState.panMode !== PanMode.Disabled) return;

// note: directly call this.app.settings instead of locally defining it here; otherwise it dereferences this

// this is a hack to ensure CellInput properly closes and updates before the cursor moves positions
if (this.app.settings.interactionState.showInput) {
this.afterShowInput = true;
Expand All @@ -43,8 +45,6 @@ export class PointerDown {
return;
}

const { settings, cursor } = this.app;
const { interactionState, setInteractionState } = settings;
const { gridOffsets } = this.sheet;

this.positionRaw = world;
Expand All @@ -54,15 +54,12 @@ export class PointerDown {

// If right click and we have a multi cell selection.
// If the user has clicked inside the selection.
if (!setInteractionState) {
throw new Error('Expected setInteractionState to be defined');
}
if (rightClick && interactionState.showMultiCursor) {
if (rightClick && this.app.settings.interactionState.showMultiCursor) {
if (
column >= interactionState.multiCursorPosition.originPosition.x &&
column <= interactionState.multiCursorPosition.terminalPosition.x &&
row >= interactionState.multiCursorPosition.originPosition.y &&
row <= interactionState.multiCursorPosition.terminalPosition.y
column >= this.app.settings.interactionState.multiCursorPosition.originPosition.x &&
column <= this.app.settings.interactionState.multiCursorPosition.terminalPosition.x &&
row >= this.app.settings.interactionState.multiCursorPosition.originPosition.y &&
row <= this.app.settings.interactionState.multiCursorPosition.terminalPosition.y
)
// Ignore this click. User is accessing the RightClickMenu.
return;
Expand Down Expand Up @@ -90,24 +87,23 @@ export class PointerDown {
// select cells between pressed and cursor position
if (event.shiftKey) {
const { column, row } = gridOffsets.getRowColumnFromWorld(world.x, world.y);
const cursorPosition = interactionState.cursorPosition;
const cursorPosition = this.app.settings.interactionState.cursorPosition;
if (column !== cursorPosition.x || row !== cursorPosition.y) {
// make origin top left, and terminal bottom right
const originX = cursorPosition.x < column ? cursorPosition.x : column;
const originY = cursorPosition.y < row ? cursorPosition.y : row;
const termX = cursorPosition.x > column ? cursorPosition.x : column;
const termY = cursorPosition.y > row ? cursorPosition.y : row;

setInteractionState({
...interactionState,
this.app.settings.setInteractionState({
...this.app.settings.interactionState,
keyboardMovePosition: { x: column, y: row },
multiCursorPosition: {
originPosition: new Point(originX, originY),
terminalPosition: new Point(termX, termY),
},
showMultiCursor: true,
});
cursor.dirty = true;
}
return;
}
Expand All @@ -125,14 +121,14 @@ export class PointerDown {

// Move cursor to mouse down position
// For single click, hide multiCursor
setInteractionState({
...interactionState,
this.app.settings.setInteractionState({
...this.app.settings.interactionState,
keyboardMovePosition: { x: column, y: row },
cursorPosition: { x: column, y: row },
multiCursorPosition: previousPosition,
showMultiCursor: false,
showInput: false,
});
cursor.dirty = true;
this.pointerMoved = false;
}

Expand All @@ -141,7 +137,7 @@ export class PointerDown {

if (!this.active) return;

const { viewport, settings, cursor } = this.app;
const { viewport, settings } = this.app;
const { gridOffsets } = this.sheet;

// for determining if double click
Expand All @@ -155,23 +151,6 @@ export class PointerDown {
}
}

// cursor intersects edges
// if (
// !this.active ||
// !this.position ||
// !this.previousPosition ||
// !this.positionRaw ||
// !settings.setInteractionState
// ) {
// if (
// Math.abs(this.app.cursor.format_indicator.x - world.x) < 3 ||
// Math.abs(this.app.cursor.format_indicator.y - world.y) < 3
// ) {
// this.app.canvas.style.cursor = 'grab';
// }

// }

// cursor intersects bottom-corner indicator (disabled for now)
if (
!this.active ||
Expand All @@ -180,9 +159,6 @@ export class PointerDown {
!this.positionRaw ||
!settings.setInteractionState
) {
// if (intersects.rectanglePoint(this.app.cursor.indicator, world)) {
// this.app.canvas.style.cursor = 'cell';
// }
return;
}

Expand Down Expand Up @@ -237,7 +213,6 @@ export class PointerDown {
showInput: false,
inputInitialValue: '',
});
cursor.dirty = true;

// update previousPosition
this.previousPosition = {
Expand Down
22 changes: 19 additions & 3 deletions src/gridGL/pixiApp/PixiApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export class PixiApp {
}

// called before and after a quadrant render
prepareForQuadrantRendering(options?: { gridLines: boolean }): Container {
prepareForCopying(options?: { gridLines: boolean }): Container {
this.gridLines.visible = options?.gridLines ?? false;
this.axesLines.visible = false;
this.cursor.visible = false;
Expand All @@ -233,7 +233,7 @@ export class PixiApp {
return this.viewportContents;
}

cleanUpAfterQuadrantRendering(): void {
cleanUpAfterCopying(): void {
this.gridLines.visible = true;
this.axesLines.visible = true;
this.cursor.visible = true;
Expand Down Expand Up @@ -272,6 +272,22 @@ export class PixiApp {
this.viewport.position.set(0, 0);
}
this.settings.setEditorInteractionState?.(editorInteractionStateDefault);
this.settings.setInteractionState?.(gridInteractionStateDefault);
this.settings.setInteractionState(gridInteractionStateDefault);
}

// Pre-renders quadrants by cycling through one quadrant per frame
preRenderQuadrants(resolve?: () => void): Promise<void> {
return new Promise((_resolve) => {
if (!resolve) {
resolve = _resolve;
}
this.quadrants.update(0);
if (this.quadrants.needsUpdating()) {
// the timeout allows the quadratic logo animation to appear smooth
setTimeout(() => this.preRenderQuadrants(resolve), 100);
} else {
resolve();
}
});
}
}
Loading

1 comment on commit 5835e35

@vercel
Copy link

@vercel vercel bot commented on 5835e35 Jun 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

quadratic – ./

quadratic-git-main-quadratic.vercel.app
quadratic-nu.vercel.app
quadratic-quadratic.vercel.app

Please sign in to comment.