Skip to content

Commit 1a86985

Browse files
authored
Merge pull request #8094 from processing/global-function-perf
Optimize bottlenecks in 2.0 code
2 parents 10a2d43 + f88689e commit 1a86985

File tree

5 files changed

+134
-38
lines changed

5 files changed

+134
-38
lines changed

src/core/friendly_errors/param_validator.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,10 @@ function validateParams(p5, fn, lifecycles) {
573573
lifecycles.presetup = function(){
574574
loadP5Constructors();
575575

576-
if(p5.disableParameterValidator !== true){
576+
if(
577+
p5.disableParameterValidator !== true &&
578+
p5.disableFriendlyErrors !== true
579+
){
577580
const excludes = ['validate'];
578581
for(const f in this){
579582
if(!excludes.includes(f) && !f.startsWith('_') && typeof this[f] === 'function'){

src/core/main.js

Lines changed: 109 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -78,28 +78,120 @@ class p5 {
7878
this._updateWindowSize();
7979

8080
const bindGlobal = property => {
81-
Object.defineProperty(window, property, {
82-
configurable: true,
83-
enumerable: true,
84-
get: () => {
85-
if(typeof this[property] === 'function'){
86-
return this[property].bind(this);
87-
}else{
88-
return this[property];
89-
}
90-
},
91-
set: newValue => {
81+
if (property === 'constructor') return;
82+
83+
// Common setter for all property types
84+
const createSetter = () => newValue => {
85+
Object.defineProperty(window, property, {
86+
configurable: true,
87+
enumerable: true,
88+
value: newValue,
89+
writable: true
90+
});
91+
if (!p5.disableFriendlyErrors) {
92+
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.`);
93+
}
94+
};
95+
96+
// Check if this property has a getter on the instance or prototype
97+
const instanceDescriptor = Object.getOwnPropertyDescriptor(this, property);
98+
const prototypeDescriptor = Object.getOwnPropertyDescriptor(p5.prototype, property);
99+
const hasGetter = (instanceDescriptor && instanceDescriptor.get) ||
100+
(prototypeDescriptor && prototypeDescriptor.get);
101+
102+
// Only check if it's a function if it doesn't have a getter
103+
// to avoid actually evaluating getters before things like the
104+
// renderer are fully constructed
105+
let isPrototypeFunction = false;
106+
let isConstant = false;
107+
let constantValue;
108+
109+
if (!hasGetter) {
110+
const prototypeValue = p5.prototype[property];
111+
isPrototypeFunction = typeof prototypeValue === 'function';
112+
113+
// Check if this is a true constant from the constants module
114+
if (!isPrototypeFunction && constants[property] !== undefined) {
115+
isConstant = true;
116+
constantValue = prototypeValue;
117+
}
118+
}
119+
120+
if (isPrototypeFunction) {
121+
// For regular functions, cache the bound function
122+
const boundFunction = p5.prototype[property].bind(this);
123+
if (p5.disableFriendlyErrors) {
92124
Object.defineProperty(window, property, {
93125
configurable: true,
94126
enumerable: true,
95-
value: newValue,
96-
writable: true
127+
value: boundFunction,
128+
});
129+
} else {
130+
Object.defineProperty(window, property, {
131+
configurable: true,
132+
enumerable: true,
133+
get() {
134+
return boundFunction;
135+
},
136+
set: createSetter()
97137
});
98-
if (!p5.disableFriendlyErrors) {
99-
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.`);
100-
}
101138
}
102-
});
139+
} else if (isConstant) {
140+
// For constants, cache the value directly
141+
if (p5.disableFriendlyErrors) {
142+
Object.defineProperty(window, property, {
143+
configurable: true,
144+
enumerable: true,
145+
value: constantValue,
146+
});
147+
} else {
148+
Object.defineProperty(window, property, {
149+
configurable: true,
150+
enumerable: true,
151+
get() {
152+
return constantValue;
153+
},
154+
set: createSetter()
155+
});
156+
}
157+
} else if (hasGetter || !isPrototypeFunction) {
158+
// For properties with getters or non-function properties, use lazy optimization
159+
// On first access, determine the type and optimize subsequent accesses
160+
let lastFunction = null;
161+
let boundFunction = null;
162+
let isFunction = null; // null = unknown, true = function, false = not function
163+
164+
Object.defineProperty(window, property, {
165+
configurable: true,
166+
enumerable: true,
167+
get: () => {
168+
const currentValue = this[property];
169+
170+
if (isFunction === null) {
171+
// First access - determine type and optimize
172+
isFunction = typeof currentValue === 'function';
173+
if (isFunction) {
174+
lastFunction = currentValue;
175+
boundFunction = currentValue.bind(this);
176+
return boundFunction;
177+
} else {
178+
return currentValue;
179+
}
180+
} else if (isFunction) {
181+
// Optimized function path - only rebind if function changed
182+
if (currentValue !== lastFunction) {
183+
lastFunction = currentValue;
184+
boundFunction = currentValue.bind(this);
185+
}
186+
return boundFunction;
187+
} else {
188+
// Optimized non-function path
189+
return currentValue;
190+
}
191+
},
192+
set: createSetter()
193+
});
194+
}
103195
};
104196
// If the user has created a global setup or draw function,
105197
// assume "global" mode and make everything global (i.e. on the window)

src/math/Matrices/Matrix.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Vector } from '../p5.Vector';
66
import { MatrixInterface } from './MatrixInterface';
77

88
const isPerfectSquare = arr => {
9-
const sqDimention = Math.sqrt(Array.from(arr).length);
9+
const sqDimention = Math.sqrt(arr.length);
1010
if (sqDimention % 1 !== 0) {
1111
throw new Error('Array length must be a perfect square.');
1212
}
@@ -29,7 +29,7 @@ export class Matrix extends MatrixInterface {
2929
// This is default behavior when object
3030
// instantiated using createMatrix()
3131
if (isMatrixArray(args[0]) && isPerfectSquare(args[0])) {
32-
const sqDimention = Math.sqrt(Array.from(args[0]).length);
32+
const sqDimention = Math.sqrt(args[0].length);
3333
this.#sqDimention = sqDimention;
3434
this.matrix = GLMAT_ARRAY_TYPE.from(args[0]);
3535
} else if (typeof args[0] === 'number') {
@@ -568,7 +568,7 @@ export class Matrix extends MatrixInterface {
568568
_src = multMatrix.matrix;
569569
} else if (isMatrixArray(multMatrix) && isPerfectSquare(multMatrix)) {
570570
_src = multMatrix;
571-
} else if (isPerfectSquare(arguments)) {
571+
} else if (isPerfectSquare(Array.from(arguments))) {
572572
_src = Array.from(arguments);
573573
} else {
574574
return; // nothing to do.

src/math/Matrices/MatrixInterface.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export class MatrixInterface {
1111
if (this.constructor === MatrixInterface) {
1212
throw new Error("Class is of abstract type and can't be instantiated");
1313
}
14-
const methods = [
14+
// TODO: don't check this at runtime but still at compile time
15+
/*const methods = [
1516
'add',
1617
'setElement',
1718
'reset',
@@ -48,6 +49,6 @@ export class MatrixInterface {
4849
if (this[method] === undefined) {
4950
throw new Error(`${method}() method must be implemented`);
5051
}
51-
});
52+
});*/
5253
}
5354
}

src/math/p5.Vector.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ class Vector {
3333
// This is how it comes in with createVector()
3434
// This check if the first argument is a function
3535
constructor(...args) {
36-
let values = args.map(arg => arg || 0);
36+
let values = args; // .map(arg => arg || 0);
3737
if (typeof args[0] === 'function') {
3838
this.isPInst = true;
3939
this._fromRadians = args[0];
4040
this._toRadians = args[1];
41-
values = args.slice(2).map(arg => arg || 0);
41+
values = args.slice(2); // .map(arg => arg || 0);
4242
}
4343
let dimensions = values.length; // TODO: make default 3 if no arguments
4444
if (dimensions === 0) {
@@ -272,7 +272,7 @@ class Vector {
272272
* </div>
273273
*/
274274
toString() {
275-
return `[${this.values.join(', ')}]`;
275+
return `[${this._values.join(', ')}]`;
276276
}
277277

278278
/**
@@ -334,13 +334,13 @@ class Vector {
334334
*/
335335
set(...args) {
336336
if (args[0] instanceof Vector) {
337-
this.values = args[0].values.slice();
337+
this._values = args[0].values.slice();
338338
} else if (Array.isArray(args[0])) {
339-
this.values = args[0].map(arg => arg || 0);
339+
this._values = args[0].map(arg => arg || 0);
340340
} else {
341-
this.values = args.map(arg => arg || 0);
341+
this._values = args.map(arg => arg || 0);
342342
}
343-
this.dimensions = this.values.length;
343+
this.dimensions = this._values.length;
344344
return this;
345345
}
346346

@@ -374,9 +374,9 @@ class Vector {
374374
*/
375375
copy() {
376376
if (this.isPInst) {
377-
return new Vector(this._fromRadians, this._toRadians, ...this.values);
377+
return new Vector(this._fromRadians, this._toRadians, ...this._values);
378378
} else {
379-
return new Vector(...this.values);
379+
return new Vector(...this._values);
380380
}
381381
}
382382

@@ -521,7 +521,7 @@ class Vector {
521521
args = args[0];
522522
}
523523
args.forEach((value, index) => {
524-
this.values[index] = (this.values[index] || 0) + (value || 0);
524+
this._values[index] = (this._values[index] || 0) + (value || 0);
525525
});
526526
return this;
527527
}
@@ -833,15 +833,15 @@ class Vector {
833833
sub(...args) {
834834
if (args[0] instanceof Vector) {
835835
args[0].values.forEach((value, index) => {
836-
this.values[index] -= value || 0;
836+
this._values[index] -= value || 0;
837837
});
838838
} else if (Array.isArray(args[0])) {
839839
args[0].forEach((value, index) => {
840-
this.values[index] -= value || 0;
840+
this._values[index] -= value || 0;
841841
});
842842
} else {
843843
args.forEach((value, index) => {
844-
this.values[index] -= value || 0;
844+
this._values[index] -= value || 0;
845845
});
846846
}
847847
return this;
@@ -1044,7 +1044,7 @@ class Vector {
10441044
mult(...args) {
10451045
if (args.length === 1 && args[0] instanceof Vector) {
10461046
const v = args[0];
1047-
const maxLen = Math.min(this.values.length, v.values.length);
1047+
const maxLen = Math.min(this._values.length, v.values.length);
10481048
for (let i = 0; i < maxLen; i++) {
10491049
if (Number.isFinite(v.values[i]) && typeof v.values[i] === 'number') {
10501050
this._values[i] *= v.values[i];
@@ -1058,7 +1058,7 @@ class Vector {
10581058
}
10591059
} else if (args.length === 1 && Array.isArray(args[0])) {
10601060
const arr = args[0];
1061-
const maxLen = Math.min(this.values.length, arr.length);
1061+
const maxLen = Math.min(this._values.length, arr.length);
10621062
for (let i = 0; i < maxLen; i++) {
10631063
if (Number.isFinite(arr[i]) && typeof arr[i] === 'number') {
10641064
this._values[i] *= arr[i];

0 commit comments

Comments
 (0)