Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/core/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ class p5 {
loadingScreen.parentNode.removeChild(loadingScreen);
}
if (!this._setupDone) {
this._lastFrameTime = window.performance.now();
this._lastTargetFrameTime = window.performance.now();
this._lastRealFrameTime = window.performance.now();
context._setup();
context._draw();
}
Expand Down Expand Up @@ -357,7 +358,8 @@ class p5 {
}
}

this._lastFrameTime = window.performance.now();
this._lastTargetFrameTime = window.performance.now();
this._lastRealFrameTime = window.performance.now();
this._setupDone = true;
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
this._updateAccsOutput();
Expand All @@ -366,7 +368,7 @@ class p5 {

this._draw = () => {
const now = window.performance.now();
const time_since_last = now - this._lastFrameTime;
const time_since_last = now - this._lastTargetFrameTime;
const target_time_between_frames = 1000 / this._targetFrameRate;

// only draw if we really need to; don't overextend the browser.
Expand All @@ -384,10 +386,12 @@ class p5 {
) {
//mandatory update values(matrixes and stack)
this.redraw();
this._frameRate = 1000.0 / (now - this._lastFrameTime);
this.deltaTime = now - this._lastFrameTime;
this._frameRate = 1000.0 / (now - this._lastRealFrameTime);
this.deltaTime = now - this._lastRealFrameTime;
this._setProperty('deltaTime', this.deltaTime);
this._lastFrameTime = now;
this._lastTargetFrameTime = Math.max(this._lastTargetFrameTime
+ target_time_between_frames, now);
this._lastRealFrameTime = now;

// If the user is actually using mouse module, then update
// coordinates, otherwise skip. We can test this by simply
Expand Down
71 changes: 71 additions & 0 deletions test/unit/core/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,77 @@ suite('Environment', function() {
test('p5.prototype.getFrameRate', function() {
assert.strictEqual(myp5.getFrameRate(), 0);
});

suite('drawing with target frame rates', function() {
let clock;
let prevRequestAnimationFrame;
let nextFrameCallback = () => {};
let controlledP5;

setup(function() {
clock = sinon.useFakeTimers(0);
sinon.stub(window.performance, 'now', Date.now);

// Save the real requestAnimationFrame so we can restore it later
prevRequestAnimationFrame = window.requestAnimationFrame;
// Use a fake requestAnimationFrame that just stores a ref to the callback
// so that we can call it manually
window.requestAnimationFrame = function(cb) {
nextFrameCallback = cb;
};

return new Promise(function(resolve) {
controlledP5 = new p5(function(p) {
p.setup = function() {
p.createCanvas(10, 10);
p.frameRate(60);
p.loop();
resolve(p);
};

p.draw = function() {};
});
});
});

teardown(function() {
clock.restore();
window.performance.now.restore();
window.requestAnimationFrame = prevRequestAnimationFrame;
nextFrameCallback = function() {};
controlledP5.remove();
});

test('draw() is called at the correct frame rate given a faster display', function() {
sinon.spy(controlledP5, 'draw');

clock.tick(1000 / 200); // Simulate a 200Hz refresh rate
nextFrameCallback(); // trigger the next requestAnimationFrame
assert(controlledP5.draw.notCalled, 'draw() should not be called before 1s/60');

// Advance until 5ms before the next frame should render.
// This should be within p5's threshold for rendering the frame.
clock.tick(1000 / 60 - 1000 / 200 - 5);
nextFrameCallback(); // trigger the next requestAnimationFrame
assert(controlledP5.draw.calledOnce, 'one frame should have been drawn');
// deltaTime should reflect real elapsed time
assert.equal(controlledP5.deltaTime, 1000 / 60 - 5);

// Advance enough time forward to be 1s/60 - 5ms from the last draw
clock.tick(1000 / 60 - 5);
nextFrameCallback(); // trigger the next requestAnimationFrame
// Even though this is 1s/60 - 5ms from the last draw, the last frame came
// in early, so we still shouldn't draw
assert(controlledP5.draw.calledOnce, 'draw() should not be called before 1s/60 past the last target draw time');

// Advance enough time forward to be 1s/60 from the last draw
clock.tick(5);
nextFrameCallback();
assert(controlledP5.draw.calledTwice); // Now it should draw again!
// deltaTime should reflect real elapsed time
assert.equal(controlledP5.deltaTime, 1000 / 60);
});
});
});

suite('p5.prototype.getTargetFrameRate', function() {
Expand Down