Skip to content

Commit

Permalink
feat(atom): add View type to create derrived views/value subscriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Mar 1, 2018
1 parent 55c6a7d commit 8c0c621
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/atom/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as api from "@thi.ng/api/api";

export type SwapFn<T> = (curr: T, ...args: any[]) => T;

export type ViewTransform<A, B> = (x: A) => B;

export interface ReadonlyAtom<T> extends
api.IDeref<T>,
api.IWatch<T> {
Expand Down
3 changes: 2 additions & 1 deletion packages/atom/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./atom";
export * from "./cursor";
export * from "./history";
export * from "./path";
export * from "./path";
export * from "./view";
74 changes: 74 additions & 0 deletions packages/atom/src/view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { IDeref, IID, IRelease } from "@thi.ng/api/api";
import { equiv as _equiv } from "@thi.ng/api/equiv";
import { ReadonlyAtom, ViewTransform } from "./api";
import { getter, toPath } from "./path";

export class View<A, B> implements
IDeref<B>,
IID<string>,
IRelease {

static NEXT_ID = 0;

readonly id: string;

readonly parent: ReadonlyAtom<A>;
readonly path: PropertyKey[];

protected state: B;
protected tx: ViewTransform<A, B>;
protected unprocessed: A;
protected isDirty: boolean;

constructor(parent: ReadonlyAtom<A>, path: PropertyKey | PropertyKey[], tx?: ViewTransform<A, B>, equiv = _equiv) {
this.parent = parent;
this.id = `view-${View.NEXT_ID++}`;
this.tx = tx || ((x: any) => x);
this.path = toPath(path);
this.isDirty = true;
const lookup = getter(this.path);
const state = this.parent.deref();
this.unprocessed = state ? lookup(state) : undefined;
parent.addWatch(this.id, (_, prev, curr) => {
const pval: A = prev ? lookup(prev) : prev;
const val: A = curr ? lookup(curr) : curr;
if (!equiv(val, pval)) {
this.unprocessed = val;
this.isDirty = true;
}
});
}

deref() {
if (this.isDirty) {
this.state = this.tx(this.unprocessed);
this.unprocessed = undefined;
this.isDirty = false;
}
return this.state;
}

/**
* Like `deref()`, but doesn't update view's cached state
* and dirty flag if value has changed. If there's an unprocessed
* value change, returns result of this sub's transformer or else
* the cached value.
*
* **Important:** Use this function only if the view's transformer
* is stateless or else might cause undefined/inconsistent
* behavior when calling deref() subsequently.
*/
view() {
return this.isDirty ? this.tx(this.unprocessed) : this.state;
}

release() {
this.unprocessed = undefined;
this.isDirty = true;
return this.parent.removeWatch(this.id);
}

changed() {
return this.isDirty;
}
}

0 comments on commit 8c0c621

Please sign in to comment.