rafa
Version:
Rafa.js is a Javascript framework for building concurrent applications.
145 lines (128 loc) • 4.6 kB
JavaScript
// # constructor(setter: A | Function): Property
// The Property object is a Stream that holds a value and can depend on other
// properties forming a graph. The constructor accepts any type of value or
// a function that is called each time the property is set. When constructed
// with a function, that function is executed immediately to set an initial
// value for the property. If the function references the value of other
// properties then those properties are considered dependants and stores as
// such in the properties attribute. Each time this property is set, all
// dependants are also set.
function Property(setter) {
/*eslint-disable*/
this.Stream.call(this);
/*eslint-enable*/
this.mark = 0;
this.properties = null;
this.setter = this.noop;
this.value = null;
if (typeof setter === "function") {
const dependencies = [];
this.setter = setter;
this.util.P = dependencies;
this.value = setter();
this.util.P = null;
const len = dependencies.length;
for (let i = 0, dependent; i < len; i++) {
dependent = dependencies[i];
if (!dependent.properties) dependent.properties = [];
dependent.properties.push(this);
}
}
else this.value = setter;
}
inherit(Property, Stream, {
// # get(): A
// Return the value this property holds. If this is called as part of
// another property's construction, then this property will put itself in
// the `P` cache on the prototype/util object so it can be placed in the
// dependant objects properties attribute.
get() {
const { P } = this.util;
if (P && P.indexOf(this) === -1) P.push(this);
return this.value;
},
// # set(value: A): Property
// Set this property. If this is a static property, meaning the setter that
// was passed tot he constructor was not a funciton, then this property's
// value is updated to `value` and propogation to dependants begins,
// otherwise its `setter` function is called and the return value is set to
// `value`.
set(value, context) {
const { setter } = this;
if (setter === this.noop) {
this.value = value;
this.util.propagate(this, context || this.context());
}
else this.value = setter();
return this;
},
// # valueOf(): A
// Return the value this property holds. Implementing this method allows
// properties to be used as a primitive.
valueOf() {
return this.get();
},
util: {
P: null,
CyclicError: new Error("Cyclic dependency found in Property graph"),
// # propagate(Property)
// Update all dependant properties of `property` by resetting all
// marks, creating an array of properties in the correct order,
// calling the `set` method of each property, and finally pushing the new
// value of each property through the stream.
propagate(property, context) {
const { properties } = property;
const message = property.message(property.value);
if (properties) {
const len = properties.length;
const ordered = [];
let i, p;
this.reset(properties);
for (i = 0; i < len; i++)
this.visit(ordered, properties[i]);
for (i = 0; i < len; i++)
ordered[i].set();
property.push(context, message);
for (i = 0; i < len; i++) {
p = ordered[i];
p.push(context, property.message(p.value));
}
}
else property.push(context, message);
},
// # reset(properties: Array[Property])
// Set the mark attribute of all dependant properties to `0`.
reset(properties) {
if (properties) {
const len = properties.length;
for (let i = 0, property; i < len; i++) {
property = properties[i];
property.mark = 0;
this.reset(property.properties);
}
}
},
// # visit(ordered: Array[Property], Property)
// Populate the ordered array with properties in the correct order
// for this directed acyclic graph.
visit(ordered, property) {
if (property.mark < 2) {
property.mark = 1;
const properties = property.properties;
if (properties) {
const len = properties.length;
let adjacent;
for (let i = 0; i < len; i++) {
adjacent = properties[i];
if (!adjacent.mark) this.visit(ordered, adjacent);
else if (adjacent.mark === 1) throw this.CyclicError;
}
}
}
if (property.mark !== 2) {
property.mark = 2;
ordered.unshift(property);
}
}
}
});