Merge branch 'with-exclusive-lock'

Conflicts:

	js-assembler/runtime-src/runtime.js
	version.rkt
	web-world/main.rkt
This commit is contained in:
Danny Yoo 2011-12-13 15:31:04 -05:00
commit 976b6112f1
7 changed files with 419 additions and 219 deletions

View File

@ -128,66 +128,77 @@
var coerseClosureToJavaScript = function (v, MACHINE) {
var f = function (succ, fail) {
succ = succ || function () {};
fail = fail || function () {};
if (!(baselib.arity.isArityMatching(v.racketArity, arguments.length - 2))) {
var msg = baselib.format.format(
"arity mismatch: ~s expected ~s argument(s) but received ~s",
[v.displayName, v.racketArity, arguments.length - 2]);
return fail(new baselib.exceptions.RacketError(
msg,
baselib.exceptions.makeExnFailContractArity(msg,
MACHINE.captureContinuationMarks())));
}
var oldVal = MACHINE.v;
var oldArgcount = MACHINE.a;
var oldProc = MACHINE.p;
var oldErrorHandler = MACHINE.params['currentErrorHandler'];
var afterGoodInvoke = function (MACHINE) {
plt.runtime.PAUSE(
function (restart) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
var returnValue = MACHINE.v;
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
succ(returnValue);
});
};
afterGoodInvoke.mvr = function (MACHINE) {
plt.runtime.PAUSE(
function (restart) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
var returnValues = [MACHINE.v], i;
for (i = 0; i < MACHINE.a - 1; i++) {
returnValues.push(MACHINE.e.pop());
}
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
succ.apply(null, returnValues);
});
};
MACHINE.c.push(
new baselib.frames.CallFrame(afterGoodInvoke, v));
MACHINE.a = arguments.length - 2;
var args = [];
var i;
for (i = 0; i < arguments.length - 2; i++) {
MACHINE.e.push(arguments[arguments.length - 1 - i]);
for (i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
MACHINE.p = v;
MACHINE.params['currentErrorHandler'] = function (MACHINE, e) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
fail(e);
};
MACHINE.trampoline(v.label);
MACHINE.exclusiveLock.acquire(
"js-as-closure",
function(releaseLock) {
succ = succ || function () {};
fail = fail || function () {};
if (!(baselib.arity.isArityMatching(v.racketArity, args.length - 2))) {
var msg = baselib.format.format(
"arity mismatch: ~s expected ~s argument(s) but received ~s",
[v.displayName, v.racketArity, args.length - 2]);
releaseLock();
return fail(new baselib.exceptions.RacketError(
msg,
baselib.exceptions.makeExnFailContractArity(msg,
MACHINE.captureContinuationMarks())));
}
var oldVal = MACHINE.v;
var oldArgcount = MACHINE.a;
var oldProc = MACHINE.p;
var oldErrorHandler = MACHINE.params['currentErrorHandler'];
var afterGoodInvoke = function (MACHINE) {
plt.runtime.PAUSE(
function (restart) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
var returnValue = MACHINE.v;
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
succ(returnValue);
});
};
afterGoodInvoke.mvr = function (MACHINE) {
plt.runtime.PAUSE(
function (restart) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
var returnValues = [MACHINE.v], i;
for (i = 0; i < MACHINE.a - 1; i++) {
returnValues.push(MACHINE.e.pop());
}
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
succ.apply(null, returnValues);
});
};
MACHINE.c.push(
new baselib.frames.CallFrame(afterGoodInvoke, v));
MACHINE.a = args.length - 2;
var i;
for (i = 0; i < args.length - 2; i++) {
MACHINE.e.push(args[args.length - 1 - i]);
}
MACHINE.p = v;
MACHINE.params['currentErrorHandler'] = function (MACHINE, e) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
fail(e);
};
MACHINE._trampoline(v.label, false, releaseLock);
});
};
return f;
};
@ -214,72 +225,89 @@
// internallCallDuringPause: call a Racket procedure and get its results.
// The use assumes the machine is in a running-but-paused state.
// The use assumes the machine is in a running-but-paused state, where the
// lock is still in effect. The lock will continue to be in effect
// after coming back from the internal call.
var internalCallDuringPause = function (MACHINE, proc, success, fail) {
var args = [];
var i;
var oldArgcount, oldVal, oldProc, oldErrorHandler;
if (! baselib.arity.isArityMatching(proc.racketArity, arguments.length - 4)) {
var msg = baselib.format.format("arity mismatch: ~s expected ~s arguments, but received ~s",
[proc.displayName, proc.racketArity, arguments.length - 4]);
return fail(baselib.exceptions.makeExnFailContractArity(msg,
MACHINE.captureContinuationMarks()));
for (i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
if (isClosure(proc)) {
oldVal = MACHINE.v;
oldArgcount = MACHINE.a;
oldProc = MACHINE.p;
var i;
var oldArgcount, oldVal, oldProc, oldErrorHandler, oldControlLength, oldEnvLength;
if (! baselib.arity.isArityMatching(proc.racketArity, args.length - 4)) {
var msg = baselib.format.format("arity mismatch: ~s expected ~s arguments, but received ~s",
[proc.displayName, proc.racketArity, args.length - 4]);
fail(baselib.exceptions.makeExnFailContractArity(msg,
MACHINE.captureContinuationMarks()));
}
oldErrorHandler = MACHINE.params['currentErrorHandler'];
var afterGoodInvoke = function (MACHINE) {
plt.runtime.PAUSE(function (restart) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
var returnValue = MACHINE.v;
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
success(returnValue);
});
};
afterGoodInvoke.mvr = function (MACHINE) {
plt.runtime.PAUSE(function (restart) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
var returnValues = [MACHINE.v];
var i;
for (i = 0; i < MACHINE.a - 1; i++) {
returnValues.push(MACHINE.e.pop());
}
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
success.apply(null, returnValues);
});
};
MACHINE.c.push(
new baselib.frames.CallFrame(afterGoodInvoke, proc));
MACHINE.a = arguments.length - 4;
for (i = 0; i < arguments.length - 4; i++) {
MACHINE.e.push(arguments[arguments.length - 1 - i]);
}
MACHINE.p = proc;
MACHINE.params['currentErrorHandler'] = function (MACHINE, e) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
fail(e);
};
MACHINE.trampoline(proc.label);
} else {
if (! isClosure(proc)) {
fail(baselib.exceptions.makeExnFail(
baselib.format.format(
"Not a procedure: ~e",
proc),
MACHINE.captureContinuationMarks()));
}
};
}
oldVal = MACHINE.v;
oldArgcount = MACHINE.a;
oldProc = MACHINE.p;
oldControlLength = MACHINE.c.length;
oldEnvLength = MACHINE.e.length;
oldErrorHandler = MACHINE.params['currentErrorHandler'];
var afterGoodInvoke = function (MACHINE) {
plt.runtime.PAUSE(function (restart) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
var returnValue = MACHINE.v;
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
success(returnValue);
});
};
afterGoodInvoke.mvr = function (MACHINE) {
plt.runtime.PAUSE(function (restart) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
var returnValues = [MACHINE.v];
var i;
for (i = 0; i < MACHINE.a - 1; i++) {
returnValues.push(MACHINE.e.pop());
}
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
success.apply(null, returnValues);
});
};
MACHINE.c.push(
new baselib.frames.CallFrame(afterGoodInvoke, proc));
MACHINE.a = args.length - 4;
for (i = 0; i < args.length - 4; i++) {
MACHINE.e.push(args[args.length - 1 - i]);
}
MACHINE.p = proc;
MACHINE.params['currentErrorHandler'] = function (MACHINE, e) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
MACHINE.v = oldVal;
MACHINE.a = oldArgcount;
MACHINE.p = oldProc;
MACHINE.c.length = oldControlLength;
MACHINE.e.length = oldEnvLength;
fail(e);
};
MACHINE._trampoline(proc.label,
false,
function() {
// The lock should still being held, so we don't
// automatically unlock control.
});
};

