UNPKG

rafa

Version:

Rafa.js is a Javascript framework for building concurrent applications.

145 lines (128 loc) 4.6 kB
// # 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); } } } });