diff --git a/js-assembler/runtime-src/baselib-functions.js b/js-assembler/runtime-src/baselib-functions.js index e22ac93..937523c 100644 --- a/js-assembler/runtime-src/baselib-functions.js +++ b/js-assembler/runtime-src/baselib-functions.js @@ -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. + }); + }; diff --git a/js-assembler/runtime-src/baselib-modules.js b/js-assembler/runtime-src/baselib-modules.js index 15aa6af..b005891 100644 --- a/js-assembler/runtime-src/baselib-modules.js +++ b/js-assembler/runtime-src/baselib-modules.js @@ -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; diff --git a/js-assembler/runtime-src/runtime.js b/js-assembler/runtime-src/runtime.js index fa4f550..8da5eaa 100644 --- a/js-assembler/runtime-src/runtime.js +++ b/js-assembler/runtime-src/runtime.js @@ -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; diff --git a/scribblings/manual.scrbl b/scribblings/manual.scrbl index b0d3739..22f9ee1 100644 --- a/scribblings/manual.scrbl +++ b/scribblings/manual.scrbl @@ -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. diff --git a/version.rkt b/version.rkt index f14d333..72acb46 100644 --- a/version.rkt +++ b/version.rkt @@ -7,4 +7,4 @@ (provide version) (: version String) -(define version "1.101") +(define version "1.102") diff --git a/web-world/js-impl.js b/web-world/js-impl.js index ea4f01d..629d020 100644 --- a/web-world/js-impl.js +++ b/web-world/js-impl.js @@ -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; diff --git a/web-world/main.rkt b/web-world/main.rkt index dcdd785..9e9f116 100644 --- a/web-world/main.rkt +++ b/web-world/main.rkt @@ -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)) - \ No newline at end of file + + + +;; 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 ...)))]))