2022-10-03 00:22:05 +08:00
const RingBuffer = require ( './RingBuffer' );
2022-09-20 23:50:01 +08: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 23:54:48 +08: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 23:50:01 +08:00
window . DOWNSYNC_MSG_ACT_HB_REQ = 1 ;
window . DOWNSYNC_MSG_ACT_INPUT_BATCH = 2 ;
2022-10-03 00:22:05 +08:00
window . DOWNSYNC_MSG_ACT_BATTLE_STOPPED = 3 ;
2022-10-01 15:14:05 +08:00
window . DOWNSYNC_MSG_ACT_FORCED_RESYNC = 4 ;
2023-01-18 15:36:04 +08:00
window . DOWNSYNC_MSG_ACT_PEER_INPUT_BATCH = 5 ;
2022-09-20 23:50:01 +08: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 ));
}
2022-12-20 16:01:44 +08:00
window . closeWSConnection = function ( code , reason ) {
if ( null == window . clientSession || window . clientSession . readyState != WebSocket . OPEN ) {
console . log ( `"window.clientSession" is already closed or destroyed.` );
return ;
}
2022-09-20 23:50:01 +08:00
console . log ( `Closing "window.clientSession" from the client-side.` );
2022-12-20 16:01:44 +08:00
window . clientSession . close ( code , reason );
2022-09-20 23:50:01 +08:00
}
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 ) {
2023-01-18 15:36:04 +08:00
window . initSecondarySession ( null , window . boundRoomId );
2022-09-20 23:50:01 +08:00
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 12:01:50 +08:00
const qDict = window . getQueryParamDict ();
if ( qDict ) {
return qDict [ "expectedRoomId" ];
2022-09-20 23:50:01 +08:00
} else {
2022-09-24 12:01:50 +08:00
if ( window . history && window . history . state ) {
return window . history . state . expectedRoomId ;
2022-09-20 23:50:01 +08:00
}
}
return null ;
};
window . initPersistentSessionClient = function ( onopenCb , expectedRoomId ) {
if ( window . clientSession && window . clientSession . readyState == WebSocket . OPEN ) {
if ( null != onopenCb ) {
onopenCb ();
}
return ;
}
2022-10-25 23:36:55 +08:00
2022-10-25 00:05:38 +08:00
const selfPlayerStr = cc . sys . localStorage . getItem ( "selfPlayer" );
2022-10-25 23:36:55 +08:00
const selfPlayer = null == selfPlayerStr ? null : JSON . parse ( selfPlayerStr );
2022-10-25 00:05:38 +08:00
const intAuthToken = null == selfPlayer ? "" : selfPlayer . intAuthToken ;
2022-09-20 23:50:01 +08: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 clientSession = new WebSocket ( urlToConnect );
clientSession . binaryType = 'arraybuffer' ; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob"
2022-10-25 09:52:38 +08:00
clientSession . onopen = function ( evt ) {
2022-12-01 11:35:56 +08:00
console . log ( "The WS clientSession is opened." );
2022-09-20 23:50:01 +08:00
window . clientSession = clientSession ;
if ( null == onopenCb ) return ;
onopenCb ();
};
2022-10-25 09:52:38 +08:00
clientSession . onmessage = function ( evt ) {
if ( null == evt || null == evt . data ) {
2022-09-20 23:50:01 +08:00
return ;
}
2022-12-20 23:51:53 +08: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 "getOrPrefabInputFrameUpsync & 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 23:50:01 +08:00
try {
2022-11-09 23:46:11 +08:00
const resp = window . pb . protos . WsResp . decode ( new Uint8Array ( evt . data ));
2022-12-18 11:39:25 +08:00
//console.log(`Got non-empty onmessage decoded: resp.act=${resp.act}`);
2022-09-20 23:50:01 +08:00
switch ( resp . act ) {
case window . DOWNSYNC_MSG_ACT_HB_REQ :
2022-12-01 12:17:30 +08:00
window . handleHbRequirements ( resp );
2022-09-20 23:50:01 +08:00
break ;
2022-10-01 23:54:48 +08:00
case window . DOWNSYNC_MSG_ACT_PLAYER_ADDED_AND_ACKED :
2022-10-03 00:22:05 +08:00
mapIns . onPlayerAdded ( resp . rdf );
2022-10-01 23:54:48 +08:00
break ;
case window . DOWNSYNC_MSG_ACT_BATTLE_READY_TO_START :
2022-11-09 23:46:11 +08:00
mapIns . onBattleReadyToStart ( resp . rdf );
2022-10-03 00:22:05 +08:00
break ;
2022-10-01 23:54:48 +08:00
case window . DOWNSYNC_MSG_ACT_BATTLE_START :
2022-10-03 11:42:19 +08:00
mapIns . onRoomDownsyncFrame ( resp . rdf );
2022-10-03 00:22:05 +08:00
break ;
case window . DOWNSYNC_MSG_ACT_BATTLE_STOPPED :
mapIns . onBattleStopped ();
2022-09-20 23:50:01 +08:00
break ;
case window . DOWNSYNC_MSG_ACT_INPUT_BATCH :
2022-10-03 00:22:05 +08:00
mapIns . onInputFrameDownsyncBatch ( resp . inputFrameDownsyncBatch );
2022-09-20 23:50:01 +08:00
break ;
2022-10-01 15:14:05 +08:00
case window . DOWNSYNC_MSG_ACT_FORCED_RESYNC :
2022-10-04 11:24:47 +08:00
if ( null == resp . inputFrameDownsyncBatch || 0 >= resp . inputFrameDownsyncBatch . length ) {
2022-12-01 12:17:30 +08: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 11:24:47 +08:00
return ;
}
2022-12-01 00:30:35 +08:00
mapIns . onRoomDownsyncFrame ( resp . rdf , resp . inputFrameDownsyncBatch );
2022-10-01 15:14:05 +08:00
break ;
2022-09-20 23:50:01 +08:00
default :
break ;
}
} catch ( e ) {
2022-10-25 09:52:38 +08:00
console . error ( "Unexpected error when parsing data of:" , evt . data , e );
2022-09-20 23:50:01 +08:00
}
};
2022-10-25 09:52:38 +08:00
clientSession . onerror = function ( evt ) {
console . error ( "Error caught on the WS clientSession: " , evt );
2022-12-20 16:01:44 +08:00
window . clearLocalStorageAndBackToLoginScene ( true );
2022-09-20 23:50:01 +08:00
};
2022-10-25 09:52:38 +08:00
clientSession . onclose = function ( evt ) {
// [WARNING] The callback "onclose" might be called AFTER the webpage is refreshed with "1001 == evt.code".
2022-12-20 16:01:44 +08:00
console . warn ( `The WS clientSession is closed: evt= ${ JSON . stringify ( evt ) } , evt.code= ${ evt . code } ` );
switch ( evt . code ) {
2022-12-22 11:35:18 +08:00
case constants . RET_CODE . CLIENT_MISMATCHED_RENDER_FRAME :
break ;
2022-12-20 16:01:44 +08:00
case constants . RET_CODE . BATTLE_STOPPED :
// deliberately do nothing
2022-12-27 10:09:53 +08:00
if ( mapIns . frameDataLoggingEnabled ) {
console . warn ( ` ${ mapIns . _stringifyRdfIdToActuallyUsedInput () } ` );
}
2022-12-20 16:01:44 +08:00
break ;
case constants . RET_CODE . PLAYER_NOT_ADDABLE_TO_ROOM :
case constants . RET_CODE . PLAYER_NOT_READDABLE_TO_ROOM :
window . clearBoundRoomIdInBothVolatileAndPersistentStorage (); // To favor the player to join other rooms
mapIns . onManualRejoinRequired ( "Couldn't join any room at the moment, please retry" );
break ;
case constants . RET_CODE . ACTIVE_WATCHDOG :
mapIns . onManualRejoinRequired ( "Disconnected due to long-time inactivity, please rejoin" );
break ;
case constants . RET_CODE . UNKNOWN_ERROR :
case constants . RET_CODE . MYSQL_ERROR :
case constants . RET_CODE . PLAYER_NOT_FOUND :
case constants . RET_CODE . PLAYER_CHEATING :
case 1006 : // Peer(i.e. the backend) gone unexpectedly
2022-12-27 10:09:53 +08:00
if ( mapIns . frameDataLoggingEnabled ) {
console . warn ( ` ${ mapIns . _stringifyRdfIdToActuallyUsedInput () } ` );
}
2022-12-20 16:01:44 +08:00
window . clearLocalStorageAndBackToLoginScene ( true );
break ;
default :
break ;
2022-09-20 23:50:01 +08:00
}
};
};
window . clearLocalStorageAndBackToLoginScene = function ( shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) {
console . warn ( "+++++++ Calling `clearLocalStorageAndBackToLoginScene`" );
if ( window . mapIns && window . mapIns . musicEffectManagerScriptIns ) {
window . mapIns . musicEffectManagerScriptIns . stopAllMusic ();
}
2022-12-20 16:01:44 +08:00
2022-09-20 23:50:01 +08:00
window . closeWSConnection ();
window . clearSelfPlayer ();
if ( true != shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) {
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ();
}
2022-09-24 12:01:50 +08:00
cc . director . loadScene ( 'login' );
2022-09-20 23:50:01 +08:00
};
2023-01-18 15:36:04 +08:00
// For secondary ws session
window . initSecondarySession = function ( onopenCb , boundRoomId ) {
if ( window . secondarySession && window . secondarySession . readyState == WebSocket . OPEN ) {
if ( null != onopenCb ) {
onopenCb ();
}
return ;
}
const selfPlayerStr = cc . sys . localStorage . getItem ( "selfPlayer" );
const selfPlayer = null == selfPlayerStr ? null : JSON . parse ( selfPlayerStr );
const intAuthToken = null == selfPlayer ? "" : selfPlayer . intAuthToken ;
let urlToConnect = backendAddress . PROTOCOL . replace ( 'http' , 'ws' ) + '://' + backendAddress . HOST + ":" + backendAddress . PORT + "/tsrhtSecondary?isSecondary=true&intAuthToken=" + intAuthToken + "&boundRoomId=" + boundRoomId ;
const clientSession = new WebSocket ( urlToConnect );
clientSession . binaryType = 'arraybuffer' ; // Make 'event.data' of 'onmessage' an "ArrayBuffer" instead of a "Blob"
clientSession . onopen = function ( evt ) {
console . warn ( "The secondary WS clientSession is opened." );
window . secondarySession = clientSession ;
if ( null == onopenCb ) return ;
onopenCb ();
};
clientSession . onmessage = function ( evt ) {
if ( null == evt || null == evt . data ) {
return ;
}
try {
const resp = window . pb . protos . WsResp . decode ( new Uint8Array ( evt . data ));
//console.log(`Got non-empty onmessage decoded: resp.act=${resp.act}`);
switch ( resp . act ) {
case window . DOWNSYNC_MSG_ACT_PEER_INPUT_BATCH :
mapIns . onPeerInputFrameUpsync ( resp . peerJoinIndex , resp . inputFrameDownsyncBatch );
break ;
default :
break ;
}
} catch ( e ) {
console . error ( "Secondary ws session, unexpected error when parsing data of:" , evt . data , e );
}
};
clientSession . onerror = function ( evt ) {
console . error ( "Secondary ws session, error caught on the WS clientSession: " , evt );
};
clientSession . onclose = function ( evt ) {
// [WARNING] The callback "onclose" might be called AFTER the webpage is refreshed with "1001 == evt.code".
console . warn ( `Secondary ws session is closed: evt= ${ JSON . stringify ( evt ) } , evt.code= ${ evt . code } ` );
};
};