@@ -274,6 +274,11 @@ class Block {
274274 this . name = protoblock . name ;
275275 this . activity = this . blocks . activity ;
276276
277+ // Cached index in blocks.blockList — set when block is added
278+ // to blockList. Avoids O(N) blockList.indexOf() lookups that
279+ // are scattered across the codebase (33+ call sites).
280+ this . blockIndex = - 1 ;
281+
277282 this . collapsed = false ; // Is this collapsible block collapsed?
278283 this . inCollapsed = false ; // Is this block in a collapsed stack?
279284 this . trash = false ; // Is this block in the trash?
@@ -1039,7 +1044,7 @@ class Block {
10391044 generateArtwork ( firstTime ) {
10401045 // Get the block labels from the protoblock.
10411046 const that = this ;
1042- const thisBlock = this . blocks . blockList . indexOf ( this ) ;
1047+ const thisBlock = this . blockIndex ;
10431048 let block_label = "" ;
10441049
10451050 /**
@@ -1071,7 +1076,7 @@ class Block {
10711076
10721077 const __callback = ( that , firstTime ) => {
10731078 that . activity . refreshCanvas ( ) ;
1074- const thisBlock = that . blocks . blockList . indexOf ( that ) ;
1079+ const thisBlock = that . blockIndex ;
10751080
10761081 if ( firstTime ) {
10771082 that . _loadEventHandlers ( ) ;
@@ -1289,7 +1294,7 @@ class Block {
12891294 artwork = artwork . replace ( "arg_label_" + i , this . protoblock . staticLabels [ i ] ) ;
12901295 }
12911296
1292- that . blocks . blockArt [ that . blocks . blockList . indexOf ( that ) ] = artwork ;
1297+ that . blocks . blockArt [ that . blockIndex ] = artwork ;
12931298
12941299 _blockMakeBitmap ( artwork , __processBitmap , this ) ;
12951300 }
@@ -1300,7 +1305,7 @@ class Block {
13001305 * @returns {void }
13011306 */
13021307 _finishImageLoad ( ) {
1303- // const thisBlock = this.blocks.blockList.indexOf(this) ;
1308+ // const thisBlock = this.blockIndex ;
13041309 let proto , obj , label , attr ;
13051310 // Value blocks get a modifiable text label.
13061311 if ( SPECIALINPUTS . includes ( this . name ) ) {
@@ -1490,7 +1495,7 @@ class Block {
14901495 * @returns {void }
14911496 */
14921497 _generateCollapseArtwork ( postProcess ) {
1493- const thisBlock = this . blocks . blockList . indexOf ( this ) ;
1498+ const thisBlock = this . blockIndex ;
14941499
14951500 /**
14961501 * Run the postprocess function after the artwork is loaded.
@@ -1746,7 +1751,7 @@ class Block {
17461751 that . _ensureDecorationOnTop ( ) ;
17471752
17481753 // Save the collapsed block artwork for export.
1749- that . blocks . blockCollapseArt [ that . blocks . blockList . indexOf ( that ) ] = that . collapseArtwork
1754+ that . blocks . blockCollapseArt [ that . blockIndex ] = that . collapseArtwork
17501755 . replace ( / f i l l _ c o l o r / g, PALETTEFILLCOLORS [ that . protoblock . palette . name ] )
17511756 . replace ( / s t r o k e _ c o l o r / g, PALETTESTROKECOLORS [ that . protoblock . palette . name ] )
17521757 . replace ( "block_label" , safeSVG ( that . collapseText . text ) ) ;
@@ -2076,7 +2081,7 @@ class Block {
20762081 */
20772082 getBlockId ( ) {
20782083 // Generate a UID based on the block index into the blockList.
2079- const number = blockBlocks . blockList . indexOf ( this ) ;
2084+ const number = this . blockIndex ;
20802085 return "_" + number . toString ( ) ;
20812086 }
20822087
@@ -2099,7 +2104,7 @@ class Block {
20992104 */
21002105 loadThumbnail ( imagePath ) {
21012106 // Load an image thumbnail onto block.
2102- const thisBlock = this . blocks . blockList . indexOf ( this ) ;
2107+ const thisBlock = this . blockIndex ;
21032108 const that = this ;
21042109
21052110 if ( this . blocks . blockList [ thisBlock ] . value === null && imagePath === null ) {
@@ -2236,7 +2241,7 @@ class Block {
22362241 collapseToggle ( ) {
22372242 // Find the blocks to collapse/expand inside of a collapable
22382243 // block.
2239- const thisBlock = this . blocks . blockList . indexOf ( this ) ;
2244+ const thisBlock = this . blockIndex ;
22402245 this . blocks . findDragGroup ( thisBlock ) ;
22412246
22422247 if ( this . collapseBlockBitmap === null ) {
@@ -2355,7 +2360,7 @@ class Block {
23552360 const intervals = [ ] ;
23562361 let i = 0 ;
23572362
2358- let c = this . blocks . blockList . indexOf ( this ) ,
2363+ let c = this . blockIndex ,
23592364 lastIntervalBlock ;
23602365 while ( c !== null ) {
23612366 lastIntervalBlock = c ;
@@ -2868,7 +2873,7 @@ class Block {
28682873 */
28692874 _loadEventHandlers ( ) {
28702875 const that = this ;
2871- const thisBlock = this . blocks . blockList . indexOf ( this ) ;
2876+ const thisBlock = this . blockIndex ;
28722877
28732878 this . _calculateBlockHitArea ( ) ;
28742879
@@ -2890,6 +2895,12 @@ class Block {
28902895 let locked = false ;
28912896 let getInput = window . hasMouse ;
28922897
2898+ // Cached drag state: computed once at mousedown, reused during pressmove.
2899+ // This avoids redundant O(N) findDragGroup and O(D) rest2 chain walks
2900+ // on every mouse move event (which fires 60+ times per second).
2901+ let _cachedDragGroup = null ;
2902+ let _dragHasRest2 = false ;
2903+
28932904 /**
28942905 * Handles the click event on the block container.
28952906 * @param {Event } event - The click event.
@@ -3012,7 +3023,7 @@ class Block {
30123023 that . blocks . mouseDownTime = new Date ( ) . getTime ( ) ;
30133024
30143025 that . blocks . longPressTimeout = setTimeout ( ( ) => {
3015- that . blocks . activeBlock = that . blocks . blockList . indexOf ( that ) ;
3026+ that . blocks . activeBlock = that . blockIndex ;
30163027 that . _triggerLongPress = true ;
30173028 that . blocks . triggerLongPress ( ) ;
30183029 } , LONGPRESSTIME ) ;
@@ -3056,6 +3067,26 @@ class Block {
30563067 x : Math . round ( that . container . x - that . original . x ) ,
30573068 y : Math . round ( that . container . y - that . original . y )
30583069 } ;
3070+
3071+ // Pre-compute the drag group and rest2 check once at mousedown.
3072+ // The drag group doesn't change during a drag (blocks only
3073+ // connect/disconnect on mouseup), so caching it avoids
3074+ // redundant O(N) recursive traversal on every mouse move.
3075+ that . blocks . findDragGroup ( thisBlock ) ;
3076+ _cachedDragGroup = that . blocks . dragGroup . slice ( ) ;
3077+
3078+ // Pre-compute whether the chain below contains a rest2 block.
3079+ // This avoids walking the entire connection chain on every
3080+ // mouse move event.
3081+ _dragHasRest2 = false ;
3082+ let checkBlock = that . blocks . blockList [ that . connections [ 1 ] ] ;
3083+ while ( checkBlock != null ) {
3084+ if ( checkBlock ?. name === "rest2" ) {
3085+ _dragHasRest2 = true ;
3086+ break ;
3087+ }
3088+ checkBlock = checkBlock ?. blocks . blockList [ checkBlock . connections [ 1 ] ] ;
3089+ }
30593090 } ) ;
30603091
30613092 /**
@@ -3079,14 +3110,11 @@ class Block {
30793110 return ;
30803111 }
30813112
3082- // Do not allow a stack of blocks to be dragged if the stack contains a silence block.
3083- let block = that . blocks . blockList [ that . connections [ 1 ] ] ;
3084- while ( block != null ) {
3085- if ( block ?. name === "rest2" ) {
3086- this . activity . errorMsg ( _ ( "Silence block cannot be removed." ) , block ) ;
3087- return ;
3088- }
3089- block = block ?. blocks . blockList [ block . connections [ 1 ] ] ;
3113+ // Use the cached rest2 check from mousedown instead of
3114+ // walking the chain on every mouse move event.
3115+ if ( _dragHasRest2 ) {
3116+ this . activity . errorMsg ( _ ( "Silence block cannot be removed." ) , that ) ;
3117+ return ;
30903118 }
30913119
30923120 if ( window . hasMouse ) {
@@ -3138,6 +3166,10 @@ class Block {
31383166 that . label . style . display = "none" ;
31393167 }
31403168
3169+ // Defer checkBounds during the entire block movement to avoid
3170+ // N separate checkBounds calls (one per block in the drag group).
3171+ that . blocks . _beginDeferCheckBounds ( ) ;
3172+
31413173 that . blocks . moveBlockRelative ( thisBlock , dx , dy ) ;
31423174
31433175 // If we are over the trash, warn the user.
@@ -3157,17 +3189,19 @@ class Block {
31573189 that . container . setChildIndex ( that . text , that . container . children . length - 1 ) ;
31583190 }
31593191
3160- // ...and move any connected blocks.
3161- that . blocks . findDragGroup ( thisBlock ) ;
3162- if ( that . blocks . dragGroup . length > 0 ) {
3163- for ( let b = 0 ; b < that . blocks . dragGroup . length ; b ++ ) {
3164- const blk = that . blocks . dragGroup [ b ] ;
3192+ // Move connected blocks using the cached drag group from
3193+ // mousedown, avoiding redundant O(N) recursive traversal.
3194+ if ( _cachedDragGroup !== null && _cachedDragGroup . length > 0 ) {
3195+ for ( let b = 0 ; b < _cachedDragGroup . length ; b ++ ) {
3196+ const blk = _cachedDragGroup [ b ] ;
31653197 if ( b !== 0 ) {
31663198 that . blocks . moveBlockRelative ( blk , dx , dy ) ;
31673199 }
31683200 }
31693201 }
31703202
3203+ that . blocks . _endDeferCheckBounds ( ) ;
3204+
31713205 that . activity . refreshCanvas ( ) ;
31723206 } ) ;
31733207
@@ -3191,6 +3225,9 @@ class Block {
31913225 }
31923226 that . blocks . activeBlock = null ;
31933227
3228+ // Clear cached drag state.
3229+ _cachedDragGroup = null ;
3230+ _dragHasRest2 = false ;
31943231 moved = false ;
31953232 } ) ;
31963233
@@ -3213,6 +3250,9 @@ class Block {
32133250 that . blocks . unhighlight ( thisBlock , true ) ;
32143251 that . blocks . activeBlock = null ;
32153252
3253+ // Clear cached drag state.
3254+ _cachedDragGroup = null ;
3255+ _dragHasRest2 = false ;
32163256 moved = false ;
32173257 } ) ;
32183258 }
@@ -3228,7 +3268,7 @@ class Block {
32283268 * @returns {void }
32293269 */
32303270 _mouseoutCallback ( event , moved , haveClick , hideDOM ) {
3231- const thisBlock = this . blocks . blockList . indexOf ( this ) ;
3271+ const thisBlock = this . blockIndex ;
32323272 if ( ! this . activity . logo . runningLilypond ) {
32333273 document . body . style . cursor = "default" ;
32343274 }
@@ -3270,7 +3310,7 @@ class Block {
32703310 // Just in case the blocks are not properly docked after
32713311 // the move (workaround for issue #38 -- Blocks fly
32723312 // apart). Still need to get to the root cause.
3273- this . blocks . adjustDocks ( this . blocks . blockList . indexOf ( this ) , true ) ;
3313+ this . blocks . adjustDocks ( this . blockIndex , true ) ;
32743314 }
32753315 } else if ( SPECIALINPUTS . includes ( this . name ) || [ "media" , "loadFile" ] . includes ( this . name ) ) {
32763316 if ( ! haveClick ) {
@@ -3328,7 +3368,7 @@ class Block {
33283368 }
33293369
33303370 // Numeric pie menus
3331- const blk = this . blocks . blockList . indexOf ( this ) ;
3371+ const blk = this . blockIndex ;
33323372
33333373 if ( this . blocks . octaveNumber ( blk ) ) {
33343374 return true ;
@@ -3385,7 +3425,7 @@ class Block {
33853425 return false ;
33863426 }
33873427
3388- return this . blocks . blockList [ cblk ] . connections [ 1 ] === this . blocks . blockList . indexOf ( this ) ;
3428+ return this . blocks . blockList [ cblk ] . connections [ 1 ] === this . blockIndex ;
33893429 }
33903430
33913431 /**
@@ -3407,7 +3447,7 @@ class Block {
34073447 return false ;
34083448 }
34093449
3410- return this . blocks . blockList [ cblk ] . connections [ 2 ] === this . blocks . blockList . indexOf ( this ) ;
3450+ return this . blocks . blockList [ cblk ] . connections [ 2 ] === this . blockIndex ;
34113451 }
34123452
34133453 /**
@@ -3429,7 +3469,7 @@ class Block {
34293469 return false ;
34303470 }
34313471
3432- return this . blocks . blockList [ cblk ] . connections [ 3 ] === this . blocks . blockList . indexOf ( this ) ;
3472+ return this . blocks . blockList [ cblk ] . connections [ 3 ] === this . blockIndex ;
34333473 }
34343474
34353475 /**
@@ -3976,7 +4016,7 @@ class Block {
39764016 } else {
39774017 // If the number block is connected to a pitch block, then
39784018 // use the pie menu for octaves. Other special cases as well.
3979- const blk = this . blocks . blockList . indexOf ( this ) ;
4019+ const blk = this . blockIndex ;
39804020 if ( this . blocks . octaveNumber ( blk ) ) {
39814021 piemenuNumber ( this , [ 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 ] , this . value ) ;
39824022 } else if ( this . blocks . noteValueNumber ( blk , 2 ) ) {
@@ -4124,7 +4164,7 @@ class Block {
41244164 }
41254165 }
41264166
4127- // const blk = this.blocks.blockList.indexOf(this) ;
4167+ // const blk = this.blockIndex ;
41284168 if ( ! this . _usePiemenu ( ) ) {
41294169 let focused = false ;
41304170
@@ -4257,7 +4297,7 @@ class Block {
42574297 _checkWidgets ( closeInput ) {
42584298 // Detect if label is changed, then reinit widget windows
42594299 // if they are open.
4260- const thisBlock = this . blocks . blockList . indexOf ( this ) ;
4300+ const thisBlock = this . blockIndex ;
42614301 const topBlock = this . blocks . findTopBlock ( thisBlock ) ;
42624302 const widgetTitle = document . getElementsByClassName ( "wftTitle" ) ;
42634303 let lockInit = false ;
@@ -4436,7 +4476,7 @@ class Block {
44364476 }
44374477
44384478 if ( isNaN ( this . value ) ) {
4439- const thisBlock = this . blocks . blockList . indexOf ( this ) ;
4479+ const thisBlock = this . blockIndex ;
44404480 this . activity . errorMsg ( newValue + ": " + _ ( "Not a number" ) , thisBlock ) ;
44414481 this . activity . refreshCanvas ( ) ;
44424482 this . value = oldValue ;
@@ -4447,15 +4487,15 @@ class Block {
44474487 this . blocks . blockList [ cblk1 ] . name === "pitch" &&
44484488 ( this . value > 8 || this . value < 1 )
44494489 ) {
4450- const thisBlock = this . blocks . blockList . indexOf ( this ) ;
4490+ const thisBlock = this . blockIndex ;
44514491 this . activity . errorMsg ( _ ( "Octave value must be between 1 and 8." ) , thisBlock ) ;
44524492 this . activity . refreshCanvas ( ) ;
44534493 this . label . value = oldValue ;
44544494 this . value = oldValue ;
44554495 }
44564496
44574497 if ( String ( this . value ) . length > 10 ) {
4458- const thisBlock = this . blocks . blockList . indexOf ( this ) ;
4498+ const thisBlock = this . blockIndex ;
44594499 this . activity . errorMsg ( _ ( "Numbers can have at most 10 digits." ) , thisBlock ) ;
44604500 this . activity . refreshCanvas ( ) ;
44614501 this . label . value = oldValue ;
@@ -4563,10 +4603,7 @@ class Block {
45634603 // Check to see which connection we are using in
45644604 // cblock. We only do something if blk is attached to
45654605 // the name connection (1).
4566- if (
4567- cblock . connections [ 1 ] === this . blocks . blockList . indexOf ( this ) &&
4568- closeInput
4569- ) {
4606+ if ( cblock . connections [ 1 ] === this . blockIndex && closeInput ) {
45704607 // If the label was the name of a storein, update the
45714608 // associated box this.blocks and the palette buttons.
45724609 if ( this . value !== "box" ) {
0 commit comments