state-trooper
Version:
Application State Manager
196 lines (166 loc) • 4.04 kB
JavaScript
;
var dispatch = require("./dispatch");
var select = require("./select");
var Channel = require("./channels").Channel;
var NO_VALUE = {};
var FnHandler = function(blockable, f) {
this.f = f;
this.blockable = blockable;
};
FnHandler.prototype.is_active = function() {
return true;
};
FnHandler.prototype.is_blockable = function() {
return this.blockable;
};
FnHandler.prototype.commit = function() {
return this.f;
};
function put_then_callback(channel, value, callback) {
var result = channel._put(value, new FnHandler(true, callback));
if (result && callback) {
callback(result.value);
}
}
function take_then_callback(channel, callback) {
var result = channel._take(new FnHandler(true, callback));
if (result) {
callback(result.value);
}
}
var Process = function(gen, onFinish, creator) {
this.gen = gen;
this.creatorFunc = creator;
this.finished = false;
this.onFinish = onFinish;
};
var Instruction = function(op, data) {
this.op = op;
this.data = data;
};
var TAKE = "take";
var PUT = "put";
var SLEEP = "sleep";
var ALTS = "alts";
// TODO FIX XXX: This is a (probably) temporary hack to avoid blowing
// up the stack, but it means double queueing when the value is not
// immediately available
Process.prototype._continue = function(response) {
var self = this;
dispatch.run(function() {
self.run(response);
});
};
Process.prototype._done = function(value) {
if (!this.finished) {
this.finished = true;
var onFinish = this.onFinish;
if (typeof onFinish === "function") {
dispatch.run(function() {
onFinish(value);
});
}
}
};
Process.prototype.run = function(response) {
if (this.finished) {
return;
}
// TODO: Shouldn't we (optionally) stop error propagation here (and
// signal the error through a channel or something)? Otherwise the
// uncaught exception will crash some runtimes (e.g. Node)
var iter = this.gen.next(response);
if (iter.done) {
this._done(iter.value);
return;
}
var ins = iter.value;
var self = this;
if (ins instanceof Instruction) {
switch (ins.op) {
case PUT:
var data = ins.data;
put_then_callback(data.channel, data.value, function(ok) {
self._continue(ok);
});
break;
case TAKE:
var channel = ins.data;
take_then_callback(channel, function(value) {
self._continue(value);
});
break;
case SLEEP:
var msecs = ins.data;
dispatch.queue_delay(function() {
self.run(null);
}, msecs);
break;
case ALTS:
select.do_alts(ins.data.operations, function(result) {
self._continue(result);
}, ins.data.options);
break;
}
}
else if(ins instanceof Channel) {
var channel = ins;
take_then_callback(channel, function(value) {
self._continue(value);
});
}
else {
this._continue(ins);
}
};
function take(channel) {
return new Instruction(TAKE, channel);
}
function put(channel, value) {
return new Instruction(PUT, {
channel: channel,
value: value
});
}
function poll(channel) {
if (channel.closed) {
return NO_VALUE;
}
var result = channel._take(new FnHandler(false));
if (result) {
return result.value;
} else {
return NO_VALUE;
}
}
function offer(channel, value) {
if (channel.closed) {
return false;
}
var result = channel._put(value, new FnHandler(false));
if (result) {
return true;
} else {
return false;
}
}
function sleep(msecs) {
return new Instruction(SLEEP, msecs);
}
function alts(operations, options) {
return new Instruction(ALTS, {
operations: operations,
options: options
});
}
exports.put_then_callback = put_then_callback;
exports.take_then_callback = take_then_callback;
exports.put = put;
exports.take = take;
exports.offer = offer;
exports.poll = poll;
exports.sleep = sleep;
exports.alts = alts;
exports.Instruction = Instruction;
exports.Process = Process;
exports.NO_VALUE = NO_VALUE;