Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 4 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ jobs:

- name: Package
if: ${{ steps.config-step.outputs.FULL_RUN }}
run: cargo package --config .cargo/release.toml --no-verify --allow-dirty -p perspective -p perspective-viewer -p perspective-js -p perspective-client -p perspective-server -p perspective-python
run: cargo package --config .cargo/release.toml --no-verify --allow-dirty -p perspective -p perspective-viewer -p perspective-js -p perspective-client -p perspective-server -p perspective-migrate -p perspective-python

- uses: actions/upload-artifact@v4
if: ${{ steps.config-step.outputs.FULL_RUN }}
Expand Down Expand Up @@ -1104,15 +1104,17 @@ jobs:
- name: Set up path overrides for perspective .crate contents
shell: bash
run: |
(cd crates && tar xvf perspective-client-*.crate && tar xvf perspective-server-*.crate)
(cd crates && tar xvf perspective-client-*.crate && tar xvf perspective-server-*.crate && tar xvf perspective-migrate-[0-9]*.crate)
CLIENT_CRATE_ROOT=$(ls -d "$PWD"/crates/perspective-client-*/ | head -n 1)
SERVER_CRATE_ROOT=$(ls -d "$PWD"/crates/perspective-server-*/ | head -n 1)
MIGRATE_CRATE_ROOT=$(ls -d "$PWD"/crates/perspective-migrate-[0-9]*/ | head -n 1)

mkdir -p $HOME/.cargo
cat > $HOME/.cargo/config.toml <<EOF
[patch.crates-io]
perspective-client = { path = "$CLIENT_CRATE_ROOT" }
perspective-server = { path = "$SERVER_CRATE_ROOT" }
perspective-migrate = { path = "$MIGRATE_CRATE_ROOT" }
EOF

