diff --git a/public/assets/glApp.js b/public/assets/glApp.js index c5772ee72..663542fae 100644 --- a/public/assets/glApp.js +++ b/public/assets/glApp.js @@ -3842,7 +3842,7 @@ uniform vec2 u_blueNoiseCoordOffset; vec3 getBlueNoise (vec2 coord) { return texture2D(u_blueNoiseTexture, coord * u_blueNoiseTexelSize + u_blueNoiseCoordOffset).rgb; -}`;class gm{constructor(){N(this,"sharedUniforms",{u_blueNoiseTexture:{value:null},u_blueNoiseTexelSize:{value:null},u_blueNoiseCoordOffset:{value:new De}});N(this,"TEXTURE_SIZE",128)}preInit(){Sn.loadTexture(at.ASSETS_PATH+"textures/LDR_RGB1_0.png",e=>{e.generateMipmaps=!1,e.minFilter=e.magFilter=Rt,e.wrapS=e.wrapT=Or,e.needsUpdate=!0,this.sharedUniforms.u_blueNoiseTexture.value=e,this.sharedUniforms.u_blueNoiseTexelSize.value=new De(1/this.TEXTURE_SIZE,1/this.TEXTURE_SIZE)}),Fe.getBlueNoise=_m}update(e){this.sharedUniforms.u_blueNoiseCoordOffset.value.set(Math.random(),Math.random())}}const Pi=new gm;class vm{constructor(){N(this,"PI",Math.PI)}clamp(e,t,n){return en?n:e}mix(e,t,n){return e+(t-e)*n}cUnMix(e,t,n){return this.clamp((n-e)/(t-e),0,1)}saturate(e){return this.clamp(e,0,1)}fit(e,t,n,r,s,o){return e=this.cUnMix(t,n,e),o&&(e=o(e)),r+e*(s-r)}}const Ie=new vm;class xm{quartInOut(e){return(e*=2)<1?.5*e*e*e*e:-.5*((e-=2)*e*e*e-2)}sineOut(e){return Math.sin(e*Math.PI/2)}backIn(e){let t=1.70158;return e*e*((t+1)*e-t)}backOut(e,t=1.70158){return--e*e*((t+1)*e+t)+1}backInOut(e){let t=2.5949095;return(e*=2)<1?.5*e*e*((t+1)*e-t):.5*((e-=2)*e*((t+1)*e+t)+2)}}function Sm(i,e,t,n,r){if(i===0)return 0;if(i===1)return 1;function s(l,c,u,f,p){const m=3*(u-c),_=3*(f-u)-m;return(((p-c-m-_)*l+_)*l+m)*l+c}function o(l,c,u,f=1e-6){let p=0,m=1,_=l;for(;p{var l;return(a==null?void 0:a.id)===((l=O.errorBlock)==null?void 0:l.id)});o&&(o.isErrorBlock=!1,Dn.resetBlockFromLogicBlock(o)),O.errorBlock=null}this.statusUpdateQueue.push(()=>t?this._updateStatusAndResult(e,t,n):this._updateStatus(e))}reset(){this._queueStatusUpdate(nt.NOT_STARTED,ot.NONE)}setStart(){this._queueStatusUpdate(nt.STARTED)}setFree(){this._queueStatusUpdate(nt.FREE)}setPause(){O.isPaused=!0}setResume(){O.isPaused=!1}setStop(){this._queueStatusUpdate(nt.RESULT,ot.STOP)}setComplete(){this._queueStatusUpdate(nt.RESULT,ot.COMPLETED,Kn.LEVEL_1)}setComplete2(){this._queueStatusUpdate(nt.RESULT,ot.COMPLETED,Kn.LEVEL_2)}setComplete3(){this._queueStatusUpdate(nt.RESULT,ot.COMPLETED,Kn.LEVEL_3)}setFail(){this._queueStatusUpdate(nt.RESULT,ot.FAILED)}setResultAnimation(){this._queueStatusUpdate(nt.RESULT_ANIMATION)}setRestartAnimation(){this._queueStatusUpdate(nt.RESTART_ANIMATION)}setRestart(){this.statusUpdateQueue.push(()=>{this._updateStatus(nt.RESTART)&&this.gameEndedSignal.dispatch()})}}const Le=new Mm;class el{constructor(e,t=!1){N(this,"id",-1);N(this,"isMoving",!1);N(this,"hasBeenSpawned",!1);N(this,"hasAnimationEnded",!1);N(this,"hasBeenEvaluated",!1);N(this,"currentTile",null);N(this,"targetTile",null);N(this,"moveAnimationRatio",0);N(this,"spawnAnimationRatio",0);N(this,"spawnAnimationRatioUnclamped",-Math.random());N(this,"easedAnimationRatio",0);N(this,"randomVector",{x:Math.random()-.5,y:Math.random()-.5});N(this,"lifeCycle",0);N(this,"easingFunction",null);N(this,"errorLifeCycle",0);N(this,"isErrorBlock",!1);N(this,"errorPreFallAnimationTime",0);N(this,"errorPreFallAnimationTimeScale",0);N(this,"errorFallAnimationTime",0);this.id=e,this.init(),t&&(this.isErrorBlock=!1,O.errorBlock=null)}init(){this._setNewEasingFunction()}_setNewEasingFunction(){const e=Math.random(),t=.25;this.easingFunction=n=>vi(Ie.fit(n,e*t,e*t+(1-t),0,1))}updateTile(){this.currentTile&&(this.currentTile.isOccupied=!0,this.currentTile.willBeOccupied=!1)}_findBestTile(e,t){return e.find(n=>n.isOccupied||n.willBeOccupied||n.isMain?!1:t||this.currentTile.priority>=n.priority)}moveToNextTile(e=!1,t=0){if(this.hasBeenEvaluated=!0,this.moveAnimationRatio=-t*(this.isErrorBlock?0:1),!this.currentTile)return;if(this.isErrorBlock){this.isMoving=!0,this.targetTile=this.currentTile;return}this.currentTile.shuffleReachableNeighbours();const n=e?this.currentTile.reachableNeighbours:this.currentTile.prioritySortedReachableNeighbours,r=this._findBestTile(n,e);r&&(!this.currentTile.isMain||Math.random()<=.8)?(this.targetTile=r,this.targetTile.willBeOccupied=!0,this.isMoving=!0):this.hasAnimationEnded=!0}resetAfterCycle(){var o;this.hasBeenEvaluated=!1,this.hasAnimationEnded=!1,this.moveAnimationRatio=0,this.easedAnimationRatio=0,this.isMoving=!1,this.lifeCycle++,this.isErrorBlock&&this.errorLifeCycle++;const e=(o=this.currentTile)==null?void 0:o.isBorder,t=!O.errorBlock,n=O.activeBlocksCount>=O.minSpawnedBlocksForTheErrorBlock,r=Math.random()<.5;Le.isFree&&r&&n&&e&&t&&(this.isErrorBlock=!0,O.errorBlock=this),this._setNewEasingFunction(),this.updateTile()}reset(e=!1){var t;this.isErrorBlock&&(this.errorLifeCycle=0,this.isErrorBlock=!1,this.currentTile.reset(),(t=this.targetTile)==null||t.reset(),this.errorFallAnimationTime=0),this.id=e?this.id:-1,this.isMoving=!1,this.hasBeenSpawned=!1,this.hasAnimationEnded=!1,this.hasBeenEvaluated=!1,this.currentTile=null,this.targetTile=null,this.moveAnimationRatio=0,this.spawnAnimationRatio=0,this.spawnAnimationRatioUnclamped=-Math.random(),this.easedAnimationRatio=0,this.lifeCycle=0,this.errorPreFallAnimationTime=0,this.errorPreFallAnimationTimeScale=0,this.errorFallAnimationTime=0}_onMovementEnd(){this.moveAnimationRatio=1,this.currentTile&&(this.currentTile.isOccupied=!1),this.currentTile=this.targetTile,this.targetTile=null,this.hasAnimationEnded=!0,this.updateTile()}_updateSpawnAnimation(e){this.spawnAnimationRatioUnclamped+=.75*O.animationSpeed*e,this.spawnAnimationRatio=Math.max(0,Math.min(1,this.spawnAnimationRatioUnclamped)),this.spawnAnimationRatio===1&&(this.hasBeenSpawned=!0)}_updateMovement(e){(this.isMoving&&!this.hasAnimationEnded||Le.isResultAnimation)&&(this.moveAnimationRatio=Math.min(1,this.moveAnimationRatio+O.animationSpeed*e*(this.isErrorBlock?.7:1)),this.easedAnimationRatio=this.easingFunction(Math.max(0,this.moveAnimationRatio)),this.easedAnimationRatio===1&&(Le.isFree||Le.isResult)&&this._onMovementEnd())}_updateTileRatios(){const e=Math.max(0,Math.min(1,this.hasBeenSpawned?this.easedAnimationRatio:this.spawnAnimationRatio));this.currentTile&&(this.currentTile.activeRatio=this.hasBeenSpawned?this.targetTile?1-e:1:this.spawnAnimationRatio),this.targetTile&&(this.targetTile.activeRatio=e),this.isErrorBlock&&this.errorLifeCycle>=O.errorBlockMaxLifeCycle-1&&(this.currentTile.activeRatio=0,this.targetTile&&(this.targetTile.activeRatio=0))}update(e){this.hasBeenSpawned?this._updateMovement(e):this._updateSpawnAnimation(e),this.errorLifeCycle>=O.errorBlockMaxLifeCycle-1&&(this.errorFallAnimationTime=this.errorFallAnimationTime+3*O.animationSpeed*e),this.isErrorBlock&&(this.errorPreFallAnimationTimeScale=this.errorPreFallAnimationTimeScale+3*e,this.errorPreFallAnimationTimeScale=Math.min(20,this.errorPreFallAnimationTimeScale),this.errorPreFallAnimationTime=this.errorPreFallAnimationTime+this.errorPreFallAnimationTimeScale*e),this._updateTileRatios()}}class Em{constructor(){N(this,"currentAnimationStyle",null);N(this,"ratio",0);N(this,"duration",8);N(this,"towerRotationRatio",0);N(this,"floatingCoinsRatio",0);N(this,"floatingCubesRatio",0);N(this,"vortexCoinsRatio",0);N(this,"pushDownRatio",0);N(this,"successColorTowerRatio",0);N(this,"floatingCubesDisplacement",1);N(this,"completeAnimationEndedSignal",new xn)}init(){Le.stateSignal.add((e,t,n)=>{e===nt.RESULT&&t===ot.COMPLETED&&this.triggerNewAnimation(n)})}triggerNewAnimation(e){this.currentAnimationStyle=e}resetRatios(){this.ratio=0,this.towerRotationRatio=0,this.floatingCoinsRatio=0,this.floatingCubesRatio=0,this.vortexCoinsRatio=0,this.pushDownRatio=0,this.successColorTowerRatio=0,this.floatingCubesDisplacement=1,this.currentAnimationStyle=null}updateRatios1(){this.floatingCubesDisplacement=1,this.towerRotationRatio=0,this.floatingCoinsRatio=0,this.floatingCubesRatio=Ie.fit(this.ratio,.2,.49,0,1.2),this.pushDownRatio=Ie.fit(this.ratio,.45,.55,0,1),this.successColorTowerRatio=Ie.fit(this.ratio,.45,.7,0,1),this.vortexCoinsRatio=Ie.fit(this.ratio,.55,1,0,1)}updateRatios2(){this.floatingCubesDisplacement=1.5,this.floatingCoinsRatio=0,this.towerRotationRatio=Ie.fit(this.ratio,.1,.45,0,1),this.floatingCubesRatio=Ie.fit(this.ratio,.15,.49,0,1.2),this.pushDownRatio=Ie.fit(this.ratio,.45,.55,0,1),this.successColorTowerRatio=Ie.fit(this.ratio,.45,.7,0,1),this.vortexCoinsRatio=Ie.fit(this.ratio,.55,1,0,1)}updateRatios3(){this.floatingCubesDisplacement=2,this.towerRotationRatio=Ie.fit(this.ratio,.1,.5,0,1),this.floatingCoinsRatio=Ie.fit(this.ratio,.2,.51,0,1),this.floatingCubesRatio=Ie.fit(this.ratio,.2,.49,0,1.2),this.pushDownRatio=Ie.fit(this.ratio,.45,.55,0,1),this.successColorTowerRatio=Ie.fit(this.ratio,.45,.7,0,1),this.vortexCoinsRatio=Ie.fit(this.ratio,.6,1,0,1)}update(e){switch(this.ratio+=(this.currentAnimationStyle?1:0)*e/this.duration,this.ratio=Ie.clamp(this.ratio,0,1),this.currentAnimationStyle){case Kn.LEVEL_1:this.updateRatios1();break;case Kn.LEVEL_2:this.updateRatios2();break;case Kn.LEVEL_3:this.updateRatios3();break}this.ratio===1&&(this.completeAnimationEndedSignal.dispatch(),this.resetRatios())}}const Dt=new Em;class ym{constructor(){N(this,"isActive",!1);N(this,"ratio",0);N(this,"duration",4);N(this,"spawnRatio",0);N(this,"pushDownRatio",0);N(this,"stopAnimationEndedSignal",new xn)}init(){Le.stateSignal.add((e,t,n)=>{e===nt.RESULT&&t===ot.STOP&&(this.isActive=!0)})}resetRatios(){this.ratio=0,this.pushDownRatio=0,this.spawnRatio=0,this.isActive=!1}update(e){this.ratio+=(this.isActive?1:0)*e/this.duration,this.ratio=Ie.clamp(this.ratio,0,1),this.spawnRatio=Ie.fit(this.ratio,0,.25,0,2.5),this.pushDownRatio=Ie.fit(this.ratio,.45,.55,0,1),this.ratio===1&&(this.stopAnimationEndedSignal.dispatch(),this.resetRatios())}}const mn=new ym;class Tm{constructor(){N(this,"isActive",!1);N(this,"ratio",0);N(this,"duration",4);N(this,"shakeRatio",0);N(this,"floatingCubesRatio",0);N(this,"spawnRatio",0);N(this,"pushDownRatio",0);N(this,"errorAnimationEndedSignal",new xn)}init(){Le.stateSignal.add((e,t,n)=>{e===nt.RESULT&&t===ot.FAILED&&(this.isActive=!0)})}resetRatios(){this.ratio=0,this.shakeRatio=0,this.floatingCubesRatio=0,this.pushDownRatio=0,this.spawnRatio=0,this.isActive=!1}update(e){this.ratio+=(this.isActive?1:0)*e/this.duration,this.ratio=Ie.clamp(this.ratio,0,1),this.shakeRatio=Ie.fit(this.ratio,0,.3,0,1),this.floatingCubesRatio=Ie.fit(this.ratio,.35,.65,0,1),this.spawnRatio=Ie.fit(this.ratio,.3,.55,0,2.5),this.pushDownRatio=Ie.fit(this.ratio,.6,.8,0,1),this.ratio===1&&(this.errorAnimationEndedSignal.dispatch(),this.resetRatios())}}const St=new Tm;class Am{constructor(){N(this,"blocks",[]);N(this,"lastSpawnedBlock",null);N(this,"cycleIndex",0);N(this,"animationSpeedRatio",0);N(this,"firstStartAnimationRatio",0);N(this,"previousSuccessBlocksAnimationRatio",0);N(this,"isEndAnimationActive",!1);N(this,"wasSuccess",!1)}init(){Le.init(),Dt.init(),mn.init(),St.init(),Jt.init(),Dt.completeAnimationEndedSignal.add(()=>{Le.setRestart(),this._startNewCycle()}),mn.stopAnimationEndedSignal.add(()=>{Le.setRestart(),this._startNewCycle()}),St.errorAnimationEndedSignal.add(()=>{Le.setRestart(),this._startNewCycle()})}_spawnBlock(){this._shouldPreventSpawn()||(Le.isSuccessResult?this._spawnMultipleBlocks():this._spawnSingleBlock(),!(this.blocks.length===O.maxFreeBlocksCount&&Le.isFree)&&Le.spawnSignal.dispatch())}_shouldPreventSpawn(){return Le.isFailResult||Le.isStopped||this.blocks.length>=$t||Jt.mainTile.isOccupied&&!Le.isSuccessResult}_spawnMultipleBlocks(){let e=$t-O.activeBlocksCount;O.errorBlock&&(O.errorBlock.currentTile.isOccupied=!1,e+=1);for(let t=0;t=O.errorBlockMaxLifeCycle),n=!!(this.blocks.lengthe.resetAfterCycle()),Le.endCycleSignal.dispatch(),this.cycleIndex++,this._spawnBlock(),this._calculatePaths()))}_calculatePaths(){var t;(t=this.lastSpawnedBlock)!=null&&t.hasBeenSpawned&&this.lastSpawnedBlock.moveToNextTile(Le.isFree,0);const e=this.cycleIndex%2===0?!0:O.activeBlocksCount{!n.hasBeenEvaluated&&n.hasBeenSpawned&&n.moveToNextTile(e,r*.2)})}reset(){this.blocks.forEach(t=>t.reset()),Dn.reset(),Jt.reset(),this.blocks=[],this.lastSpawnedBlock=null,this.cycleIndex=0,this.animationSpeedRatio=0,this.previousSuccessBlocksAnimationRatio=this.wasSuccess?1:0,this.isEndAnimationActive=!1;const e=at.AUTO_RESTART&&[ot.FAILED,ot.COMPLETED].includes(Le.result);Le.reset(),this._startNewCycle(),e&&(Le.setStart(),Le.updateFlags())}_updateAnimationRatios(e){const{isResult:t}=Le;this.firstStartAnimationRatio=Ie.saturate(this.firstStartAnimationRatio+e*(O.showVisual?1:0)),this.animationSpeedRatio=Math.min(1,this.animationSpeedRatio+e*(t?1:0)),this.previousSuccessBlocksAnimationRatio=Ie.saturate(this.previousSuccessBlocksAnimationRatio-.25*e)}_checkCycleCompletion(){let e=!0;return this.lastSpawnedBlock&&(e=e&&this.lastSpawnedBlock.hasBeenSpawned),this.blocks.forEach(t=>{t.lifeCycle>0?e=e&&t.hasBeenEvaluated&&t.hasAnimationEnded:e=e&&t.spawnAnimationRatio===1}),e||Le.isResultAnimation||Le.isFailResult||Le.isStopped}update(e){if(this._updateAnimationRatios(e),Dt.update(e),mn.update(e),St.update(e),Le.hasNotStarted){this._startNewCycle();return}if(Le.isRestart){this.wasSuccess=Le.result===ot.COMPLETED,this.reset();return}Le.isResultAnimation&&Le.setRestartAnimation(),Jt.preUpdate(e),this.lastSpawnedBlock&&this.lastSpawnedBlock.update(e),this.blocks.forEach(n=>n.update(e)),Jt.update(e),this._checkCycleCompletion()&&this._startNewCycle()}}const Mt=new Am,bm=Math.PI/2,_i=new U;class wm{constructor(){this.animation=0,this.boardDir=new De,this.boardPos=new De,this.pos=new U,this.orient=new Yt,this.showRatio=0,this.spinPivot=new U,this.spinOrient=new Yt,this.spinOrient2=new Yt}reset(){this.animation=0,this.boardDir.set(0,0),this.boardPos.set(0,0),this.pos.set(0,0,0),this.orient.identity(),this.showRatio=0,this.spinPivot.set(0,0,0),this.spinOrient.identity()}update(e){this.pos.set(this.boardPos.x,0,-this.boardPos.y),this.spinPivot.set(this.boardDir.x*.5,-.5,-this.boardDir.y*.5),_i.set(-this.boardDir.y,0,-this.boardDir.x),this.spinOrient.setFromAxisAngle(_i,this.animation*bm)}addsFallAnimation(e){_i.set(this.boardDir.x,-e,-this.boardDir.y),this.pos.addScaledVector(_i,e),_i.set(this.boardDir.x*.5,0,-this.boardDir.y*.5),this.spinPivot.lerp(_i,Ie.saturate(e))}}var Tr=`#ifndef IS_BASE +}`;class gm{constructor(){N(this,"sharedUniforms",{u_blueNoiseTexture:{value:null},u_blueNoiseTexelSize:{value:null},u_blueNoiseCoordOffset:{value:new De}});N(this,"TEXTURE_SIZE",128)}preInit(){Sn.loadTexture(at.ASSETS_PATH+"textures/LDR_RGB1_0.png",e=>{e.generateMipmaps=!1,e.minFilter=e.magFilter=Rt,e.wrapS=e.wrapT=Or,e.needsUpdate=!0,this.sharedUniforms.u_blueNoiseTexture.value=e,this.sharedUniforms.u_blueNoiseTexelSize.value=new De(1/this.TEXTURE_SIZE,1/this.TEXTURE_SIZE)}),Fe.getBlueNoise=_m}update(e){this.sharedUniforms.u_blueNoiseCoordOffset.value.set(Math.random(),Math.random())}}const Pi=new gm;class vm{constructor(){N(this,"PI",Math.PI)}clamp(e,t,n){return en?n:e}mix(e,t,n){return e+(t-e)*n}cUnMix(e,t,n){return this.clamp((n-e)/(t-e),0,1)}saturate(e){return this.clamp(e,0,1)}fit(e,t,n,r,s,o){return e=this.cUnMix(t,n,e),o&&(e=o(e)),r+e*(s-r)}}const Ie=new vm;class xm{quartInOut(e){return(e*=2)<1?.5*e*e*e*e:-.5*((e-=2)*e*e*e-2)}sineOut(e){return Math.sin(e*Math.PI/2)}backIn(e){let t=1.70158;return e*e*((t+1)*e-t)}backOut(e,t=1.70158){return--e*e*((t+1)*e+t)+1}backInOut(e){let t=2.5949095;return(e*=2)<1?.5*e*e*((t+1)*e-t):.5*((e-=2)*e*((t+1)*e+t)+2)}}function Sm(i,e,t,n,r){if(i===0)return 0;if(i===1)return 1;function s(l,c,u,f,p){const m=3*(u-c),_=3*(f-u)-m;return(((p-c-m-_)*l+_)*l+m)*l+c}function o(l,c,u,f=1e-6){let p=0,m=1,_=l;for(;p{var l;return(a==null?void 0:a.id)===((l=O.errorBlock)==null?void 0:l.id)});o&&(o.isErrorBlock=!1,Dn.resetBlockFromLogicBlock(o)),O.errorBlock=null}this.statusUpdateQueue.push(()=>t?this._updateStatusAndResult(e,t,n):this._updateStatus(e))}reset(){this._queueStatusUpdate(nt.NOT_STARTED,ot.NONE)}setStart(){this._queueStatusUpdate(nt.STARTED)}setFree(){this._queueStatusUpdate(nt.FREE)}setPause(){O.isPaused=!0}setResume(){O.isPaused=!1}setStop(){this._queueStatusUpdate(nt.RESULT,ot.STOP)}setComplete(){this._queueStatusUpdate(nt.RESULT,ot.COMPLETED,Kn.LEVEL_1)}setComplete2(){this._queueStatusUpdate(nt.RESULT,ot.COMPLETED,Kn.LEVEL_2)}setComplete3(){this._queueStatusUpdate(nt.RESULT,ot.COMPLETED,Kn.LEVEL_3)}setFail(){this._queueStatusUpdate(nt.RESULT,ot.FAILED)}setResultAnimation(){this._queueStatusUpdate(nt.RESULT_ANIMATION)}setRestartAnimation(){this._queueStatusUpdate(nt.RESTART_ANIMATION)}setRestart(){this.statusUpdateQueue.push(()=>{this._updateStatus(nt.RESTART)&&this.gameEndedSignal.dispatch()})}}const Le=new Mm;class el{constructor(e,t=!1){N(this,"id",-1);N(this,"isMoving",!1);N(this,"hasBeenSpawned",!1);N(this,"hasAnimationEnded",!1);N(this,"hasBeenEvaluated",!1);N(this,"currentTile",null);N(this,"targetTile",null);N(this,"moveAnimationRatio",0);N(this,"spawnAnimationRatio",0);N(this,"spawnAnimationRatioUnclamped",-Math.random());N(this,"easedAnimationRatio",0);N(this,"randomVector",{x:Math.random()-.5,y:Math.random()-.5});N(this,"lifeCycle",0);N(this,"easingFunction",null);N(this,"errorLifeCycle",0);N(this,"isErrorBlock",!1);N(this,"errorPreFallAnimationTime",0);N(this,"errorPreFallAnimationTimeScale",0);N(this,"errorFallAnimationTime",0);this.id=e,this.init(),t&&(this.isErrorBlock=!1,O.errorBlock=null)}init(){this._setNewEasingFunction()}_setNewEasingFunction(){const e=Math.random(),t=.25;this.easingFunction=n=>vi(Ie.fit(n,e*t,e*t+(1-t),0,1))}updateTile(){this.currentTile&&(this.currentTile.isOccupied=!0,this.currentTile.willBeOccupied=!1)}_findBestTile(e,t){return e.find(n=>n.isOccupied||n.willBeOccupied||n.isMain?!1:t||this.currentTile.priority>=n.priority)}moveToNextTile(e=!1,t=0){if(this.hasBeenEvaluated=!0,this.moveAnimationRatio=-t*(this.isErrorBlock?0:1),!this.currentTile)return;if(this.isErrorBlock){this.isMoving=!0,this.targetTile=this.currentTile;return}this.currentTile.shuffleReachableNeighbours();const n=e?this.currentTile.reachableNeighbours:this.currentTile.prioritySortedReachableNeighbours,r=this._findBestTile(n,e);r&&(!this.currentTile.isMain||Math.random()<=.8)?(this.targetTile=r,this.targetTile.willBeOccupied=!0,this.isMoving=!0):this.hasAnimationEnded=!0}resetAfterCycle(){var a;this.hasBeenEvaluated=!1,this.hasAnimationEnded=!1,this.moveAnimationRatio=0,this.easedAnimationRatio=0,this.isMoving=!1,this.lifeCycle++,this.isErrorBlock&&this.errorLifeCycle++;const e=(a=this.currentTile)==null?void 0:a.isBorder,t=!O.errorBlock,n=O.activeBlocksCount>=O.minSpawnedBlocksForTheErrorBlock,s=window.crypto.getRandomValues(new Uint32Array(1))[0]*Math.pow(2,-32)<.5;Le.isFree&&s&&n&&e&&t&&(this.isErrorBlock=!0,O.errorBlock=this),this._setNewEasingFunction(),this.updateTile()}reset(e=!1){var t;this.isErrorBlock&&(this.errorLifeCycle=0,this.isErrorBlock=!1,this.currentTile.reset(),(t=this.targetTile)==null||t.reset(),this.errorFallAnimationTime=0),this.id=e?this.id:-1,this.isMoving=!1,this.hasBeenSpawned=!1,this.hasAnimationEnded=!1,this.hasBeenEvaluated=!1,this.currentTile=null,this.targetTile=null,this.moveAnimationRatio=0,this.spawnAnimationRatio=0,this.spawnAnimationRatioUnclamped=-Math.random(),this.easedAnimationRatio=0,this.lifeCycle=0,this.errorPreFallAnimationTime=0,this.errorPreFallAnimationTimeScale=0,this.errorFallAnimationTime=0}_onMovementEnd(){this.moveAnimationRatio=1,this.currentTile&&(this.currentTile.isOccupied=!1),this.currentTile=this.targetTile,this.targetTile=null,this.hasAnimationEnded=!0,this.updateTile()}_updateSpawnAnimation(e){this.spawnAnimationRatioUnclamped+=.75*O.animationSpeed*e,this.spawnAnimationRatio=Math.max(0,Math.min(1,this.spawnAnimationRatioUnclamped)),this.spawnAnimationRatio===1&&(this.hasBeenSpawned=!0)}_updateMovement(e){(this.isMoving&&!this.hasAnimationEnded||Le.isResultAnimation)&&(this.moveAnimationRatio=Math.min(1,this.moveAnimationRatio+O.animationSpeed*e*(this.isErrorBlock?.7:1)),this.easedAnimationRatio=this.easingFunction(Math.max(0,this.moveAnimationRatio)),this.easedAnimationRatio===1&&(Le.isFree||Le.isResult)&&this._onMovementEnd())}_updateTileRatios(){const e=Math.max(0,Math.min(1,this.hasBeenSpawned?this.easedAnimationRatio:this.spawnAnimationRatio));this.currentTile&&(this.currentTile.activeRatio=this.hasBeenSpawned?this.targetTile?1-e:1:this.spawnAnimationRatio),this.targetTile&&(this.targetTile.activeRatio=e),this.isErrorBlock&&this.errorLifeCycle>=O.errorBlockMaxLifeCycle-1&&(this.currentTile.activeRatio=0,this.targetTile&&(this.targetTile.activeRatio=0))}update(e){this.hasBeenSpawned?this._updateMovement(e):this._updateSpawnAnimation(e),this.errorLifeCycle>=O.errorBlockMaxLifeCycle-1&&(this.errorFallAnimationTime=this.errorFallAnimationTime+3*O.animationSpeed*e),this.isErrorBlock&&(this.errorPreFallAnimationTimeScale=this.errorPreFallAnimationTimeScale+3*e,this.errorPreFallAnimationTimeScale=Math.min(20,this.errorPreFallAnimationTimeScale),this.errorPreFallAnimationTime=this.errorPreFallAnimationTime+this.errorPreFallAnimationTimeScale*e),this._updateTileRatios()}}class Em{constructor(){N(this,"currentAnimationStyle",null);N(this,"ratio",0);N(this,"duration",8);N(this,"towerRotationRatio",0);N(this,"floatingCoinsRatio",0);N(this,"floatingCubesRatio",0);N(this,"vortexCoinsRatio",0);N(this,"pushDownRatio",0);N(this,"successColorTowerRatio",0);N(this,"floatingCubesDisplacement",1);N(this,"completeAnimationEndedSignal",new xn)}init(){Le.stateSignal.add((e,t,n)=>{e===nt.RESULT&&t===ot.COMPLETED&&this.triggerNewAnimation(n)})}triggerNewAnimation(e){this.currentAnimationStyle=e}resetRatios(){this.ratio=0,this.towerRotationRatio=0,this.floatingCoinsRatio=0,this.floatingCubesRatio=0,this.vortexCoinsRatio=0,this.pushDownRatio=0,this.successColorTowerRatio=0,this.floatingCubesDisplacement=1,this.currentAnimationStyle=null}updateRatios1(){this.floatingCubesDisplacement=1,this.towerRotationRatio=0,this.floatingCoinsRatio=0,this.floatingCubesRatio=Ie.fit(this.ratio,.2,.49,0,1.2),this.pushDownRatio=Ie.fit(this.ratio,.45,.55,0,1),this.successColorTowerRatio=Ie.fit(this.ratio,.45,.7,0,1),this.vortexCoinsRatio=Ie.fit(this.ratio,.55,1,0,1)}updateRatios2(){this.floatingCubesDisplacement=1.5,this.floatingCoinsRatio=0,this.towerRotationRatio=Ie.fit(this.ratio,.1,.45,0,1),this.floatingCubesRatio=Ie.fit(this.ratio,.15,.49,0,1.2),this.pushDownRatio=Ie.fit(this.ratio,.45,.55,0,1),this.successColorTowerRatio=Ie.fit(this.ratio,.45,.7,0,1),this.vortexCoinsRatio=Ie.fit(this.ratio,.55,1,0,1)}updateRatios3(){this.floatingCubesDisplacement=2,this.towerRotationRatio=Ie.fit(this.ratio,.1,.5,0,1),this.floatingCoinsRatio=Ie.fit(this.ratio,.2,.51,0,1),this.floatingCubesRatio=Ie.fit(this.ratio,.2,.49,0,1.2),this.pushDownRatio=Ie.fit(this.ratio,.45,.55,0,1),this.successColorTowerRatio=Ie.fit(this.ratio,.45,.7,0,1),this.vortexCoinsRatio=Ie.fit(this.ratio,.6,1,0,1)}update(e){switch(this.ratio+=(this.currentAnimationStyle?1:0)*e/this.duration,this.ratio=Ie.clamp(this.ratio,0,1),this.currentAnimationStyle){case Kn.LEVEL_1:this.updateRatios1();break;case Kn.LEVEL_2:this.updateRatios2();break;case Kn.LEVEL_3:this.updateRatios3();break}this.ratio===1&&(this.completeAnimationEndedSignal.dispatch(),this.resetRatios())}}const Dt=new Em;class ym{constructor(){N(this,"isActive",!1);N(this,"ratio",0);N(this,"duration",4);N(this,"spawnRatio",0);N(this,"pushDownRatio",0);N(this,"stopAnimationEndedSignal",new xn)}init(){Le.stateSignal.add((e,t,n)=>{e===nt.RESULT&&t===ot.STOP&&(this.isActive=!0)})}resetRatios(){this.ratio=0,this.pushDownRatio=0,this.spawnRatio=0,this.isActive=!1}update(e){this.ratio+=(this.isActive?1:0)*e/this.duration,this.ratio=Ie.clamp(this.ratio,0,1),this.spawnRatio=Ie.fit(this.ratio,0,.25,0,2.5),this.pushDownRatio=Ie.fit(this.ratio,.45,.55,0,1),this.ratio===1&&(this.stopAnimationEndedSignal.dispatch(),this.resetRatios())}}const mn=new ym;class Tm{constructor(){N(this,"isActive",!1);N(this,"ratio",0);N(this,"duration",4);N(this,"shakeRatio",0);N(this,"floatingCubesRatio",0);N(this,"spawnRatio",0);N(this,"pushDownRatio",0);N(this,"errorAnimationEndedSignal",new xn)}init(){Le.stateSignal.add((e,t,n)=>{e===nt.RESULT&&t===ot.FAILED&&(this.isActive=!0)})}resetRatios(){this.ratio=0,this.shakeRatio=0,this.floatingCubesRatio=0,this.pushDownRatio=0,this.spawnRatio=0,this.isActive=!1}update(e){this.ratio+=(this.isActive?1:0)*e/this.duration,this.ratio=Ie.clamp(this.ratio,0,1),this.shakeRatio=Ie.fit(this.ratio,0,.3,0,1),this.floatingCubesRatio=Ie.fit(this.ratio,.35,.65,0,1),this.spawnRatio=Ie.fit(this.ratio,.3,.55,0,2.5),this.pushDownRatio=Ie.fit(this.ratio,.6,.8,0,1),this.ratio===1&&(this.errorAnimationEndedSignal.dispatch(),this.resetRatios())}}const St=new Tm;class Am{constructor(){N(this,"blocks",[]);N(this,"lastSpawnedBlock",null);N(this,"cycleIndex",0);N(this,"animationSpeedRatio",0);N(this,"firstStartAnimationRatio",0);N(this,"previousSuccessBlocksAnimationRatio",0);N(this,"isEndAnimationActive",!1);N(this,"wasSuccess",!1)}init(){Le.init(),Dt.init(),mn.init(),St.init(),Jt.init(),Dt.completeAnimationEndedSignal.add(()=>{Le.setRestart(),this._startNewCycle()}),mn.stopAnimationEndedSignal.add(()=>{Le.setRestart(),this._startNewCycle()}),St.errorAnimationEndedSignal.add(()=>{Le.setRestart(),this._startNewCycle()})}_spawnBlock(){this._shouldPreventSpawn()||(Le.isSuccessResult?this._spawnMultipleBlocks():this._spawnSingleBlock(),!(this.blocks.length===O.maxFreeBlocksCount&&Le.isFree)&&Le.spawnSignal.dispatch())}_shouldPreventSpawn(){return Le.isFailResult||Le.isStopped||this.blocks.length>=$t||Jt.mainTile.isOccupied&&!Le.isSuccessResult}_spawnMultipleBlocks(){let e=$t-O.activeBlocksCount;O.errorBlock&&(O.errorBlock.currentTile.isOccupied=!1,e+=1);for(let t=0;t=O.errorBlockMaxLifeCycle),n=!!(this.blocks.lengthe.resetAfterCycle()),Le.endCycleSignal.dispatch(),this.cycleIndex++,this._spawnBlock(),this._calculatePaths()))}_calculatePaths(){var t;(t=this.lastSpawnedBlock)!=null&&t.hasBeenSpawned&&this.lastSpawnedBlock.moveToNextTile(Le.isFree,0);const e=this.cycleIndex%2===0?!0:O.activeBlocksCount{!n.hasBeenEvaluated&&n.hasBeenSpawned&&n.moveToNextTile(e,r*.2)})}reset(){this.blocks.forEach(t=>t.reset()),Dn.reset(),Jt.reset(),this.blocks=[],this.lastSpawnedBlock=null,this.cycleIndex=0,this.animationSpeedRatio=0,this.previousSuccessBlocksAnimationRatio=this.wasSuccess?1:0,this.isEndAnimationActive=!1;const e=at.AUTO_RESTART&&[ot.FAILED,ot.COMPLETED].includes(Le.result);Le.reset(),this._startNewCycle(),e&&(Le.setStart(),Le.updateFlags())}_updateAnimationRatios(e){const{isResult:t}=Le;this.firstStartAnimationRatio=Ie.saturate(this.firstStartAnimationRatio+e*(O.showVisual?1:0)),this.animationSpeedRatio=Math.min(1,this.animationSpeedRatio+e*(t?1:0)),this.previousSuccessBlocksAnimationRatio=Ie.saturate(this.previousSuccessBlocksAnimationRatio-.25*e)}_checkCycleCompletion(){let e=!0;return this.lastSpawnedBlock&&(e=e&&this.lastSpawnedBlock.hasBeenSpawned),this.blocks.forEach(t=>{t.lifeCycle>0?e=e&&t.hasBeenEvaluated&&t.hasAnimationEnded:e=e&&t.spawnAnimationRatio===1}),e||Le.isResultAnimation||Le.isFailResult||Le.isStopped}update(e){if(this._updateAnimationRatios(e),Dt.update(e),mn.update(e),St.update(e),Le.hasNotStarted){this._startNewCycle();return}if(Le.isRestart){this.wasSuccess=Le.result===ot.COMPLETED,this.reset();return}Le.isResultAnimation&&Le.setRestartAnimation(),Jt.preUpdate(e),this.lastSpawnedBlock&&this.lastSpawnedBlock.update(e),this.blocks.forEach(n=>n.update(e)),Jt.update(e),this._checkCycleCompletion()&&this._startNewCycle()}}const Mt=new Am,bm=Math.PI/2,_i=new U;class wm{constructor(){this.animation=0,this.boardDir=new De,this.boardPos=new De,this.pos=new U,this.orient=new Yt,this.showRatio=0,this.spinPivot=new U,this.spinOrient=new Yt,this.spinOrient2=new Yt}reset(){this.animation=0,this.boardDir.set(0,0),this.boardPos.set(0,0),this.pos.set(0,0,0),this.orient.identity(),this.showRatio=0,this.spinPivot.set(0,0,0),this.spinOrient.identity()}update(e){this.pos.set(this.boardPos.x,0,-this.boardPos.y),this.spinPivot.set(this.boardDir.x*.5,-.5,-this.boardDir.y*.5),_i.set(-this.boardDir.y,0,-this.boardDir.x),this.spinOrient.setFromAxisAngle(_i,this.animation*bm)}addsFallAnimation(e){_i.set(this.boardDir.x,-e,-this.boardDir.y),this.pos.addScaledVector(_i,e),_i.set(this.boardDir.x*.5,0,-this.boardDir.y*.5),this.spinPivot.lerp(_i,Ie.saturate(e))}}var Tr=`#ifndef IS_BASE attribute vec3 instancePos; attribute vec4 instanceOrient; attribute float instanceShowRatio; diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index c80694fcb..7a524bac7 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -14,12 +14,13 @@ use crate::p2pool::models::Stats; use crate::progress_tracker::ProgressTracker; use crate::systemtray_manager::{SystemtrayManager, SystrayData}; use crate::tor_adapter::TorConfig; +use crate::utils::shutdown_utils::stop_all_processes; use crate::wallet_adapter::TransactionInfo; use crate::wallet_manager::WalletManagerError; use crate::{ - setup_inner, stop_all_miners, ApplicationsVersions, BaseNodeStatus, CpuMinerMetrics, - GpuMinerMetrics, MaxUsageLevels, MinerMetrics, TariWalletDetails, UniverseAppState, - APPLICATION_FOLDER_ID, MAX_ACCEPTABLE_COMMAND_TIME, + setup_inner, ApplicationsVersions, BaseNodeStatus, CpuMinerMetrics, GpuMinerMetrics, + MaxUsageLevels, MinerMetrics, TariWalletDetails, UniverseAppState, APPLICATION_FOLDER_ID, + MAX_ACCEPTABLE_COMMAND_TIME, }; use keyring::Entry; use log::{debug, error, info, warn}; @@ -83,7 +84,7 @@ pub async fn exit_application( state: tauri::State<'_, UniverseAppState>, app: tauri::AppHandle, ) -> Result<(), String> { - stop_all_miners(state.inner().clone(), 5).await?; + stop_all_processes(state.inner().clone(), true).await?; app.exit(0); Ok(()) @@ -635,7 +636,7 @@ pub async fn import_seed_words( .app_local_data_dir() .expect("Could not get data dir"); - stop_all_miners(state.inner().clone(), 5).await?; + stop_all_processes(state.inner().clone(), false).await?; tauri::async_runtime::spawn(async move { match InternalWallet::create_from_seed(config_path, seed_words).await { @@ -687,7 +688,7 @@ pub async fn reset_settings<'r>( state: tauri::State<'_, UniverseAppState>, app: tauri::AppHandle, ) -> Result<(), String> { - stop_all_miners(state.inner().clone(), 5).await?; + stop_all_processes(state.inner().clone(), true).await?; let network = Network::get_current_or_user_setting_or_default().as_key_str(); let app_config_dir = app.path_resolver().app_config_dir(); @@ -801,7 +802,7 @@ pub async fn restart_application( app: tauri::AppHandle, ) -> Result<(), String> { if should_stop_miners { - stop_all_miners(state.inner().clone(), 5).await?; + stop_all_processes(state.inner().clone(), true).await?; } app.restart(); diff --git a/src-tauri/src/cpu_miner.rs b/src-tauri/src/cpu_miner.rs index e83651db1..209f4c73d 100644 --- a/src-tauri/src/cpu_miner.rs +++ b/src-tauri/src/cpu_miner.rs @@ -107,6 +107,16 @@ impl CpuMiner { Ok(()) } + pub async fn is_running(&self) -> bool { + let lock = self.watcher.read().await; + lock.is_running() + } + + pub async fn is_pid_file_exists(&self, base_path: PathBuf) -> bool { + let lock = self.watcher.read().await; + lock.is_pid_file_exists(base_path) + } + pub async fn status( &self, network_hash_rate: u64, diff --git a/src-tauri/src/gpu_miner.rs b/src-tauri/src/gpu_miner.rs index ac760b4ec..4098c8896 100644 --- a/src-tauri/src/gpu_miner.rs +++ b/src-tauri/src/gpu_miner.rs @@ -103,6 +103,16 @@ impl GpuMiner { Ok(()) } + pub async fn is_running(&self) -> bool { + let process_watcher = self.watcher.read().await; + process_watcher.is_running() + } + + pub async fn is_pid_file_exists(&self, base_path: PathBuf) -> bool { + let lock = self.watcher.read().await; + lock.is_pid_file_exists(base_path) + } + pub async fn status( &self, network_hash_rate: u64, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index f267ebff4..761c08a66 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -46,6 +46,7 @@ use crate::tor_manager::TorManager; use crate::utils::auto_rollback::AutoRollback; use crate::wallet_adapter::WalletBalance; use crate::wallet_manager::WalletManager; +use utils::shutdown_utils::stop_all_processes; mod app_config; mod app_in_memory_config; @@ -117,50 +118,6 @@ struct UpdateProgressRustEvent { downloaded: u64, } -async fn stop_all_miners(state: UniverseAppState, sleep_secs: u64) -> Result<(), String> { - state - .cpu_miner - .write() - .await - .stop() - .await - .map_err(|e| e.to_string())?; - state - .gpu_miner - .write() - .await - .stop() - .await - .map_err(|e| e.to_string())?; - let exit_code = state - .wallet_manager - .stop() - .await - .map_err(|e| e.to_string())?; - info!(target: LOG_TARGET, "Wallet manager stopped with exit code: {}", exit_code); - state - .mm_proxy_manager - .stop() - .await - .map_err(|e| e.to_string())?; - let exit_code = state.node_manager.stop().await.map_err(|e| e.to_string())?; - info!(target: LOG_TARGET, "Node manager stopped with exit code: {}", exit_code); - let exit_code = state - .p2pool_manager - .stop() - .await - .map_err(|e| e.to_string())?; - info!(target: LOG_TARGET, "P2Pool manager stopped with exit code: {}", exit_code); - - let exit_code = state.tor_manager.stop().await.map_err(|e| e.to_string())?; - info!(target: LOG_TARGET, "Tor manager stopped with exit code: {}", exit_code); - state.shutdown.clone().trigger(); - - // TODO: Find a better way of knowing that all miners have stopped - sleep(std::time::Duration::from_secs(sleep_secs)); - Ok(()) -} - #[allow(clippy::too_many_lines)] async fn setup_inner( window: tauri::Window, @@ -906,12 +863,12 @@ fn main() { tauri::RunEvent::ExitRequested { api: _, .. } => { // api.prevent_exit(); info!(target: LOG_TARGET, "App shutdown caught"); - let _unused = block_on(stop_all_miners(app_state.clone(), 2)); + let _unused = block_on(stop_all_processes(app_state.clone(), true)); info!(target: LOG_TARGET, "App shutdown complete"); } tauri::RunEvent::Exit => { info!(target: LOG_TARGET, "App shutdown caught"); - let _unused = block_on(stop_all_miners(app_state.clone(), 2)); + let _unused = block_on(stop_all_processes(app_state.clone(), true)); info!(target: LOG_TARGET, "Tari Universe v{} shut down successfully", _app_handle.package_info().version); } RunEvent::MainEventsCleared => { diff --git a/src-tauri/src/mm_proxy_manager.rs b/src-tauri/src/mm_proxy_manager.rs index 7a86282fa..b4ab44c36 100644 --- a/src-tauri/src/mm_proxy_manager.rs +++ b/src-tauri/src/mm_proxy_manager.rs @@ -88,6 +88,7 @@ impl MmProxyManager { let mut current_start_config = self.start_config.write().await; *current_start_config = Some(config.clone()); let mut process_watcher = self.watcher.write().await; + let new_config = MergeMiningProxyConfig { tari_address: config.tari_address.clone(), base_node_grpc_port: config.base_node_grpc_port, @@ -135,4 +136,14 @@ impl MmProxyManager { process_watcher.stop().await?; Ok(()) } + + pub async fn is_running(&self) -> bool { + let lock = self.watcher.read().await; + lock.is_running() + } + + pub async fn is_pid_file_exists(&self, base_path: PathBuf) -> bool { + let lock = self.watcher.read().await; + lock.is_pid_file_exists(base_path) + } } diff --git a/src-tauri/src/node_manager.rs b/src-tauri/src/node_manager.rs index 125cf739e..1e8b0fc65 100644 --- a/src-tauri/src/node_manager.rs +++ b/src-tauri/src/node_manager.rs @@ -77,6 +77,7 @@ impl NodeManager { ) -> Result<(), NodeManagerError> { { let mut process_watcher = self.watcher.write().await; + process_watcher.adapter.use_tor = use_tor; process_watcher.adapter.tor_control_port = tor_control_port; process_watcher.stop_on_exit_codes = vec![114]; @@ -206,6 +207,16 @@ impl NodeManager { Ok(exit_code) } + pub async fn is_running(&self) -> bool { + let process_watcher = self.watcher.read().await; + process_watcher.is_running() + } + + pub async fn is_pid_file_exists(&self, base_path: PathBuf) -> bool { + let lock = self.watcher.read().await; + lock.is_pid_file_exists(base_path) + } + pub async fn check_if_is_orphan_chain(&self) -> Result { let mut status_monitor_lock = self.watcher.write().await; let status_monitor = status_monitor_lock diff --git a/src-tauri/src/p2pool_manager.rs b/src-tauri/src/p2pool_manager.rs index c3f704f06..360db7aa4 100644 --- a/src-tauri/src/p2pool_manager.rs +++ b/src-tauri/src/p2pool_manager.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; +use futures_util::future::FusedFuture; use log::warn; use tari_shutdown::ShutdownSignal; use tokio::sync::RwLock; @@ -96,12 +97,14 @@ impl P2poolManager { } } - pub async fn is_running(&self) -> Result { + pub async fn is_running(&self) -> bool { let process_watcher = self.watcher.read().await; - if process_watcher.is_running() { - return Ok(true); - } - Ok(false) + process_watcher.is_running() + } + + pub async fn is_pid_file_exists(&self, base_path: PathBuf) -> bool { + let lock = self.watcher.read().await; + lock.is_pid_file_exists(base_path) } pub async fn ensure_started( @@ -113,15 +116,13 @@ impl P2poolManager { log_path: PathBuf, ) -> Result<(), anyhow::Error> { let mut process_watcher = self.watcher.write().await; - if process_watcher.is_running() { - return Ok(()); - } + process_watcher.adapter.config = Some(config); process_watcher.health_timeout = Duration::from_secs(28); process_watcher.poll_time = Duration::from_secs(30); process_watcher .start( - app_shutdown, + app_shutdown.clone(), base_path, config_path, log_path, @@ -131,6 +132,9 @@ impl P2poolManager { process_watcher.wait_ready().await?; if let Some(status_monitor) = &process_watcher.status_monitor { loop { + if app_shutdown.is_terminated() || app_shutdown.is_triggered() { + break; + } sleep(Duration::from_secs(5)).await; if let Ok(_stats) = status_monitor.status().await { break; diff --git a/src-tauri/src/port_allocator.rs b/src-tauri/src/port_allocator.rs index 9f01034e1..6b06a8731 100644 --- a/src-tauri/src/port_allocator.rs +++ b/src-tauri/src/port_allocator.rs @@ -2,9 +2,9 @@ use anyhow::{anyhow, Error}; use log::{error, info, warn}; use std::net::TcpListener; -const LOG_TARGET: &str = "tari::universe::systemtray_manager"; +const LOG_TARGET: &str = "tari::universe::port_allocator"; const ADDRESS: &str = "127.0.0.1"; -const MAX_TRIES: u16 = 10; +const MAX_RETRIES: u16 = 10; const FALLBACK_PORT_RANGE: std::ops::Range = 49152..65535; pub struct PortAllocator {} @@ -55,9 +55,9 @@ impl PortAllocator { while !self.check_if_port_is_free(port) { port = self.get_port()?; tries += 1; - if tries >= MAX_TRIES { - warn!(target: LOG_TARGET, "Failed to assign port after {} tries", MAX_TRIES); - return Err(anyhow!("Failed to assign port after {} tries", MAX_TRIES)); + if tries >= MAX_RETRIES { + warn!(target: LOG_TARGET, "Failed to assign port after {} tries", MAX_RETRIES); + return Err(anyhow!("Failed to assign port after {} tries", MAX_RETRIES)); } } @@ -76,8 +76,8 @@ impl PortAllocator { .get_port() .unwrap_or_else(|_| self.asign_port_from_fallback_range()); tries += 1; - if tries >= MAX_TRIES { - warn!(target: LOG_TARGET, "Failed to assign port after {} tries", MAX_TRIES); + if tries >= MAX_RETRIES { + warn!(target: LOG_TARGET, "Failed to assign port after {} tries", MAX_RETRIES); info!(target: LOG_TARGET, "Assigning port from fallback range"); return self.asign_port_from_fallback_range(); } diff --git a/src-tauri/src/process_adapter.rs b/src-tauri/src/process_adapter.rs index 417a1e7a2..32c645037 100644 --- a/src-tauri/src/process_adapter.rs +++ b/src-tauri/src/process_adapter.rs @@ -1,5 +1,6 @@ use anyhow::{anyhow, Error}; use async_trait::async_trait; +use futures_util::future::FusedFuture; use log::{error, info, warn}; use sentry::protocol::Event; use sentry_tauri::sentry; @@ -41,6 +42,12 @@ pub(crate) trait ProcessAdapter { fn pid_file_name(&self) -> &str; + fn pid_file_exisits(&self, base_folder: PathBuf) -> bool { + std::path::Path::new(&base_folder) + .join(self.pid_file_name()) + .exists() + } + async fn kill_previous_instances(&self, base_folder: PathBuf) -> Result<(), Error> { info!(target: LOG_TARGET, "Killing previous instances of {}", self.name()); match fs::read_to_string(base_folder.join(self.pid_file_name())) { @@ -119,6 +126,12 @@ impl ProcessInstance { // Reset the shutdown each time. self.shutdown = Shutdown::new(); let shutdown_signal = self.shutdown.to_signal(); + + if shutdown_signal.is_terminated() || shutdown_signal.is_triggered() { + warn!(target: LOG_TARGET, "Shutdown signal is triggered. Not starting process"); + return Ok(()); + }; + self.handle = Some(tokio::spawn(async move { crate::download_utils::set_permissions(&spec.file_path).await?; // start diff --git a/src-tauri/src/process_watcher.rs b/src-tauri/src/process_watcher.rs index 09602cc4c..0b0117596 100644 --- a/src-tauri/src/process_watcher.rs +++ b/src-tauri/src/process_watcher.rs @@ -1,5 +1,6 @@ use crate::binaries::{Binaries, BinaryResolver}; use crate::process_adapter::{HealthStatus, ProcessAdapter, ProcessInstance, StatusMonitor}; +use futures_util::future::FusedFuture; use log::{error, info, warn}; use std::path::PathBuf; use std::time::{Duration, Instant}; @@ -53,6 +54,11 @@ impl ProcessWatcher { log_path: PathBuf, binary: Binaries, ) -> Result<(), anyhow::Error> { + info!(target: LOG_TARGET, "App shutdown triggered or terminated status for {} = {} | {}", self.adapter.name(),app_shutdown.is_triggered(),app_shutdown.is_terminated()); + if app_shutdown.is_terminated() || app_shutdown.is_triggered() { + return Ok(()); + } + let name = self.adapter.name().to_string(); if self.watcher_task.is_some() { warn!(target: LOG_TARGET, "Tried to start process watcher for {} twice", name); @@ -122,6 +128,10 @@ impl ProcessWatcher { } } + pub fn is_pid_file_exists(&self, base_path: PathBuf) -> bool { + self.adapter.pid_file_exisits(base_path) + } + pub async fn wait_ready(&self) -> Result<(), anyhow::Error> { if let Some(ref task) = self.watcher_task { if task.inner().is_finished() { diff --git a/src-tauri/src/telemetry_manager.rs b/src-tauri/src/telemetry_manager.rs index 37c8d634b..97af722ce 100644 --- a/src-tauri/src/telemetry_manager.rs +++ b/src-tauri/src/telemetry_manager.rs @@ -467,8 +467,7 @@ async fn get_telemetry_data( // let p2pool_gpu_stats_sha3 = p2pool_stats.as_ref().map(|s| s.sha3x_stats.clone()); // let p2pool_cpu_stats_randomx = p2pool_stats.as_ref().map(|s| s.randomx_stats.clone()); - let p2pool_enabled = - config_guard.p2pool_enabled() && p2pool_manager.is_running().await.unwrap_or(false); + let p2pool_enabled = config_guard.p2pool_enabled() && p2pool_manager.is_running().await; // let (cpu_tribe_name, cpu_tribe_id) = if p2pool_enabled { // if let Some(randomx_stats) = p2pool_cpu_stats_randomx { // ( diff --git a/src-tauri/src/tor_manager.rs b/src-tauri/src/tor_manager.rs index 560b827a5..cdd5b34a7 100644 --- a/src-tauri/src/tor_manager.rs +++ b/src-tauri/src/tor_manager.rs @@ -1,9 +1,8 @@ -use std::{path::PathBuf, sync::Arc}; -use tokio::sync::RwLock; - use crate::process_watcher::ProcessWatcher; use crate::tor_adapter::{TorAdapter, TorConfig}; +use std::{path::PathBuf, sync::Arc}; use tari_shutdown::ShutdownSignal; +use tokio::sync::RwLock; pub(crate) struct TorManager { watcher: Arc>>, @@ -36,6 +35,7 @@ impl TorManager { ) -> Result<(), anyhow::Error> { { let mut process_watcher = self.watcher.write().await; + process_watcher .adapter .load_or_create_config(config_path.clone()) @@ -102,4 +102,14 @@ impl TorManager { let exit_code = process_watcher.stop().await?; Ok(exit_code) } + + pub async fn is_running(&self) -> bool { + let process_watcher = self.watcher.read().await; + process_watcher.is_running() + } + + pub async fn is_pid_file_exists(&self, base_path: PathBuf) -> bool { + let lock = self.watcher.read().await; + lock.is_pid_file_exists(base_path) + } } diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs index 12f7fea47..0a0a64e53 100644 --- a/src-tauri/src/utils/mod.rs +++ b/src-tauri/src/utils/mod.rs @@ -3,3 +3,4 @@ pub mod file_utils; pub mod logging_utils; pub mod platform_utils; pub mod setup_utils; +pub mod shutdown_utils; diff --git a/src-tauri/src/utils/shutdown_utils.rs b/src-tauri/src/utils/shutdown_utils.rs new file mode 100644 index 000000000..37593c0d0 --- /dev/null +++ b/src-tauri/src/utils/shutdown_utils.rs @@ -0,0 +1,100 @@ +use crate::{UniverseAppState, APPLICATION_FOLDER_ID}; +use log::info; +use tauri::api::path::local_data_dir; + +static LOG_TARGET: &str = "tari::universe::shutdown_utils"; + +pub async fn stop_all_processes( + state: UniverseAppState, + should_shutdown: bool, +) -> Result<(), String> { + info!(target: LOG_TARGET, "Stopping all miners"); + + info!(target: LOG_TARGET, "Entering shutdown sequence"); + if should_shutdown { + state.shutdown.clone().trigger(); + } + + let base_path = local_data_dir() + .expect("Could not get data dir") + .join(APPLICATION_FOLDER_ID); + + let mut cpu_miner = state.cpu_miner.write().await; + let cpu_miner_pid_file_exists = cpu_miner.is_pid_file_exists(base_path.clone()).await; + let cpu_miner_is_running = cpu_miner.is_running().await; + + info!(target: LOG_TARGET, "CPU Miner: pid file exists: {}, is running: {}", cpu_miner_pid_file_exists, cpu_miner_is_running); + + if cpu_miner_is_running || cpu_miner_pid_file_exists { + cpu_miner.stop().await.map_err(|e| e.to_string())?; + drop(cpu_miner); + } + + let gpu_miner = state.gpu_miner.read().await; + let gpu_miner_pid_file_exists = gpu_miner.is_pid_file_exists(base_path.clone()).await; + let gpu_miner_is_running = gpu_miner.is_running().await; + + info!(target: LOG_TARGET, "GPU Miner: pid file exists: {}, is running: {}", gpu_miner_pid_file_exists, gpu_miner_is_running); + + if gpu_miner_is_running || gpu_miner_pid_file_exists { + gpu_miner.stop().await.map_err(|e| e.to_string())?; + drop(gpu_miner); + } + + let wallet_manager = state.wallet_manager; + let wallet_manager_is_running = wallet_manager.is_running().await; + let wallet_manager_pid_file_exists = wallet_manager.is_pid_file_exists(base_path.clone()).await; + + info!(target: LOG_TARGET, "Wallet Manager: pid file exists: {}, is running: {}", wallet_manager_pid_file_exists, wallet_manager_is_running); + + if wallet_manager_is_running || wallet_manager_pid_file_exists { + wallet_manager.stop().await.map_err(|e| e.to_string())?; + } + + let node_manager = state.node_manager; + let node_manager_is_running = node_manager.is_running().await; + let node_manager_pid_file_exists = node_manager.is_pid_file_exists(base_path.clone()).await; + + info!(target: LOG_TARGET, "Node Manager: pid file exists: {}, is running: {}", node_manager_pid_file_exists, node_manager_is_running); + + if node_manager_is_running || node_manager_pid_file_exists { + node_manager.stop().await.map_err(|e| e.to_string())?; + } + + let mm_proxy_manager = state.mm_proxy_manager; + let mm_proxy_manager_is_running = mm_proxy_manager.is_running().await; + let mm_proxy_manager_pid_file_exists = + mm_proxy_manager.is_pid_file_exists(base_path.clone()).await; + + info!(target: LOG_TARGET, "MM Proxy Manager: pid file exists: {}, is running: {}", mm_proxy_manager_pid_file_exists, mm_proxy_manager_is_running); + + if mm_proxy_manager_is_running || mm_proxy_manager_pid_file_exists { + mm_proxy_manager.stop().await.map_err(|e| e.to_string())?; + } + + let p2pool_manager = state.p2pool_manager; + let p2pool_manager_is_running = p2pool_manager.is_running().await; + let p2pool_manager_pid_file_exists = p2pool_manager.is_pid_file_exists(base_path.clone()).await; + + info!(target: LOG_TARGET, "P2Pool Manager: pid file exists: {}, is running: {}", p2pool_manager_pid_file_exists, p2pool_manager_is_running); + + if p2pool_manager_is_running || p2pool_manager_pid_file_exists { + p2pool_manager.stop().await.map_err(|e| e.to_string())?; + } + + let tor_manager = state.tor_manager; + let tor_manager_is_running = tor_manager.is_running().await; + let tor_manager_pid_file_exists = tor_manager.is_pid_file_exists(base_path.clone()).await; + + info!(target: LOG_TARGET, "Tor Manager: pid file exists: {}, is running: {}", tor_manager_pid_file_exists, tor_manager_is_running); + + if tor_manager_is_running || tor_manager_pid_file_exists { + tor_manager.stop().await.map_err(|e| e.to_string())?; + } + + if should_shutdown { + state.shutdown.clone().trigger(); + } + + Ok(()) +} diff --git a/src-tauri/src/wallet_manager.rs b/src-tauri/src/wallet_manager.rs index 990094fb1..14d4772ea 100644 --- a/src-tauri/src/wallet_manager.rs +++ b/src-tauri/src/wallet_manager.rs @@ -4,6 +4,7 @@ use crate::process_watcher::ProcessWatcher; use crate::wallet_adapter::TransactionInfo; use crate::wallet_adapter::WalletStatusMonitorError; use crate::wallet_adapter::{WalletAdapter, WalletBalance}; +use futures_util::future::FusedFuture; use std::path::PathBuf; use std::sync::Arc; use tari_shutdown::ShutdownSignal; @@ -59,6 +60,14 @@ impl WalletManager { let base_node_tcp_port = self.node_manager.get_tcp_listener_port().await; let mut process_watcher = self.watcher.write().await; + + if process_watcher.is_running() + || app_shutdown.is_terminated() + || app_shutdown.is_triggered() + { + return Ok(()); + } + process_watcher.adapter.base_node_public_key = Some(node_identity.public_key.clone()); process_watcher.adapter.base_node_address = Some(format!("/ip4/127.0.0.1/tcp/{}", base_node_tcp_port)); @@ -123,6 +132,16 @@ impl WalletManager { .map_err(WalletManagerError::UnknownError) } + pub async fn is_running(&self) -> bool { + let process_watcher = self.watcher.read().await; + process_watcher.is_running() + } + + pub async fn is_pid_file_exists(&self, base_path: PathBuf) -> bool { + let lock = self.watcher.read().await; + lock.is_pid_file_exists(base_path) + } + #[deprecated( note = "Do not use. Use internal wallet instead. This address is the address of the view key wallet and not the internal wallet." )] diff --git a/src/App/App.tsx b/src/App/App.tsx index 81f5e0eb6..89f4c654c 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -51,7 +51,7 @@ export default function App() { - {isSettingUp ? ( + {!isShuttingDown && isSettingUp ? (