View File

@ -62,19 +62,28 @@
if (this.isInvoked) {
succ();
} else {
MACHINE.params['currentErrorHandler'] = function (MACHINE, anError) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
fail(MACHINE, anError);
};
MACHINE.c.push(new plt.baselib.frames.CallFrame(afterGoodInvoke, null));
if (isInternal) {
MACHINE.params['currentErrorHandler'] = function (MACHINE, anError) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
fail(MACHINE, anError);
};
MACHINE.c.push(new plt.baselib.frames.CallFrame(afterGoodInvoke, null));
throw that.label;
} else {
MACHINE.trampoline(that.label);
MACHINE.exclusiveLock.acquire(
undefined,
function(release) {
MACHINE.params['currentErrorHandler'] = function (MACHINE, anError) {
MACHINE.params['currentErrorHandler'] = oldErrorHandler;
fail(MACHINE, anError);
};
MACHINE.c.push(new plt.baselib.frames.CallFrame(afterGoodInvoke, null));
MACHINE._trampoline(that.label, false, release);
});
}
}
};
exports.ModuleRecord = ModuleRecord;

View File

@ -114,7 +114,7 @@
var ArityAtLeast = baselib.arity.ArityAtLeast;
var makeArityAtLeast = baselib.arity.makeArityAtLeast;
var isArityMatching = baselib.arity.isArityMatching;
var testArgument = baselib.check.testArgument;
var testArity = baselib.check.testArity;
@ -138,17 +138,17 @@
var defaultCurrentPrintImplementation = function (MACHINE) {
if(--MACHINE.cbt < 0) {
throw defaultCurrentPrintImplementation;
if(--MACHINE.cbt < 0) {
throw defaultCurrentPrintImplementation;
}
var oldArgcount = MACHINE.a;
var elt = MACHINE.e[MACHINE.e.length - 1];
var outputPort =
var outputPort =
MACHINE.params.currentOutputPort;
if (elt !== VOID) {
outputPort.writeDomNode(
MACHINE,
MACHINE,
toDomNode(elt, MACHINE.params['print-mode']));
outputPort.writeDomNode(MACHINE, toDomNode("\n", 'display'));
}
@ -161,6 +161,76 @@
defaultCurrentPrintImplementation);
//////////////////////////////////////////////////////////////////////
// Exclusive Locks. Even though JavaScript is a single-threaded
// evaluator, we still have a need to create exclusive regions
// of evaluation, since we might inadvertantly access some state
// with two computations, with use of setTimeout.
var ExclusiveLock = function() {
this.locked = false; // (U false string)
this.waiters = [];
};
// makeRandomNonce: -> string
// Creates a randomly-generated nonce.
ExclusiveLock.makeRandomNonce = function() {
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
var LEN = 32;
var result = [];
var i;
for (i = 0; i < LEN; i++) {
result.push(chars.charAt(Math.floor(Math.random() * chars.length)));
}
return result.join('');
};
ExclusiveLock.prototype.acquire = function(id, onAcquire) {
var that = this;
if (!id) {
id = ExclusiveLock.makeRandomNonce();
}
var alreadyReleased = false;
if (this.locked === false) {
this.locked = id;
onAcquire.call(
that,
// releaseLock
function() {
var waiter;
if (alreadyReleased) {
throw new Error(
"Internal error: trying to release the lock, but already released");
}
if (that.locked === false) {
throw new Error(
"Internal error: trying to unlock the lock, but already unlocked");
}
that.locked = false;
alreadyReleased = true;
if (that.waiters.length > 0) {
waiter = that.waiters.shift();
setTimeout(
function() {
that.acquire(waiter.id, waiter.onAcquire);
},
0);
}
});
} else {
this.waiters.push({ id: id,
onAcquire: onAcquire } );
}
};
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////]
// The MACHINE
@ -188,7 +258,7 @@
'currentDisplayer': function(MACHINE, domNode) {
$(domNode).appendTo(document.body);
},
// currentErrorDisplayer: DomNode -> Void
// currentErrorDisplayer is responsible for displaying errors to the browser.
'currentErrorDisplayer': function(MACHINE, domNode) {
@ -196,7 +266,7 @@
},
'currentInspector': baselib.inspectors.DEFAULT_INSPECTOR,
'currentOutputPort': new StandardOutputPort(),
'currentErrorPort': new StandardErrorPort(),
'currentInputPort': new StandardInputPort(),
@ -206,9 +276,9 @@
MACHINE,
toDomNode(exn, MACHINE.params['print-mode']));
},
'currentNamespace': {},
// These parameters control how often
// control yields back to the browser
// for response. The implementation is a
@ -229,8 +299,9 @@
};
this.primitives = Primitives;
this.exclusiveLock = new ExclusiveLock();
};
// Try to get the continuation mark key used for procedure application tracing.
var getTracedAppKey = function(MACHINE) {
@ -260,7 +331,7 @@
return MACHINE.c.slice(i + 1,
MACHINE.c.length - skip);
}
}
}
raise(MACHINE, new Error("captureControl: unable to find tag " + tag));
};
@ -268,20 +339,20 @@
// restoreControl clears the control stack (up to, but not including the
// prompt tagged by tag), and then appends the rest of the control frames.
// At the moment, the rest of the control frames is assumed to be in the
// At the moment, the rest of the control frames is assumed to be in the
// top of the environment.
Machine.prototype.restoreControl = function(tag) {
var MACHINE = this;
var i;
for (i = MACHINE.c.length - 1; i >= 0; i--) {
if (MACHINE.c[i].tag === tag) {
MACHINE.c =
MACHINE.c =
MACHINE.c.slice(0, i+1).concat(
MACHINE.e[MACHINE.e.length - 1]);
return;
}
}
raise(MACHINE, new Error("restoreControl: unable to find tag " + tag));
raise(MACHINE, new Error("restoreControl: unable to find tag " + tag));
};
@ -309,11 +380,11 @@
var lst = NULL;
var i;
for (i = 0; i < length; i++) {
lst = makePair(MACHINE.e[MACHINE.e.length - depth - length + i],
lst = makePair(MACHINE.e[MACHINE.e.length - depth - length + i],
lst);
}
MACHINE.e.splice(MACHINE.e.length - depth - length,
length,
length,
lst);
MACHINE.a = MACHINE.a - length + 1;
};
@ -347,8 +418,8 @@
if (control[i].marks.length !== 0) {
kvLists.push(control[i].marks);
}
if (tracedCalleeKey !== null &&
if (tracedCalleeKey !== null &&
control[i] instanceof CallFrame &&
control[i].p !== null) {
kvLists.push([[tracedCalleeKey, control[i].p]]);
@ -356,7 +427,7 @@
}
return new baselib.contmarks.ContinuationMarkSet(kvLists);
};
@ -376,26 +447,27 @@
//
var recomputeMaxNumBouncesBeforeYield;
var scheduleTrampoline = function(MACHINE, f) {
// FIXME: at the moment, the setTimeout is breaking when we get
// a rapid set of events from web-world. We are running into
// a very ugly re-entrancy bug. https://github.com/dyoo/whalesong/issues/70
// setTimeout(
// function() {
return MACHINE.trampoline(f);
// },
// 0);
var scheduleTrampoline = function(MACHINE, f, release) {
setTimeout(
function() {
MACHINE._trampoline(f, false, release);
},
0);
};
// Creates a restarting function, that reschedules f in a context
// with the old argcount in place.
// with the old argcount in place.
// Meant to be used only by the trampoline.
var makeRestartFunction = function(MACHINE) {
var makeRestartFunction = function(MACHINE, release, pauseLock) {
var oldArgcount = MACHINE.a;
return function(f) {
MACHINE.a = oldArgcount;
return scheduleTrampoline(MACHINE, f);
return function(f) {
pauseLock.acquire(
undefined,
function(pauseReleaseLock) {
MACHINE.a = oldArgcount;
MACHINE._trampoline(f, false, release);
pauseReleaseLock();
});
};
};
@ -419,18 +491,35 @@
};
// WARNING WARNING WARNING
//
// Make sure to get an exclusive lock before jumping into trampoline.
// Otherwise, Bad Things will happen.
//
// e.g. machine.lock.acquire('id', function(release) { machine.trampoline... release();});
Machine.prototype.trampoline = function(initialJump, noJumpingOff) {
var thunk = initialJump;
var startTime = (new Date()).valueOf();
this.cbt = STACK_LIMIT_ESTIMATE;
this.params.numBouncesBeforeYield =
this.params.maxNumBouncesBeforeYield;
this.running = true;
var that = this;
while(true) {
that.exclusiveLock.acquire(
'trampoline',
function(release) {
that._trampoline(initialJump, noJumpingOff, release);
});
};
Machine.prototype._trampoline = function(initialJump, noJumpingOff, release) {
var that = this;
var thunk = initialJump;
var startTime = (new Date()).valueOf();
that.cbt = STACK_LIMIT_ESTIMATE;
that.params.numBouncesBeforeYield =
that.params.maxNumBouncesBeforeYield;
that.running = true;
while(true) {
try {
thunk(this);
break;
thunk(that);
break;
} catch (e) {
// There are a few kinds of things that can get thrown
// during racket evaluation:
@ -452,9 +541,9 @@
// Everything else: otherwise, we send the exception value
// to the current error handler and exit.
// The running flag is set to false.
if (typeof(e) === 'function') {
if (typeof(e) === 'function') {
thunk = e;
this.cbt = STACK_LIMIT_ESTIMATE;
that.cbt = STACK_LIMIT_ESTIMATE;
// If we're running an a model that prohibits
@ -463,48 +552,88 @@
continue;
}
if (this.params.numBouncesBeforeYield-- < 0) {
recomputeMaxNumBouncesBeforeYield(
this,
(new Date()).valueOf() - startTime);
scheduleTrampoline(this, thunk);
return;
}
} else if (e instanceof Pause) {
var restart = makeRestartFunction(this);
e.onPause(restart);
if (that.params.numBouncesBeforeYield-- < 0) {
recomputeMaxNumBouncesBeforeYield(
that,
(new Date()).valueOf() - startTime);
scheduleTrampoline(that, thunk, release);
return;
}
} else if (e instanceof Pause) {
var pauseLock = new ExclusiveLock();
var oldArgcount = that.a;
var restarted = false;
var restart = function(f) {
pauseLock.acquire(
undefined,
function(releasePauseLock) {
restarted = true;
that.a = oldArgcount;
that._trampoline(f, false, release);
releasePauseLock();
});
};
var internalCall = function(proc, success, fail) {
var i;
if (restarted) {
return;
}
var args = [];
for (i = 3; i < arguments.length; i++) {
args.push(arguments[i]);
}
pauseLock.acquire(
undefined,
function(release) {
var newSuccess = function() {
success.apply(null, arguments);
release();
};
var newFail = function() {
fail.apply(null, arguments);
release();
};
baselib.functions.internalCallDuringPause.apply(
null, [that, proc, newSuccess, newFail].concat(args));
});
};
e.onPause(restart, internalCall);
return;
} else if (e instanceof HaltError) {
this.running = false;
e.onHalt(this);
that.running = false;
e.onHalt(that);
release();
return;
} else {
// General error condition: just exit out
// of the trampoline and call the current error handler.
this.running = false;
this.params.currentErrorHandler(this, e);
return;
}
// General error condition: just exit out
// of the trampoline and call the current error handler.
that.running = false;
that.params.currentErrorHandler(that, e);
release();
return;
}
}
}
this.running = false;
var that = this;
this.params.currentSuccessHandler(this);
return;
}
that.running = false;
that.params.currentSuccessHandler(that);
release();
return;
};
// recomputeGas: state number -> number
recomputeMaxNumBouncesBeforeYield = function(MACHINE, observedDelay) {
// We'd like to see a delay of DESIRED_DELAY_BETWEEN_BOUNCES so
// that we get MACHINE.params.desiredYieldsPerSecond bounces per
// second.
var DESIRED_DELAY_BETWEEN_BOUNCES =
var DESIRED_DELAY_BETWEEN_BOUNCES =
(1000 / MACHINE.params.desiredYieldsPerSecond);
var ALPHA = 50;
var delta = (ALPHA * ((DESIRED_DELAY_BETWEEN_BOUNCES -
observedDelay) /
observedDelay) /
DESIRED_DELAY_BETWEEN_BOUNCES));
MACHINE.params.maxNumBouncesBeforeYield =
MACHINE.params.maxNumBouncesBeforeYield =
Math.max(MACHINE.params.maxNumBouncesBeforeYield + delta,
1);
};
@ -538,7 +667,7 @@
@ -684,7 +813,7 @@
exports['installPrimitiveProcedure'] = installPrimitiveProcedure;
exports['makePrimitiveProcedure'] = makePrimitiveProcedure;
exports['Primitives'] = Primitives;
exports['ready'] = ready;
// Private: the runtime library will set this flag to true when
// the library has finished loading.
@ -699,7 +828,7 @@
exports['ModuleRecord'] = ModuleRecord;
exports['VariableReference'] = VariableReference;
exports['ContinuationPromptTag'] = ContinuationPromptTag;
exports['DEFAULT_CONTINUATION_PROMPT_TAG'] =
exports['DEFAULT_CONTINUATION_PROMPT_TAG'] =
DEFAULT_CONTINUATION_PROMPT_TAG;
exports['NULL'] = NULL;
exports['VOID'] = VOID;

