Skip to content

Commit

Permalink
Add timerOnce.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Feb 2, 2016
1 parent 9fbb307 commit 1e7ee4c
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 48 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ In a vanilla environment, a `d3_timer` global is exported. [Try d3-timer in your

Schedules a new timer, invoking the specified *callback* repeatedly until the timer is [stopped](#timer_stop). An optional numeric *delay* in milliseconds may be specified to invoke the given *callback* after a delay; if *delay* is not specified, it defaults to zero. The delay is relative to the specified *time* in milliseconds since UNIX epoch; if *time* is not specified, it defaults to [Date.now](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/now).

The *callback* is passed two arguments when it is invoked: the elapsed time since the timer became active, and the current time. The latter is useful for precise scheduling of secondary timers. For example:
The *callback* is passed two arguments when it is invoked: the *elapsed* time since the timer became active, and the current time, *now*. The latter is useful for precise scheduling of secondary timers. For example:

```js
var t = d3.timer(function(elapsed, time) {
console.log(elapsed, time);
var t = d3.timer(function(elapsed, now) {
console.log(elapsed, now);
if (elapsed > 200) t.stop();
}, 150);
```
Expand Down Expand Up @@ -65,6 +65,10 @@ Stops this timer, preventing subsequent callbacks. This method has no effect if

An opaque, unique identifier for this timer.

<a name="timerOnce" href="#timerOnce">#</a> d3.<b>timerOnce</b>(<i>callback</i>[, <i>delay</i>[, <i>time</i>]])

Like [timer](#timer), except the timer automatically [stops](#timer_stop) on its first callback.

<a name="timerFlush" href="#timerFlush">#</a> d3.<b>timerFlush</b>([<i>time</i>])

Immediately execute (invoke once) any eligible timer callbacks. If *time* is specified, it represents the current time; if not specified, it defaults to Date.now. Specifying an explicit time helps ensure deterministic behavior.
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export {
timer,
timerOnce,
timerFlush
} from "./src/timer";
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "d3-timer",
"version": "0.1.1",
"version": "0.1.2",
"description": "An efficient queue capable of managing thousands of concurrent animations.",
"keywords": [
"d3",
Expand Down
19 changes: 13 additions & 6 deletions src/timer.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ var setFrame = typeof window !== "undefined"
|| window.oRequestAnimationFrame)
|| function(callback) { return setTimeout(callback, 17); };

function Timer(callback, delay, time) {
function Timer() {
this.id = ++taskId;
this.restart(callback, delay, time);
}

Timer.prototype = timer.prototype = {
Expand Down Expand Up @@ -43,16 +42,24 @@ Timer.prototype = timer.prototype = {
};

export function timer(callback, delay, time) {
return new Timer(callback, delay, time);
var t = new Timer;
t.restart(callback, delay, time);
return t;
}

export function timerFlush(time) {
time = time == null ? Date.now() : +time;
export function timerOnce(callback, delay, time) {
var t = new Timer;
t.restart(function(elapsed, now) { t.stop(); callback(elapsed, now); }, delay, time);
return t;
}

export function timerFlush(now) {
now = now == null ? Date.now() : +now;
++frame; // Pretend we’ve set an alarm, if we haven’t already.
try {
var t = taskHead, c;
while (t) {
if (time >= t.time) c = t.callback, c(time - t.time, time);
if (now >= t.time) c = t.callback, c(now - t.time, now);
t = t.next;
}
} finally {
Expand Down
7 changes: 7 additions & 0 deletions test/end.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Some tests need a trailing frame after timers are stopped to cleanup the
// queue and clear the alarm. Let that finish before starting a new test.
module.exports = function(test) {
setTimeout(function() {
test.end();
}, 24);
};
40 changes: 2 additions & 38 deletions test/timer-test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
var tape = require("tape"),
timer = require("../");
timer = require("../"),
end = require("./end");

require("./inRange");

// Some tests need a trailing frame after timers are stopped to cleanup the
// queue and clear the alarm. Let that finish before starting a new test.
function end(test) {
setTimeout(function() {
test.end();
}, 24);
}

tape("timer(callback) returns an instanceof timer", function(test) {
var t = timer.timer(function() {});
test.equal(t instanceof timer.timer, true);
Expand Down Expand Up @@ -402,32 +395,3 @@ tape("timer.id is a positive integer", function(test) {
t.stop();
end(test);
});

tape("timerFlush() immediately invokes any eligible timers", function(test) {
var count = 0;
var t = timer.timer(function() { ++count; t.stop(); });
timer.timerFlush();
timer.timerFlush();
test.equal(count, 1);
end(test);
});

tape("timerFlush() within timerFlush() still executes all eligible timers", function(test) {
var count = 0;
var t = timer.timer(function() { if (++count >= 3) t.stop(); timer.timerFlush(); });
timer.timerFlush();
test.equal(count, 3);
end(test);
});

tape("timerFlush(time) observes the specified time", function(test) {
var start = Date.now(), count = 0;
var t = timer.timer(function() { if (++count >= 2) t.stop(); }, 0, start);
timer.timerFlush(start - 1);
test.equal(count, 0);
timer.timerFlush(start);
test.equal(count, 1);
timer.timerFlush(start + 1);
test.equal(count, 2);
end(test);
});
32 changes: 32 additions & 0 deletions test/timerFlush-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
var tape = require("tape"),
timer = require("../"),
end = require("./end");

tape("timerFlush() immediately invokes any eligible timers", function(test) {
var count = 0;
var t = timer.timer(function() { ++count; t.stop(); });
timer.timerFlush();
timer.timerFlush();
test.equal(count, 1);
end(test);
});

tape("timerFlush() within timerFlush() still executes all eligible timers", function(test) {
var count = 0;
var t = timer.timer(function() { if (++count >= 3) t.stop(); timer.timerFlush(); });
timer.timerFlush();
test.equal(count, 3);
end(test);
});

tape("timerFlush(time) observes the specified time", function(test) {
var start = Date.now(), count = 0;
var t = timer.timer(function() { if (++count >= 2) t.stop(); }, 0, start);
timer.timerFlush(start - 1);
test.equal(count, 0);
timer.timerFlush(start);
test.equal(count, 1);
timer.timerFlush(start + 1);
test.equal(count, 2);
end(test);
});
56 changes: 56 additions & 0 deletions test/timerOnce-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
var tape = require("tape"),
timer = require("../"),
end = require("./end");

require("./inRange");

tape("timerOnce(callback) invokes the callback once", function(test) {
var count = 0;
timer.timerOnce(function() {
test.equal(++count, 1);
end(test);
});
});

tape("timerOnce(callback, delay) invokes the callback once after the specified delay", function(test) {
var time = Date.now(), delay = 50;
timer.timerOnce(function(elapsed, now) {
test.inRange(now - time, delay - 10, delay + 10);
end(test);
}, delay);
});

tape("timerOnce(callback, delay, time) invokes the callback once after the specified delay relative to the given time", function(test) {
var time = Date.now() + 50, delay = 50;
timer.timerOnce(function(elapsed, now) {
test.inRange(now - time, delay - 10, delay + 10);
end(test);
}, delay, time);
});

tape("timerOnce(callback) uses the global context for the callback", function(test) {
timer.timerOnce(function() {
test.equal(this, global);
end(test);
});
});

tape("timerOnce(callback) passes the callback the elapsed and current time", function(test) {
var time = Date.now(), count = 0;
timer.timerOnce(function(elapsed, now) {
test.equal(elapsed, now - time);
test.inRange(now, Date.now() - 10, Date.now());
end(test);
}, 0, time);
});

tape("timerOnce(callback) returns a timer", function(test) {
var count = 0;
var t = timer.timerOnce(function() { ++count; });
test.equal(t instanceof timer.timer, true);
t.stop();
setTimeout(function() {
test.equal(count, 0);
end(test);
}, 100);
});

0 comments on commit 1e7ee4c

Please sign in to comment.