//"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; } };