Skip to content

Commit

Permalink
Merge pull request #37 from okcontract/proxy-name
Browse files Browse the repository at this point in the history
Proxy names, improved debugging
  • Loading branch information
hbbio committed Jul 25, 2024
2 parents 5cea8dd + 12c7bd0 commit bc357f4
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 41 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@okcontract/cells",
"version": "0.3.1",
"version": "0.3.2",
"description": "Simplified reactive functional programming for the web",
"private": false,
"main": "dist/cells.umd.cjs",
Expand Down
54 changes: 41 additions & 13 deletions src/array.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { type AnyCell, type MapCell, ValueCell } from "./cell";
import type { AnyCell, MapCell, ValueCell } from "./cell";
import { collector, reuseOrCreate } from "./gc";
import type { SheetProxy } from "./proxy";

export type CellArray<T> = AnyCell<AnyCell<T>[]>;
export type ValueCellArray<T> = ValueCell<ValueCell<T>[]>;

/**
* mapArray implements .map() for a cellified array.
Expand Down Expand Up @@ -53,20 +54,48 @@ export const mapArray = <T, U>(
name
);

/**
* Compares two values and returns a number indicating their order.
*
* This function is designed to be used as a comparator in sorting functions.
* It returns:
* - `1` if `a` is greater than `b`
* - `-1` if `a` is less than `b`
* - `0` if `a` is equal to `b`
*
* @param {*} a - The first value to compare.
* @param {*} b - The second value to compare.
* @returns {number} - Order.
*
* @example
* const array = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
* array.sort(defaultComparator);
*/
export const defaultComparator = <T>(a: T, b: T): number =>
a > b ? 1 : a < b ? -1 : 0;

/**
* implementation of sort for a cellified array.
* @description this implementation relies on pointers but reuses the original cells.
* @param proxy
* @param arr
* @param compare comparison function
* @param noFail should be true when the following conditions are met:
* 1. compare must be defined
* 2. cells must be an array (possibly empty)
* 3. compare should never fail
*/
export const sort = <T>(
export const sort = <T, NF extends boolean = false>(
proxy: SheetProxy,
arr: CellArray<T>,
compare: (a: T, b: T) => number = (a, b) => (a > b ? 1 : a < b ? -1 : 0)
): CellArray<T> => {
const coll = collector<CellArray<T>>(proxy);
return proxy.map(
compare: (a: T, b: T) => number = defaultComparator,
noFail?: NF
): MapCell<typeof arr extends AnyCell<infer U> ? U : never, NF> => {
const coll =
collector<MapCell<typeof arr extends AnyCell<infer U> ? U : never, NF>>(
proxy
);
return proxy.mapNoPrevious(
[arr],
(cells) =>
coll(
Expand All @@ -77,10 +106,12 @@ export const sort = <T>(
.map((_, index) => index)
.sort((indexA, indexB) => compare(_cells[indexA], _cells[indexB]))
.map((index) => cells[index]),
"_sort"
"_sort",
noFail || false
)
),
"sort"
"sort",
noFail || false
);
};

Expand Down Expand Up @@ -177,7 +208,7 @@ export const reduce = <
>(
proxy: SheetProxy,
arr: CellArray<T>,
fn: (acc: R, elt: T, index?: number, length?: number) => R,
fn: (acc: R, elt: T, index?: number, cell?: AnyCell<T>) => R,
init: R,
name = "reduce",
nf?: NF
Expand All @@ -190,10 +221,7 @@ export const reduce = <
proxy.mapNoPrevious(
cells,
(..._cells) =>
_cells.reduce(
(acc, elt, i) => fn(acc, elt, i, _cells.length),
init
),
_cells.reduce((acc, elt, i) => fn(acc, elt, i, cells[i]), init),
"_reduce"
)
),
Expand Down
25 changes: 9 additions & 16 deletions src/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,19 +628,11 @@ export class Cell<
noFail?: NF
): MapCell<T, NF> =>
this._sheet instanceof SheetProxy
? ((this._sheet as SheetProxy).map(
[this as AnyCell<V>],
fn,
name,
noFail
) as MapCell<T, NF>)
: ((this._sheet as Sheet).map(
[this as AnyCell<V>],
fn,
name,
undefined,
noFail
) as MapCell<T, NF>);
? (this._sheet.map([this], fn, name, noFail) as MapCell<T, NF>)
: (this._sheet.map([this], fn, name, undefined, noFail) as MapCell<
T,
NF
>);

init = <T>(
fn: (v: V) => T | Promise<T>,
Expand Down Expand Up @@ -823,7 +815,8 @@ export class ValueCell<V> extends Cell<V, false, false> {
);
}
const nv = fn(v);
this.set(nv);
// We ignore undefined values (but not cell nor promises).
if (nv !== undefined) this.set(nv);
};

