From d964f85379d0067145cfeaac855f862c6a462239 Mon Sep 17 00:00:00 2001 From: Roman Rodionov Date: Wed, 13 Mar 2024 19:09:12 +0100 Subject: [PATCH] #4231 - Macro: Maximum call stack size exceeded error appears during snake layout for large chains - replaced recursion with stack implementation --- .../render/renderers/RenderersManager.ts | 112 +++++++------ .../domain/entities/DrawingEntitiesManager.ts | 158 +++++++++++------- 2 files changed, 156 insertions(+), 114 deletions(-) diff --git a/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts b/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts index 29182a5ddc..962c928ee5 100644 --- a/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts +++ b/packages/ketcher-core/src/application/render/renderers/RenderersManager.ts @@ -141,32 +141,39 @@ export class RenderersManager { private recalculatePeptideChainEnumeration( peptideRenderer: PeptideRenderer, - currentEnumeration = 1, + _currentEnumeration = 1, ) { - peptideRenderer.setEnumeration(currentEnumeration); - peptideRenderer.redrawEnumeration(); + let currentEnumeration = _currentEnumeration; + const stack = [{ monomerRenderer: peptideRenderer }]; - const nextMonomer = getNextMonomerInChain(peptideRenderer.monomer); + while (stack.length > 0) { + const stackItem = stack.pop(); + assert(stackItem); + const { monomerRenderer } = stackItem; - if (!(nextMonomer instanceof Peptide)) { - return; - } + monomerRenderer.setEnumeration(currentEnumeration); + monomerRenderer.redrawEnumeration(); - const isR2R1Connection = checkIsR2R1Connection( - peptideRenderer.monomer, - nextMonomer, - ); + const nextMonomer = getNextMonomerInChain(monomerRenderer.monomer); - if (!isR2R1Connection) { - return; - } + if (!(nextMonomer instanceof Peptide)) { + return; + } + + const isR2R1Connection = checkIsR2R1Connection( + monomerRenderer.monomer, + nextMonomer, + ); - assert(nextMonomer.renderer); + if (!isR2R1Connection) { + return; + } + + assert(nextMonomer.renderer); - this.recalculatePeptideChainEnumeration( - nextMonomer.renderer as PeptideRenderer, - currentEnumeration + 1, - ); + stack.push({ monomerRenderer: nextMonomer.renderer as PeptideRenderer }); + currentEnumeration++; + } } private recalculateRnaChainEnumeration( @@ -174,42 +181,49 @@ export class RenderersManager { _currentEnumeration = 1, ) { let currentEnumeration = _currentEnumeration; - if (rnaComponentRenderer instanceof SugarRenderer) { - const rnaBaseMonomer = getRnaBaseFromSugar( - rnaComponentRenderer.monomer as Sugar, - ); - if (rnaBaseMonomer instanceof RNABase) { - rnaBaseMonomer.renderer?.setEnumeration(currentEnumeration); - rnaBaseMonomer.renderer?.redrawEnumeration(); - currentEnumeration++; + const stack = [{ monomerRenderer: rnaComponentRenderer }]; + + while (stack.length > 0) { + const stackItem = stack.pop(); + assert(stackItem); + const { monomerRenderer } = stackItem; + + if (monomerRenderer instanceof SugarRenderer) { + const rnaBaseMonomer = getRnaBaseFromSugar( + monomerRenderer.monomer as Sugar, + ); + if (rnaBaseMonomer instanceof RNABase) { + rnaBaseMonomer.renderer?.setEnumeration(currentEnumeration); + rnaBaseMonomer.renderer?.redrawEnumeration(); + currentEnumeration++; + } } - } - const nextMonomer = getNextMonomerInChain(rnaComponentRenderer.monomer); + const nextMonomer = getNextMonomerInChain(monomerRenderer.monomer); - if ( - !(nextMonomer instanceof Sugar) && - !(nextMonomer instanceof Phosphate) - ) { - return; - } + if ( + !(nextMonomer instanceof Sugar) && + !(nextMonomer instanceof Phosphate) + ) { + return; + } - const isR2R1Connection = checkIsR2R1Connection( - rnaComponentRenderer.monomer, - nextMonomer, - ); + const isR2R1Connection = checkIsR2R1Connection( + monomerRenderer.monomer, + nextMonomer, + ); - if ( - !isR2R1Connection || - !(nextMonomer.renderer instanceof BaseMonomerRenderer) - ) { - return; - } + if ( + !isR2R1Connection || + !(nextMonomer.renderer instanceof BaseMonomerRenderer) + ) { + return; + } - this.recalculateRnaChainEnumeration( - nextMonomer.renderer, - currentEnumeration, - ); + stack.push({ + monomerRenderer: nextMonomer.renderer, + }); + } } private recalculatePeptideEnumeration(peptideRenderer: PeptideRenderer) { diff --git a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts index 529accd88a..5a500a2624 100644 --- a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts +++ b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts @@ -921,16 +921,23 @@ export class DrawingEntitiesManager { command.addOperation(operation); rearrangedMonomersSet.add(monomer.id); - return this.reArrangeNextMonomer( - monomer, - monomerWidth, - lastPosition, - canvasWidth, - rearrangedMonomersSet, - monomersWithSideChain, - maxVerticalDistance, + const nextPositionAndVerticalDistance = + this.getNextMonomerPositionForSnakeLayout( + monomer, + monomerWidth, + lastPosition, + canvasWidth, + rearrangedMonomersSet, + monomersWithSideChain, + maxVerticalDistance, + ); + + return { + lastPosition: nextPositionAndVerticalDistance?.lastPosition, + maxVerticalDistance: nextPositionAndVerticalDistance?.maxVerticalDistance, + nextMonomer: getNextMonomerInChain(monomer), command, - ); + }; } private reArrangeRnaChain( @@ -988,31 +995,39 @@ export class DrawingEntitiesManager { ); rearrangedMonomersSet.add(nucleotide.phosphate?.id); } - const nextMonomer = + const lastMonomerInNucleotide = nucleotide.baseMonomer === nucleotide.sugar && nucleotide.phosphate ? nucleotide.phosphate : nucleotide.sugar; - const nextMonomerResult = this.reArrangeNextMonomer( - nextMonomer, - width, - lastPosition, - canvasWidth, - rearrangedMonomersSet, - monomersWithSideChain, - maxVerticalDistance, - command, - ); - ({ lastPosition, maxVerticalDistance } = nextMonomerResult); + + const nextMonomerPositionAndVerticalDistance = + this.getNextMonomerPositionForSnakeLayout( + lastMonomerInNucleotide, + width, + lastPosition, + canvasWidth, + rearrangedMonomersSet, + monomersWithSideChain, + maxVerticalDistance, + ); this.setRnaBaseSideChainMonomers( nucleotide.rnaBase, rearrangedMonomersSet, monomersWithSideChain, ); - return { command, lastPosition, maxVerticalDistance }; + const nextMonomer = getNextMonomerInChain(lastMonomerInNucleotide); + + return { + command, + lastPosition: nextMonomerPositionAndVerticalDistance?.lastPosition, + maxVerticalDistance: + nextMonomerPositionAndVerticalDistance?.maxVerticalDistance, + nextMonomer, + }; } - private reArrangeNextMonomer( + private getNextMonomerPositionForSnakeLayout( monomer: BaseMonomer, width: number, lastPosition: Vec2, @@ -1020,8 +1035,8 @@ export class DrawingEntitiesManager { rearrangedMonomersSet: Set, monomersWithSideChain: Array, maxVerticalDistance: number, - command: Command, ) { + let nextPositionAndDistance; for (const attachmentPointName in monomer.attachmentPointsToBonds) { const polymerBond = monomer.attachmentPointsToBonds[attachmentPointName]; const nextMonomer = polymerBond?.getAnotherMonomer(monomer); @@ -1034,28 +1049,17 @@ export class DrawingEntitiesManager { (attachmentPointName === 'R1' && nextMonomer.getAttachmentPointByBond(polymerBond) === 'R2') ) { - ({ lastPosition, maxVerticalDistance } = - this.getNextPositionAndDistance( - lastPosition, - width, - maxVerticalDistance, - canvasWidth, - )); - const rearrangeResult = this.reArrangeChainInRecursive( - nextMonomer, + nextPositionAndDistance = this.getNextPositionAndDistance( lastPosition, - canvasWidth, - rearrangedMonomersSet, - monomersWithSideChain, + width, maxVerticalDistance, + canvasWidth, ); - ({ lastPosition, maxVerticalDistance } = rearrangeResult); - command.merge(rearrangeResult.command); } else { monomersWithSideChain.push(nextMonomer); } } - return { command, lastPosition, maxVerticalDistance }; + return nextPositionAndDistance; } private setRnaBaseSideChainMonomers( @@ -1210,47 +1214,71 @@ export class DrawingEntitiesManager { } public reArrangeChainInRecursive( - monomer: BaseMonomer, - lastPosition: Vec2, + _monomer: BaseMonomer, + _lastPosition: Vec2, canvasWidth: number, rearrangedMonomersSet: Set, monomersWithSideChain: Array, - maxVerticalDistance: number, + _maxVerticalDistance: number, ) { const command = new Command(); - if (monomer instanceof Sugar || monomer instanceof Phosphate) { - const nucleotideOrNucleoside = - getNucleotideOrNucleoSideFromFirstMonomer(monomer); - if (nucleotideOrNucleoside) { - const rearrangeRnaResult = this.reArrangeRnaChain( - nucleotideOrNucleoside, + const stack = [ + { + monomer: _monomer, + lastPosition: _lastPosition, + maxVerticalDistance: _maxVerticalDistance, + }, + ]; + let lastRearrangeResult; + while (stack.length > 0) { + const stackItem = stack.pop(); + assert(stackItem); + const { lastPosition, maxVerticalDistance, monomer } = stackItem; + let rearrangeResult; + + if (monomer instanceof Sugar || monomer instanceof Phosphate) { + const nucleotideOrNucleoside = + getNucleotideOrNucleoSideFromFirstMonomer(monomer); + if (nucleotideOrNucleoside) { + rearrangeResult = this.reArrangeRnaChain( + nucleotideOrNucleoside, + lastPosition, + canvasWidth, + rearrangedMonomersSet, + monomersWithSideChain, + maxVerticalDistance, + ); + } + } else { + rearrangeResult = this.reArrangeChain( + monomer, lastPosition, canvasWidth, rearrangedMonomersSet, monomersWithSideChain, maxVerticalDistance, ); - command.merge(rearrangeRnaResult.command); - return { - command, - lastPosition: rearrangeRnaResult.lastPosition, - maxVerticalDistance: rearrangeRnaResult.maxVerticalDistance, - }; + } + + command.merge(rearrangeResult.command); + + if (rearrangeResult?.lastPosition) { + lastRearrangeResult = rearrangeResult; + } + + if (rearrangeResult?.nextMonomer) { + stack.push({ + monomer: rearrangeResult.nextMonomer, + lastPosition: rearrangeResult.lastPosition, + maxVerticalDistance: rearrangeResult.maxVerticalDistance, + }); } } - const rearrangeResult = this.reArrangeChain( - monomer, - lastPosition, - canvasWidth, - rearrangedMonomersSet, - monomersWithSideChain, - maxVerticalDistance, - ); - command.merge(rearrangeResult.command); + return { command, - lastPosition: rearrangeResult.lastPosition, - maxVerticalDistance: rearrangeResult.maxVerticalDistance, + lastPosition: lastRearrangeResult.lastPosition, + maxVerticalDistance: lastRearrangeResult.maxVerticalDistance, }; }