Skip to content

Commit

Permalink
Merge 8a76196 into 5cea8dd
Browse files Browse the repository at this point in the history
  • Loading branch information
hbbio committed May 26, 2024
2 parents 5cea8dd + 8a76196 commit c12b4e1
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 36 deletions.
92 changes: 56 additions & 36 deletions src/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ export class Cell<
it means that [v] will be invalidated by an ongoing computation */
protected _valueRank = 0;
protected _currentComputationRank = 0;
private _version = 0;

protected _pending_: PendingMaybe<V, MaybeError> | undefined;
private _pendingRank: number | null = null;
Expand Down Expand Up @@ -214,6 +215,10 @@ export class Cell<
// console.log(`Cell ${id}: `, "constructed")
}

get version(): number {
return this._version;
}

get isPointer(): boolean {
return this._isPointer;
}
Expand Down Expand Up @@ -543,16 +548,41 @@ export class Cell<
return;
}

const needUpdate = !this._sheet.equals(this.v, newValue);
this.sheet.debug([this.id], () => `Cell ${this.name} <== ${newValue}`, {
currentValue: simplifier(this.value),
currentRank: this._currentComputationRank,
newValueRank: computationRank
});
this.v = newValue;
this._valueRank = computationRank;
// @todo This could be done later.
// this.v = newValue;
// Update localStorage if set.
if (needUpdate && this._storageKey) {

if (newValue instanceof Error && !(newValue instanceof CellError)) {
this._sheet.errors._setCellError(this.id, newValue);
this._lastStateIsError = true;
} else {
if (this._lastStateIsError) this._sheet.errors._unsetCellError(this.id);
}
if (newValue instanceof Cell) {
this.setPointed(newValue.id);
} else {
if (this._isPointer)
if (newValue === null) {
this.setPointed(null);
} else {
DEBUG_RANK &&
console.log("unsetting pointer", { cell: this.name, newValue });
this.unsetPointed();
}
}

// We managed errors and cells, so we should be able to stop here
// if the value is unchanged.
const needUpdate = !this._sheet.equals(this.v, newValue);
if (!needUpdate) return;

if (this._storageKey) {
try {
const j = this.sheet._marshaller(newValue);
localStorage.setItem(this._storageKey, j);
Expand All @@ -571,40 +601,24 @@ export class Cell<
);
}
}
if (this.v instanceof Error && !(this.v instanceof CellError)) {
this._sheet.errors._setCellError(this.id, this.v);
this._lastStateIsError = true;
} else {
if (this._lastStateIsError) this._sheet.errors._unsetCellError(this.id);
}
if (newValue instanceof Cell) {
this.setPointed(newValue.id);
} else {
if (this._isPointer)
if (newValue === null) {
this.setPointed(null);
} else {
DEBUG_RANK &&
console.log("unsetting pointer", { cell: this.name, newValue });
this.unsetPointed();
}
}
this.v = newValue;

// only updating if we are the last ongoing computation
if (this._currentComputationRank === computationRank) {
// only updating if we are the last ongoing computation
if (needUpdate) {
if (!skipSubscribers) {
// @todo : remember the last notify rank and run notify on last computations, even if canceled,
// if lastNotified < valueRank.
// This requires to
// 1. have a list of ranks of pending computations
// 2. on computation success or cancel, remove the rank from the list
// 3. if lastNotified < valueRank, and no pending have rank > valueRank, then notify the new value.
this._notifySubscribers();
}
if (update) {
// console.log(`Cell ${this.name}: `, `updating as value changed`);
this._sheet._update(this.id);
}
// @todo This is already done, but could be done here.
this._version++;
if (!skipSubscribers) {
// @todo : remember the last notify rank and run notify on last computations, even if canceled,
// if lastNotified < valueRank.
// This requires to
// 1. have a list of ranks of pending computations
// 2. on computation success or cancel, remove the rank from the list
// 3. if lastNotified < valueRank, and no pending have rank > valueRank, then notify the new value.
this._notifySubscribers();
}
if (update) {
// console.log(`Cell ${this.name}: `, `updating as value changed`);
this._sheet._update(this.id);
}
}
}
Expand Down Expand Up @@ -744,6 +758,9 @@ export class ValueCell<V> extends Cell<V, false, false> {

if (value !== undefined) {
this.v = value;
// @ts-expect-error: _version is private, this is the single instance where
// we update it when creating a new ValueCell with a direct value.
this._version++;
if (value instanceof Cell) {
this.setPointed(value.id);
} else {
Expand All @@ -768,6 +785,9 @@ export class ValueCell<V> extends Cell<V, false, false> {
cell: this.fullName,
value
});
// @todo We could accelerate by testing early in that case, but
// then we should assume the check is already made.
// if (this.sheet.equals(this.v, value)) return;
this._currentComputationRank += 1;
const computationRank = this._currentComputationRank;

Expand Down
52 changes: 52 additions & 0 deletions src/version.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { expect, test } from "vitest";

import { isEqual } from "./isEqual.test";
import { delayed, sleep } from "./promise";
import { Sheet } from "./sheet";

test("version for ValueCell", async () => {
const proxy = new Sheet(isEqual).newProxy();
const a = proxy.new(1);
expect(a.version).toBe(1);
a.set(2);
expect(a.version).toBe(2);
a.update((v) => v + 1);
expect(a.version).toBe(3);
a.update((v) => delayed(v + 1, 10));
expect(a.version).toBe(3);
await sleep(20);
expect(a.version).toBe(4);
});

test("version for ValueCell promise", async () => {
const proxy = new Sheet(isEqual).newProxy();
const a = proxy.new(delayed(1, 10));
expect(a.version).toBe(0);
await sleep(20);
expect(a.version).toBe(1);
});

test("version doesn't update when isEqual", () => {
const proxy = new Sheet(isEqual).newProxy();
const a = proxy.new(1);
expect(a.version).toBe(1);
a.set(1);
expect(a.version).toBe(1);
});

test("version for MapCell", async () => {
const proxy = new Sheet(isEqual).newProxy();
const a = proxy.new(1);
const b = a.map((v) => v + 1);
expect(b.version).toBe(1);
a.set(2);
expect(b.version).toBe(2);
const c = a.map((v) => delayed(v * 2, 10));
expect(c.version).toBe(0);
await sleep(20);
expect(c.version).toBe(1);
a.set(3);
expect(c.version).toBe(1);
await sleep(20);
expect(c.version).toBe(2);
});

0 comments on commit c12b4e1

Please sign in to comment.