sodiumjs
Version:
A Functional Reactive Programming (FRP) library for JavaScript
371 lines (341 loc) • 12.2 kB
text/typescript
import { Transaction } from "./Transaction";
import { Set } from "typescript-collections";
let totalRegistrations : number = 0;
export function getTotalRegistrations() : number {
return totalRegistrations;
}
export class Source {
// Note:
// When register_ == null, a rank-independent source is constructed (a vertex which is just kept alive for the
// lifetime of vertex that contains this source).
// When register_ != null it is likely to be a rank-dependent source, but this will depend on the code inside register_.
//
// rank-independent souces DO NOT bump up the rank of the vertex containing those sources.
// rank-depdendent sources DO bump up the rank of the vertex containing thoses sources when required.
constructor(
origin : Vertex,
register_ : () => () => void
) {
if (origin === null)
throw new Error("null origin!");
this.origin = origin;
this.register_ = register_;
}
origin : Vertex;
private register_ : () => () => void;
private registered : boolean = false;
private deregister_ : () => void = null;
register(target : Vertex) : void {
if (!this.registered) {
this.registered = true;
if (this.register_ !== null)
this.deregister_ = this.register_();
else {
// Note: The use of Vertex.NULL here instead of "target" is not a bug, this is done to create a
// rank-independent source. (see note at constructor for more details.). The origin vertex still gets
// added target vertex's children for the memory management algorithm.
this.origin.increment(Vertex.NULL);
target.childrn.push(this.origin);
this.deregister_ = () => {
this.origin.decrement(Vertex.NULL);
for (let i = target.childrn.length-1; i >= 0; --i) {
if (target.childrn[i] === this.origin) {
target.childrn.splice(i, 1);
break;
}
}
}
}
}
}
deregister(target : Vertex) : void {
if (this.registered) {
this.registered = false;
if (this.deregister_ !== null)
this.deregister_();
}
}
}
export enum Color { black, gray, white, purple };
let roots : Vertex[] = [];
let nextID : number = 0;
let verbose : boolean = false;
export function setVerbose(v : boolean) : void { verbose = v; }
export function describeAll(v : Vertex, visited : Set<number>)
{
if (visited.contains(v.id)) return;
console.log(v.descr());
visited.add(v.id);
let chs = v.children();
for (let i = 0; i < chs.length; i++)
describeAll(chs[i], visited);
}
export class Vertex {
static NULL : Vertex = new Vertex("user", 1e12, []);
static collectingCycles : boolean = false;
static toBeFreedList : Vertex[] = [];
id : number;
constructor(name : string, rank : number, sources : Source[]) {
this.name = name;
this.rank = rank;
this.sources = sources;
this.id = nextID++;
}
name : string;
rank : number;
sources : Source[];
targets : Vertex[] = [];
childrn : Vertex[] = [];
refCount() : number { return this.targets.length; };
visited : boolean = false;
register(target : Vertex) : boolean {
return this.increment(target);
}
deregister(target : Vertex) : void {
if (verbose)
console.log("deregister "+this.descr()+" => "+target.descr());
this.decrement(target);
Transaction._collectCyclesAtEnd();
}
private incRefCount(target : Vertex) : boolean {
let anyChanged : boolean = false;
if (this.refCount() == 0) {
for (let i = 0; i < this.sources.length; i++)
this.sources[i].register(this);
}
this.targets.push(target);
target.childrn.push(this);
if (target.ensureBiggerThan(this.rank))
anyChanged = true;
totalRegistrations++;
return anyChanged;
}
private decRefCount(target : Vertex) : void {
if (verbose)
console.log("DEC "+this.descr());
let matched = false;
for (let i = target.childrn.length-1; i >= 0; i--)
if (target.childrn[i] === this) {
target.childrn.splice(i, 1);
break;
}
for (let i = 0; i < this.targets.length; i++)
if (this.targets[i] === target) {
this.targets.splice(i, 1);
matched = true;
break;
}
if (matched) {
if (this.refCount() == 0) {
for (let i = 0; i < this.sources.length; i++)
this.sources[i].deregister(this);
}
totalRegistrations--;
}
}
addSource(src : Source) : void {
this.sources.push(src);
if (this.refCount() > 0)
src.register(this);
}
private ensureBiggerThan(limit : number) : boolean {
if (this.visited) {
// Undoing cycle detection for now until TimerSystem.ts ranks are checked.
//throw new Error("Vertex cycle detected.");
return false;
}
if (this.rank > limit)
return false;
this.visited = true;
this.rank = limit + 1;
for (let i = 0; i < this.targets.length; i++)
this.targets[i].ensureBiggerThan(this.rank);
this.visited = false;
return true;
}
descr() : string {
let colStr : string = null;
switch (this.color) {
case Color.black: colStr = "black"; break;
case Color.gray: colStr = "gray"; break;
case Color.white: colStr = "white"; break;
case Color.purple: colStr = "purple"; break;
}
let str = this.id+" "+this.name+" ["+this.refCount()+"/"+this.refCountAdj+"] "+colStr+" ->";
let chs = this.children();
for (let i = 0; i < chs.length; i++) {
str = str + " " + chs[i].id;
}
return str;
}
// --------------------------------------------------------
// Synchronous Cycle Collection algorithm presented in "Concurrent
// Cycle Collection in Reference Counted Systems" by David F. Bacon
// and V.T. Rajan.
color : Color = Color.black;
buffered : boolean = false;
refCountAdj : number = 0;
children() : Vertex[] { return this.childrn; }
increment(referrer : Vertex) : boolean {
return this.incRefCount(referrer);
}
decrement(referrer : Vertex) : void {
this.decRefCount(referrer);
if (this.refCount() == 0)
this.release();
else
this.possibleRoots();
}
release() : void {
this.color = Color.black;
if (!this.buffered)
this.free();
}
free() : void {
while (this.targets.length > 0)
this.decRefCount(this.targets[0]);
}
possibleRoots() : void {
if (this.color != Color.purple) {
this.color = Color.purple;
if (!this.buffered) {
this.buffered = true;
roots.push(this);
}
}
}
static collectCycles() : void {
if (Vertex.collectingCycles) {
return;
}
try {
Vertex.collectingCycles = true;
Vertex.markRoots();
Vertex.scanRoots();
Vertex.collectRoots();
for (let i = Vertex.toBeFreedList.length-1; i >= 0; --i) {
let vertex = Vertex.toBeFreedList.splice(i, 1)[0];
vertex.free();
}
} finally {
Vertex.collectingCycles = false;
}
}
static markRoots() : void {
const newRoots : Vertex[] = [];
// check refCountAdj was restored to zero before mark roots
if (verbose) {
let stack: Vertex[] = roots.slice(0);
let visited: Set<number> = new Set();
while (stack.length != 0) {
let vertex = stack.pop();
if (visited.contains(vertex.id)) {
continue;
}
visited.add(vertex.id);
if (vertex.refCountAdj != 0) {
console.log("markRoots(): reachable refCountAdj was not reset to zero: " + vertex.descr());
}
for (let i = 0; i < vertex.childrn.length; ++i) {
let child = vertex.childrn[i];
stack.push(child);
}
}
}
//
for (let i = 0; i < roots.length; i++) {
if (verbose)
console.log("markRoots "+roots[i].descr()); // ###
if (roots[i].color == Color.purple) {
roots[i].markGray();
newRoots.push(roots[i]);
}
else {
roots[i].buffered = false;
if (roots[i].color == Color.black && roots[i].refCount() == 0)
Vertex.toBeFreedList.push(roots[i]);
}
}
roots = newRoots;
}
static scanRoots() : void {
for (let i = 0; i < roots.length; i++)
roots[i].scan();
}
static collectRoots() : void {
for (let i = 0; i < roots.length; i++) {
roots[i].buffered = false;
roots[i].collectWhite();
}
if (verbose) { // double check adjRefCount is zero for all vertices reachable by roots
let stack: Vertex[] = roots.slice(0);
let visited: Set<number> = new Set();
while (stack.length != 0) {
let vertex = stack.pop();
if (visited.contains(vertex.id)) {
continue;
}
visited.add(vertex.id);
if (vertex.refCountAdj != 0) {
console.log("collectRoots(): reachable refCountAdj was not reset to zero: " + vertex.descr());
}
for (let i = 0; i < vertex.childrn.length; ++i) {
let child = vertex.childrn[i];
stack.push(child);
}
}
}
roots = [];
}
markGray() : void {
if (this.color != Color.gray) {
this.color = Color.gray;
let chs = this.children();
for (let i = 0; i < chs.length; i++) {
chs[i].refCountAdj--;
if (verbose)
console.log("markGray "+this.descr());
chs[i].markGray();
}
}
}
scan() : void {
if (verbose)
console.log("scan "+this.descr());
if (this.color == Color.gray) {
if (this.refCount()+this.refCountAdj > 0)
this.scanBlack();
else {
this.color = Color.white;
if (verbose)
console.log("scan WHITE "+this.descr());
let chs = this.children();
for (let i = 0; i < chs.length; i++)
chs[i].scan();
}
}
}
scanBlack() : void {
this.refCountAdj = 0;
this.color = Color.black;
let chs = this.children();
for (let i = 0; i < chs.length; i++) {
if (verbose)
console.log("scanBlack "+this.descr());
if (chs[i].color != Color.black)
chs[i].scanBlack();
}
}
collectWhite() : void {
if (this.color == Color.white && !this.buffered) {
if (verbose)
console.log("collectWhite "+this.descr());
this.color = Color.black;
this.refCountAdj = 0;
let chs = this.children();
for (let i = 0; i < chs.length; i++)
chs[i].collectWhite();
Vertex.toBeFreedList.push(this);
}
}
}