diff --git a/src/core/friendly_errors/param_validator.js b/src/core/friendly_errors/param_validator.js index b391150e04..97f23dfff4 100644 --- a/src/core/friendly_errors/param_validator.js +++ b/src/core/friendly_errors/param_validator.js @@ -573,7 +573,10 @@ function validateParams(p5, fn, lifecycles) { lifecycles.presetup = function(){ loadP5Constructors(); - if(p5.disableParameterValidator !== true){ + if( + p5.disableParameterValidator !== true && + p5.disableFriendlyErrors !== true + ){ const excludes = ['validate']; for(const f in this){ if(!excludes.includes(f) && !f.startsWith('_') && typeof this[f] === 'function'){ diff --git a/src/core/main.js b/src/core/main.js index 8978dd0a3a..6cafe4d375 100644 --- a/src/core/main.js +++ b/src/core/main.js @@ -78,28 +78,120 @@ class p5 { this._updateWindowSize(); const bindGlobal = property => { - Object.defineProperty(window, property, { - configurable: true, - enumerable: true, - get: () => { - if(typeof this[property] === 'function'){ - return this[property].bind(this); - }else{ - return this[property]; - } - }, - set: newValue => { + if (property === 'constructor') return; + + // Common setter for all property types + const createSetter = () => newValue => { + Object.defineProperty(window, property, { + configurable: true, + enumerable: true, + value: newValue, + writable: true + }); + if (!p5.disableFriendlyErrors) { + console.log(`You just changed the value of "${property}", which was a p5 global value. This could cause problems later if you're not careful.`); + } + }; + + // Check if this property has a getter on the instance or prototype + const instanceDescriptor = Object.getOwnPropertyDescriptor(this, property); + const prototypeDescriptor = Object.getOwnPropertyDescriptor(p5.prototype, property); + const hasGetter = (instanceDescriptor && instanceDescriptor.get) || + (prototypeDescriptor && prototypeDescriptor.get); + + // Only check if it's a function if it doesn't have a getter + // to avoid actually evaluating getters before things like the + // renderer are fully constructed + let isPrototypeFunction = false; + let isConstant = false; + let constantValue; + + if (!hasGetter) { + const prototypeValue = p5.prototype[property]; + isPrototypeFunction = typeof prototypeValue === 'function'; + + // Check if this is a true constant from the constants module + if (!isPrototypeFunction && constants[property] !== undefined) { + isConstant = true; + constantValue = prototypeValue; + } + } + + if (isPrototypeFunction) { + // For regular functions, cache the bound function + const boundFunction = p5.prototype[property].bind(this); + if (p5.disableFriendlyErrors) { Object.defineProperty(window, property, { configurable: true, enumerable: true, - value: newValue, - writable: true + value: boundFunction, + }); + } else { + Object.defineProperty(window, property, { + configurable: true, + enumerable: true, + get() { + return boundFunction; + }, + set: createSetter() }); - if (!p5.disableFriendlyErrors) { - console.log(`You just changed the value of "${property}", which was a p5 global value. This could cause problems later if you're not careful.`); - } } - }); + } else if (isConstant) { + // For constants, cache the value directly + if (p5.disableFriendlyErrors) { + Object.defineProperty(window, property, { + configurable: true, + enumerable: true, + value: constantValue, + }); + } else { + Object.defineProperty(window, property, { + configurable: true, + enumerable: true, + get() { + return constantValue; + }, + set: createSetter() + }); + } + } else if (hasGetter || !isPrototypeFunction) { + // For properties with getters or non-function properties, use lazy optimization + // On first access, determine the type and optimize subsequent accesses + let lastFunction = null; + let boundFunction = null; + let isFunction = null; // null = unknown, true = function, false = not function + + Object.defineProperty(window, property, { + configurable: true, + enumerable: true, + get: () => { + const currentValue = this[property]; + + if (isFunction === null) { + // First access - determine type and optimize + isFunction = typeof currentValue === 'function'; + if (isFunction) { + lastFunction = currentValue; + boundFunction = currentValue.bind(this); + return boundFunction; + } else { + return currentValue; + } + } else if (isFunction) { + // Optimized function path - only rebind if function changed + if (currentValue !== lastFunction) { + lastFunction = currentValue; + boundFunction = currentValue.bind(this); + } + return boundFunction; + } else { + // Optimized non-function path + return currentValue; + } + }, + set: createSetter() + }); + } }; // If the user has created a global setup or draw function, // assume "global" mode and make everything global (i.e. on the window) diff --git a/src/math/Matrices/Matrix.js b/src/math/Matrices/Matrix.js index eba767f33a..f3a9d305d8 100644 --- a/src/math/Matrices/Matrix.js +++ b/src/math/Matrices/Matrix.js @@ -6,7 +6,7 @@ import { Vector } from '../p5.Vector'; import { MatrixInterface } from './MatrixInterface'; const isPerfectSquare = arr => { - const sqDimention = Math.sqrt(Array.from(arr).length); + const sqDimention = Math.sqrt(arr.length); if (sqDimention % 1 !== 0) { throw new Error('Array length must be a perfect square.'); } @@ -29,7 +29,7 @@ export class Matrix extends MatrixInterface { // This is default behavior when object // instantiated using createMatrix() if (isMatrixArray(args[0]) && isPerfectSquare(args[0])) { - const sqDimention = Math.sqrt(Array.from(args[0]).length); + const sqDimention = Math.sqrt(args[0].length); this.#sqDimention = sqDimention; this.matrix = GLMAT_ARRAY_TYPE.from(args[0]); } else if (typeof args[0] === 'number') { @@ -568,7 +568,7 @@ export class Matrix extends MatrixInterface { _src = multMatrix.matrix; } else if (isMatrixArray(multMatrix) && isPerfectSquare(multMatrix)) { _src = multMatrix; - } else if (isPerfectSquare(arguments)) { + } else if (isPerfectSquare(Array.from(arguments))) { _src = Array.from(arguments); } else { return; // nothing to do. diff --git a/src/math/Matrices/MatrixInterface.js b/src/math/Matrices/MatrixInterface.js index e9778ba82a..8f428e219a 100644 --- a/src/math/Matrices/MatrixInterface.js +++ b/src/math/Matrices/MatrixInterface.js @@ -11,7 +11,8 @@ export class MatrixInterface { if (this.constructor === MatrixInterface) { throw new Error("Class is of abstract type and can't be instantiated"); } - const methods = [ + // TODO: don't check this at runtime but still at compile time + /*const methods = [ 'add', 'setElement', 'reset', @@ -48,6 +49,6 @@ export class MatrixInterface { if (this[method] === undefined) { throw new Error(`${method}() method must be implemented`); } - }); + });*/ } } diff --git a/src/math/p5.Vector.js b/src/math/p5.Vector.js index a244720170..2b033456e0 100644 --- a/src/math/p5.Vector.js +++ b/src/math/p5.Vector.js @@ -33,12 +33,12 @@ class Vector { // This is how it comes in with createVector() // This check if the first argument is a function constructor(...args) { - let values = args.map(arg => arg || 0); + let values = args; // .map(arg => arg || 0); if (typeof args[0] === 'function') { this.isPInst = true; this._fromRadians = args[0]; this._toRadians = args[1]; - values = args.slice(2).map(arg => arg || 0); + values = args.slice(2); // .map(arg => arg || 0); } let dimensions = values.length; // TODO: make default 3 if no arguments if (dimensions === 0) { @@ -272,7 +272,7 @@ class Vector { * */ toString() { - return `[${this.values.join(', ')}]`; + return `[${this._values.join(', ')}]`; } /** @@ -334,13 +334,13 @@ class Vector { */ set(...args) { if (args[0] instanceof Vector) { - this.values = args[0].values.slice(); + this._values = args[0].values.slice(); } else if (Array.isArray(args[0])) { - this.values = args[0].map(arg => arg || 0); + this._values = args[0].map(arg => arg || 0); } else { - this.values = args.map(arg => arg || 0); + this._values = args.map(arg => arg || 0); } - this.dimensions = this.values.length; + this.dimensions = this._values.length; return this; } @@ -374,9 +374,9 @@ class Vector { */ copy() { if (this.isPInst) { - return new Vector(this._fromRadians, this._toRadians, ...this.values); + return new Vector(this._fromRadians, this._toRadians, ...this._values); } else { - return new Vector(...this.values); + return new Vector(...this._values); } } @@ -521,7 +521,7 @@ class Vector { args = args[0]; } args.forEach((value, index) => { - this.values[index] = (this.values[index] || 0) + (value || 0); + this._values[index] = (this._values[index] || 0) + (value || 0); }); return this; } @@ -833,15 +833,15 @@ class Vector { sub(...args) { if (args[0] instanceof Vector) { args[0].values.forEach((value, index) => { - this.values[index] -= value || 0; + this._values[index] -= value || 0; }); } else if (Array.isArray(args[0])) { args[0].forEach((value, index) => { - this.values[index] -= value || 0; + this._values[index] -= value || 0; }); } else { args.forEach((value, index) => { - this.values[index] -= value || 0; + this._values[index] -= value || 0; }); } return this; @@ -1044,7 +1044,7 @@ class Vector { mult(...args) { if (args.length === 1 && args[0] instanceof Vector) { const v = args[0]; - const maxLen = Math.min(this.values.length, v.values.length); + const maxLen = Math.min(this._values.length, v.values.length); for (let i = 0; i < maxLen; i++) { if (Number.isFinite(v.values[i]) && typeof v.values[i] === 'number') { this._values[i] *= v.values[i]; @@ -1058,7 +1058,7 @@ class Vector { } } else if (args.length === 1 && Array.isArray(args[0])) { const arr = args[0]; - const maxLen = Math.min(this.values.length, arr.length); + const maxLen = Math.min(this._values.length, arr.length); for (let i = 0; i < maxLen; i++) { if (Number.isFinite(arr[i]) && typeof arr[i] === 'number') { this._values[i] *= arr[i];