whalesong/world/kernel.js

375 lines
10 KiB
JavaScript

var imageLibrary = MACHINE.modules['whalesong/image/private/main.rkt'].privateExports;
var isImage = imageLibrary.isImage;
var PAUSE = plt.runtime.PAUSE;
var EMPTY = plt.baselib.lists.EMPTY;
var isString = plt.baselib.strings.isString;
var isBoolean = function(x) { return x === true || x === false; }
var isSymbol = plt.baselib.symbols.isSymbol;
var makePair = plt.baselib.lists.makePair;
var makeList = plt.baselib.lists.makeList;
var makeRational = plt.baselib.numbers.makeRational;
var finalizeClosureCall = plt.baselib.functions.finalizeClosureCall;
//////////////////////////////////////////////////////////////////////
var bigBang = function(MACHINE, initW, handlers) {
var outerToplevelNode = $('<span/>').css('padding', '0px').get(0);
MACHINE.params.currentOutputPort.writeDomNode(MACHINE, outerToplevelNode);
var toplevelNode = $('<span/>').css('padding', '0px').appendTo(outerToplevelNode).get(0);
var configs = [];
var isOutputConfigSeen = false;
for (var i = 0 ; i < handlers.length; i++) {
if (isWorldConfigOption(handlers[i])) {
configs.push(handlers[i].toRawHandler(MACHINE, toplevelNode));
}
else {
configs.push(handlers[i]);
}
if (isOutputConfig(handlers[i])) { isOutputConfigSeen = true; }
}
// If we haven't seen an onDraw function, use the default one.
if (! isOutputConfigSeen) {
configs.push(new DefaultDrawingOutput().toRawHandler(MACHINE, toplevelNode));
}
PAUSE(function(restart) {
// var onBreak = function() {
// bigBangController.breaker();
// }
// state.addBreakRequestedListener(onBreak);
var bigBangController = rawJsworld.bigBang(
toplevelNode,
initW,
configs,
{},
function(finalWorldValue) {
// state.removeBreakRequestedListener(onBreak);
restart(function(MACHINE) {
finalizeClosureCall(
MACHINE,
finalWorldValue);
});
});
});
};
//////////////////////////////////////////////////////////////////////
// Every world configuration function (on-tick, stop-when, ...)
// produces a WorldConfigOption instance.
var WorldConfigOption = function(name) {
this.name = name;
};
WorldConfigOption.prototype.configure = function(config) {
throw new Error('unimplemented WorldConfigOption');
};
WorldConfigOption.prototype.toDomNode = function(params) {
var span = document.createElement('span');
span.appendChild(document.createTextNode("(" + this.name + " ...)"));
return span;
};
WorldConfigOption.prototype.toWrittenString = function(cache) {
return "(" + this.name + " ...)";
};
WorldConfigOption.prototype.toDisplayedString = function(cache) {
return "(" + this.name + " ...)";
};
var isWorldConfigOption = plt.baselib.makeClassPredicate(WorldConfigOption);
//////////////////////////////////////////////////////////////////////
// adaptWorldFunction: Racket-function -> World-CPS
// Takes a racket function and converts it to the CPS-style function
// that our world implementation expects.
var adaptWorldFunction = function(worldFunction) {
return function() {
// Consumes any number of arguments.
var success = arguments[arguments.length - 1];
plt.baselib.functions.internalCallDuringPause.apply(
null,
[MACHINE,
worldFunction,
function(v) {
success(v);
},
function(err) {
// FIXME: do error trapping
if (window.console && window.console.log) {
window.console.log(err);
} else {
throw err;
}
}].concat([].slice.call(arguments, 0, arguments.length - 1)));
};
};
//////////////////////////////////////////////////////////////////////
// OnTick: racket-function javascript-float -> handler
var OnTick = function(handler, aDelay) {
WorldConfigOption.call(this, 'on-tick');
this.handler = handler;
this.delay = aDelay;
};
OnTick.prototype = plt.baselib.heir(WorldConfigOption.prototype);
OnTick.prototype.toRawHandler = function(MACHINE, toplevelNode) {
var that = this;
var worldFunction = adaptWorldFunction(that.handler);
return rawJsworld.on_tick(this.delay, worldFunction);
};
//////////////////////////////////////////////////////////////////////
var OnKey = function(handler) {
WorldConfigOption.call(this, 'on-key');
this.handler = handler;
}
OnKey.prototype = plt.baselib.heir(WorldConfigOption.prototype);
OnKey.prototype.toRawHandler = function(MACHINE, toplevelNode) {
var that = this;
var worldFunction = adaptWorldFunction(that.handler);
return rawJsworld.on_key(
function(w, e, success) {
worldFunction(w, getKeyCodeName(e), success);
});
};
var getKeyCodeName = function(e) {
var code = e.charCode || e.keyCode;
var keyname;
switch(code) {
case 16: keyname = "shift"; break;
case 17: keyname = "control"; break;
case 19: keyname = "pause"; break;
case 27: keyname = "escape"; break;
case 33: keyname = "prior"; break;
case 34: keyname = "next"; break;
case 35: keyname = "end"; break;
case 36: keyname = "home"; break;
case 37: keyname = "left"; break;
case 38: keyname = "up"; break;
case 39: keyname = "right"; break;
case 40: keyname = "down"; break;
case 42: keyname = "print"; break;
case 45: keyname = "insert"; break;
case 46: keyname = String.fromCharCode(127); break;
case 106: keyname = "*"; break;
case 107: keyname = "+"; break;
case 109: keyname = "-"; break;
case 110: keyname = "."; break;
case 111: keyname = "/"; break;
case 144: keyname = "numlock"; break;
case 145: keyname = "scroll"; break;
case 186: keyname = ";"; break;
case 187: keyname = "="; break;
case 188: keyname = ","; break;
case 189: keyname = "-"; break;
case 190: keyname = "."; break;
case 191: keyname = "/"; break;
case 192: keyname = "`"; break;
case 219: keyname = "["; break;
case 220: keyname = "\\"; break;
case 221: keyname = "]"; break;
case 222: keyname = "'"; break;
default:
if (code >= 96 && code <= 105) {
keyname = (code - 96).toString();
} else if (code >= 112 && code <= 123) {
keyname = "f" + (code - 111);
} else {
keyname = String.fromCharCode(code).toLowerCase();
}
break;
}
return keyname;
}
//////////////////////////////////////////////////////////////////////
var OnMouse = function(handler) {
WorldConfigOption.call(this, 'on-mouse');
this.handler = handler;
}
OnMouse.prototype = plt.baselib.heir(WorldConfigOption.prototype);
OnMouse.prototype.toRawHandler = function(MACHINE, toplevelNode) {
var that = this;
var worldFunction = adaptWorldFunction(that.handler);
return rawJsworld.on_mouse(
function(w, x, y, type, success) {
worldFunction(w, x, y, type, success);
});
};
var OutputConfig = function() {}
OutputConfig.prototype = plt.baselib.heir(WorldConfigOption.prototype);
var isOutputConfig = plt.baselib.makeClassPredicate(OutputConfig);
// // ToDraw
var ToDraw = function(handler) {
WorldConfigOption.call(this, 'to-draw');
this.handler = handler;
};
ToDraw.prototype = plt.baselib.heir(OutputConfig.prototype);
ToDraw.prototype.toRawHandler = function(MACHINE, toplevelNode) {
var that = this;
var reusableCanvas;
var reusableCanvasNode;
var adaptedWorldFunction = adaptWorldFunction(this.handler);
var worldFunction = function(world, success) {
adaptedWorldFunction(
world,
function(v) {
// fixme: once jsworld supports fail continuations, use them
// to check the status of the scene object and make sure it's an
// image.
if (isImage(v) ) {
var width = v.getWidth();
var height = v.getHeight();
if (! reusableCanvas) {
reusableCanvas = imageLibrary.makeCanvas(width, height);
// Note: the canvas object may itself manage objects,
// as in the case of an excanvas. In that case, we must make
// sure jsworld doesn't try to disrupt its contents!
reusableCanvas.jsworldOpaque = true;
reusableCanvasNode = rawJsworld.node_to_tree(reusableCanvas);
}
if (reusableCanvas.width !== width) {
reusableCanvas.width = width;
}
if (reusableCanvas.height !== height) {
reusableCanvas.height = height;
}
var ctx = reusableCanvas.getContext("2d");
v.render(ctx, 0, 0);
success([toplevelNode, reusableCanvasNode]);
} else {
success([toplevelNode, rawJsworld.node_to_tree(plt.baselib.format.toDomNode(v, MACHINE.params['print-mode']))]);
}
});
};
var cssFunction = function(w, k) {
if (reusableCanvas) {
k([[reusableCanvas,
["padding", "0px"],
["width", reusableCanvas.width + "px"],
["height", reusableCanvas.height + "px"]]]);
} else {
k([]);
}
}
return rawJsworld.on_draw(worldFunction, cssFunction);
};
var DefaultDrawingOutput = function() {
WorldConfigOption.call(this, 'to-draw');
};
DefaultDrawingOutput.prototype = plt.baselib.heir(WorldConfigOption.prototype);
DefaultDrawingOutput.prototype.toRawHandler = function(MACHINE, toplevelNode) {
var that = this;
var worldFunction = function(world, success) {
success([toplevelNode,
rawJsworld.node_to_tree(plt.baselib.format.toDomNode(world,
MACHINE.params['print-mode']))]);
//k(rawJsworld.node_to_tree(plt.baselib.format.toDomNode(world)));
};
var cssFunction = function(w, success) { success([]); }
return rawJsworld.on_draw(worldFunction, cssFunction);
};
//////////////////////////////////////////////////////////////////////
var StopWhen = function(handler) {
WorldConfigOption.call(this, 'stop-when');
this.handler = handler;
};
StopWhen.prototype = plt.baselib.heir(WorldConfigOption.prototype);
StopWhen.prototype.toRawHandler = function(MACHINE, toplevelNode) {
var that = this;
var worldFunction = adaptWorldFunction(that.handler);
return rawJsworld.stop_when(worldFunction);
};