/**
Expand Down Expand Up @@ -1228,5 +1221,5 @@ export class CellErrors extends SubscribeBench<ErrorsList> {
export type AnyCell<
T,
C extends boolean = boolean,
N extends boolean = boolean
> = Cell<T, C, N> | ValueCell<T> | MapCell<T, N>;
NF extends boolean = false
> = Cell<T, C, NF> | ValueCell<T> | MapCell<T, NF>;
1 change: 1 addition & 0 deletions src/debug.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ test("Debugger", () => {
throw new Error("no");
});
expect(debug.p(1)).toBe("[] ==> {1} ==> [2]");
expect(debug.p(1, 2)).toBeUndefined();
const errs = debug.e;
expect(errs.length).toBe(1);
expect(errs[0].cell).toBe(3);
Expand Down
14 changes: 12 additions & 2 deletions src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,13 @@ export class Debugger {
* print: a cell and all its dependencies.
* @param cell number
*/
p(cell: number) {
p(cell: number, to?: number) {
// List all cell names in range.
if (to) {
for (let i = cell; i <= to; i++)
console.log(`${i}: ${this.cells?.[i]?.name}`);
return;
}
if (!this.cells[cell]) {
console.log("not found");
return;
Expand Down Expand Up @@ -156,7 +162,11 @@ export class Debugger {
for (const [id, cell] of Object.entries(this.cells)) {
if (cell.value === undefined) {
ids.push(+id);
console.log({ id, name: this.g.name(+id), cell, undefined: true });
console.log({
id,
name: cell.name || this.g.name(+id),
undefined: true
});
}
}
return this.g.topologicalSort()?.filter((id) => ids.includes(id));
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type { ComputeFn, Unsubscriber } from "./types";
// Utilities

export {
defaultComparator,
filter,
filterPredicateCell,
find,
Expand All @@ -38,7 +39,8 @@ export {
mapArrayCell,
reduce,
sort,
type CellArray
type CellArray,
type ValueCellArray
} from "./array";
export {
cellify,
Expand Down
1 change: 1 addition & 0 deletions src/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const nextSubscriber = <V>(
cb: (v: V) => void,
_expectedCount = 2
) => {
if (!cell) return;
// biome-ignore lint/style/useConst: need reference
let uns: Unsubscriber;
let count = 0;
Expand Down
6 changes: 3 additions & 3 deletions src/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class SheetProxy {
this.working = new Working(sh.working);
this.errors = new CellErrors(sh.errors);
if (name) this._name = name;
this._id = sh.addProxy();
this._id = sh.addProxy(name);
}

// a Sheet has id 0, proxies > 0
Expand Down Expand Up @@ -213,7 +213,7 @@ export class SheetProxy {
// @ts-expect-error conflict with overloaded definitions
const cell = this._sheet.map(dependencies, computeFn, name, this, noFail);
this._list.push(cell);
this._sheet.addProxyDependencies(this._id, dependencies);
this._sheet.addProxyDependencies(this._id, dependencies, cell.id);
return cell as MapCell<V, NF>;
}

Expand All @@ -239,7 +239,7 @@ export class SheetProxy {
noFail
);
this._list.push(cell);
this._sheet.addProxyDependencies(this._id, dependencies);
this._sheet.addProxyDependencies(this._id, dependencies, cell.id);
return cell;
}

Expand Down
22 changes: 17 additions & 5 deletions src/sheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export class Sheet {
private _gc: Set<number>;

private _proxies: Graph<number>;
private _proxyNames: Record<number, string>;

/**
* @param equality function comparing a new value with previous value for updates
Expand All @@ -141,29 +142,40 @@ export class Sheet {
this._queue = [];
this._proxies = new Graph();
this._proxies.addNode(0);
this._proxyNames = {};
}

// a Sheet has id 0, proxies > 0
get id() {
return 0;
}

addProxy() {
addProxy(name?: string) {
// keeping 0 as original Sheet
this[proxies]++;
const id = this[proxies];
this._proxies.addNode(id);
this._proxyNames[id] = name;
return id;
}

addProxyEdge(from: number, to: number) {
this._proxies.addEdge(from, to);
}
addProxyDependencies(id: number, deps: AnyCellArray<unknown[]>) {
addProxyDependencies(
id: number,
deps: AnyCellArray<unknown[]>,
cell: number
) {
for (const dep of deps)
if (dep._proxy !== id) this.addProxyEdge(dep._proxy, id);
if (this._proxies.topologicalSort() === null)
this.debug(undefined, "proxy cycle", { proxy: id, deps });
this.debug(undefined, "proxy cycle", {
proxy: id,
cell,
name: this._proxyNames[id],
deps
});
this._queue = [];
}

Expand Down Expand Up @@ -217,7 +229,7 @@ export class Sheet {
* @param pointed the formerly pointed node
*/
private unsetPointerDep(node: number, pointed: number) {
if (!this._cells[node].dependencies.includes(pointed)) {
if (!this._cells?.[node]?.dependencies?.includes(pointed)) {
this._pointers.removeEdge(pointed, node);
}
}
Expand Down Expand Up @@ -837,7 +849,7 @@ export class Sheet {
includeRoots: false,
next
}) // we remove ids as they should have been computed/modified in the right order.
.filter((id) => !ids.has(id)) || [];
?.filter((id) => !ids.has(id)) || [];
/** List of nodes that will be updated that currently are pointers */
const pointersToBeUpdated = mightChange.filter(isPointer);

Expand Down

0 comments on commit bc357f4

Please sign in to comment.