Skip to content

Commit

Permalink
Merge branch 'main' into python-quality-of-life-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
ddimaria authored Mar 13, 2024
2 parents 8d79a08 + ab4f50f commit 4f3d118
Show file tree
Hide file tree
Showing 31 changed files with 363 additions and 87 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"actix",
"bigdecimal",
"bindgen",
"dashmap",
"dbgjs",
"dcell",
"ddimaria",
Expand Down
14 changes: 9 additions & 5 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ for more more information.

#### Docker Compose

Docker Compose is a utility that's built into Docker Desktop and is a compact
Docker Compose is a utility that's built into Docker Desktop and is a compact
infrastructure-as-code framework. Services (e.g. running Docker containers) are
defined, along with configuration information, in the `docker-compose.yml` file.
Services can talk to each other and can communicate with services in the user's host
Expand Down Expand Up @@ -216,7 +216,7 @@ See the README in each crate for more information.

#### Rust Coverage

In CI, coverage is automatically collected and sent to CodeCov.
In CI, coverage is automatically collected and sent to CodeCov.

For local coverage information, you'll need to install some dependencies first:

Expand All @@ -226,7 +226,7 @@ cargo install grcov
rustup component add llvm-tools-preview
```

To run coverage and generate HTML reports, bring up the docker network
To run coverage and generate HTML reports, bring up the docker network
(`npm run docker:up`) and navigate to the individual crate and enter:

```shell
Expand Down Expand Up @@ -258,7 +258,7 @@ Local load testing is performed by [JMeter](https://jmeter.apache.org/). First,
brew install jmeter
```

Load tests are located in the `/tests/load` directory. Run run jmeter:
Load tests are located in the `/tests/load` directory. Run run jmeter:

```shell
bash jmeter
Expand All @@ -278,4 +278,8 @@ bash jmeter -n -t PATH_TO_JMX_FILE
JAVA_HOME="/opt/homebrew/opt/openjdk" bash /opt/homebrew/Cellar/jmeter/5.6.3/libexec/bin/jmeter -n -t test/load/load-test-quadratic-multiplayer.jmx
```

Output will be located in the terminal.
Output will be located in the terminal.

# Prompting user after version change

The current version numbers are stored in `updateAlertVersion.json`. This JSON is ready by both the client and the multiplayer server. When the client has a lower version number then the multiplayer (the version is sent by the multiplayer server with the EnterFileRoom message), then the user is prompted to refresh with slightly different experience based on required vs. recommended changes.
2 changes: 2 additions & 0 deletions dev/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export class CLI {
.name("node dev")
.description("Runs the Quadratic dev server. By default, only React runs in watch mode.")
.option("-a, --api", "Watch the quadratic-api directory")
.option("-r, --react", "Do NOT watch quadratic-client (React)")
.option("-c, --core", "Watch the quadratic-core directory")
.option("-m, --multiplayer", "Watch the quadratic-multiplayer directory")
.option("-f, --files", "Watch the quadratic-files directory")
Expand All @@ -25,6 +26,7 @@ export class CLI {
.showHelpAfterError();
program.parse();
this.options = program.opts();
this.options.client = !program.opts().react;
if (this.options.all) {
this.options.api = true;
this.options.core = true;
Expand Down
9 changes: 7 additions & 2 deletions dev/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Command } from "commander";

export class CLI {
options: {
client: boolean;
api: boolean;
core: boolean;
multiplayer: boolean;
Expand All @@ -28,10 +29,14 @@ export class CLI {
"Runs the Quadratic dev server. By default, only React runs in watch mode."
)
.option("-a, --api", "Watch the quadratic-api directory")
.option("-r, --react", "Do NOT watch quadratic-client (React)")
.option("-c, --core", "Watch the quadratic-core directory")
.option("-m, --multiplayer", "Watch the quadratic-multiplayer directory")
.option("-f, --files", "Watch the quadratic-files directory")
.option("-y, --python", "Watch the quadratic-kernels/python-wasm directory")
.option(
"-y, --python",
"Watch the quadratic-kernels/python-wasm directory"
)
.option("-l, --all", "Watch all directories")
.option("-s, --skipTypes", "Skip WASM types compilation")
.option(
Expand All @@ -50,7 +55,7 @@ export class CLI {

program.parse();
this.options = program.opts();

this.options.client = !program.opts().react;
if (this.options.all) {
this.options.api = true;
this.options.core = true;
Expand Down
12 changes: 10 additions & 2 deletions dev/control.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,16 @@ export class Control {
// clean the node_modules/.vite directory to avoid client errors
const clean = exec("rm -rf quadratic-client/node_modules/.vite");
clean.on("close", () => {
this.client = spawn("npm", ["start", "--workspace=quadratic-client"], {
this.client = spawn("npm", [
"run",
this.cli.options.client ? "start" : "start:no-hmr",
"--workspace=quadratic-client",
], {
signal: this.signals.client.signal,
});
this.ui.printOutput("client", (data) => {
this.handleResponse("client", data, {
success: "Found 0 errors.",
success: ["Found 0 errors.", "Network: use --host to expose"],
error: ["ERROR(", "npm ERR!"],
start: "> quadratic-client@",
});
Expand All @@ -181,6 +185,10 @@ export class Control {
});
});
}
restartClient() {
this.cli.options.client = !this.cli.options.client;
this.runClient();
}
togglePerf() {
this.cli.options.perf = !this.cli.options.perf;
this.restartCore();
Expand Down
21 changes: 17 additions & 4 deletions dev/control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,20 @@ export class Control {
// clean the node_modules/.vite directory to avoid client errors
const clean = exec("rm -rf quadratic-client/node_modules/.vite");
clean.on("close", () => {
this.client = spawn("npm", ["start", "--workspace=quadratic-client"], {
signal: this.signals.client.signal,
});
this.client = spawn(
"npm",
[
"run",
this.cli.options.client ? "start" : "start:no-hmr",
"--workspace=quadratic-client",
],
{
signal: this.signals.client.signal,
}
);
this.ui.printOutput("client", (data) => {
this.handleResponse("client", data, {
success: "Found 0 errors.",
success: ["Found 0 errors.", "Network: use --host to expose"],
error: ["ERROR(", "npm ERR!"],
start: "> quadratic-client@",
});
Expand All @@ -216,6 +224,11 @@ export class Control {
});
}

restartClient() {
this.cli.options.client = !this.cli.options.client;
this.runClient();
}

togglePerf() {
this.cli.options.perf = !this.cli.options.perf;
this.restartCore();
Expand Down
3 changes: 3 additions & 0 deletions dev/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export class Input {
case "a": // toggle API
this.control.restartApi();
break;
case "r": // toggle client
this.control.restartClient();
break;
case "l": // watch all
if (this.cli.options.api != true) {
this.cli.options.api = true;
Expand Down
3 changes: 3 additions & 0 deletions dev/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export class Input {
case "a": // toggle API
this.control.restartApi();
break;
case "r": // toggle client
this.control.restartClient();
break;
case "l": // watch all
if (this.cli.options.api != true) {
this.cli.options.api = true;
Expand Down
Empty file removed dev/status.ts
Empty file.
6 changes: 3 additions & 3 deletions dev/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class UI {
}
}
}
statusItem(component, alwaysWatch) {
statusItem(component) {
const error = this.control.status[component] === "error";
const { name, color, dark, shortcut } = COMPONENTS[component];
const index = name.toLowerCase().indexOf(shortcut.toLowerCase());
Expand All @@ -100,7 +100,7 @@ export class UI {
else if (!this.control.status[component]) {
this.write(" " + ANIMATE_STATUS[this.spin], "gray");
}
else if (this.cli.options[component] || alwaysWatch) {
else if (this.cli.options[component]) {
this.write(" " + WATCH, "gray");
}
else {
Expand Down Expand Up @@ -145,7 +145,7 @@ export class UI {
prompt() {
this.clear();
this.write("\n");
this.statusItem("client", true);
this.statusItem("client");
this.statusItem("api");
this.statusItem("core");
this.statusItem("multiplayer");
Expand Down
6 changes: 3 additions & 3 deletions dev/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export class UI {
}
}

statusItem(component: string, alwaysWatch?: boolean) {
statusItem(component: string) {
const error = this.control.status[component] === "error";
const { name, color, dark, shortcut } = COMPONENTS[component];
const index = name.toLowerCase().indexOf(shortcut.toLowerCase());
Expand All @@ -123,7 +123,7 @@ export class UI {
this.write(" " + KILLED);
} else if (!this.control.status[component]) {
this.write(" " + ANIMATE_STATUS[this.spin], "gray");
} else if (this.cli.options[component] || alwaysWatch) {
} else if (this.cli.options[component]) {
this.write(" " + WATCH, "gray");
} else {
this.write(" " + DONE, "green");
Expand Down Expand Up @@ -167,7 +167,7 @@ export class UI {
prompt() {
this.clear();
this.write("\n");
this.statusItem("client", true);
this.statusItem("client");
this.statusItem("api");
this.statusItem("core");
this.statusItem("multiplayer");
Expand Down
1 change: 1 addition & 0 deletions quadratic-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"main": "public/electron.js",
"scripts": {
"start": "export VITE_VERSION=$(git rev-parse HEAD) && export NODE_OPTIONS=--max-old-space-size=8192 && vite dev",
"start:no-hmr": "npm run build && vite preview --port 3000",
"build": "export VITE_VERSION=$(git rev-parse HEAD) && export NODE_OPTIONS=--max-old-space-size=8192 && vite build",
"build:wasm": "npm run build:wasm:javascript && npm run build:wasm:nodejs && npm run build:wasm:types",
"build:wasm:types": "cd .. && cd quadratic-core && cargo run --bin export_types",
Expand Down

Large diffs are not rendered by default.

69 changes: 63 additions & 6 deletions quadratic-client/src/gridGL/cells/CellsTextHash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import { sheetHashHeight, sheetHashWidth } from './CellsTypes';
import { CellLabel } from './cellsLabel/CellLabel';
import { LabelMeshes } from './cellsLabel/LabelMeshes';

interface TrackClip {
column: number;
row: number;
hashX: number;
hashY: number;
}

// Draw hashed regions of cell glyphs (the text + text formatting)
export class CellsTextHash extends Container<LabelMeshes> {
private cellsSheet: CellsSheet;
Expand Down Expand Up @@ -42,6 +49,10 @@ export class CellsTextHash extends Container<LabelMeshes> {
// color to use for drawDebugBox
debugColor = Math.floor(Math.random() * 0xffffff);

// keep track of what neighbors we've clipped
clipLeft: TrackClip[] = [];
clipRight: TrackClip[] = [];

constructor(cellsSheet: CellsSheet, x: number, y: number) {
super();
this.cellsSheet = cellsSheet;
Expand Down Expand Up @@ -135,10 +146,41 @@ export class CellsTextHash extends Container<LabelMeshes> {
if (!bounds) return;

if (debugShowHashUpdates) console.log(`[CellsTextHash] overflowClip for ${this.hashX}, ${this.hashY}`);
this.cellLabels.forEach((cellLabel) => this.checkClip(bounds, cellLabel));
const clipLeft: TrackClip[] = [];
const clipRight: TrackClip[] = [];

this.cellLabels.forEach((cellLabel) => this.checkClip(bounds, cellLabel, clipLeft, clipRight));

// we need to update any hashes that we may no longer be clipping
this.clipLeft.forEach((clip) => {
if (
!clipLeft.find(
(c) => c.column === clip.column && c.row === clip.row && c.hashX === clip.hashX && c.hashY === clip.hashY
)
) {
const hash = this.cellsSheet.getCellsHash(clip.hashX, clip.hashY, false);
if (hash) {
hash.dirty = true;
}
}
});
this.clipRight.forEach((clip) => {
if (
!clipRight.find(
(c) => c.column === clip.column && c.row === clip.row && c.hashX === clip.hashX && c.hashY === clip.hashY
)
) {
const hash = this.cellsSheet.getCellsHash(clip.hashX, clip.hashY, false);
if (hash) {
hash.dirty = true;
}
}
});
this.clipLeft = clipLeft;
this.clipRight = clipRight;
}

private checkClip(bounds: Rectangle, label: CellLabel): void {
private checkClip(bounds: Rectangle, label: CellLabel, clipLeft: TrackClip[], clipRight: TrackClip[]): void {
if (debugShowHashUpdates) console.log(`[CellsTextHash] checkClip for ${this.hashX}, ${this.hashY}`);
let column = label.location.x - 1;
const row = label.location.y;
Expand All @@ -148,17 +190,26 @@ export class CellsTextHash extends Container<LabelMeshes> {
if (column < currentHash.AABB.x) {
// find hash to the left of current hash (skip over empty hashes)
currentHash = this.findPreviousHash(column, row, bounds);
if (!currentHash) return;
if (!currentHash) break;
}
const neighborLabel = currentHash.getLabel(column, row);
if (neighborLabel) {
neighborLabel.checkRightClip(label.AABB.left);
const clipRightResult = neighborLabel.checkRightClip(label.AABB.left);
if (clipRightResult) {
if (currentHash !== this) {
clipLeft.push({ column, row, hashX: currentHash.hashX, hashY: currentHash.hashY });
if (clipRightResult !== 'same') {
currentHash.dirty = true;
}
}
}
label.checkLeftClip(neighborLabel.AABB.right);
return;
break;
}
column--;
}

currentHash = this;
column = label.location.x + 1;
while (column <= bounds.right) {
if (column > currentHash.AABB.right) {
Expand All @@ -168,7 +219,13 @@ export class CellsTextHash extends Container<LabelMeshes> {
}
const neighborLabel = currentHash.getLabel(column, row);
if (neighborLabel) {
neighborLabel.checkLeftClip(label.AABB.right);
const leftClipResult = neighborLabel.checkLeftClip(label.AABB.right);
if (leftClipResult && currentHash !== this) {
clipRight.push({ column, row, hashX: currentHash.hashX, hashY: currentHash.hashY });
if (leftClipResult !== 'same') {
currentHash.dirty = true;
}
}
label.checkRightClip(neighborLabel.AABB.left);
return;
}
Expand Down
Loading

0 comments on commit 4f3d118

Please sign in to comment.