@@ -77,122 +77,7 @@ class p5 {
7777 // ensure correct reporting of window dimensions
7878 this . _updateWindowSize ( ) ;
7979
80- const bindGlobal = property => {
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 ) {
124- Object . defineProperty ( window , property , {
125- configurable : true ,
126- enumerable : 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 ( )
137- } ) ;
138- }
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- }
195- } ;
80+ const bindGlobal = createBindGlobal ( this ) ;
19681 // If the user has created a global setup or draw function,
19782 // assume "global" mode and make everything global (i.e. on the window)
19883 if ( ! sketch ) {
@@ -259,7 +144,23 @@ class p5 {
259144
260145 static registerAddon ( addon ) {
261146 const lifecycles = { } ;
262- addon ( p5 , p5 . prototype , lifecycles ) ;
147+
148+ // Create a proxy for p5.prototype that automatically re-binds globals when properties are added
149+ const prototypeProxy = new Proxy ( p5 . prototype , {
150+ set ( target , property , value ) {
151+ const result = Reflect . set ( target , property , value ) ;
152+
153+ // If we have a global instance and this is a new property, bind it globally
154+ if ( p5 . instance && p5 . instance . _isGlobal && property [ 0 ] !== '_' ) {
155+ const bindGlobal = createBindGlobal ( p5 . instance ) ;
156+ bindGlobal ( property ) ;
157+ }
158+
159+ return result ;
160+ }
161+ } ) ;
162+
163+ addon ( p5 , prototypeProxy , lifecycles ) ;
263164
264165 const validLifecycles = Object . keys ( p5 . lifecycleHooks ) ;
265166 for ( const name of validLifecycles ) {
@@ -492,9 +393,28 @@ class p5 {
492393 }
493394
494395 async _runLifecycleHook ( hookName ) {
396+ // Create a proxy for the instance that automatically re-binds globals when
397+ // properties are added over the course of the lifecycle.
398+ // Afterward, we skip global binding for efficiency.
399+ let inLifecycle = true ;
400+ const instanceProxy = new Proxy ( this , {
401+ set ( target , property , value ) {
402+ const result = Reflect . set ( target , property , value ) ;
403+
404+ // If this is a global instance and this is a new property, bind it globally
405+ if ( inLifecycle && target . _isGlobal && property [ 0 ] !== '_' ) {
406+ const bindGlobal = createBindGlobal ( target ) ;
407+ bindGlobal ( property ) ;
408+ }
409+
410+ return result ;
411+ }
412+ } ) ;
413+
495414 await Promise . all ( p5 . lifecycleHooks [ hookName ] . map ( hook => {
496- return hook . call ( this ) ;
415+ return hook . call ( instanceProxy ) ;
497416 } ) ) ;
417+ inLifecycle = false ;
498418 }
499419
500420 _initializeInstanceVariables ( ) {
@@ -511,6 +431,126 @@ class p5 {
511431 }
512432}
513433
434+ // Global helper function for binding properties to window in global mode
435+ function createBindGlobal ( instance ) {
436+ return function bindGlobal ( property ) {
437+ if ( property === 'constructor' ) return ;
438+
439+ // Common setter for all property types
440+ const createSetter = ( ) => newValue => {
441+ Object . defineProperty ( window , property , {
442+ configurable : true ,
443+ enumerable : true ,
444+ value : newValue ,
445+ writable : true
446+ } ) ;
447+ if ( ! p5 . disableFriendlyErrors ) {
448+ 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.` ) ;
449+ }
450+ } ;
451+
452+ // Check if this property has a getter on the instance or prototype
453+ const instanceDescriptor = Object . getOwnPropertyDescriptor ( instance , property ) ;
454+ const prototypeDescriptor = Object . getOwnPropertyDescriptor ( p5 . prototype , property ) ;
455+ const hasGetter = ( instanceDescriptor && instanceDescriptor . get ) ||
456+ ( prototypeDescriptor && prototypeDescriptor . get ) ;
457+
458+ // Only check if it's a function if it doesn't have a getter
459+ // to avoid actually evaluating getters before things like the
460+ // renderer are fully constructed
461+ let isPrototypeFunction = false ;
462+ let isConstant = false ;
463+ let constantValue ;
464+
465+ if ( ! hasGetter ) {
466+ const prototypeValue = p5 . prototype [ property ] ;
467+ isPrototypeFunction = typeof prototypeValue === 'function' ;
468+
469+ // Check if this is a true constant from the constants module
470+ if ( ! isPrototypeFunction && constants [ property ] !== undefined ) {
471+ isConstant = true ;
472+ constantValue = prototypeValue ;
473+ }
474+ }
475+
476+ if ( isPrototypeFunction ) {
477+ // For regular functions, cache the bound function
478+ const boundFunction = p5 . prototype [ property ] . bind ( instance ) ;
479+ if ( p5 . disableFriendlyErrors ) {
480+ Object . defineProperty ( window , property , {
481+ configurable : true ,
482+ enumerable : true ,
483+ value : boundFunction ,
484+ } ) ;
485+ } else {
486+ Object . defineProperty ( window , property , {
487+ configurable : true ,
488+ enumerable : true ,
489+ get ( ) {
490+ return boundFunction ;
491+ } ,
492+ set : createSetter ( )
493+ } ) ;
494+ }
495+ } else if ( isConstant ) {
496+ // For constants, cache the value directly
497+ if ( p5 . disableFriendlyErrors ) {
498+ Object . defineProperty ( window , property , {
499+ configurable : true ,
500+ enumerable : true ,
501+ value : constantValue ,
502+ } ) ;
503+ } else {
504+ Object . defineProperty ( window , property , {
505+ configurable : true ,
506+ enumerable : true ,
507+ get ( ) {
508+ return constantValue ;
509+ } ,
510+ set : createSetter ( )
511+ } ) ;
512+ }
513+ } else if ( hasGetter || ! isPrototypeFunction ) {
514+ // For properties with getters or non-function properties, use lazy optimization
515+ // On first access, determine the type and optimize subsequent accesses
516+ let lastFunction = null ;
517+ let boundFunction = null ;
518+ let isFunction = null ; // null = unknown, true = function, false = not function
519+
520+ Object . defineProperty ( window , property , {
521+ configurable : true ,
522+ enumerable : true ,
523+ get : ( ) => {
524+ const currentValue = instance [ property ] ;
525+
526+ if ( isFunction === null ) {
527+ // First access - determine type and optimize
528+ isFunction = typeof currentValue === 'function' ;
529+ if ( isFunction ) {
530+ lastFunction = currentValue ;
531+ boundFunction = currentValue . bind ( instance ) ;
532+ return boundFunction ;
533+ } else {
534+ return currentValue ;
535+ }
536+ } else if ( isFunction ) {
537+ // Optimized function path - only rebind if function changed
538+ if ( currentValue !== lastFunction ) {
539+ lastFunction = currentValue ;
540+ boundFunction = currentValue . bind ( instance ) ;
541+ }
542+ return boundFunction ;
543+ } else {
544+ // Optimized non-function path
545+ return currentValue ;
546+ }
547+ } ,
548+ set : createSetter ( )
549+ } ) ;
550+ }
551+ } ;
552+ }
553+
514554// Attach constants to p5 prototype
515555for ( const k in constants ) {
516556 p5 . prototype [ k ] = constants [ k ] ;
0 commit comments