blob: 1a98ca16f3f0cbbf3f07b730ee0f97de065616e6 [file] [log] [blame]
//"use strict";
//
// A framework to make building Emscripten easier. Lets you write modular
// code to handle specific issues.
//
// Actor - Some code that processes items.
// Item - Some data that is processed by actors.
// Substrate - A 'world' with some actors and some items.
//
// The idea is to create a substrate, fill it with the proper items
// and actors, and then run it to completion. Actors will process
// the items they are given, and forward the results to other actors,
// until we are finished. Some of the results are then the final
// output of the entire calculation done in the substrate.
//
var DEBUG = 0;
var DEBUG_MEMORY = 0;
var MemoryDebugger = {
clear: function() {
MemoryDebugger.time = Date.now();
MemoryDebugger.datas = {};
var info = MemoryDebugger.doGC();
MemoryDebugger.last = info[2];
MemoryDebugger.max = 0;
MemoryDebugger.tick('--clear--');
},
doGC: function() {
var raw = gc().replace('\n', '');
print('zz raw gc info: ' + raw);
return /before (\d+), after (\d+),.*/.exec(raw);
},
tick: function(name) {
var now = Date.now();
if (now - this.time > 1000) {
// ..
this.time = now;
}
// assume |name| exists from now on
var raw = gc().replace('\n', '');
var info = MemoryDebugger.doGC();
var before = info[1];
var after = info[2];
MemoryDebugger.max = Math.max(MemoryDebugger.max, before, after);
// A GC not called by us may have done some work 'silently'
var garbage = before - after;
var real = after - MemoryDebugger.last;
print('zz gc stats changes: ' + [name, real, garbage]);
MemoryDebugger.last = after;
if (Math.abs(garbage) + Math.abs(real) > 0) {
var data = MemoryDebugger.datas[name];
if (!data) {
data = MemoryDebugger.datas[name] = {
name: name,
count: 0,
garbage: 0,
real: 0
};
}
data.garbage += garbage;
data.real += real;
data.count++;
}
MemoryDebugger.dump();
},
dump: function() {
var vals = values(MemoryDebugger.datas);
print('zz max: ' + (MemoryDebugger.max/(1024*1024)).toFixed(3));
print('zz real:');
vals.sort(function(x, y) { return y.real - x.real });
vals.forEach(function(v) { if (Math.abs(v.real) > 1024*1024) print('zz ' + v.name + ' real = ' + (v.real/(1024*1024)).toFixed(3) + ' mb'); });
print('zz garbage:');
vals.sort(function(x, y) { return y.garbage - x.garbage });
vals.forEach(function(v) { if (Math.abs(v.garbage) > 1024*1024) print('zz ' + v.name + ' garbage = ' + (v.garbage/(1024*1024)).toFixed(3) + ' mb'); });
}
}
if (DEBUG_MEMORY) MemoryDebugger.clear();
function Substrate(name_) {
this.name_ = name_;
this.actors = {};
this.currUid = 1;
};
Substrate.prototype = {
addItem: function(item, targetActor) {
if (targetActor == '/dev/null') return;
if (targetActor == '/dev/stdout') {
this.results.push(item);
return;
}
this.actors[targetActor].inbox.push(item);
},
addItems: function(items, targetActor) {
if (targetActor == '/dev/null') return;
if (targetActor == '/dev/stdout') {
this.results = this.results.concat(items);
return;
}
this.actors[targetActor].inbox = this.actors[targetActor].inbox.concat(items);
},
checkInbox: function(actor) {
var items = actor.inbox;
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (!item.__uid__) {
item.__uid__ = this.currUid;
this.currUid ++;
}
}
var MAX_INCOMING = Infinity;
if (MAX_INCOMING == Infinity) {
actor.inbox = [];
actor.items = actor.items.concat(items);
} else {
throw 'Warning: Enter this code at your own risk. It can save memory, but often regresses speed.';
actor.inbox = items.slice(MAX_INCOMING);
actor.items = actor.items.concat(items.slice(0, MAX_INCOMING));
}
},
addActor: function(name_, actor) {
assert(name_ && actor);
actor.name_ = name_;
actor.items = [];
actor.inbox = [];
actor.forwardItem = bind(this, this.addItem);
actor.forwardItems = bind(this, this.addItems);
this.actors[name_] = actor;
if (!actor.process) actor.process = Actor.prototype.process;
return actor;
},
solve: function() {
dprint('framework', "// Solving " + this.name_ + "...");
if (DEBUG_MEMORY) MemoryDebugger.tick('pre-run substrate ' + this.name_ + ' (priors may be cleaned)');
var startTime = Date.now();
var midTime = startTime;
var that = this;
function midComment() {
var curr = Date.now();
if (curr - midTime > 500) {
dprint('framework', '// Working on ' + that.name_ + ', so far ' + ((curr-startTime)/1000).toString().substr(0,10) + ' seconds.');
midTime = curr;
}
}
function finalComment() {
dprint('framework', '// Completed ' + that.name_ + ' in ' + ((Date.now() - startTime)/1000).toString().substr(0,10) + ' seconds.');
}
var ret = null;
var finalResult = null;
this.results = [];
var finished = false;
var onResult = this.onResult;
var actors = values(this.actors); // Assumes actors are not constantly added
while (!finished) {
dprint('framework', "Cycle start, items: ");// + values(this.actors).map(function(actor) actor.items).reduce(function(x,y) x+y, 0));
var hadProcessing = false;
for (var i = 0; i < actors.length; i++) {
var actor = actors[i];
if (actor.inbox.length == 0 && actor.items.length == 0) continue;
midComment();
this.checkInbox(actor);
var outputs;
var currResultCount = this.results.length;
dprint('framework', 'Processing using ' + actor.name_ + ': ' + actor.items.length);
outputs = actor.process(actor.items);
actor.items = [];
if (DEBUG_MEMORY) MemoryDebugger.tick('actor ' + actor.name_);
dprint('framework', 'New results: ' + (outputs.length + this.results.length - currResultCount) + ' out of ' + (this.results.length + outputs.length));
hadProcessing = true;
if (outputs && outputs.length > 0) {
if (outputs.length === 1 && outputs[0].__finalResult__) {
if (DEBUG) print("Solving complete: __finalResult__");
delete outputs[0].__finalResult__; // Might recycle this
delete outputs[0].__uid__;
finalComment();
finished = true;
finalResult = outputs[0];
} else {
this.results = this.results.concat(outputs);
}
}
}
if (!hadProcessing) {
if (DEBUG) print("Solving complete: no remaining items");
finalComment();
this.results.forEach(function(result) {
delete result.__uid__; // Might recycle these
if (onResult) onResult(result);
});
ret = this.results;
this.results = null; // No need to hold on to them any more
break;
}
if (finalResult) {
ret = finalResult;
break;
}
midComment();
}
// Clear the actors. Do not forget about the actors, though, to make this substrate reusable.
actors.forEach(function(actor) {
actor.items = null;
actor.inbox = null;
});
if (DEBUG_MEMORY) MemoryDebugger.tick('finished ' + this.name_);
return ret;
}
};
// Global access to the currently-being processed item.
// Note that if you overload process in Actor, this will need to be set if you rely on it.
var Framework = {
currItem: null
};
function Actor() { };
Actor.prototype = {
process: function(items) {
var ret = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
items[i] = null; // items may be very very large. Allow GCing to occur in the loop by releasing refs here
dprint('frameworkLines', 'Processing item for llvm line ' + item.lineNum);
Framework.currItem = item;
var outputs = this.processItem(item);
Framework.currItem = null;
if (outputs) {
ret = ret.concat(outputs);
}
}
return ret;
}
};