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 ;
2023-01-18 07:36:04 +00:00
window . DOWNSYNC _MSG _ACT _PEER _INPUT _BATCH = 5 ;
2023-01-25 10:26:13 +00:00
window . DOWNSYNC _MSG _ACT _PEER _UDP _ADDR = 6 ;
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 ) ) ;
}
2022-12-20 08:01:44 +00: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 ;
}
2023-01-25 15:47:54 +00:00
console . log ( ` Closing "window.clientSession" from the client-side with code= ${ code } . ` ) ;
2022-12-20 08:01:44 +00:00
window . clientSession . close ( code , reason ) ;
2022-09-20 15:50:01 +00: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" ) ;
} ;
2023-01-25 10:26:13 +00:00
window . getBoundRoomCapacityFromPersistentStorage = function ( ) {
const boundRoomIdExpiresAt = parseInt ( cc . sys . localStorage . getItem ( "boundRoomIdExpiresAt" ) ) ;
if ( ! boundRoomIdExpiresAt || Date . now ( ) >= boundRoomIdExpiresAt ) {
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
return null ;
}
return cc . sys . localStorage . getItem ( "boundRoomCapacity" ) ;
} ;
2022-09-20 15:50:01 +00:00
window . clearBoundRoomIdInBothVolatileAndPersistentStorage = function ( ) {
window . boundRoomId = null ;
cc . sys . localStorage . removeItem ( "boundRoomId" ) ;
2023-01-25 10:26:13 +00:00
cc . sys . localStorage . removeItem ( "boundRoomCapacity" ) ;
2022-09-20 15:50:01 +00:00
cc . sys . localStorage . removeItem ( "boundRoomIdExpiresAt" ) ;
} ;
window . clearSelfPlayer = function ( ) {
cc . sys . localStorage . removeItem ( "selfPlayer" ) ;
} ;
window . boundRoomId = getBoundRoomIdFromPersistentStorage ( ) ;
2023-01-25 10:26:13 +00:00
window . boundRoomCapacity = getBoundRoomCapacityFromPersistentStorage ( ) ;
2022-09-20 15:50:01 +00:00
window . handleHbRequirements = function ( resp ) {
2023-01-25 10:26:13 +00:00
console . log ( ` Handle hb requirements #1 ` ) ;
2022-09-20 15:50:01 +00:00
if ( constants . RET _CODE . OK != resp . ret ) return ;
2023-01-25 10:26:13 +00:00
// The assignment of "window.mapIns" is inside "Map.onLoad", which precedes "initPersistentSessionClient".
window . mapIns . selfPlayerInfo = JSON . parse ( cc . sys . localStorage . getItem ( 'selfPlayer' ) ) ; // This field is kept for distinguishing "self" and "others".
window . mapIns . selfPlayerInfo . Id = window . mapIns . selfPlayerInfo . playerId ;
window . mapIns . selfPlayerInfo . JoinIndex = resp . peerJoinIndex ;
console . log ( ` Handle hb requirements #2 ` ) ;
if ( null == window . boundRoomId || null == window . boundRoomCapacity ) {
2022-09-20 15:50:01 +00:00
window . boundRoomId = resp . bciFrame . boundRoomId ;
2023-01-25 10:26:13 +00:00
window . boundRoomCapacity = resp . bciFrame . boundRoomCapacity ;
2022-09-20 15:50:01 +00:00
cc . sys . localStorage . setItem ( 'boundRoomId' , window . boundRoomId ) ;
2023-01-25 10:26:13 +00:00
cc . sys . localStorage . setItem ( 'boundRoomCapacity' , window . boundRoomCapacity ) ;
2022-09-20 15:50:01 +00:00
cc . sys . localStorage . setItem ( 'boundRoomIdExpiresAt' , Date . now ( ) + 10 * 60 * 1000 ) ; // Temporarily hardcoded, for `boundRoomId` only.
}
2023-01-25 10:26:13 +00:00
console . log ( ` Handle hb requirements #3 ` ) ;
2022-09-20 15:50:01 +00:00
if ( window . handleBattleColliderInfo ) {
window . handleBattleColliderInfo ( resp . bciFrame ) ;
}
2023-01-25 10:26:13 +00:00
console . log ( ` Handle hb requirements #4 ` ) ;
if ( ! cc . sys . isNative ) {
console . log ( ` Handle hb requirements #5, web ` ) ;
window . initSecondarySession ( null , window . boundRoomId ) ;
} else {
console . log ( ` Handle hb requirements #5, native ` ) ;
const res1 = DelayNoMore . UdpSession . openUdpSession ( 8888 + window . mapIns . selfPlayerInfo . JoinIndex ) ;
const intAuthToken = window . mapIns . selfPlayerInfo . intAuthToken ;
const authKey = Math . floor ( Math . random ( ) * 65535 ) ;
window . mapIns . selfPlayerInfo . authKey = authKey ;
const holePunchData = window . pb . protos . HolePunchUpsync . encode ( {
boundRoomId : window . boundRoomId ,
intAuthToken : intAuthToken ,
authKey : authKey ,
} ) . finish ( ) ;
const res2 = DelayNoMore . UdpSession . punchToServer ( backendAddress . HOST , 3000 , holePunchData ) ;
}
2022-09-20 15:50:01 +00:00
} ;
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 ( ) ;
2023-01-25 10:26:13 +00:00
window . boundRoomCapacity = getBoundRoomCapacityFromPersistentStorage ( ) ;
2022-09-20 15:50:01 +00:00
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 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-20 15:51:53 +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 "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 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 ;
2023-01-25 10:26:13 +00:00
case window . DOWNSYNC _MSG _ACT _PEER _UDP _ADDR :
console . warn ( ` Got DOWNSYNC_MSG_ACT_PEER_UDP_ADDR resp= ${ JSON . stringify ( resp , null , 2 ) } ` ) ;
if ( cc . sys . isNative ) {
const peerJoinIndex = resp . peerJoinIndex ;
const peerAddrList = resp . rdf . peerUdpAddrList ;
const peerAddr = peerAddrList [ peerJoinIndex - 1 ] ;
console . log ( ` Got DOWNSYNC_MSG_ACT_PEER_UDP_ADDR peerAddr= ${ peerAddr } ; boundRoomCapacity= ${ window . boundRoomCapacity } , mapIns.selfPlayerInfo= ${ window . mapIns . selfPlayerInfo } ` ) ;
DelayNoMore . UdpSession . upsertPeerUdpAddr ( peerJoinIndex , peerAddr . ip , peerAddr . port , peerAddr . authKey , window . boundRoomCapacity , window . mapIns . selfPlayerInfo . JoinIndex ) ;
}
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 ) ;
2022-12-20 08:01:44 +00:00
window . clearLocalStorageAndBackToLoginScene ( true ) ;
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".
2022-12-20 08:01:44 +00:00
console . warn ( ` The WS clientSession is closed: evt= ${ JSON . stringify ( evt ) } , evt.code= ${ evt . code } ` ) ;
2023-01-25 15:47:54 +00:00
if ( cc . sys . isNative ) {
if ( mapIns . frameDataLoggingEnabled ) {
console . warn ( ` ${ mapIns . _stringifyRdfIdToActuallyUsedInput ( ) } ` ) ;
}
DelayNoMore . UdpSession . closeUdpSession ( ) ;
}
2022-12-20 08:01:44 +00:00
switch ( evt . code ) {
2022-12-22 03:35:18 +00:00
case constants . RET _CODE . CLIENT _MISMATCHED _RENDER _FRAME :
break ;
2022-12-20 08:01:44 +00:00
case constants . RET _CODE . BATTLE _STOPPED :
// deliberately do nothing
2022-12-27 02:09:53 +00:00
if ( mapIns . frameDataLoggingEnabled ) {
console . warn ( ` ${ mapIns . _stringifyRdfIdToActuallyUsedInput ( ) } ` ) ;
}
2022-12-20 08:01:44 +00: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 :
2023-01-25 15:47:54 +00:00
case 1006 : // Peer(i.e. the backend) gone unexpectedly, but not working for "cc.sys.isNative"
2022-12-27 02:09:53 +00:00
if ( mapIns . frameDataLoggingEnabled ) {
console . warn ( ` ${ mapIns . _stringifyRdfIdToActuallyUsedInput ( ) } ` ) ;
}
2022-12-20 08:01:44 +00:00
window . clearLocalStorageAndBackToLoginScene ( true ) ;
break ;
default :
break ;
2022-09-20 15:50:01 +00:00
}
} ;
} ;
window . clearLocalStorageAndBackToLoginScene = function ( shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) {
console . warn ( "+++++++ Calling `clearLocalStorageAndBackToLoginScene`" ) ;
2023-01-25 15:47:54 +00:00
window . clearSelfPlayer ( ) ;
if ( true != shouldRetainBoundRoomIdInBothVolatileAndPersistentStorage ) {
window . clearBoundRoomIdInBothVolatileAndPersistentStorage ( ) ;
}
2022-09-20 15:50:01 +00:00
if ( window . mapIns && window . mapIns . musicEffectManagerScriptIns ) {
window . mapIns . musicEffectManagerScriptIns . stopAllMusic ( ) ;
}
2022-12-20 08:01:44 +00:00
2022-09-24 04:01:50 +00:00
cc . director . loadScene ( 'login' ) ;
2022-09-20 15:50:01 +00:00
} ;
2023-01-18 07:36:04 +00: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 } ` ) ;
} ;
} ;