Hi David, I'll go through this in more detail when I have time, but just to say this is great, and definately the way we need to be going in.
cheers, dave On Thu, 2011-02-17 at 06:19 +0100, David Kaloper wrote: > Hello, list! > > I know I should have consulted the devs ahead, but I got carried away... and > patched continuations into fluxus. > > The story is this: after recently discovering this wonderful piece of software > and playing around for a bit, I got tired of the inversion of control that > every-frame imposes. So I went after spawn-timed-task, thinking it's a good > start and expecting to define-syntax something around it and go on my merry > way. > > Well, it turned out that timed tasks didn't want to spawn other timed tasks. > So > that got fixed. While at it, I whipped up a few priority queues and replaced > the > list there with the fastest one. Well, one thing led to another... and this > is > what I currently have: > > > * The spawn macro (probably a misnomer). It is executed synchronously, and by > itself, it doesn't really do a thing. > > (let ([t #f]) > (list > (time-now) > (begin (spawn (set! t (time-now))) t) > (time-now))) > > * The restart-after procedure. If called during a dynamic extent of a spawn, > it > does just that - restarts after some time. > > (define (cube v) > (with-state (colour (rndvec)) (translate v) > (build-cube))) > > (let ([c1 (cube #(-2 0 0))] > [c2 (cube #(2 0 0))]) > (spawn > (do () (#f) > (with-primitive c1 (translate #(0 0.5 0))) > (restart-after 0.5) > (with-primitive c2 (translate #(0 -0.5 0))) > (restart-after 0.5)))) > > This creates two cubes and alternates between pushing each every half a > second. > > Well, actually, it can be simplified: > > (define (push-forever v t) > (do () (#f) (translate v) (restart-after t))) > > (with-primitive (cube #(-2 0 0)) > (spawn (push-forever #(0 0.5 0) 1))) > > (with-primitive (cube #(2 0 0)) > (spawn (restart-after 0.5) (push-forever #(0 -0.5 0) 1))) > > > That is, spawn picks up the current grab. And restart-after keeps it. > > (define c1 (cube #(-2 0 0))) > (define c2 (cube #(0 0 0))) > > (with-primitive c1 > (spawn (do () (#f) > (restart-after 0.5) > (translate #(0 1 0)) (restart-after 0.5) > (with-primitive c2 > (translate #(0 1 0)) (restart-after 0.5) > (translate #(0 -1 0))) > (restart-after 0.5) > (translate #(0 0.5 0))))) > > > If not within the context of a grab, they pick up the global state, instead: > > (translate #(0 2 0)) > > (build-cube) > > (spawn (restart-after 2) > (colour (rndvec)) (translate #(2 0 0)) > (build-cube)) > > (identity) > (build-cube) > > > ... for each spawn independently: > > (define (sequence n d) > (spawn (for ([_ (in-range n)]) > (translate d) (scale #3(0.9)) (build-cube) > (restart-after 0.1)))) > > (sequence 10 #(0.1 -1 0)) > (sequence 10 #(-0.1 -1 0)) > (sequence 20 #(0 1 0)) > > > * Then there's restart-next-frame procedure. It can be used to synchronize > with > the frame rate: > > (define (rndvec* [c 1]) > (vmul (vadd (rndvec) #3(-0.5)) c)) > > (define (walkabout) > (with-primitive (build-cube) > (colour (rndvec)) (scale (vadd #3(1) (rndvec* 0.5))) > (spawn (do () (#f) > (restart-after (random 4)) > (let* ([point (vmul (rndvec*) 3)] > [direction (vnormalise point)]) > (let loop ([hop #3(0)] [path (vmag point)]) > (when (positive? path) > (translate hop) > (restart-next-frame) > (loop (vmul direction (* 5 (delta))) > (- path (vmag hop)))))))))) > > (for ([_ (in-range 10)]) > (translate (rndvec* 5)) (walkabout)) > > > Using restart-after and restart-next-frame moves the spawn'd task between > spawn-task and spawn-timed-task scheduling mechanisms. > > Here's an example that tests if everything is working: > > ; For cartoonish effect > (define (clear*) > (clear-colour #3(0.1)) (clear) > (rm-all-tasks) > (hint-on 'wire) (wire-colour #3()) (line-width 2) > (show-fps 1)) > > (define (cube-stream t1 t2) > (spawn > (do () (#f) > (translate (rndvec* 3)) > (scale (let ([c (+ (* (random) 0.3) 0.85)]) > (vector c c c))) > (take-cube (with-state > (rotate (rndvec* 90)) > (build-cube)) t2) > (restart-after t1)))) > > (define (take-cube c t) > (define (rot) > (rotate (vector 0 (* (delta) 90) 0))) > > (with-primitive c > (spawn > (do ([stop (+ (time-now) t)]) ((> (time-now) stop)) > (rot) (restart-next-frame)) > (for ([o (in-range 1 0 -0.1)]) > (rot) (opacity o) (restart-next-frame)) > (destroy c)))) > > > (clear*) > > (for ([_ (in-range 5)]) > (cube-stream (random) (* (random) 3))) > > > *** > > every-frame seems to work best when the vis is mostly a function of time and > other inputs, and for attending to created objects and animating them. But for > large, discrete events, that programmatically happen here and there and make > big > edits to the scene graph, I think this really comes in handy. And it meshes > well > with regular spawn-task and spawn-timed-task. > > *** > > Changes are mostly localized to tasks.ss and restartable.ss. The latter > implements this trickery, the former got a little streamlined. > > Existing stuff visibly changed: > > - spawn-timed-task works recursively :) > > - tasks are no longer executed in lexicographical order of their keys; sorting > and traversing the list gets a little slow for large numbers of tasks, so > this > is now handled by a persistent hash. If the ordered execution is widely > used, > I can add an ordered-dict? structure in its place. Splay trees and skip > lists > that come with racket were way too heavy. > > - clear clears all the timed tasks, often a saving grace. ... > > Internally changed stuff: > > - the structure supporting timed tasks can take a much larger beating now. > > - with-primitive and with-state don't longer simply expand to (begin > (grab/push) > (let ([res ...]) (ungrab/pop) res)); now they guard their dynamic extent and > redo the initialization task every time they are reactivated by a > continuation > entry (dynamic-wind). with-state furthermore saves the opengl state on exit > and restores it on enter, so the state observed within the bracket is > consistent between exits/enters. This comes at negligible cost for normal > code > paths, but the way state is saved and restored if continuations are involved > is open for debate. > > To do: > > - saving / restoring of opengl state was the _intention_, currently only the > transformation matrix is handled. I don't know how to get hold of the rest. > Maybe a little help from C++? Ideally, we could get an opaque reference to > everything push/pop acts on, and avoid marshalling data into scheme. > Affected > functions are get-ogl-state and apply-saved-ogl-state in building-blocks.ss. > > - if there is a way to check if a grab is currently active, with-... can be a > little simplified (no dynamic parameter). > > > Umm.. That's all. I think this post has more lines than the code! So, like, a > pull request? > > github.com/pqwy/fluxus, branch restartable-tasks. > > > Cheers, David > >