View File

@ -114,58 +114,74 @@ Prerequisites: at least @link["http://racket-lang.org/"]{Racket
Here are a collection of programs that use the @emph{web-world} library described
later in this document:
@itemize[
@item{@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.html"]{attr-animation.html} [@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.rkt"]{src}] Uses @racket[update-view-attr] and @racket[on-tick] to perform a simple color animation.}
@item{@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.html"]{attr-animation.html}
[@link["http://hashcollision.org/whalesong/examples/attr-animation/attr-animation.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/attr-animation/index.html"]{index.html}
@link["http://hashcollision.org/whalesong/examples/attr-animation/style.css"]{style.css}]
Uses @racket[update-view-attr] and @racket[on-tick] to perform a simple color animation.}
@item{@link["http://hashcollision.org/whalesong/examples/boid/boid.html"]{boid.html} [@link["http://hashcollision.org/whalesong/examples/boid/boid.rkt"]{src}] Uses @racket[update-view-css] and @racket[on-tick] to perform an animation of a flock of @link["http://en.wikipedia.org/wiki/Boids"]{boids}.}
@item{@link["http://hashcollision.org/whalesong/examples/boid/boid.html"]{boid.html}
[@link["http://hashcollision.org/whalesong/examples/boid/boid.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/boid/index.html"]{index.html}] Uses @racket[update-view-css] and @racket[on-tick] to perform an animation of a flock of @link["http://en.wikipedia.org/wiki/Boids"]{boids}.}
@item{@link["http://hashcollision.org/whalesong/examples/dwarves/dwarves.html"]{dwarves.html}
[@link["http://hashcollision.org/whalesong/examples/dwarves/dwarves.rkt"]{src}]
[@link["http://hashcollision.org/whalesong/examples/dwarves/dwarves.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/dwarves/index.html"]{index.html}]
Uses @racket[view-show] and @racket[view-hide] to manipulate a view. Click on a dwarf to make them hide.
}
@item{@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/dwarves-with-remove.html"]{dwarves-with-remove.html}
[@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/dwarves-with-remove.rkt"]{src}]
[@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/dwarves-with-remove.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/dwarves-with-remove/index.html"]{index.html}]
Uses @racket[view-focus?] and @racket[view-remove] to see if a dwarf should be removed from the view.
}
@item{@link["http://hashcollision.org/whalesong/examples/field/field.html"]{field.html}
[@link["http://hashcollision.org/whalesong/examples/field/field.rkt"]{src}]
[@link["http://hashcollision.org/whalesong/examples/field/field.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/field/index.html"]{index.html}]
Uses @racket[view-bind] to read a text field, and @racket[update-view-text] to change
the text content of an element.
}
@item{@link["http://hashcollision.org/whalesong/examples/phases/phases.html"]{phases.html}
[@link["http://hashcollision.org/whalesong/examples/phases/phases.rkt"]{src}]
[@link["http://hashcollision.org/whalesong/examples/phases/phases.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/phases/index1.html"]{index1.html}
@link["http://hashcollision.org/whalesong/examples/phases/index2.html"]{index2.html}]
Switches out one view entirely in place of another. Different views can correspond to phases in a program.
}
@item{@link["http://hashcollision.org/whalesong/examples/tick-tock/tick-tock.html"]{tick-tock.html}
[@link["http://hashcollision.org/whalesong/examples/tick-tock/tick-tock.rkt"]{src}]
[@link["http://hashcollision.org/whalesong/examples/tick-tock/tick-tock.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/tick-tock/index.html"]{index.html}]
Uses @racket[on-tick] to show a timer counting up.
}
@item{@link["http://hashcollision.org/whalesong/examples/redirected/redirected.html"]{redirected.html}
[@link["http://hashcollision.org/whalesong/examples/redirected/redirected.rkt"]{src}]
[@link["http://hashcollision.org/whalesong/examples/redirected/redirected.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/redirected/index.html"]{index.html}]
Uses @racket[on-tick] to show a timer counting up, and also uses @racket[open-output-element] to
pipe side-effecting @racket[printf]s to a hidden @tt{div}.
}
@item{@link["http://hashcollision.org/whalesong/examples/todo/todo.html"]{todo.html}
[@link["http://hashcollision.org/whalesong/examples/todo/todo.rkt"]{src}]
[@link["http://hashcollision.org/whalesong/examples/todo/todo.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/todo/index.html"]{index.html}]
A simple TODO list manager.
}
@item{@link["http://hashcollision.org/whalesong/examples/where-am-i/where-am-i.html"]{where-am-i.html}
[@link["http://hashcollision.org/whalesong/examples/where-am-i/where-am-i.rkt"]{src}]
[@link["http://hashcollision.org/whalesong/examples/where-am-i/where-am-i.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/where-am-i/index.html"]{index.html}]
Uses @racket[on-location-change] and @racket[on-mock-location-change] to demonstrate location services.
}
@item{@link["http://hashcollision.org/whalesong/examples/hot-cross-buns/hot-cross-buns.html"]{hot-cross-buns.html}
[@link["http://hashcollision.org/whalesong/examples/hot-cross-buns/hot-cross-buns.rkt"]{src}]
[@link["http://hashcollision.org/whalesong/examples/hot-cross-buns/hot-cross-buns.rkt"]{src}
@link["http://hashcollision.org/whalesong/examples/hot-cross-buns/index.html"]{index.html}]
Demonstrates use of checkboxes. Uses @racket[view-has-attr?] to see if a checkbox has been
checked, and @racket[remove-view-attr] to change the @emph{checked} attribute when the user
wants to reset the page.

View File

@ -7,4 +7,4 @@
(provide version)
(: version String)
(define version "1.101")
(define version "1.102")

View File

@ -1171,7 +1171,7 @@
MACHINE.params.currentOutputPort = find(handlers, isWithOutputToHandler).outputPort;
}
PAUSE(function(restart) {
PAUSE(function(restart, internalCall) {
var onCleanRestart, onMessyRestart,
startEventHandlers, stopEventHandlers,
startEventHandler, stopEventHandler,
@ -1260,7 +1260,7 @@
var onGoodWorldUpdate =
function(newWorld) {
world = newWorld;
stopWhen(MACHINE,
stopWhen(internalCall,
world,
mockView,
function(shouldStop) {
@ -1274,14 +1274,14 @@
fail);
};
if (plt.baselib.arity.isArityMatching(racketWorldCallback.racketArity, 3)) {
racketWorldCallback(MACHINE,
racketWorldCallback(internalCall,
world,
mockView,
data,
onGoodWorldUpdate,
fail);
} else {
racketWorldCallback(MACHINE,
racketWorldCallback(internalCall,
world,
mockView,
onGoodWorldUpdate,
@ -1299,7 +1299,7 @@
// update, and have to do it from scratch.
var nonce = Math.random();
var originalMockView = view.getMockAndResetFocus(nonce);
toDraw(MACHINE,
toDraw(internalCall,
world,
originalMockView,
function(newMockView) {
@ -1334,15 +1334,14 @@
};
var wrapFunction = function(proc) {
var f = function(MACHINE) {
var f = function(internalCall) {
var success = arguments[arguments.length - 2];
var fail = arguments[arguments.length - 1];
var args = [].slice.call(arguments, 1, arguments.length - 2);
return plt.baselib.functions.internalCallDuringPause.apply(null,
[MACHINE,
proc,
success,
fail].concat(args));
return internalCall.apply(null,
[proc,
success,
fail].concat(args));
};
f.racketArity = proc.racketArity;
return f;

View File

@ -2,7 +2,8 @@
(require "impl.rkt"
"helpers.rkt"
"event.rkt")
"event.rkt"
(for-syntax racket/base))
(require (for-syntax racket/base racket/stxparam-exptime)
(only-in "../lang/kernel.rkt" define-syntax-parameter syntax-parameterize))
@ -18,6 +19,7 @@
(all-from-out "helpers.rkt")
(all-from-out "event.rkt"))
(provide view-bind*)
(provide (rename-out [internal-big-bang big-bang]
[big-bang big-bang/f]
@ -76,4 +78,21 @@
on-mock-location-change
on-location-change
to-draw))
;; A syntactic form to make it more convenient to focus and bind multiple things
;; (view-bind* a-view
;; [id type function]
;; [id type function] ...)
(define-syntax (view-bind* stx)
(syntax-case stx ()
[(_ a-view [a-selector a-type a-function] ...)
(foldl (lambda (a-selector a-type a-function a-view-stx)
#'(view-bind (view-focus #,a-view-stx #,a-selector)
#,a-type
#,a-function))
#'a-view
(syntax->list #'(a-selector ...))
(syntax->list #'(a-type ...))
(syntax->list #'(a-function ...)))]))