2022-10-02 16:22:05 +00:00
const RingBuffer = require ( './RingBuffer' ) ;
2022-09-20 15:50:01 +00:00
window . UPSYNC _MSG _ACT _HB _PING = 1 ;
window . UPSYNC _MSG _ACT _PLAYER _CMD = 2 ;
window . UPSYNC _MSG _ACT _PLAYER _COLLIDER _ACK = 3 ;
2022-10-01 15:54:48 +00:00
window . DOWNSYNC _MSG _ACT _PLAYER _ADDED _AND _ACKED = - 98 ;
window . DOWNSYNC _MSG _ACT _BATTLE _READY _TO _START = - 1 ;
window . DOWNSYNC _MSG _ACT _BATTLE _START = 0 ;
2022-09-20 15:50:01 +00:00
window . DOWNSYNC _MSG _ACT _HB _REQ = 1 ;
window . DOWNSYNC _MSG _ACT _INPUT _BATCH = 2 ;
2022-10-02 16:22:05 +00:00
window . DOWNSYNC _MSG _ACT _BATTLE _STOPPED = 3 ;
2022-10-01 07:14:05 +00:00
window . DOWNSYNC _MSG _ACT _FORCED _RESYNC = 4 ;
2022-09-20 15:50:01 +00:00
2022-10-01 15:54:48 +00:00
2022-09-20 15:50:01 +00:00
window . sendSafely = function ( msgStr ) {
/ * *
* - "If the data can't be sent (for example, because it needs to be buffered but the buffer is full), the socket is closed automatically."
*
* from https : //developer.mozilla.org/en-US/docs/Web/API/WebSocket/send.
* /
if ( null == window . clientSession || window . clientSession . readyState != WebSocket . OPEN ) return false ;
window . clientSession . send ( msgStr ) ;
}
window . sendUint8AsBase64Safely = function ( msgUint8Arr ) {
if ( null == window . clientSession || window . clientSession . readyState != WebSocket . OPEN ) return false ;
window . clientSession . send ( _uint8ToBase64 ( msgUint8Arr ) ) ;
}
window . closeWSConnection = function ( ) {
if ( null == window . clientSession || window . clientSession . readyState != WebSocket . OPEN ) return ;
console . log ( ` Closing "window.clientSession" from the client-side. ` ) ;
window . clientSession . close ( ) ;
}
window . getBoundRoomIdFromPersistentStorage = function ( ) {
const boundRoomIdExpiresAt = parseInt ( cc . sys . localStorage . getItem ( "boundRoomIdExpiresAt" ) ) ;
if ( ! boundRoomIdExpiresAt || Date . now ( ) >= boundRoomIdExpiresAt ) {
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
return null ;
}
return cc . sys . localStorage . getItem ( "boundRoomId" ) ;
} ;
window . clearBoundRoomIdInBothVolatileAndPersistentStorage = function ( ) {
window . boundRoomId = null ;
cc . sys . localStorage . removeItem ( "boundRoomId" ) ;
cc . sys . localStorage . removeItem ( "boundRoomIdExpiresAt" ) ;
} ;
window . clearSelfPlayer = function ( ) {
cc . sys . localStorage . removeItem ( "selfPlayer" ) ;
} ;
window . boundRoomId = getBoundRoomIdFromPersistentStorage ( ) ;
window . handleHbRequirements = function ( resp ) {
if ( constants . RET _CODE . OK != resp . ret ) return ;
if ( null == window . boundRoomId ) {
window . boundRoomId = resp . bciFrame . boundRoomId ;
cc . sys . localStorage . setItem ( 'boundRoomId' , window . boundRoomId ) ;
cc . sys . localStorage . setItem ( 'boundRoomIdExpiresAt' , Date . now ( ) + 10 * 60 * 1000 ) ; // Temporarily hardcoded, for `boundRoomId` only.
}
if ( window . handleBattleColliderInfo ) {
window . handleBattleColliderInfo ( resp . bciFrame ) ;
}
} ;
function _uint8ToBase64 ( uint8Arr ) {
return window . btoa ( uint8Arr ) ;
}
function _base64ToUint8Array ( base64 ) {
var origBytes = null ;
if ( null != window . atob ) {
var origBinaryStr = window . atob ( base64 ) ;
var origLen = origBinaryStr . length ;
origBytes = new Uint8Array ( origLen ) ;
for ( var i = 0 ; i < origLen ; i ++ ) {
origBytes [ i ] = origBinaryStr . charCodeAt ( i ) ;
}
return origBytes ;
} else {
return null ;
}
}
function _base64ToArrayBuffer ( base64 ) {
return _base64ToUint8Array ( base64 ) . buffer ;
}
window . getExpectedRoomIdSync = function ( ) {
2022-09-24 04:01:50 +00:00
const qDict = window . getQueryParamDict ( ) ;
if ( qDict ) {
return qDict [ "expectedRoomId" ] ;
2022-09-20 15:50:01 +00:00
} else {
2022-09-24 04:01:50 +00:00
if ( window . history && window . history . state ) {
return window . history . state . expectedRoomId ;
2022-09-20 15:50:01 +00:00
}
}
return null ;
} ;
window . initPersistentSessionClient = function ( onopenCb , expectedRoomId ) {
if ( window . clientSession && window . clientSession . readyState == WebSocket . OPEN ) {
if ( null != onopenCb ) {
onopenCb ( ) ;
}
return ;
}
2022-10-25 15:36:55 +00:00
2022-10-24 16:05:38 +00:00
const selfPlayerStr = cc . sys . localStorage . getItem ( "selfPlayer" ) ;
2022-10-25 15:36:55 +00:00
const selfPlayer = null == selfPlayerStr ? null : JSON . parse ( selfPlayerStr ) ;
2022-10-24 16:05:38 +00:00
const intAuthToken = null == selfPlayer ? "" : selfPlayer . intAuthToken ;
2022-09-20 15:50:01 +00:00
let urlToConnect = backendAddress . PROTOCOL . replace ( 'http' , 'ws' ) + '://' + backendAddress . HOST + ":" + backendAddress . PORT + backendAddress . WS _PATH _PREFIX + "?intAuthToken=" + intAuthToken ;
if ( null != expectedRoomId ) {
console . log ( "initPersistentSessionClient with expectedRoomId == " + expectedRoomId ) ;
urlToConnect = urlToConnect + "&expectedRoomId=" + expectedRoomId ;
} else {
window . boundRoomId = getBoundRoomIdFromPersistentStorage ( ) ;
if ( null != window . boundRoomId ) {
console . log ( "initPersistentSessionClient with boundRoomId == " + boundRoomId ) ;
urlToConnect = urlToConnect + "&boundRoomId=" + window . boundRoomId ;
}
}
const currentHistoryState = window . history && window . history . state ? window . history . state : { } ;
const clientSession = new WebSocket ( urlToConnect ) ;
clientSession . binaryType = 'arraybuffer' ; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob"
2022-10-25 01:52:38 +00:00
clientSession . onopen = function ( evt ) {
2022-12-01 03:35:56 +00:00
console . log ( "The WS clientSession is opened." ) ;
2022-09-20 15:50:01 +00:00
window . clientSession = clientSession ;
if ( null == onopenCb ) return ;
onopenCb ( ) ;
} ;
2022-10-25 01:52:38 +00:00
clientSession . onmessage = function ( evt ) {
if ( null == evt || null == evt . data ) {
2022-09-20 15:50:01 +00:00
return ;
}
2022-12-19 11:51:55 +00:00
// FIXME: In practice, it seems like the thread invoking "onmessage" could be different from "Map.update(dt)", which makes it necessary to guard "recentRenderCache & recentInputCache" for "_generateInputFrameUpsync & rollbackAndChase & onRoomDownsyncFrame & onInputFrameDownsyncBatch" to avoid mysterious RAM contamination, but there's no explicit mutex in JavaScript for browsers -- this issue is found in Firefox (108.0.1, 64-bit, Windows 11), but not in Chrome (108.0.5359.125, Official Build, 64-bit, Windows 11) -- just breakpoint in "Map.rollbackAndChase" then see whether the logs of "onmessage" can still be printed and whether the values of "recentRenderCache & recentInputCache" change in console).
2022-09-20 15:50:01 +00:00
try {
2022-11-09 15:46:11 +00:00
const resp = window . pb . protos . WsResp . decode ( new Uint8Array ( evt . data ) ) ;
2022-12-18 03:39:25 +00:00
//console.log(`Got non-empty onmessage decoded: resp.act=${resp.act}`);
2022-09-20 15:50:01 +00:00
switch ( resp . act ) {
case window . DOWNSYNC _MSG _ACT _HB _REQ :
2022-12-01 04:17:30 +00:00
window . handleHbRequirements ( resp ) ;
2022-09-20 15:50:01 +00:00
break ;
2022-10-01 15:54:48 +00:00
case window . DOWNSYNC _MSG _ACT _PLAYER _ADDED _AND _ACKED :
2022-10-02 16:22:05 +00:00
mapIns . onPlayerAdded ( resp . rdf ) ;
2022-10-01 15:54:48 +00:00
break ;
case window . DOWNSYNC _MSG _ACT _BATTLE _READY _TO _START :
2022-11-09 15:46:11 +00:00
mapIns . onBattleReadyToStart ( resp . rdf ) ;
2022-10-02 16:22:05 +00:00
break ;
2022-10-01 15:54:48 +00:00
case window . DOWNSYNC _MSG _ACT _BATTLE _START :
2022-10-03 03:42:19 +00:00
mapIns . onRoomDownsyncFrame ( resp . rdf ) ;
2022-10-02 16:22:05 +00:00
break ;
case window . DOWNSYNC _MSG _ACT _BATTLE _STOPPED :
mapIns . onBattleStopped ( ) ;
2022-09-20 15:50:01 +00:00
break ;
case window . DOWNSYNC _MSG _ACT _INPUT _BATCH :
2022-10-02 16:22:05 +00:00
mapIns . onInputFrameDownsyncBatch ( resp . inputFrameDownsyncBatch ) ;
2022-09-20 15:50:01 +00:00
break ;
2022-10-01 07:14:05 +00:00
case window . DOWNSYNC _MSG _ACT _FORCED _RESYNC :
2022-10-04 03:24:47 +00:00
if ( null == resp . inputFrameDownsyncBatch || 0 >= resp . inputFrameDownsyncBatch . length ) {
2022-12-01 04:17:30 +00:00
console . error ( ` Got empty inputFrameDownsyncBatch upon resync@localRenderFrameId= ${ mapIns . renderFrameId } , @lastAllConfirmedRenderFrameId= ${ mapIns . lastAllConfirmedRenderFrameId } , @lastAllConfirmedInputFrameId= ${ mapIns . lastAllConfirmedInputFrameId } , @chaserRenderFrameId= ${ mapIns . chaserRenderFrameId } , @localRecentInputCache= ${ mapIns . _stringifyRecentInputCache ( false ) } , the incoming resp= ${ JSON . stringify ( resp , null , 2 ) } ` ) ;
2022-10-04 03:24:47 +00:00
return ;
}
2022-11-30 16:30:35 +00:00
mapIns . onRoomDownsyncFrame ( resp . rdf , resp . inputFrameDownsyncBatch ) ;
2022-10-01 07:14:05 +00:00
break ;
2022-09-20 15:50:01 +00:00
default :
break ;
}
} catch ( e ) {
2022-10-25 01:52:38 +00:00
console . error ( "Unexpected error when parsing data of:" , evt . data , e ) ;
2022-09-20 15:50:01 +00:00
}
} ;
2022-10-25 01:52:38 +00:00
clientSession . onerror = function ( evt ) {
console . error ( "Error caught on the WS clientSession: " , evt ) ;
if ( window . handleClientSessionError ) {
window . handleClientSessionError ( ) ;
2022-09-20 15:50:01 +00:00
}
} ;
2022-10-25 01:52:38 +00:00
clientSession . onclose = function ( evt ) {
// [WARNING] The callback "onclose" might be called AFTER the webpage is refreshed with "1001 == evt.code".
console . warn ( "The WS clientSession is closed: " , evt , clientSession ) ;
if ( false == evt . wasClean ) {
/ *
Chrome doesn ' t allow the use of "CustomCloseCode" s ( yet ) and will callback with a "WebsocketStdCloseCode 1006" and "false == evt.wasClean" here . See https : //tools.ietf.org/html/rfc6455#section-7.4 for more information.
* /
if ( window . handleClientSessionError ) {
window . handleClientSessionError ( ) ;
2022-09-20 15:50:01 +00:00
}
} else {
2022-10-25 01:52:38 +00:00
switch ( evt . code ) {
case constants . RET _CODE . PLAYER _NOT _ADDABLE _TO _ROOM :
case constants . RET _CODE . PLAYER _NOT _READDABLE _TO _ROOM :
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
break ;
case constants . RET _CODE . UNKNOWN _ERROR :
case constants . RET _CODE . MYSQL _ERROR :
2022-09-20 15:50:01 +00:00
case constants . RET _CODE . PLAYER _NOT _FOUND :
case constants . RET _CODE . PLAYER _CHEATING :
2022-10-25 15:36:55 +00:00
case 1006 : // Peer(i.e. the backend) gone unexpectedly
2022-10-25 01:52:38 +00:00
if ( window . handleClientSessionError ) {
window . handleClientSessionError ( ) ;
2022-10-25 15:36:55 +00:00
}
2022-09-20 15:50:01 +00:00
break ;
default :
break ;
}
}
} ;
} ;
window . clearLocalStorageAndBackToLoginScene = function ( shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) {
console . warn ( "+++++++ Calling `clearLocalStorageAndBackToLoginScene`" ) ;
if ( window . mapIns && window . mapIns . musicEffectManagerScriptIns ) {
window . mapIns . musicEffectManagerScriptIns . stopAllMusic ( ) ;
}
/ * *
2022-10-25 01:52:38 +00:00
* Here I deliberately removed the callback in the "common `handleClientSessionError` callback"
2022-09-20 15:50:01 +00:00
* within which another invocation to ` clearLocalStorageAndBackToLoginScene ` will be made .
*
* It ' ll be re - assigned to the common one upon reentrance of ` Map.onLoad ` .
*
* -- YFLu 2019 - 04 - 06
* /
2022-10-25 01:52:38 +00:00
window . handleClientSessionError = ( ) => {
console . warn ( "+++++++ Special handleClientSessionError() assigned within `clearLocalStorageAndBackToLoginScene`" ) ;
2022-09-20 15:50:01 +00:00
// TBD.
2022-10-25 01:52:38 +00:00
window . handleClientSessionError = null ; // To ensure that it's called at most once.
2022-09-20 15:50:01 +00:00
} ;
window . closeWSConnection ( ) ;
window . clearSelfPlayer ( ) ;
if ( true != shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) {
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
}
2022-09-24 04:01:50 +00:00
cc . director . loadScene ( 'login' ) ;
2022-09-20 15:50:01 +00:00
} ;