- name: Install perspective-python sdist
Expand Down
9 changes: 0 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ members = [
"rust/perspective-client",
"rust/perspective-js",
"rust/perspective-migrate",
"rust/perspective-migrate-js",
"rust/perspective-python",
"rust/perspective-server",
"rust/perspective-viewer",
Expand Down
13 changes: 8 additions & 5 deletions docs/md/explanation/view/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ view.remove_update(callback);
<div class="python">

```python
def on_update(port_id, delta):
def on_update(port_id, delta, reset):
print("View updated", port_id)

view.on_update(on_update, mode="row")
Expand All @@ -233,16 +233,19 @@ view.remove_update(on_update)

When `mode` is set to `"row"`, the callback receives a delta of only the rows
that changed (as Apache Arrow), which is useful for efficiently synchronizing
tables across clients.
tables across clients. The callback also receives `reset`, which is `true` when
the update step began with `clear()` or `replace()`; the delta is then relative
to an empty `Table`, so a replica should apply it with `replace()` rather than
`update()`.

## Remove Callbacks

Register a callback to be notified whenever rows are removed from the underlying
`Table` by `remove()`, which requires an `index`. The callback receives the
`port_id` and the removed `index` column values as an Apache Arrow with one
column per index column, named after that column. It fires once per update step, only for rows which
existed before that step; `replace()` reports the keys it does not re-supply,
and `clear()` reports every key:
column per index column, named after that column. It fires once per update step,
only for rows which existed before that step; `replace()` reports the keys it
does not re-supply, and `clear()` reports every key:

<div class="javascript">

Expand Down
18 changes: 8 additions & 10 deletions docs/md/how_to/javascript/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ A layout is built from two kinds of node:
- `split-layout` divides its space among its `children`. The `orientation`
decides the direction: `"horizontal"` places the children side by side as
columns, left to right, and `"vertical"` stacks them as rows, top to bottom.
`sizes` gives each child's fraction of the split, one number per child, and
the sizes must sum to 1 (`[0.5, 0.5]` is an even split, `[0.25, 0.75]` a
`sizes` gives each child's share of the split, one number per child, as
proportions of their total (`[0.5, 0.5]` is an even split, `[0.25, 0.75]` a
narrow column next to a wide one).
- `tab-layout` is a leaf cell holding a stack of panels, listed by panel id in
`tabs`. Only one panel of a stack is visible at a time; `selected` is the
Expand All @@ -27,7 +27,8 @@ across the bottom row. A split nested directly inside a split of the same
orientation is flattened into its parent, so `saveWorkspace()` may return an
equivalent but flatter tree than the one you restored.

Every panel must appear in the layout exactly once.
Each panel may appear in the layout at most once. A panel the layout leaves out
is removed, with a warning logged to the console.

```javascript
const layout = {
Expand Down Expand Up @@ -111,7 +112,7 @@ side by side; arrange them afterwards by restoring a `layout`, as below.
## Rearranging existing panels

A `restoreWorkspace()` call with a `layout` but no `panels` keeps every panel
and only moves them. Name the panels by their existing ids, from
the layout names and only moves them. Name the panels by their existing ids, from
`getPanelNames()` or a `saveWorkspace()` token. This is how to move or swap a
panel, resize a row or column, change a row into a column, or stack panels as
tabs; nothing is re-created or re-queried.
Expand Down Expand Up @@ -149,12 +150,9 @@ the viewer is left exactly as it was. A layout is rejected when:

- a panel id in `tabs` is unknown — without `panels` the ids must be existing
panel ids, and with `panels` they must be `panels` keys;
- a panel is missing from the layout, or appears more than once — every panel
must be placed exactly once;
- a `split-layout` has no `children`, or its `sizes` do not have one entry per
child, or a size is zero or negative, or the sizes do not sum to 1;
- a `tab-layout` has empty `tabs`, or its `selected` index is past the end of
`tabs`.
- a panel appears more than once;
- a `split-layout`'s `sizes` do not have one entry per child, or a size is
negative or not finite, or every size is zero.

The error message names the offending node by its path, e.g.
`layout.children[1].tabs`, and lists the valid panel ids.
Expand Down
2 changes: 1 addition & 1 deletion docs/md/how_to/rust/migrate.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ await viewer.restoreWorkspace(value);
```

`MIGRATE_WASM` is
`@perspective-dev/client/dist/wasm/perspective-migrate-js.wasm`. In Node.js,
`@perspective-dev/client/dist/wasm/perspective-migrate.wasm`. In Node.js,
`perspective.migrate()` needs no registration.

```python
Expand Down
1 change: 1 addition & 0 deletions rust/perspective-client/perspective.proto
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ message ViewOnUpdateReq {
message ViewOnUpdateResp {
optional bytes delta = 1;
uint32 port_id = 2;
bool reset = 3;
}

message ViewOnDeleteReq {}
Expand Down
10 changes: 7 additions & 3 deletions rust/perspective-client/src/rust/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,9 +627,13 @@ impl Client {

let table_ = table.clone();
let callback = asyncfn!(table_, update, async move |update: OnUpdateData| {
let update = UpdateData::Arrow(update.delta.expect("Malformed message").into());
let options = crate::UpdateOptions::default();
table_.update(update, options).await.unwrap_or_log();
let data = UpdateData::Arrow(update.delta.expect("Malformed message").into());
if update.reset {
table_.replace(data).await.unwrap_or_log();
} else {
let options = crate::UpdateOptions::default();
table_.update(data, options).await.unwrap_or_log();
}
});

let options = OnUpdateOptions {
Expand Down
1 change: 1 addition & 0 deletions rust/perspective-client/src/rust/utils/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ impl std::fmt::Display for Response {
client_resp: Some(ClientResp::ViewOnUpdateResp(ViewOnUpdateResp {
delta: x.delta.as_ref().map(|_| vec![]),
port_id: x.port_id,
reset: x.reset,
})),
..msg.clone()
},
Expand Down
10 changes: 6 additions & 4 deletions rust/perspective-client/src/rust/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,16 +566,18 @@ impl View {

/// Register a callback with this [`View`]. Whenever the view's underlying
/// table emits an update, this callback will be invoked with an object
/// containing `port_id`, indicating which port the update fired on, and
/// containing `port_id`, indicating which port the update fired on,
/// `reset`, which is `true` when the step began with [`Table::clear`] or
/// [`Table::replace`] so `delta` is relative to an empty table, and
/// optionally `delta`, which is the new data that was updated for each
/// cell or each row.
///
/// # Arguments
///
/// - `on_update` - A callback function invoked on update, which receives an
/// object with two keys: `port_id`, indicating which port the update was
/// triggered on, and `delta`, whose value is dependent on the mode
/// parameter.
/// object with three keys: `port_id`, indicating which port the update
/// was triggered on, `reset`, and `delta`, whose value is dependent on
/// the mode parameter.
/// - `options` - If this is provided as `OnUpdateOptions { mode:
/// Some(OnUpdateMode::Row) }`, then `delta` is an Arrow of the updated
/// rows. Otherwise `delta` will be [`Option::None`].
Expand Down
3 changes: 2 additions & 1 deletion rust/perspective-client/src/rust/virtual_server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,8 @@ impl<T: VirtualServerHandler> VirtualServer<T> {
ViewOnUpdateReq(_) => {
respond!(msg, ViewOnUpdateResp {
delta: None,
port_id: 0
port_id: 0,
reset: false
})
},
ViewOnDeleteReq(_) => {
Expand Down
6 changes: 3 additions & 3 deletions rust/perspective-js/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,13 @@ async function build_rust() {
);

execSync(
`PSP_ROOT_DIR=../.. cargo bundle --target=${get_host()} -- perspective_migrate_js ${release_flag} --no-tracing`,
`PSP_ROOT_DIR=../.. cargo bundle --target=${get_host()} -- perspective_migrate ${release_flag} --no-tracing`,
INHERIT,
);

await compress(
"dist/wasm/perspective-migrate-js.wasm",
"dist/wasm/perspective-migrate-js.wasm",
"dist/wasm/perspective-migrate.wasm",
"dist/wasm/perspective-migrate.wasm",
);
}

Expand Down
2 changes: 1 addition & 1 deletion rust/perspective-js/src/ts/perspective.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ function is_wasm_source(
* perspective.init_server({
* wasm32: () => fetch("perspective-server.wasm"),
* wasm64: () => fetch("perspective-server.memory64.wasm"),
* migrate: () => fetch("perspective-migrate-js.wasm"),
* migrate: () => fetch("perspective-migrate.wasm"),
* });
* ```
*/
Expand Down
2 changes: 1 addition & 1 deletion rust/perspective-js/src/ts/perspective.cdn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ register_default({
),
),
migrate: () =>
fetch(new URL("../wasm/perspective-migrate-js.wasm", import.meta.url)),
fetch(new URL("../wasm/perspective-migrate.wasm", import.meta.url)),
});

export default perspective;
2 changes: 1 addition & 1 deletion rust/perspective-js/src/ts/perspective.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ await perspective_client.default({ module_or_path: uncompressed_client_wasm });
const MIGRATE = new MigrateLoader();
MIGRATE.register(async () => {
const buffer = await fs.readFile(
path.join(__dirname, "../../dist/wasm/perspective-migrate-js.wasm"),
path.join(__dirname, "../../dist/wasm/perspective-migrate.wasm"),
);

return await load_wasm_stage_0(buffer.buffer as ArrayBuffer);
Expand Down
4 changes: 2 additions & 2 deletions rust/perspective-js/src/ts/wasm/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import * as migrate_wasm from "../../../dist/wasm/perspective-migrate-js.js";
import * as migrate_wasm from "../../../dist/wasm/perspective-migrate.js";

/**
* The kind of persisted config `migrate()` upgrades: an engine `ViewConfig`,
Expand Down Expand Up @@ -57,7 +57,7 @@ export class MigrateLoader {
): Promise<MigrateResult<T>> {
if (this.module === undefined) {
if (this.source === undefined) {
throw new Error("Missing perspective-migrate-js.wasm");
throw new Error("Missing perspective-migrate.wasm");
}

const source = this.source;
Expand Down
4 changes: 2 additions & 2 deletions rust/perspective-js/src/ts/wasm/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ export function register(
* - `wasm` - Binaries by role: `client` (`perspective-js.wasm`), `viewer`
* (`perspective-viewer.wasm`, which also backs every `Client`), `server`
* (one engine build) or `server32` / `server64` (selected by Memory64
* support), and `migrate` (`perspective-migrate-js.wasm`).
* support), and `migrate` (`perspective-migrate.wasm`).
* - `options` - `disable_stage_0` skips decompression for these sources.
*
* # Examples
Expand Down Expand Up @@ -500,7 +500,7 @@ export function resolve_migrate(): Promise<ArrayBuffer | WebAssembly.Module> {
if (entry === undefined) {
return Promise.reject(
new Error(
"Missing perspective-migrate-js.wasm: register `migrate` with init()",
"Missing perspective-migrate.wasm: register `migrate` with init()",
),
);
}
Expand Down
Loading
Loading