relu-core
Version:
559 lines (524 loc) • 13.4 kB
JavaScript
var rpPrototype = {
_toString: function() {
return this.constructor.name + this._info() + "{" + this._getUndependend() + "}" + this._refs;
},
_info: function() { return "" },
// events
onceChanged: function(l) {
if(!this._onceChangedListeners) {
this._onceChangedListeners = [l];
} else {
this._onceChangedListeners.push(l);
}
},
onChanged: function(l) {
if(!this._changedListeners) {
this._changedListeners = [l];
} else {
this._changedListeners.push(l);
}
},
onUpdated: function(l, prio) {
if(!this._updatedListeners) {
this._updatedListeners = [l];
} else {
this._updatedListeners[prio?"unshift":"push"](l);
}
},
onAdded: function(l, prio) {
if(!this._addedListeners) {
this._addedListeners = [l];
} else {
this._addedListeners[prio?"unshift":"push"](l);
}
},
onRemoved: function(l, prio) {
if(!this._removedListeners) {
this._removedListeners = [l];
} else {
this._removedListeners[prio?"unshift":"push"](l);
}
},
onNested: function(l) {
if(!this._nestedListeners) {
this._nestedListeners = [l];
} else {
this._nestedListeners.push(l);
}
},
onceDisposed: function(l) {
if(!this._disposeListeners) {
this._disposeListeners = [l];
} else {
this._disposeListeners.push(l);
}
},
removeOnceChangedListener: function(l) {
if(!this._onceChangedListeners) return;
var idx = this._onceChangedListeners.indexOf(l);
if(idx < 0) return;
this._onceChangedListeners.splice(idx, 1);
},
removeChangedListener: function(l) {
if(!this._changedListeners) return;
var idx = this._changedListeners.indexOf(l);
if(idx < 0) return;
this._changedListeners.splice(idx, 1);
},
removeUpdatedListener: function(l) {
if(!this._updatedListeners) return;
var idx = this._updatedListeners.indexOf(l);
if(idx < 0) return;
this._updatedListeners.splice(idx, 1);
},
removeAddedListener: function(l) {
if(!this._addedListeners) return;
var idx = this._addedListeners.indexOf(l);
if(idx < 0) return;
this._addedListeners.splice(idx, 1);
},
removeRemovedListener: function(l) {
if(!this._removedListeners) return;
var idx = this._removedListeners.indexOf(l);
if(idx < 0) return;
this._removedListeners.splice(idx, 1);
},
removeNestedListener: function(l) {
if(!this._nestedListeners) return;
var idx = this._nestedListeners.indexOf(l);
if(idx < 0) return;
this._nestedListeners.splice(idx, 1);
},
removeDisposedListener: function(l) {
if(!this._disposeListeners) return;
var idx = this._disposeListeners.indexOf(l);
if(idx < 0) return;
this._disposeListeners.splice(idx, 1);
},
_changed: function() {
var onceChangedListeners = this._onceChangedListeners && this._onceChangedListeners.slice();
var changedListeners = this._changedListeners && this._changedListeners.slice();
this._onceChangedListeners = undefined;
if(onceChangedListeners) for(var i = 0; i < onceChangedListeners.length; i++)
onceChangedListeners[i].call(this);
if(changedListeners) for(i = 0; i < changedListeners.length; i++)
changedListeners[i].call(this);
},
_updated: function(newValue, oldValue) {
if(!this._updatedListeners) return;
var updatedListeners = this._updatedListeners.slice();
for(var i = 0; i < updatedListeners.length; i++)
updatedListeners[i].call(this, newValue, oldValue);
},
_added: function(idx, newValue) {
if(!this._addedListeners) return;
var addedListeners = this._addedListeners.slice();
for(var i = 0; i < addedListeners.length; i++)
addedListeners[i].call(this, idx, newValue);
},
_removed: function(idx, oldValue) {
if(!this._removedListeners) return;
var removedListeners = this._removedListeners.slice();
for(var i = 0; i < removedListeners.length; i++)
removedListeners[i].call(this, idx, oldValue);
},
_nested: function(event, path, arg1, arg2) {
if(!this._nestedListeners) return;
var nestedListeners = this._nestedListeners.slice();
for(var i = 0; i < nestedListeners.length; i++)
nestedListeners[i].call(this, event, path, arg1, arg2);
},
_disposed: function() {
if(!this._disposeListeners) return;
var disposeListeners = this._disposeListeners.slice();
for(var i = 0; i < disposeListeners.length; i++)
disposeListeners[i].call(this);
},
// construct
init: function() {
var p = this;
p._onceChangedListeners = undefined;
p._changedListeners = undefined;
p._updatedListeners = undefined;
p._addedListeners = undefined;
p._removedListeners = undefined;
p._refs = 1;
p._weakRefs = 0;
p._temporaryIndex = -1;
function _changed() {
p._changed();
};
p._changedAtAtomicEnd = function() {
base.atAtomicEnd(_changed);
};
base.addTemporary(p);
},
// tracking
depend: function() {
base.addDependency(this);
},
scope: function() {
base.addScopeItem(this);
if(base.removeTemporary(this))
this.unref("temporary");
return this;
},
// reference counting
ref: function() {
if(this._refs <= 0) throw new Error("Called ref, but RP is already disposed");
this._refs++;
return this;
},
unref: function() {
this._refs--;
if(this._refs === 0) {
this._dispose();
} else if(this._refs < this._weakRefs) throw new Error("RP is already disposed (too much unref)");
},
_addWeakRef: function() {
this._refs--;
this._weakRefs--;
},
_dispose: function() {
this._disposed();
if(this._attrs) Object.keys(this._attrs).forEach(function(attrName) {
this._attrs[attrName].unref(this);
}, this);
},
temporary: function() {
base.addTemporary(this.ref("temporary"));
return this;
},
isDisposed: function() {
return this._refs <= 0;
},
// getter
g: function() {
if(arguments.length === 0) {
return this._get();
} else {
var p = this;
for(var i = 0; i < arguments.length; i++) {
var name = arguments[i];
p = p.access(name);
}
return p;
}
},
access: function(name) {
var me = this;
if(base.isRp(name)) {
return name.computed(function(name) {
return me.access(name);
}).delegated();
}
if(Array.isArray(name)) {
for(var i = 0; i < name.length; i++)
me = me.access(name[i]);
return me;
}
if(typeof name === "number") {
return this._element(name);
}
return this._property(name);
},
// setter
set: function(newValue) {
var p = this;
var args = arguments;
base.atomic(function() {
if(args.length <= 1) {
p._update(newValue);
} else {
for(var i = 0; i < args.length - 1; i++)
p = p.access(args[i]);
p._update(args[args.length-1]);
}
});
},
modify: function(fn) {
var p = this;
var args = arguments;
base.atomic(function() {
if(args.length <= 1) {
p._modify(fn);
} else {
for(var i = 0; i < args.length - 1; i++)
p = p.access(args[i]);
p._modify(args[args.length-1]);
}
});
},
// attribute
attr: function(name, initial) {
if(!this._attrs) this._attrs = {};
var a = this._attrs[name];
if(!a) {
a = this._makeAttr(name, initial);
this._attrs[name] = a;
a.ref(this);
}
return a.temporary();
},
hasAttr: function(name) {
if(!this._attrs) return false;
return !!this._attrs[name];
},
// to be overwritten
_get: function() {
throw new Error("not readable");
},
_getUndependend: function() {
throw new Error("not readable");
},
_actionsTarget: function() {
throw new Error("not writable");
},
_actionsList: function() {
throw new Error("not writable");
},
isConst: function() {
return false;
},
_update: function(newValue) {
var al = this._actionsList();
if(!al) return;
al.update([], newValue);
},
_modify: function(fn) {
var al = this._actionsList();
if(!al) return;
al.modify([], fn);
},
_splice: function() {
var al = this._actionsList();
if(!al) return;
al.splice.apply(al, [[]].concat(Array.prototype.slice.call(arguments)));
},
_spliceBefore: function() {
var al = this._actionsList();
if(!al) return;
al.spliceBefore.apply(al, [[]].concat(Array.prototype.slice.call(arguments)));
},
};
function base() {
var p = function rp() {
return p.g.apply(p, arguments);
};
p.__proto__ = rpPrototype;
p.init();
return p;
}
base.prototype = rpPrototype;
var dependencies = null;
var scopeItems = [];
base.globalScope = scopeItems;
base._refs = [];
base.capture = function(fn) {
var _d = dependencies;
var _s = scopeItems;
dependencies = [];
scopeItems = [];
try {
var value = fn();
var result = {
dependencies: dependencies,
scope: scopeItems,
value: value
};
return result;
} finally {
dependencies = _d
scopeItems = _s;
}
};
base.uncaptured = function(fn) {
var _d = dependencies;
var _s = scopeItems;
dependencies = [];
scopeItems = [];
try {
var value = fn();
base.unrefAll(scopeItems, "scope");
return value;
} finally {
dependencies = _d
scopeItems = _s;
}
};
base.undependend = function(fn) {
var _d = dependencies;
dependencies = null;
try {
return fn();
} finally {
dependencies = _d
}
};
base.scope = function(fn) {
var result = {
items: [],
_refs: 1,
ref: function() {
this._refs++;
},
unref: function() {
if(--this._refs === 0) {
base.unrefAll(this.items.slice(), "scope");
this.items.length = 0;
var idx = parentScope.indexOf(this);
if(idx < 0) throw new Error("Tried to dispose scope, but it isn't in parentScope anymore");
if(idx >= 0) parentScope.splice(idx, 1);
}
},
temporary: function() {
base.addTemporary(result);
},
run: function(fn) {
if(this._refs <= 0) return; // Cannot run in disposed scope
var _s = scopeItems;
scopeItems = this.items;
try {
return fn();
} finally {
scopeItems = _s;
}
},
add: function() {
for(var i = 0; i < arguments.length; i++) {
arguments[i].ref();
this.items.push(arguments[i]);
}
},
scope: function(fn) {
return this.run(function() {
return base.scope(fn);
});
},
value: undefined
};
var parentScope = base.addSubScope(result);
if(!fn) return result;
var _s = scopeItems;
scopeItems = result.items;
try {
result.value = fn();
return result;
} finally {
scopeItems = _s;
}
};
base.isCapturing = function() {
return !!dependencies;
};
base.checkNotCapturing = function() {
if(dependencies) throw new Error("modification in a computing function");
};
base.addDependency = function(p) {
if(!dependencies) return;
if(dependencies.indexOf(p) >= 0) return;
dependencies.push(p);
};
base.addScopeItem = function(p) {
if(scopeItems.indexOf(p) >= 0) return;
p.ref("scope");
scopeItems.push(p);
};
base.addSubScope = function(s) {
scopeItems.push(s);
return scopeItems;
};
base.refAll = function(ps, o) {
for(var i = 0, l = ps.length; i < l; i++)
ps[i].ref(o);
return ps;
};
base.unrefAll = function(ps, o) {
for(var i = 0, l = ps.length; i < l; i++)
ps[i].unref(o);
};
base.atomic = function(fn) {
var later = [], after = [];
var _aae = base.atAtomicEnd;
var _aa = base.afterAtomic;
base.atAtomicEnd = function(fn) {
if(typeof fn !== "function") throw new Error("fn should be a function");
if(later.indexOf(fn) >= 0) return;
later.push(fn);
};
base.afterAtomic = function(fn) {
after.push(fn);
};
fn();
while(later.length > 0) {
var l = later;
later = [];
if(_aae === orginalAtAtomicEnd) {
for(var i = 0; i < l.length; i++)
l[i]();
} else {
for(var i = 0; i < l.length; i++)
_aae.call(base, l[i]);
}
}
base.atAtomicEnd = _aae;
base.afterAtomic = _aa;
for(var i = 0; i < after.length; i++) {
_aa.call(base, after[i]);
}
};
function orginalAtAtomicEnd(fn) {
throw new Error("Called atAtomicEnd outside of an atomic block");
// atomic checks for this function and execute it directly
};
base.atAtomicEnd = orginalAtAtomicEnd;
base.afterAtomic = function(fn) {
fn();
};
base.isAtomic = function isAtomic() {
return base.atAtomicEnd !== orginalAtAtomicEnd;
};
var temporary = undefined;
base.addTemporary = function(p) {
if(!temporary) {
temporary = [p];
p._temporaryIndex = 0;
base.scheduleTemporary(function() {
var t = temporary;
temporary = undefined;
for(var i = 0; i < t.length; i++) {
var p = t[i];
if(p) p._temporaryIndex = -1;
}
for(var i = 0; i < t.length; i++) {
var p = t[i];
if(p) p.unref("temporary");
}
});
} else {
p._temporaryIndex = temporary.length;
temporary.push(p);
}
};
base.removeTemporary = function(p) {
if(!temporary) return false;
var idx = p._temporaryIndex;
if(idx >= 0) {
temporary[idx] = undefined;
p._temporaryIndex = -1;
return true;
}
return false;
};
base.scheduleTemporary =
typeof process === "object" &&
typeof process.nextTick === "function" ?
process.nextTick :
function(fn) { setTimeout(fn, 1); };
base.setTemporaryScheduler = function(fn) {
base.scheduleTemporary = fn;
};
base.isRp = function(x) {
return x instanceof base;
};
module.exports = base;