diff --git a/contributor_docs/creating_libraries.md b/contributor_docs/creating_libraries.md
index 35fd893399..2c3b1ae854 100644
--- a/contributor_docs/creating_libraries.md
+++ b/contributor_docs/creating_libraries.md
@@ -92,6 +92,10 @@ Method names you can register and unregister include the following list. Note th
* **post** — Called at the end of `draw()`.
* **remove** — Called when `remove()` is called.
* **init** — Called when the sketch is first initialized, just before the sketch initialization function (the one that was passed into the `p5` constructor) is executed. This is also called before any global mode setup, so your library can add anything to the sketch and it will automatically be copied to `window` if global mode is active.
+ * **beforePreload** — Called before the `preload()` function is executed.
+ * **afterPreload** — Called after the `preload()` function is executed.
+ * **beforeSetup** — Called before the `setup()` function is executed.
+ * **afterSetup** — Called after the `setup()` function is executed.
More to come shortly, lining up roughly with this list:
https://GitHub.com/processing/processing/wiki/Library-Basics#library-methods
diff --git a/src/core/main.js b/src/core/main.js
index f7f94dfbd6..4e4e37cef5 100644
--- a/src/core/main.js
+++ b/src/core/main.js
@@ -231,6 +231,20 @@ class p5 {
this._events.devicemotion = null;
}
+ // Function to invoke registered hooks before or after events such as preload, setup, and pre/post draw.
+ p5.prototype.callRegisteredHooksFor = function (hookName) {
+ const target = this || p5.prototype;
+ const context = this._isGlobal ? window : this;
+ if (target._registeredMethods.hasOwnProperty(hookName)) {
+ const methods = target._registeredMethods[hookName];
+ for (const method of methods) {
+ if (typeof method === 'function') {
+ method.call(context);
+ }
+ }
+ }
+ };
+
this._start = () => {
// Find node if id given
if (this._userNode) {
@@ -241,6 +255,7 @@ class p5 {
const context = this._isGlobal ? window : this;
if (context.preload) {
+ this.callRegisteredHooksFor('beforePreload');
// Setup loading screen
// Set loading screen into dom if not present
// Otherwise displays and removes user provided loading screen
@@ -286,6 +301,7 @@ class p5 {
if (loadingScreen) {
loadingScreen.parentNode.removeChild(loadingScreen);
}
+ this.callRegisteredHooksFor('afterPreload');
if (!this._setupDone) {
this._lastTargetFrameTime = window.performance.now();
this._lastRealFrameTime = window.performance.now();
@@ -322,6 +338,7 @@ class p5 {
};
this._setup = () => {
+ this.callRegisteredHooksFor('beforeSetup');
// Always create a default canvas.
// Later on if the user calls createCanvas, this default one
// will be replaced
@@ -369,6 +386,7 @@ class p5 {
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
this._updateAccsOutput();
}
+ this.callRegisteredHooksFor('afterSetup');
};
this._draw = () => {
diff --git a/src/core/structure.js b/src/core/structure.js
index 85f66004dc..539a9c1192 100644
--- a/src/core/structure.js
+++ b/src/core/structure.js
@@ -6,7 +6,6 @@
*/
import p5 from './main';
-
/**
* Stops p5.js from continuously executing the code within draw().
* If loop() is called, the code in draw()
@@ -471,9 +470,6 @@ p5.prototype.redraw = function(n) {
if (typeof context.setup === 'undefined') {
context.scale(context._pixelDensity, context._pixelDensity);
}
- const callMethod = f => {
- f.call(context);
- };
for (let idxRedraw = 0; idxRedraw < numberOfRedraws; idxRedraw++) {
context.resetMatrix();
if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
@@ -483,14 +479,14 @@ p5.prototype.redraw = function(n) {
context._renderer._update();
}
context._setProperty('frameCount', context.frameCount + 1);
- context._registeredMethods.pre.forEach(callMethod);
+ this.callRegisteredHooksFor('pre');
this._inUserDraw = true;
try {
context.draw();
} finally {
this._inUserDraw = false;
}
- context._registeredMethods.post.forEach(callMethod);
+ this.callRegisteredHooksFor('post');
}
}
};
diff --git a/test/unit/core/main.js b/test/unit/core/main.js
index bc73fab76c..7df683542f 100644
--- a/test/unit/core/main.js
+++ b/test/unit/core/main.js
@@ -1,68 +1,157 @@
-const { expect } = require('chai');
+const { expect, assert } = require('chai');
-suite('Core', function() {
- suite('p5.prototype.registerMethod', function() {
- test('should register and call "init" methods', function() {
- var originalInit = p5.prototype._registeredMethods.init;
+suite('Core', function () {
+ suite('p5.prototype.registerMethod', function () {
+ teardown(function() {
+ p5.prototype._registeredMethods.init = [];
+ p5.prototype._registeredMethods.beforePreload = [];
+ p5.prototype._registeredMethods.preload = [];
+ p5.prototype._registeredMethods.afterPreload = [];
+ p5.prototype._registeredMethods.beforeSetup = [];
+ p5.prototype._registeredMethods.setup = [];
+ p5.prototype._registeredMethods.afterSetup = [];
+ p5.prototype._registeredMethods.pre = [];
+ p5.prototype._registeredMethods.draw = [];
+ p5.prototype._registeredMethods.post = [];
+ });
+ test('should register and call "init" methods', function () {
var myp5, myInitCalled;
+ p5.prototype.registerMethod('init', function myInit() {
+ assert(
+ !myInitCalled,
+ 'myInit should only be called once during test suite'
+ );
+ myInitCalled = true;
- p5.prototype._registeredMethods.init = [];
+ this.myInitCalled = true;
+ });
+
+ myp5 = new p5(function (sketch) {
+ assert(sketch.hasOwnProperty('myInitCalled'));
+ assert(sketch.myInitCalled);
- try {
- p5.prototype.registerMethod('init', function myInit() {
- assert(
- !myInitCalled,
- 'myInit should only be called once during test suite'
- );
- myInitCalled = true;
+ sketch.sketchFunctionCalled = true;
+ });
+
+ assert(myp5.sketchFunctionCalled);
+ });
+ test('should register and call before and after "preload" hooks', function () {
+ return new Promise(resolve => {
+ let beforePreloadCalled = false;
+ let preloadCalled = false;
+ let afterPreloadCalled = false;
- this.myInitCalled = true;
+ p5.prototype.registerMethod('beforePreload', () => {
+ beforePreloadCalled = true;
});
- myp5 = new p5(function(sketch) {
- assert(sketch.hasOwnProperty('myInitCalled'));
- assert(sketch.myInitCalled);
+ p5.prototype.registerMethod('preload', () => {
+ assert.equal(beforePreloadCalled, true);
+ preloadCalled = true;
+ });
- sketch.sketchFunctionCalled = true;
+ p5.prototype.registerMethod('afterPreload', () => {
+ if (beforePreloadCalled && preloadCalled) afterPreloadCalled = true;
});
- assert(myp5.sketchFunctionCalled);
- } finally {
- p5.prototype._registeredMethods.init = originalInit;
- }
+ myp5 = new p5(function (sketch) {
+ sketch.preload = () => {};
+ sketch.setup = () => {
+ assert.equal(afterPreloadCalled, true);
+ resolve();
+ };
+ });
+ });
+ });
+ test('should register and call before and after "setup" hooks', function () {
+ return new Promise(resolve => {
+ let beforeSetupCalled = false;
+ let setupCalled = false;
+ let afterSetupCalled = false;
+
+ p5.prototype.registerMethod('beforeSetup', () => {
+ beforeSetupCalled = true;
+ });
+
+ p5.prototype.registerMethod('setup', () => {
+ assert.equal(beforeSetupCalled, true);
+ setupCalled = true;
+ });
+
+ p5.prototype.registerMethod('afterSetup', () => {
+ if (beforeSetupCalled && setupCalled) afterSetupCalled = true;
+ });
+
+ myp5 = new p5(function (sketch) {
+ sketch.setup = () => {};
+ sketch.draw = () => {
+ assert.equal(afterSetupCalled, true);
+ resolve();
+ };
+ });
+ });
+ });
+ test('should register and call pre and post "draw" hooks', function () {
+ return new Promise(resolve => {
+ let preDrawCalled = false;
+ let drawCalled = false;
+ let postDrawCalled = false;
+
+ p5.prototype.registerMethod('pre', () => {
+ preDrawCalled = true;
+ });
+
+ p5.prototype.registerMethod('draw', () => {
+ assert.equal(preDrawCalled, true);
+ drawCalled = true;
+ });
+
+ p5.prototype.registerMethod('post', () => {
+ if (preDrawCalled && drawCalled) postDrawCalled = true;
+ });
+
+ myp5 = new p5(function (sketch) {
+ sketch.draw = () => {
+ if (sketch.frameCount === 2) {
+ assert.equal(postDrawCalled, true);
+ resolve();
+ }
+ };
+ });
+ });
});
});
- suite('new p5() / global mode', function() {
+ suite('new p5() / global mode', function () {
var iframe;
- teardown(function() {
+ teardown(function () {
if (iframe) {
iframe.teardown();
iframe = null;
}
});
- test('is triggered when "setup" is in window', function() {
- return new Promise(function(resolve, reject) {
+ test('is triggered when "setup" is in window', function () {
+ return new Promise(function (resolve, reject) {
iframe = createP5Iframe();
- iframe.elt.contentWindow.setup = function() {
+ iframe.elt.contentWindow.setup = function () {
resolve();
};
});
});
- test('is triggered when "draw" is in window', function() {
- return new Promise(function(resolve, reject) {
+ test('is triggered when "draw" is in window', function () {
+ return new Promise(function (resolve, reject) {
iframe = createP5Iframe();
- iframe.elt.contentWindow.draw = function() {
+ iframe.elt.contentWindow.draw = function () {
resolve();
};
});
});
- test('works when p5.js is loaded asynchronously', function() {
- return new Promise(function(resolve, reject) {
+ test('works when p5.js is loaded asynchronously', function () {
+ return new Promise(function (resolve, reject) {
iframe = createP5Iframe(`