diff --git a/scripts/Globals.js b/scripts/Globals.js index 1a21a055..51c27459 100644 --- a/scripts/Globals.js +++ b/scripts/Globals.js @@ -897,7 +897,7 @@ Audio_WebAudio=1, g_CurrentDepthIdStack =[], g_gmlConst =null, - g_AudioBusMain = null; + g_AudioBusMain = null, g_AudioMainVolumeNode =null, g_WebAudioContext =null, g_dialogs = null, @@ -1030,6 +1030,7 @@ Audio_WebAudio=1, c_greenA = 0, c_blueA = 0, g_isZeus = 0, + g_requestAnimationID = 0, g_crcTable=[], g_CanvasName = 'canvas', g_Hex='0123456789ABCDEF'; diff --git a/scripts/_GameMaker.js b/scripts/_GameMaker.js index d32f0a7b..762658b8 100644 --- a/scripts/_GameMaker.js +++ b/scripts/_GameMaker.js @@ -98,6 +98,13 @@ window.yyRequestAnimationFrame = window.oRequestAnimationFrame || window.msRequestAnimationFrame; +window.yyCancelAnimationFrame = + window["cancelAnimationFrame"] || + window["webkitCancelAnimationFrame"] || + window["mozCancelAnimationFrame"] || + window["oCancelAnimationFrame"] || + window["msCancelAnimationFrame"]; + if (!window.yyRequestAnimationFrame) { // if RAF is somehow amiss but we need short timeouts, register a message handler // https://dbaron.org/log/20100309-faster-timeouts @@ -151,13 +158,13 @@ function exception_unhandled_handler( func ) function yyUnhandledExceptionHandler( event ) { - if ((g_GMLUnhandledExceptionHandler == undefined) || !(g_GMLUnhandledExceptionHandler instanceof Function)) { - var string = "Unhandled Exception - " + event.message + " in file " + event.filename + " at line " + event.lineno ; - print( string ); - //alert( string ); - game_end(-1); - } // end if - else { + if ((g_GMLUnhandledExceptionHandler == undefined) || !(g_GMLUnhandledExceptionHandler instanceof Function)) { + var string = "Unhandled Exception - " + event.message + " in file " + event.filename + " at line " + event.lineno; + print( string ); + //alert( string ); + game_end(-1); + } // end if + else { let error = event.error; // Construct a GML struct to encapsulate the error details. @@ -172,9 +179,13 @@ function yyUnhandledExceptionHandler( event ) // Pass the error struct to the custom error handler var ret = g_GMLUnhandledExceptionHandler( undefined, undefined, errorStruct ); game_end( ret ); - } - debugger; - return false; + } + if (g_StartUpState < 3) { + // Something went wrong before the game reached its final startup state, we must abort *now*. + Run_EndGame(false); + } + debugger; + return false; } function yyUnhandledRejectionHandler( error ) @@ -831,7 +842,7 @@ if( div_a>360 ) div_a-=360; // ############################################################################################# function animate() { // once in-game, timing is handled by GameMaker_Tick - if (g_StartUpState != 3) window.requestAnimFrame(animate); + if (g_StartUpState != 3) g_requestAnimationID = window.requestAnimFrame(animate); if (g_LoadingCanvasCreated) { @@ -1592,22 +1603,26 @@ function Run_EndGame(_reset) { } g_pInstanceManager.Clear(); - // @if feature("audio") if (_reset) { // Just stops all audio instances. + // @if feature("audio") audio_stop_all(); + // @endif audio } else { // Destroys the AudioContext instance. + // @if feature("audio") Audio_Quit(); + // @endif audio + + // Cancels the animation frame request. + if (g_requestAnimationID !== 0 && typeof yyCancelAnimationFrame === "function") { + yyCancelAnimationFrame(g_requestAnimationID); + } } - // @endif audio } - - - // ############################################################################################# /// Function: /// Render any system level stuff we need to render. @@ -2316,7 +2331,7 @@ function GameMaker_Tick() // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers setTimeout(function() { if (window.yyRequestAnimationFrame) { - window.yyRequestAnimationFrame(animate); + g_requestAnimationID = window.yyRequestAnimationFrame(animate); } else { // Don't re-enter, that would be bad. //animate(); @@ -2324,7 +2339,7 @@ function GameMaker_Tick() }, delay); } else { if (window.yyRequestAnimationFrame) { - window.yyRequestAnimationFrame(animate); + g_requestAnimationID = window.yyRequestAnimationFrame(animate); } else { window.postMessage("yyRequestAnimationFrame", "*"); } diff --git a/scripts/functions/Function_Sound.js b/scripts/functions/Function_Sound.js index feb237f5..7bae6411 100644 --- a/scripts/functions/Function_Sound.js +++ b/scripts/functions/Function_Sound.js @@ -1023,7 +1023,7 @@ function Audio_GetEngineState() { function Audio_WebAudioContextTryUnlock() { if ( g_WaitingForWebAudioTouchUnlock ) - return; + return; g_WaitingForWebAudioTouchUnlock = true; @@ -1035,7 +1035,7 @@ function Audio_WebAudioContextTryUnlock() eventTypeStart = "touchstart"; eventTypeEnd = "touchend"; } - if ((window.PointerEvent) || (window.navigator.pointerEnabled)||(window.navigator.msPointerEnabled)) { + if ((window.PointerEvent) || (window.navigator.pointerEnabled) || (window.navigator.msPointerEnabled)) { eventTypeStart = "pointerdown"; eventTypeEnd = "pointerup"; } // end if @@ -1044,6 +1044,10 @@ function Audio_WebAudioContextTryUnlock() // Set up context unlock events var unlockWebAudioContext = function () { + if ( !Audio_ContextExists() ) + // This case should only happen if the game ends or crashes before attempting to unlock the AudioContext instance. + // However, let's not make assumptions and keep the event listeners active. + return; g_WebAudioContext.resume().then( function () { document.body.removeEventListener( eventTypeStart, unlockWebAudioContext );