Skip to content

Instantly share code, notes, and snippets.

@micha
Last active July 7, 2017 06:09
Show Gist options
  • Save micha/c0d77a9ddf80f61f7812830be6fecafe to your computer and use it in GitHub Desktop.
Save micha/c0d77a9ddf80f61f7812830be6fecafe to your computer and use it in GitHub Desktop.
const { is } = require('immutable');
/*
* const { List } = require('immutable');
* const { input, formula } = require('cell');
*
* var a = input(100);
* var b = input(200);
* var c = formula((x, y) => List([x, y]))(a, b);
* var d = formula((x, y) => x + y)(a, b);
* var e = formula((x, y) => x.push(y))(c, d);
*
* e.get().toJS(); // [100, 200, 300]
* a.set(50);
* e.get().toJS(); // [50, 200, 250]
*
*****************************************************************************/
var rank = 0;
var tx = null;
function binaryInsert(arr, item) {
let left = 0, right = arr.length, middle, cmp, found;
while (left < right) {
middle = (left + right) >> 1;
cmp = item.rank - arr[middle].rank;
if (cmp > 0) {
left = middle + 1; }
else if (cmp < 0) {
right = middle; }
else {
return; } }
arr.splice(left, 0, item); }
function propagate(sorted) {
let next, oldval, newval;
while ((next = sorted.shift())) {
oldval = next.prev;
newval = next.thunk ? next.thunk() : next.state;
if (! is(oldval, newval)) {
next.prev = newval;
next.notifyWatches(oldval, newval);
for (var i = 0; i < next.sinks.length; i++) {
binaryInsert(sorted, next.sinks[i]); } } } }
class Cell {
constructor(value) {
this.state = value;
this.rank = rank++;
this.prev = value;
this.pending = null;
this.sources = [];
this.sinks = [];
this.thunk = null;
this.update = null;
this.watches = {};
this.constant = false; }
static input(value) {
return new Cell(value); }
static formula(f, update) {
return (...sources) => {
return (new Cell(null)).setFormula(f, sources, update); }; }
static isCell(x) {
return x != null && x.constructor === Cell; }
isConstant() {
return !!this.constant; }
destroy(keepWatches) {
let srcs = this.sources, src, sinks;
this.sources = [];
this.update = null;
this.thunk = null;
if (! keepWatches) {
this.watches = {}; }
for (var i = 0; i < srcs.length; i++) {
if ((src = srcs[i])) {
sinks = src.sinks;
for (var j = 0; j < sinks.length; j++) {
if (sinks[j] === this) {
sinks.splice(j, 1); } } } }
return this; }
setFormula(f, sources = [], update) {
let source, dep;
this.destroy(true);
if (f) {
sources.push(f);
this.sources = sources;
this.update = update;
this.constant = true;
for (var i = 0; i < sources.length; i++) {
if (Cell.isCell((source = sources[i]))) {
if (this.constant && ! source.constant) {
this.constant = false; }
source.sinks.push(this);
if (source.rank > this.rank) {
var q = source.sinks.slice();
while ((dep = q.shift())) {
if (Cell.isCell(dep)) {
dep.rank = rank++;
q.concat(dep.sinks); } } } } }
this.thunk = function() {
var argv = this.sources.slice();
var f = argv.pop();
if (Cell.isCell(f)) {
f = f.get(); }
for (var i = 0; i < argv.length; i++) {
if (Cell.isCell(argv[i])) {
argv[i] = argv[i].get(); } }
return this.state = f.apply(null, argv); }; }
propagate([this]);
return(this); }
notifyWatches(oldval, newval) {
for (var k in this.watches) {
this.watches[k].call(null, k, this, oldval, newval); }
return this; }
addWatch(k, f) {
this.watches[k] = f;
return this; }
removeWatch(k) {
delete this.watches[k];
return this; }
set(value) {
if (this.update) {
this.update(value); }
else if (!this.thunk) {
this.state = value;
propagate([this]); }
else {
throw new Error("can't set formula cell"); }
return this; }
swap(f) {
return this.set(f(this.state)); }
get() {
return this.state; } }
module.exports = Cell;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment