last mod: 17-Jan-08 sciss
The Collapse
class is useful for deferring actions to a certain clock or scheduling them while reducing system load to a minimum. The Collapse
constructor takes a function to be deferred, a delta time span and a clock to defer to:
Collapse.new( <(Function) func>, <(Number) delta = 0.0>, <(Clock) clock = AppClock> )
An action is deferred by calling the defer
method with arbitrary arguments. The function's value
method is called with these arguments after the scheduled delay. When defer
is called before the function was executed, the function is deferred again by the schedule delay and the pending call is cancelled. The new arguments overwrite the previous (pending) arguments. An example for the usage of defer
is to allow a MIDI controller dial to be turned until the target position has been found, so that an action is executed only after the dial has been released for a given amount of time (e.g. 100ms).
In the following example, a ring is drawn around the mouse position if the mousebutton is hold down and the mouse dragged over the userview. A new ring is only drawn if the mouse is hold still for 500 milliseconds:
( var ring; c = Collapse({ arg x, y; ring = x @ y }, 0.5 ); w = JSCWindow( "Drag the mouse and hold...", resizable: false ).front; f = { arg view, x, y; c.defer( x, y )}; u = JSCUserView( w, w.view.bounds ) .focusVisible_( false ) .refreshOnFocus_( false ) .clearOnRefresh_( false ) .background_( Color.blue( alpha: 0.05 )) .mouseDownAction_( f ) .mouseMoveAction_( f ) .drawFunc_({ if( ring.notNil, { JPen.width = 20; JPen.strokeOval( Rect.aboutPoint( ring, 100, 100 )); ring = nil; })}); fork { while({ u.notClosed }, { u.refresh; 0.02.wait })}; )
On the contrary, the instantaneous
method schedules the function, but will not re-schedule the execution when called successively (just the arguments get replaced). An example usage of instantaneous
is to filter out the amount of actions executed per time unit, so as to save processing power. Imagine a custom JSCUserView consisting of several hundred objects all of which can be configured (e.g. in size and colour). If each configuration call will refresh the view, a heavy OSC load will result. Wrapping refresh
inside a function and executing that using instananeous
, all the necessary refreshes will collapse into a single final refresh.
The following example modifies the initial example to use instantaneos
instead of defer
. Note how new rings are created at a regular interval of 500 milliseconds, while the mouse is being dragged:
( w.name = "Keep the mouse dragging..."; f = { arg view, x, y; c.instantaneous( x, y )}; u.mouseDownAction_( f ).mouseMoveAction_( f ); )
Sometimes a condition occurs that makes it necessary to cancel an already scheduled action. This can be done by calling the cancel
method. After cancellation, the Collapse
will not respond to defer
or instantaneous
any more, unless reschedule
is called which "revives" the object.
The following example again modifies the initial example. Releasing the mouse button will call cancel
so no ring is created after the button is released. Inversely, when pressing the button reschedule
is called to revive the process:
( w.name = "Release mouse to cancel..."; u.mouseDownAction = { c.reschedule }; u.mouseMoveAction = { arg view, x, y; c.defer( x, y )}; u.mouseUpAction = { c.cancel; "Cancel!".postln }; )