mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
1 Commits
feat/impro
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
![]() |
625fef0d39 |
@@ -46,8 +46,8 @@ module.exports = {
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.setHelpPath('/all-features/multiplayer')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -74,8 +74,8 @@ module.exports = {
|
||||
.addParameter('yesorno', _('Show close button'), '', false)
|
||||
.setHelpPath('/all-features/multiplayer')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -102,8 +102,8 @@ module.exports = {
|
||||
)
|
||||
.setHelpPath('/all-features/multiplayer')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -132,8 +132,8 @@ module.exports = {
|
||||
.addParameter('string', _('Message name'), '', false)
|
||||
.addParameter('string', _('Message content'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -162,8 +162,8 @@ module.exports = {
|
||||
.addParameter('string', _('Message name'), '', false)
|
||||
.addParameter('variable', _('Variable'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -194,8 +194,8 @@ module.exports = {
|
||||
.addParameter('string', _('Message name'), '', false)
|
||||
.addParameter('variable', _('Variable'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -222,8 +222,8 @@ module.exports = {
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -247,8 +247,8 @@ module.exports = {
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -272,8 +272,8 @@ module.exports = {
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -297,8 +297,8 @@ module.exports = {
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -325,8 +325,8 @@ module.exports = {
|
||||
)
|
||||
.addParameter('string', _('Message name'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -352,8 +352,8 @@ module.exports = {
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -377,8 +377,8 @@ module.exports = {
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -403,8 +403,8 @@ module.exports = {
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.addParameter('number', _('Player number'), '', false)
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -429,8 +429,8 @@ module.exports = {
|
||||
)
|
||||
.addParameter('string', _('Message name'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -453,8 +453,8 @@ module.exports = {
|
||||
)
|
||||
.addParameter('string', _('Message name'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -477,8 +477,8 @@ module.exports = {
|
||||
_('Lobbies'),
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -502,8 +502,8 @@ module.exports = {
|
||||
_('Lobbies'),
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -532,8 +532,8 @@ module.exports = {
|
||||
false
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -555,8 +555,8 @@ module.exports = {
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -584,8 +584,8 @@ module.exports = {
|
||||
false
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -607,8 +607,8 @@ module.exports = {
|
||||
'JsPlatform/Extensions/multiplayer.svg'
|
||||
)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -637,8 +637,8 @@ module.exports = {
|
||||
'number',
|
||||
gd.ParameterOptions.makeNewOptions().setDescription(_('Player number'))
|
||||
)
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -669,8 +669,8 @@ module.exports = {
|
||||
.addParameter('variable', _('Variable'), '', false)
|
||||
.setHelpPath('/all-features/multiplayer')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -700,8 +700,8 @@ module.exports = {
|
||||
.addParameter('variable', _('Variable'), '', false)
|
||||
.setHelpPath('/all-features/multiplayer')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -731,8 +731,8 @@ module.exports = {
|
||||
.addParameter('variable', _('Variable'), '', false)
|
||||
.setHelpPath('/all-features/multiplayer')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
@@ -837,8 +837,8 @@ module.exports = {
|
||||
multiplayerObjectBehavior,
|
||||
sharedData
|
||||
)
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setIncludeFile('Extensions/Multiplayer/peer.js')
|
||||
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
|
||||
.addIncludeFile(
|
||||
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
|
||||
)
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -228,7 +228,7 @@ namespace gdjs {
|
||||
// Before sending the change owner message, if we are becoming the new owner,
|
||||
// we want to ensure this message is acknowledged, by everyone we're connected to.
|
||||
if (variableData.newVariableOwner === currentPlayerNumber) {
|
||||
const otherPeerIds = gdjs.evtTools.p2p.getAllPeers();
|
||||
const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||
const variableOwnerChangedMessageName = gdjs.multiplayerMessageManager.createVariableOwnerChangedMessageNameFromChangeVariableOwnerMessage(
|
||||
messageName
|
||||
);
|
||||
@@ -243,14 +243,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
debugLogger.info('Sending change owner message', messageName);
|
||||
const connectedPeerIds = gdjs.evtTools.p2p.getAllPeers();
|
||||
for (const peerId of connectedPeerIds) {
|
||||
gdjs.multiplayerMessageManager.sendDataTo(
|
||||
peerId,
|
||||
messageName,
|
||||
messageData
|
||||
);
|
||||
}
|
||||
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||
gdjs.multiplayerMessageManager.sendDataTo(
|
||||
connectedPeerIds,
|
||||
messageName,
|
||||
messageData
|
||||
);
|
||||
|
||||
// Remove the variable from the list of variables ownership changes to sync.
|
||||
delete variableOwnershipChangesToSyncAtEndOfFrame[variableNetworkId];
|
||||
|
@@ -101,17 +101,19 @@ namespace gdjs {
|
||||
}, this._timeBeforeDestroyingObjectWithoutNetworkIdInMs);
|
||||
}
|
||||
|
||||
private _sendDataToPeersWithIncreasedClock(
|
||||
private _sendDataToPeersWithIncreasedClock = async (
|
||||
messageName: string,
|
||||
data: Object
|
||||
) {
|
||||
) => {
|
||||
this._clock++;
|
||||
data['_clock'] = this._clock;
|
||||
const connectedPeerIds = gdjs.evtTools.p2p.getAllPeers();
|
||||
for (const peerId of connectedPeerIds) {
|
||||
gdjs.multiplayerMessageManager.sendDataTo(peerId, messageName, data);
|
||||
}
|
||||
}
|
||||
const connectedPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||
await gdjs.multiplayerMessageManager.sendDataTo(
|
||||
connectedPeerIds,
|
||||
messageName,
|
||||
data
|
||||
);
|
||||
};
|
||||
|
||||
private _isOwnerAsPlayerOrHost() {
|
||||
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
|
||||
@@ -453,7 +455,7 @@ namespace gdjs {
|
||||
// If we are player 1, we are connected to everyone, so we expect an acknowledgment from everyone.
|
||||
// If we are another player, we are only connected to player 1, so we expect an acknowledgment from player 1.
|
||||
// In both cases, this represents the list of peers the current user is connected to.
|
||||
const otherPeerIds = gdjs.evtTools.p2p.getAllPeers();
|
||||
const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||
const {
|
||||
messageName: destroyMessageName,
|
||||
messageData: destroyMessageData,
|
||||
@@ -563,7 +565,7 @@ namespace gdjs {
|
||||
// If we are another player, we are only connected to player 1, so we expect an acknowledgment from player 1.
|
||||
// In both cases, this represents the list of peers the current user is connected to.
|
||||
if (newObjectPlayerNumber === currentPlayerNumber) {
|
||||
const otherPeerIds = gdjs.evtTools.p2p.getAllPeers();
|
||||
const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||
const changeOwnerAcknowledgedMessageName = gdjs.multiplayerMessageManager.createInstanceOwnerChangedMessageNameFromChangeInstanceOwnerMessage(
|
||||
messageName
|
||||
);
|
||||
@@ -586,6 +588,9 @@ namespace gdjs {
|
||||
// If we are the new owner, also send directly an update of the position,
|
||||
// so that the object is immediately moved on the screen and we don't wait for the next tick.
|
||||
if (newObjectPlayerNumber === currentPlayerNumber) {
|
||||
debugLogger.info(
|
||||
'Sending update message to move the object immediately.'
|
||||
);
|
||||
const objectNetworkSyncData = this.owner.getNetworkSyncData();
|
||||
const {
|
||||
messageName: updateMessageName,
|
||||
|
@@ -562,7 +562,7 @@ namespace gdjs {
|
||||
// When the connectionId is received, initialise PeerJS so players can connect to each others afterwards.
|
||||
if (validIceServers.length) {
|
||||
for (const server of validIceServers) {
|
||||
gdjs.evtTools.p2p.useCustomICECandidate(
|
||||
gdjs.multiplayerPeerJsHelper.useCustomICECandidate(
|
||||
server.urls,
|
||||
server.username,
|
||||
server.credential
|
||||
@@ -570,7 +570,7 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
if (brokerServerConfig) {
|
||||
gdjs.evtTools.p2p.useCustomBrokerServer(
|
||||
gdjs.multiplayerPeerJsHelper.useCustomBrokerServer(
|
||||
brokerServerConfig.hostname,
|
||||
brokerServerConfig.port,
|
||||
brokerServerConfig.path,
|
||||
@@ -578,7 +578,7 @@ namespace gdjs {
|
||||
brokerServerConfig.secure
|
||||
);
|
||||
} else {
|
||||
gdjs.evtTools.p2p.useDefaultBrokerServer();
|
||||
gdjs.multiplayerPeerJsHelper.useDefaultBrokerServer();
|
||||
}
|
||||
|
||||
_connectionId = connectionId;
|
||||
@@ -705,7 +705,7 @@ namespace gdjs {
|
||||
// It is possible the connection to other players didn't work.
|
||||
// If that's the case, show an error message and leave the lobby.
|
||||
// If we are the host, still start the game, as this allows a player to test the game alone.
|
||||
const allConnectedPeers = gdjs.evtTools.p2p.getAllPeers();
|
||||
const allConnectedPeers = gdjs.multiplayerPeerJsHelper.getAllPeers();
|
||||
if (!isPlayerHost() && allConnectedPeers.length === 0) {
|
||||
gdjs.multiplayerComponents.displayConnectionErrorNotification(
|
||||
runtimeScene
|
||||
@@ -775,7 +775,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
// Disconnect from any P2P connections.
|
||||
gdjs.evtTools.p2p.disconnectFromAllPeers();
|
||||
gdjs.multiplayerPeerJsHelper.disconnectFromAllPeers();
|
||||
|
||||
// Clear the expected acknowledgments, as the game is ending.
|
||||
gdjs.multiplayerMessageManager.clearExpectedMessageAcknowledgements();
|
||||
@@ -787,7 +787,7 @@ namespace gdjs {
|
||||
*/
|
||||
const handlePeerIdEvent = function (peerId: string) {
|
||||
// When a peerId is received, trigger a P2P connection with the peer.
|
||||
const currentPeerId = gdjs.evtTools.p2p.getCurrentId();
|
||||
const currentPeerId = gdjs.multiplayerPeerJsHelper.getCurrentId();
|
||||
if (!currentPeerId) {
|
||||
logger.error(
|
||||
'No peerId found, the player does not seem connected to the broker server.'
|
||||
@@ -800,7 +800,7 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
|
||||
gdjs.evtTools.p2p.connect(peerId);
|
||||
gdjs.multiplayerPeerJsHelper.connect(peerId);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -862,6 +862,8 @@ namespace gdjs {
|
||||
// Consider the game is ended, so that we don't listen to other players disconnecting.
|
||||
_isLobbyGameRunning = false;
|
||||
|
||||
logger.info('Ending the lobby game.');
|
||||
|
||||
// Inform the players that the game has ended.
|
||||
gdjs.multiplayerMessageManager.sendEndGameMessage();
|
||||
|
||||
@@ -912,7 +914,7 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
|
||||
const peerId = gdjs.evtTools.p2p.getCurrentId();
|
||||
const peerId = gdjs.multiplayerPeerJsHelper.getCurrentId();
|
||||
if (!peerId) {
|
||||
logger.error(
|
||||
"No peerId found, the player doesn't seem connected to the broker server."
|
||||
|
10
Extensions/Multiplayer/peer.js
Normal file
10
Extensions/Multiplayer/peer.js
Normal file
File diff suppressed because one or more lines are too long
1
Extensions/Multiplayer/peer.js.map
Normal file
1
Extensions/Multiplayer/peer.js.map
Normal file
File diff suppressed because one or more lines are too long
414
Extensions/Multiplayer/peerJsHelper.ts
Normal file
414
Extensions/Multiplayer/peerJsHelper.ts
Normal file
@@ -0,0 +1,414 @@
|
||||
/// <reference path="peerjs.d.ts" />
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Multiplayer');
|
||||
export namespace multiplayerPeerJsHelper {
|
||||
/**
|
||||
* The type of the data that is sent across peerjs.
|
||||
* We use UInt8Array to send compressed data, but we only manipulate objects once received.
|
||||
*/
|
||||
type NetworkMessage = {
|
||||
messageName: string;
|
||||
data: Uint8Array;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to discard invalid messages when received.
|
||||
*/
|
||||
const isValidNetworkMessage = (
|
||||
message: unknown
|
||||
): message is NetworkMessage =>
|
||||
typeof message === 'object' &&
|
||||
message !== null &&
|
||||
typeof message['messageName'] === 'string' &&
|
||||
typeof message['data'] === 'object';
|
||||
|
||||
export interface IMessageData {
|
||||
readonly data: any; // The data sent with the message, an object with unknown content.
|
||||
readonly sender: String;
|
||||
getData(): any;
|
||||
getSender(): string;
|
||||
}
|
||||
/**
|
||||
* The data bound to a message name.
|
||||
*/
|
||||
export class MessageData implements IMessageData {
|
||||
public readonly data: any;
|
||||
public readonly sender: string;
|
||||
constructor(data: object, sender: string) {
|
||||
this.data = data;
|
||||
this.sender = sender;
|
||||
}
|
||||
public getData(): any {
|
||||
return this.data;
|
||||
}
|
||||
public getSender(): string {
|
||||
return this.sender;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IMessagesList {
|
||||
getName(): string;
|
||||
getMessages(): IMessageData[];
|
||||
pushMessage(data: object, sender: string): void;
|
||||
}
|
||||
export class MessagesList implements IMessagesList {
|
||||
private readonly data: IMessageData[] = [];
|
||||
private readonly messageName: string;
|
||||
|
||||
constructor(messageName: string) {
|
||||
this.messageName = messageName;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return this.messageName;
|
||||
}
|
||||
|
||||
public getMessages(): IMessageData[] {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public pushMessage(data: object, sender: string): void {
|
||||
this.data.push(new MessageData(data, sender));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The peer to peer configuration.
|
||||
*/
|
||||
let peerConfig: Peer.PeerJSOption = { debug: 1 };
|
||||
|
||||
/**
|
||||
* The p2p client.
|
||||
*/
|
||||
let peer: Peer<NetworkMessage> | null = null;
|
||||
|
||||
/**
|
||||
* All connected p2p clients, keyed by their ID.
|
||||
*/
|
||||
const connections = new Map<string, Peer.DataConnection<NetworkMessage>>();
|
||||
|
||||
/**
|
||||
* Contains a map of message triggered by other p2p clients.
|
||||
* It is keyed by the event name.
|
||||
*/
|
||||
const allMessages = new Map<string, IMessagesList>();
|
||||
|
||||
/**
|
||||
* True if PeerJS is initialized and ready.
|
||||
*/
|
||||
let ready = false;
|
||||
|
||||
/**
|
||||
* List of IDs of peers that just disconnected.
|
||||
*/
|
||||
const justDisconnectedPeers: string[] = [];
|
||||
|
||||
/**
|
||||
* List of IDs of peers that just remotely initiated a connection.
|
||||
*/
|
||||
const justConnectedPeers: string[] = [];
|
||||
|
||||
/**
|
||||
* Helper function to compress data sent over the network.
|
||||
*/
|
||||
async function compressData(data: object): Promise<Uint8Array> {
|
||||
const jsonString = JSON.stringify(data); // Convert object to JSON string
|
||||
const encoder = new TextEncoder();
|
||||
const array = encoder.encode(jsonString); // Convert string to Uint8Array
|
||||
|
||||
// @ts-ignore - As of 09/2023 the CompressionStream is now available in all browsers.
|
||||
const cs = new CompressionStream('gzip'); // Create a CompressionStream with gzip
|
||||
const writer = cs.writable.getWriter();
|
||||
writer.write(array);
|
||||
writer.close();
|
||||
|
||||
const compressedStream = cs.readable;
|
||||
const reader = compressedStream.getReader();
|
||||
const chunks: any[] = [];
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
}
|
||||
|
||||
const compressedData = new Uint8Array(
|
||||
chunks.reduce((acc, chunk) => acc.concat(Array.from(chunk)), [])
|
||||
);
|
||||
return compressedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to decompress data received over the network.
|
||||
* It returns the parsed JSON object, if valid, or undefined.
|
||||
*/
|
||||
async function decompressData(
|
||||
compressedData: Uint8Array
|
||||
): Promise<object | undefined> {
|
||||
// @ts-ignore - As of 09/2023 the DecompressionStream is now available in all browsers.
|
||||
const ds = new DecompressionStream('gzip'); // Create a DecompressionStream with gzip
|
||||
const writer = ds.writable.getWriter();
|
||||
writer.write(compressedData);
|
||||
writer.close();
|
||||
|
||||
const decompressedStream = ds.readable;
|
||||
const reader = decompressedStream.getReader();
|
||||
const chunks: any[] = [];
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
chunks.push(value);
|
||||
}
|
||||
|
||||
const decompressedData = new Uint8Array(
|
||||
chunks.reduce((acc, chunk) => acc.concat(Array.from(chunk)), [])
|
||||
);
|
||||
const decoder = new TextDecoder();
|
||||
const jsonStringData = decoder.decode(decompressedData); // Convert Uint8Array back to string
|
||||
try {
|
||||
const parsedData = JSON.parse(jsonStringData);
|
||||
return parsedData;
|
||||
} catch (e) {
|
||||
logger.error(`Error while parsing message: ${e.toString()}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the messages list for a given message name.
|
||||
*/
|
||||
export const getOrCreateMessagesList = (
|
||||
messageName: string
|
||||
): IMessagesList => {
|
||||
const messagesList = allMessages.get(messageName);
|
||||
if (messagesList) return messagesList;
|
||||
const newMessagesList = new MessagesList(messageName);
|
||||
allMessages.set(messageName, newMessagesList);
|
||||
return newMessagesList;
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal function called when a connection with a remote peer is initiated.
|
||||
* @param connection The DataConnection of the peer
|
||||
*/
|
||||
const _onConnect = (connection: Peer.DataConnection<NetworkMessage>) => {
|
||||
connections.set(connection.peer, connection);
|
||||
connection.on('data', async (data) => {
|
||||
if (isValidNetworkMessage(data)) {
|
||||
const messagesList = getOrCreateMessagesList(data.messageName);
|
||||
const messageSender = connection.peer;
|
||||
const decompressedData = await decompressData(data.data);
|
||||
if (!decompressedData) return;
|
||||
|
||||
messagesList.pushMessage(decompressedData, messageSender);
|
||||
}
|
||||
});
|
||||
|
||||
// Close event is only for graceful disconnection,
|
||||
// but we want onDisconnect to trigger for any type of disconnection,
|
||||
// so we register a listener for both event types.
|
||||
connection.on('error', () => {
|
||||
_onDisconnect(connection.peer);
|
||||
});
|
||||
connection.on('close', () => {
|
||||
_onDisconnect(connection.peer);
|
||||
});
|
||||
|
||||
// Regularly check for disconnection as the built in way is not reliable.
|
||||
(function disconnectChecker() {
|
||||
if (
|
||||
connection.peerConnection &&
|
||||
(connection.peerConnection.connectionState === 'failed' ||
|
||||
connection.peerConnection.connectionState === 'disconnected' ||
|
||||
connection.peerConnection.connectionState === 'closed')
|
||||
) {
|
||||
_onDisconnect(connection.peer);
|
||||
} else {
|
||||
setTimeout(disconnectChecker, 1000);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal function called when a remote client disconnects.
|
||||
* @param connectionID The ID of the peer that disconnected.
|
||||
*/
|
||||
const _onDisconnect = (connectionID: string) => {
|
||||
if (!connections.has(connectionID)) return;
|
||||
justDisconnectedPeers.push(connectionID);
|
||||
connections.delete(connectionID);
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal function called to initialize PeerJS after it
|
||||
* has been configured.
|
||||
*/
|
||||
const loadPeerJS = () => {
|
||||
if (peer !== null) return;
|
||||
peer = new Peer(peerConfig);
|
||||
peer.on('open', () => {
|
||||
ready = true;
|
||||
});
|
||||
peer.on('error', (errorMessage) => {
|
||||
logger.error('PeerJS error:', errorMessage);
|
||||
});
|
||||
peer.on('connection', (connection) => {
|
||||
connection.on('open', () => {
|
||||
_onConnect(connection);
|
||||
justConnectedPeers.push(connection.peer);
|
||||
});
|
||||
});
|
||||
peer.on('close', () => {
|
||||
peer = null;
|
||||
loadPeerJS();
|
||||
});
|
||||
peer.on('disconnected', peer.reconnect);
|
||||
};
|
||||
|
||||
/**
|
||||
* Connects to another p2p client.
|
||||
* @param id - The other client's ID.
|
||||
*/
|
||||
export const connect = (id: string) => {
|
||||
if (peer === null) return;
|
||||
const connection = peer.connect(id);
|
||||
connection.on('open', () => {
|
||||
_onConnect(connection);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects from all other p2p clients.
|
||||
*/
|
||||
export const disconnectFromAllPeers = () => {
|
||||
for (const connection of connections.values()) connection.close();
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a message to a specific peer.
|
||||
* @param ids - The IDs of the clients to send the event to.
|
||||
* @param messageName - The event to trigger.
|
||||
* @param eventData - Additional data to send with the event.
|
||||
*/
|
||||
export const sendDataTo = async (
|
||||
ids: string[],
|
||||
messageName: string,
|
||||
messageData: object
|
||||
) => {
|
||||
if (!ids.length) return;
|
||||
|
||||
const compressedData = await compressData(messageData);
|
||||
|
||||
for (const id of ids) {
|
||||
const connection = connections.get(id);
|
||||
if (connection) {
|
||||
connection.send({
|
||||
messageName,
|
||||
data: compressedData,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllMessagesMap = () => allMessages;
|
||||
|
||||
/**
|
||||
* Connects to a custom broker server.
|
||||
* @param host The host of the broker server.
|
||||
* @param port The port of the broker server.
|
||||
* @param path The path (part of the url after the host) to the broker server.
|
||||
* @param key Optional password to connect to the broker server.
|
||||
* @param ssl Use ssl?
|
||||
*/
|
||||
export const useCustomBrokerServer = (
|
||||
host: string,
|
||||
port: number,
|
||||
path: string,
|
||||
key: string,
|
||||
ssl: boolean
|
||||
) => {
|
||||
Object.assign(peerConfig, {
|
||||
host,
|
||||
port,
|
||||
path,
|
||||
secure: ssl,
|
||||
// All servers have "peerjs" as default key
|
||||
key: key.length === 0 ? 'peerjs' : key,
|
||||
});
|
||||
loadPeerJS();
|
||||
};
|
||||
|
||||
export const useDefaultBrokerServer = loadPeerJS;
|
||||
|
||||
/**
|
||||
* Adds an ICE server candidate, and removes the default ones provided by PeerJs. Must be called before connecting to a broker.
|
||||
* @param urls The URL of the STUN/TURN server.
|
||||
* @param username An optional username to send to the server.
|
||||
* @param credential An optional password to send to the server.
|
||||
*/
|
||||
export const useCustomICECandidate = (
|
||||
urls: string,
|
||||
username?: string,
|
||||
credential?: string
|
||||
) => {
|
||||
peerConfig.config = peerConfig.config || {};
|
||||
peerConfig.config.iceServers = peerConfig.config.iceServers || [];
|
||||
peerConfig.config.iceServers.push({
|
||||
urls,
|
||||
username,
|
||||
credential,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Forces the usage of a relay (TURN) server, to avoid sharing IP addresses with the other peers.
|
||||
* @param shouldUseRelayServer Whether relay-only should be enabled or disabled.
|
||||
*/
|
||||
export const forceUseRelayServer = (shouldUseRelayServer: boolean) => {
|
||||
peerConfig.config = peerConfig.config || {};
|
||||
peerConfig.config.iceTransportPolicy = shouldUseRelayServer
|
||||
? 'relay'
|
||||
: 'all';
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the own current peer ID.
|
||||
* @see Peer.id
|
||||
*/
|
||||
export const getCurrentId = (): string => {
|
||||
if (peer == undefined) return '';
|
||||
return peer.id || '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true once PeerJS finished initialization.
|
||||
* @see ready
|
||||
*/
|
||||
export const isReady = () => ready;
|
||||
|
||||
/**
|
||||
* Return any disconnected peers.
|
||||
*/
|
||||
export const getJustDisconnectedPeers = () => justDisconnectedPeers;
|
||||
|
||||
/**
|
||||
* Returns the list of all currently connected peers.
|
||||
*/
|
||||
export const getAllPeers = () => Array.from(connections.keys());
|
||||
|
||||
gdjs.callbacksRuntimeScenePostEvents.push(() => {
|
||||
// Clear the list of messages at the end of the frame, assuming they've been all processed.
|
||||
for (const messagesList of allMessages.values()) {
|
||||
messagesList.getMessages().length = 0;
|
||||
}
|
||||
// Clear the list of just connected and disconnected peers.
|
||||
if (justDisconnectedPeers.length > 0) {
|
||||
justDisconnectedPeers.length = 0;
|
||||
}
|
||||
if (justConnectedPeers.length > 0) {
|
||||
justConnectedPeers.length = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
509
Extensions/Multiplayer/peerjs.d.ts
vendored
Normal file
509
Extensions/Multiplayer/peerjs.d.ts
vendored
Normal file
@@ -0,0 +1,509 @@
|
||||
/**
|
||||
* Minimal `EventEmitter` interface that is molded against the Node.js
|
||||
* `EventEmitter` interface.
|
||||
*/
|
||||
declare class EventEmitter<
|
||||
EventTypes extends EventEmitter.ValidEventTypes = string | symbol,
|
||||
Context extends any = any
|
||||
> {
|
||||
static prefixed: string | boolean;
|
||||
|
||||
/**
|
||||
* Return an array listing the events for which the emitter has registered
|
||||
* listeners.
|
||||
*/
|
||||
eventNames(): Array<EventEmitter.EventNames<EventTypes>>;
|
||||
|
||||
/**
|
||||
* Return the listeners registered for a given event.
|
||||
*/
|
||||
listeners<T extends EventEmitter.EventNames<EventTypes>>(
|
||||
event: T
|
||||
): Array<EventEmitter.EventListener<EventTypes, T>>;
|
||||
|
||||
/**
|
||||
* Return the number of listeners listening to a given event.
|
||||
*/
|
||||
listenerCount(event: EventEmitter.EventNames<EventTypes>): number;
|
||||
|
||||
/**
|
||||
* Calls each of the listeners registered for a given event.
|
||||
*/
|
||||
emit<T extends EventEmitter.EventNames<EventTypes>>(
|
||||
event: T,
|
||||
...args: EventEmitter.EventArgs<EventTypes, T>
|
||||
): boolean;
|
||||
|
||||
/**
|
||||
* Add a listener for a given event.
|
||||
*/
|
||||
on<T extends EventEmitter.EventNames<EventTypes>>(
|
||||
event: T,
|
||||
fn: EventEmitter.EventListener<EventTypes, T>,
|
||||
context?: Context
|
||||
): this;
|
||||
addListener<T extends EventEmitter.EventNames<EventTypes>>(
|
||||
event: T,
|
||||
fn: EventEmitter.EventListener<EventTypes, T>,
|
||||
context?: Context
|
||||
): this;
|
||||
|
||||
/**
|
||||
* Add a one-time listener for a given event.
|
||||
*/
|
||||
once<T extends EventEmitter.EventNames<EventTypes>>(
|
||||
event: T,
|
||||
fn: EventEmitter.EventListener<EventTypes, T>,
|
||||
context?: Context
|
||||
): this;
|
||||
|
||||
/**
|
||||
* Remove the listeners of a given event.
|
||||
*/
|
||||
removeListener<T extends EventEmitter.EventNames<EventTypes>>(
|
||||
event: T,
|
||||
fn?: EventEmitter.EventListener<EventTypes, T>,
|
||||
context?: Context,
|
||||
once?: boolean
|
||||
): this;
|
||||
off<T extends EventEmitter.EventNames<EventTypes>>(
|
||||
event: T,
|
||||
fn?: EventEmitter.EventListener<EventTypes, T>,
|
||||
context?: Context,
|
||||
once?: boolean
|
||||
): this;
|
||||
|
||||
/**
|
||||
* Remove all listeners, or those of the specified event.
|
||||
*/
|
||||
removeAllListeners(event?: EventEmitter.EventNames<EventTypes>): this;
|
||||
}
|
||||
|
||||
declare namespace EventEmitter {
|
||||
export interface ListenerFn<Args extends any[] = any[]> {
|
||||
(...args: Args): void;
|
||||
}
|
||||
|
||||
export interface EventEmitterStatic {
|
||||
new <
|
||||
EventTypes extends ValidEventTypes = string | symbol,
|
||||
Context = any
|
||||
>(): EventEmitter<EventTypes, Context>;
|
||||
}
|
||||
|
||||
/**
|
||||
* `object` should be in either of the following forms:
|
||||
* ```
|
||||
* interface EventTypes {
|
||||
* 'event-with-parameters': any[]
|
||||
* 'event-with-example-handler': (...args: any[]) => void
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export type ValidEventTypes = string | symbol | object;
|
||||
|
||||
export type EventNames<T extends ValidEventTypes> = T extends string | symbol
|
||||
? T
|
||||
: keyof T;
|
||||
|
||||
export type ArgumentMap<T extends object> = {
|
||||
[K in keyof T]: T[K] extends (...args: any[]) => void
|
||||
? Parameters<T[K]>
|
||||
: T[K] extends any[]
|
||||
? T[K]
|
||||
: any[];
|
||||
};
|
||||
|
||||
export type EventListener<
|
||||
T extends ValidEventTypes,
|
||||
K extends EventNames<T>
|
||||
> = T extends string | symbol
|
||||
? (...args: any[]) => void
|
||||
: (
|
||||
...args: ArgumentMap<Exclude<T, string | symbol>>[Extract<K, keyof T>]
|
||||
) => void;
|
||||
|
||||
export type EventArgs<
|
||||
T extends ValidEventTypes,
|
||||
K extends EventNames<T>
|
||||
> = Parameters<EventListener<T, K>>;
|
||||
|
||||
export const EventEmitter: EventEmitterStatic;
|
||||
}
|
||||
|
||||
declare namespace Peer {
|
||||
export interface UtilSupportsObj {
|
||||
browser: boolean;
|
||||
webRTC: boolean;
|
||||
audioVideo: boolean;
|
||||
data: boolean;
|
||||
binaryBlob: boolean;
|
||||
reliable: boolean;
|
||||
}
|
||||
class Util {
|
||||
noop(): void;
|
||||
readonly CLOUD_HOST = '0.peerjs.com';
|
||||
readonly CLOUD_PORT = 443;
|
||||
readonly chunkedBrowsers: {
|
||||
Chrome: number;
|
||||
chrome: number;
|
||||
};
|
||||
readonly chunkedMTU = 16300;
|
||||
readonly defaultConfig: {
|
||||
iceServers: (
|
||||
| {
|
||||
urls: string;
|
||||
username?: undefined;
|
||||
credential?: undefined;
|
||||
}
|
||||
| {
|
||||
urls: string[];
|
||||
username: string;
|
||||
credential: string;
|
||||
}
|
||||
)[];
|
||||
sdpSemantics: string;
|
||||
};
|
||||
readonly browser: string;
|
||||
readonly browserVersion: number;
|
||||
readonly supports: UtilSupportsObj;
|
||||
validateId(id: string): boolean;
|
||||
pack: any;
|
||||
unpack: any;
|
||||
chunk(
|
||||
blob: Blob
|
||||
): {
|
||||
__peerData: number;
|
||||
n: number;
|
||||
total: number;
|
||||
data: Blob;
|
||||
}[];
|
||||
blobToArrayBuffer(
|
||||
blob: Blob,
|
||||
cb: (arg: ArrayBuffer | null) => void
|
||||
): FileReader;
|
||||
binaryStringToArrayBuffer(binary: string): ArrayBuffer | SharedArrayBuffer;
|
||||
randomToken(): string;
|
||||
isSecure(): boolean;
|
||||
}
|
||||
export const util: Util;
|
||||
export enum LogLevel {
|
||||
Disabled = 0,
|
||||
Errors = 1,
|
||||
Warnings = 2,
|
||||
All = 3,
|
||||
}
|
||||
export enum ConnectionType {
|
||||
Data = 'data',
|
||||
Media = 'media',
|
||||
}
|
||||
export enum PeerErrorType {
|
||||
BrowserIncompatible = 'browser-incompatible',
|
||||
Disconnected = 'disconnected',
|
||||
InvalidID = 'invalid-id',
|
||||
InvalidKey = 'invalid-key',
|
||||
Network = 'network',
|
||||
PeerUnavailable = 'peer-unavailable',
|
||||
SslUnavailable = 'ssl-unavailable',
|
||||
ServerError = 'server-error',
|
||||
SocketError = 'socket-error',
|
||||
SocketClosed = 'socket-closed',
|
||||
UnavailableID = 'unavailable-id',
|
||||
WebRTC = 'webrtc',
|
||||
}
|
||||
export enum SerializationType {
|
||||
Binary = 'binary',
|
||||
BinaryUTF8 = 'binary-utf8',
|
||||
JSON = 'json',
|
||||
}
|
||||
export enum SocketEventType {
|
||||
Message = 'message',
|
||||
Disconnected = 'disconnected',
|
||||
Error = 'error',
|
||||
Close = 'close',
|
||||
}
|
||||
export enum ServerMessageType {
|
||||
Heartbeat = 'HEARTBEAT',
|
||||
Candidate = 'CANDIDATE',
|
||||
Offer = 'OFFER',
|
||||
Answer = 'ANSWER',
|
||||
Open = 'OPEN',
|
||||
Error = 'ERROR',
|
||||
IdTaken = 'ID-TAKEN',
|
||||
InvalidKey = 'INVALID-KEY',
|
||||
Leave = 'LEAVE',
|
||||
Expire = 'EXPIRE',
|
||||
}
|
||||
/**
|
||||
* An abstraction on top of WebSockets to provide fastest
|
||||
* possible connection for peers.
|
||||
*/
|
||||
class Socket extends EventEmitter {
|
||||
constructor(
|
||||
secure: any,
|
||||
host: string,
|
||||
port: number,
|
||||
path: string,
|
||||
key: string,
|
||||
pingInterval?: number
|
||||
);
|
||||
start(id: string, token: string): void;
|
||||
/** Exposed send for DC & Peer. */
|
||||
send(data: any): void;
|
||||
close(): void;
|
||||
}
|
||||
class ServerMessage {
|
||||
type: ServerMessageType;
|
||||
payload: any;
|
||||
src: string;
|
||||
}
|
||||
type BaseConnectionEvents = {
|
||||
/**
|
||||
* Emitted when either you or the remote peer closes the connection.
|
||||
*/
|
||||
close: () => void;
|
||||
error: (error: Error) => void;
|
||||
iceStateChanged: (state: RTCIceConnectionState) => void;
|
||||
};
|
||||
abstract class BaseConnection<
|
||||
T extends EventEmitter.ValidEventTypes,
|
||||
TT
|
||||
> extends EventEmitter<T & BaseConnectionEvents> {
|
||||
readonly peer: string;
|
||||
provider: Peer<TT>;
|
||||
readonly options: any;
|
||||
protected _open: boolean;
|
||||
readonly metadata: any;
|
||||
connectionId: string;
|
||||
peerConnection: RTCPeerConnection;
|
||||
abstract get type(): ConnectionType;
|
||||
get open(): boolean;
|
||||
constructor(peer: string, provider: Peer<TT>, options: any);
|
||||
abstract close(): void;
|
||||
abstract handleMessage(message: ServerMessage): void;
|
||||
}
|
||||
type DataConnectionEvents<T> = {
|
||||
/**
|
||||
* Emitted when data is received from the remote peer.
|
||||
*/
|
||||
data: (data: T) => void;
|
||||
/**
|
||||
* Emitted when the connection is established and ready-to-use.
|
||||
*/
|
||||
open: () => void;
|
||||
};
|
||||
/**
|
||||
* Wraps a DataChannel between two Peers.
|
||||
*/
|
||||
export class DataConnection<T> extends BaseConnection<
|
||||
DataConnectionEvents<T>,
|
||||
T
|
||||
> {
|
||||
readonly label: string;
|
||||
readonly serialization: SerializationType;
|
||||
readonly reliable: boolean;
|
||||
stringify: (data: any) => string;
|
||||
parse: (data: string) => any;
|
||||
get type(): ConnectionType;
|
||||
get dataChannel(): RTCDataChannel;
|
||||
get bufferSize(): number;
|
||||
constructor(peerId: string, provider: Peer<T>, options: any);
|
||||
/** Called by the Negotiator when the DataChannel is ready. */
|
||||
initialize(dc: RTCDataChannel): void;
|
||||
/**
|
||||
* Exposed functionality for users.
|
||||
*/
|
||||
/** Allows user to close connection. */
|
||||
close(): void;
|
||||
/** Allows user to send data. */
|
||||
send(data: T, chunked?: boolean): void;
|
||||
handleMessage(message: ServerMessage): void;
|
||||
}
|
||||
export interface AnswerOption {
|
||||
sdpTransform?: Function;
|
||||
}
|
||||
export interface PeerJSOption {
|
||||
key?: string;
|
||||
host?: string;
|
||||
port?: number;
|
||||
path?: string;
|
||||
secure?: boolean;
|
||||
token?: string;
|
||||
config?: RTCConfiguration;
|
||||
debug?: number;
|
||||
referrerPolicy?: ReferrerPolicy;
|
||||
}
|
||||
export interface PeerConnectOption {
|
||||
label?: string;
|
||||
metadata?: any;
|
||||
serialization?: string;
|
||||
reliable?: boolean;
|
||||
}
|
||||
export interface CallOption {
|
||||
metadata?: any;
|
||||
sdpTransform?: Function;
|
||||
}
|
||||
type MediaConnectionEvents = {
|
||||
/**
|
||||
* Emitted when a connection to the PeerServer is established.
|
||||
*/
|
||||
stream: (stream: MediaStream) => void;
|
||||
};
|
||||
/**
|
||||
* Wraps the streaming interface between two Peers.
|
||||
*/
|
||||
export class MediaConnection<T> extends BaseConnection<
|
||||
MediaConnectionEvents,
|
||||
T
|
||||
> {
|
||||
get type(): ConnectionType;
|
||||
get localStream(): MediaStream;
|
||||
get remoteStream(): MediaStream;
|
||||
constructor(peerId: string, provider: Peer<T>, options: any);
|
||||
addStream(remoteStream: any): void;
|
||||
handleMessage(message: ServerMessage): void;
|
||||
answer(stream?: MediaStream, options?: AnswerOption): void;
|
||||
/**
|
||||
* Exposed functionality for users.
|
||||
*/
|
||||
/** Allows user to close connection. */
|
||||
close(): void;
|
||||
}
|
||||
class PeerOptions implements PeerJSOption {
|
||||
debug?: LogLevel;
|
||||
host?: string;
|
||||
port?: number;
|
||||
path?: string;
|
||||
key?: string;
|
||||
token?: string;
|
||||
config?: any;
|
||||
secure?: boolean;
|
||||
pingInterval?: number;
|
||||
referrerPolicy?: ReferrerPolicy;
|
||||
logFunction?: (logLevel: LogLevel, ...rest: any[]) => void;
|
||||
}
|
||||
type PeerEvents<T> = {
|
||||
/**
|
||||
* Emitted when a connection to the PeerServer is established.
|
||||
*/
|
||||
open: (id: string) => void;
|
||||
/**
|
||||
* Emitted when a new data connection is established from a remote peer.
|
||||
*/
|
||||
connection: (dataConnection: DataConnection<T>) => void;
|
||||
/**
|
||||
* Emitted when a remote peer attempts to call you.
|
||||
*/
|
||||
call: (mediaConnection: MediaConnection<T>) => void;
|
||||
/**
|
||||
* Emitted when the peer is destroyed and can no longer accept or create any new connections.
|
||||
*/
|
||||
close: () => void;
|
||||
/**
|
||||
* Emitted when the peer is disconnected from the signalling server
|
||||
*/
|
||||
disconnected: (currentId: string) => void;
|
||||
/**
|
||||
* Errors on the peer are almost always fatal and will destroy the peer.
|
||||
*/
|
||||
error: (error: Error) => void;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A peer who can initiate connections with other peers.
|
||||
*/
|
||||
declare class Peer<T> extends EventEmitter<Peer.PeerEvents<T>> {
|
||||
/**
|
||||
* The brokering ID of this peer
|
||||
*/
|
||||
get id(): string;
|
||||
get options(): Peer.PeerOptions;
|
||||
get open(): boolean;
|
||||
get socket(): Peer.Socket;
|
||||
/**
|
||||
* A hash of all connections associated with this peer, keyed by the remote peer's ID.
|
||||
* @deprecated
|
||||
* Return type will change from Object to Map<string,[]>
|
||||
*/
|
||||
get connections(): Object;
|
||||
/**
|
||||
* true if this peer and all of its connections can no longer be used.
|
||||
*/
|
||||
get destroyed(): boolean;
|
||||
/**
|
||||
* false if there is an active connection to the PeerServer.
|
||||
*/
|
||||
get disconnected(): boolean;
|
||||
/**
|
||||
* A peer can connect to other peers and listen for connections.
|
||||
*/
|
||||
constructor();
|
||||
/**
|
||||
* A peer can connect to other peers and listen for connections.
|
||||
* @param options for specifying details about PeerServer
|
||||
*/
|
||||
constructor(options: Peer.PeerOptions);
|
||||
/**
|
||||
* A peer can connect to other peers and listen for connections.
|
||||
* @param id Other peers can connect to this peer using the provided ID.
|
||||
* If no ID is given, one will be generated by the brokering server.
|
||||
* @param options for specifying details about PeerServer
|
||||
*/
|
||||
constructor(id: string, options?: Peer.PeerOptions);
|
||||
/** Retrieve messages from lost message store */
|
||||
_getMessages(connectionId: string): Peer.ServerMessage[];
|
||||
/**
|
||||
* Connects to the remote peer specified by id and returns a data connection.
|
||||
* @param peer The brokering ID of the remote peer (their peer.id).
|
||||
* @param options for specifying details about Peer Connection
|
||||
*/
|
||||
connect(
|
||||
peer: string,
|
||||
options?: Peer.PeerConnectOption
|
||||
): Peer.DataConnection<T>;
|
||||
/**
|
||||
* Calls the remote peer specified by id and returns a media connection.
|
||||
* @param peer The brokering ID of the remote peer (their peer.id).
|
||||
* @param stream The caller's media stream
|
||||
* @param options Metadata associated with the connection, passed in by whoever initiated the connection.
|
||||
*/
|
||||
call(
|
||||
peer: string,
|
||||
stream: MediaStream,
|
||||
options?: Peer.CallOption
|
||||
): Peer.MediaConnection<T>;
|
||||
_removeConnection(
|
||||
connection: Peer.DataConnection<T> | Peer.MediaConnection<T>
|
||||
): void;
|
||||
/** Retrieve a data/media connection for this peer. */
|
||||
getConnection(
|
||||
peerId: string,
|
||||
connectionId: string
|
||||
): null | Peer.DataConnection<T> | Peer.MediaConnection<T>;
|
||||
/** Emits a typed error message. */
|
||||
emitError(type: Peer.PeerErrorType, err: string | Error): void;
|
||||
/**
|
||||
* Destroys the Peer: closes all active connections as well as the connection
|
||||
* to the server.
|
||||
* Warning: The peer can no longer create or accept connections after being
|
||||
* destroyed.
|
||||
*/
|
||||
destroy(): void;
|
||||
/**
|
||||
* Disconnects the Peer's connection to the PeerServer. Does not close any
|
||||
* active connections.
|
||||
* Warning: The peer can no longer create or accept connections after being
|
||||
* disconnected. It also cannot reconnect to the server.
|
||||
*/
|
||||
disconnect(): void;
|
||||
/** Attempts to reconnect with the same ID. */
|
||||
reconnect(): void;
|
||||
/**
|
||||
* Get a list of available peer IDs. If you're running your own server, you'll
|
||||
* want to set allow_discovery: true in the PeerServer options. If you're using
|
||||
* the cloud server, email team@peerjs.com to get the functionality enabled for
|
||||
* your key.
|
||||
*/
|
||||
listAllPeers(cb?: (_: any[]) => void): void;
|
||||
}
|
@@ -91,12 +91,12 @@ describe('Multiplayer', () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* A mocked P2P event data.
|
||||
* @implements {gdjs.evtTools.p2p.IEventData}
|
||||
* A mocked P2P message data.
|
||||
* @implements {gdjs.multiplayerPeerJsHelper.IMessageData}
|
||||
*/
|
||||
class MockedEventData {
|
||||
class MockedMessageData {
|
||||
/**
|
||||
* @param {string} data
|
||||
* @param {object} data
|
||||
* @param {string} sender
|
||||
**/
|
||||
constructor(data, sender) {
|
||||
@@ -107,59 +107,63 @@ describe('Multiplayer', () => {
|
||||
/**
|
||||
* The data sent alongside the event.
|
||||
*/
|
||||
data = '';
|
||||
data = {};
|
||||
|
||||
/**
|
||||
* The ID of the sender of the event.
|
||||
*/
|
||||
sender = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* A mocked P2P event.
|
||||
* @implements {gdjs.evtTools.p2p.IEvent}
|
||||
*/
|
||||
class MockedEvent {
|
||||
data = [];
|
||||
dataloss = false;
|
||||
|
||||
isTriggered() {
|
||||
return this.data.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {gdjs.evtTools.p2p.IEventData} newData
|
||||
*/
|
||||
pushData(newData) {
|
||||
if (this.dataloss && this.data.length > 0) this.data[0] = newData;
|
||||
else this.data.push(newData);
|
||||
}
|
||||
|
||||
popData() {
|
||||
this.data.shift();
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this.data.length === 0 ? '' : this.data[0].data;
|
||||
return this.data;
|
||||
}
|
||||
|
||||
getSender() {
|
||||
return this.data.length === 0 ? '' : this.data[0].sender;
|
||||
return this.sender;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mocked P2P messages list.
|
||||
* @implements {gdjs.multiplayerPeerJsHelper.IMessagesList}
|
||||
*/
|
||||
class MockedMessagesList {
|
||||
data = [];
|
||||
messageName = 'some-message-name';
|
||||
|
||||
constructor(messageName) {
|
||||
this.messageName = messageName;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.messageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} newData
|
||||
* @param {string} sender
|
||||
*/
|
||||
pushMessage(newData, sender) {
|
||||
this.data.push(new MockedMessageData(newData, sender));
|
||||
}
|
||||
|
||||
getMessages() {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mocked P2P handler.
|
||||
* It stores the events sent to/from peers.
|
||||
* It stores the messages sent to/from peers.
|
||||
*/
|
||||
const createP2PAndMultiplayerManagersMock = () => {
|
||||
const createMultiplayerManagersMock = () => {
|
||||
const p2pState = {
|
||||
currentPeerId: '',
|
||||
otherPeerIds: [],
|
||||
};
|
||||
|
||||
/** @type {Record<string, Map<string, MockedEvent>>} */
|
||||
const peerEvents = {};
|
||||
/** @type {Record<string, Map<string, MockedMessagesList>>} */
|
||||
const peerAllMessagesMap = {};
|
||||
|
||||
/** @type {Record<string, gdjs.MultiplayerMessageManager>} */
|
||||
const peerMultiplayerMessageManager = {};
|
||||
@@ -167,88 +171,68 @@ describe('Multiplayer', () => {
|
||||
/** @type {Record<string, gdjs.MultiplayerVariablesManager>} */
|
||||
const peerMultiplayerVariablesManager = {};
|
||||
|
||||
const getPeerEvents = (peerId) =>
|
||||
(peerEvents[peerId] = peerEvents[peerId] || new Map());
|
||||
const getPeerMessages = (peerId) =>
|
||||
(peerAllMessagesMap[peerId] = peerAllMessagesMap[peerId] || new Map());
|
||||
|
||||
/**
|
||||
* @param {string} eventName
|
||||
* @returns {gdjs.evtTools.p2p.IEvent}
|
||||
* @param {string} messageName
|
||||
* @returns {gdjs.multiplayerPeerJsHelper.IMessagesList}
|
||||
*/
|
||||
const getEvent = (eventName) => {
|
||||
const events = getPeerEvents(p2pState.currentPeerId);
|
||||
let event = events.get(eventName);
|
||||
if (!event) events.set(eventName, (event = new MockedEvent()));
|
||||
return event;
|
||||
const getOrCreateMessagesList = (messageName) => {
|
||||
const allMessagesMap = getPeerMessages(p2pState.currentPeerId);
|
||||
const messagesList = allMessagesMap.get(messageName);
|
||||
if (messagesList) return messagesList;
|
||||
const newMessagesList = new MockedMessagesList(messageName);
|
||||
allMessagesMap.set(messageName, newMessagesList);
|
||||
return newMessagesList;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} peerId
|
||||
* @param {string} eventName
|
||||
* @param {string} eventData
|
||||
* @param {string[]} peerIds
|
||||
* @param {string} messageName
|
||||
* @param {object} messageData
|
||||
*/
|
||||
const sendDataTo = (peerId, eventName, eventData) => {
|
||||
// console.log(`## SENDING DATA TO ${peerId}:`, eventName, eventData);
|
||||
const events = getPeerEvents(peerId);
|
||||
let event = events.get(eventName);
|
||||
if (!event) events.set(eventName, (event = new MockedEvent()));
|
||||
event.pushData(new MockedEventData(eventData, peerId));
|
||||
const sendDataTo = async (peerIds, messageName, messageData) => {
|
||||
for (const peerId of peerIds) {
|
||||
// console.log(`## SENDING DATA TO ${peerId}:`, messageName, messageData);
|
||||
const peerAllMessagesMap = getPeerMessages(peerId);
|
||||
let peerMessagesList = peerAllMessagesMap.get(messageName);
|
||||
if (!peerMessagesList) {
|
||||
peerMessagesList = new MockedMessagesList(messageName);
|
||||
peerAllMessagesMap.set(messageName, peerMessagesList);
|
||||
}
|
||||
|
||||
peerMessagesList.pushMessage(messageData, p2pState.currentPeerId);
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {typeof gdjs.evtTools.p2p} */
|
||||
const p2pMock = {
|
||||
/** @type {typeof gdjs.multiplayerPeerJsHelper} */
|
||||
const peerJsHelperMock = {
|
||||
// @ts-ignore - this is a mock so private properties can't be the same.
|
||||
Event: MockedEvent,
|
||||
EventData: MockedEventData,
|
||||
sendVariableTo: () => {},
|
||||
sendVariableToAll: () => {},
|
||||
getEventVariable: (eventName, variable) => {
|
||||
variable.fromJSON(getEvent(eventName).getData());
|
||||
},
|
||||
onEvent: (eventName, dataloss) => {
|
||||
const event = getEvent(eventName);
|
||||
event.dataloss = dataloss;
|
||||
const isTriggered = event.isTriggered();
|
||||
return isTriggered;
|
||||
},
|
||||
getEvent,
|
||||
MessagesList: MockedMessagesList,
|
||||
MessageData: MockedMessageData,
|
||||
getOrCreateMessagesList,
|
||||
connect: (id) => {},
|
||||
disconnectFromPeer: (id) => {},
|
||||
disconnectFromAllPeers: () => {},
|
||||
disconnectFromAll: () => {},
|
||||
disconnectFromBroker: () => {},
|
||||
sendDataTo,
|
||||
sendDataToAll: (eventName, eventData) => {
|
||||
p2pState.otherPeerIds.forEach((peerId) => {
|
||||
sendDataTo(peerId, eventName, eventData);
|
||||
});
|
||||
},
|
||||
getEventData: (eventName) => getEvent(eventName).getData(),
|
||||
getEventSender: (eventName) => getEvent(eventName).getSender(),
|
||||
getEvents: () => getPeerEvents(p2pState.currentPeerId),
|
||||
getAllMessagesMap: () => getPeerMessages(p2pState.currentPeerId),
|
||||
useCustomBrokerServer: () => {},
|
||||
useDefaultBrokerServer: () => {},
|
||||
useCustomICECandidate: () => {},
|
||||
forceUseRelayServer: (shouldUseRelayServer) => {},
|
||||
overrideId: (id) => {},
|
||||
getCurrentId: () => 'fake-current-id',
|
||||
isReady: () => true,
|
||||
onError: () => false,
|
||||
getLastError: () => '',
|
||||
onDisconnect: () => false,
|
||||
getDisconnectedPeer: () => '',
|
||||
onConnection: () => false,
|
||||
getConnectedPeer: () => '',
|
||||
getJustDisconnectedPeers: () => [],
|
||||
getAllPeers: () => p2pState.otherPeerIds,
|
||||
getConnectionInstance: () => undefined,
|
||||
};
|
||||
|
||||
gdjs.evtTools.p2p = p2pMock;
|
||||
gdjs.multiplayerPeerJsHelper = peerJsHelperMock;
|
||||
|
||||
return {
|
||||
switchToPeer: ({ peerId, otherPeerIds, playerNumber }) => {
|
||||
// console.log('## SWITCHING TO PEER', peerId);
|
||||
|
||||
// Switch the state of the P2P mock.
|
||||
// Switch the state of the peerJs mock.
|
||||
p2pState.currentPeerId = peerId;
|
||||
p2pState.otherPeerIds = otherPeerIds;
|
||||
|
||||
@@ -273,25 +257,29 @@ describe('Multiplayer', () => {
|
||||
// Switch the state of the game.
|
||||
gdjs.multiplayer.playerNumber = playerNumber;
|
||||
},
|
||||
logEvents: () => {
|
||||
Object.keys(peerEvents).forEach((peerId) => {
|
||||
console.log(`## PEER ${peerId} events:`);
|
||||
for (const [eventName, event] of peerEvents[peerId]) {
|
||||
console.log(`${eventName}: ${JSON.stringify(event.data)}`);
|
||||
logMessages: () => {
|
||||
Object.keys(peerAllMessagesMap).forEach((peerId) => {
|
||||
console.log(`## PEER ${peerId} messages:`);
|
||||
for (const [messageName, messagesList] of peerAllMessagesMap[
|
||||
peerId
|
||||
]) {
|
||||
console.log(
|
||||
`${messageName}: ${JSON.stringify(messagesList.getMessages())}`
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
markAllPeerEventsAsProcessed: () => {
|
||||
for (const events of Object.values(peerEvents)) {
|
||||
for (const event of events.values()) {
|
||||
event.popData();
|
||||
markAllPeerMessagesAsProcessed: () => {
|
||||
for (const allMessagesList of Object.values(peerAllMessagesMap)) {
|
||||
for (const messagesList of allMessagesList.values()) {
|
||||
messagesList.data = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
expectNoEventsToBeProcessed: () => {
|
||||
for (const events of Object.values(peerEvents)) {
|
||||
for (const event of events.values()) {
|
||||
expect(event.isTriggered()).to.be(false);
|
||||
expectNoMessagesToBeProcessed: () => {
|
||||
for (const allMessagesList of Object.values(peerAllMessagesMap)) {
|
||||
for (const messagesList of allMessagesList.values()) {
|
||||
expect(messagesList.getMessages().length).to.be(0);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -337,8 +325,8 @@ describe('Multiplayer', () => {
|
||||
it('synchronizes scene/global variables from the host to other players', () => {
|
||||
const {
|
||||
switchToPeer,
|
||||
markAllPeerEventsAsProcessed,
|
||||
} = createP2PAndMultiplayerManagersMock();
|
||||
markAllPeerMessagesAsProcessed,
|
||||
} = createMultiplayerManagersMock();
|
||||
|
||||
switchToPeer({
|
||||
peerId: 'player-1',
|
||||
@@ -370,7 +358,7 @@ describe('Multiplayer', () => {
|
||||
|
||||
const p2RuntimeScene = makeTestRuntimeSceneWithNetworkId();
|
||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
expect(p2RuntimeScene.getVariables().has('MyString_Variable')).to.be(
|
||||
true
|
||||
);
|
||||
@@ -459,7 +447,7 @@ describe('Multiplayer', () => {
|
||||
});
|
||||
|
||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
expect(
|
||||
p2RuntimeScene.getGame().getVariables().has('MyGlobalStringVariable')
|
||||
).to.be(true);
|
||||
@@ -529,9 +517,9 @@ describe('Multiplayer', () => {
|
||||
it('overrides a scene/global variable, modified by a player, when synchronized by the host', () => {
|
||||
const {
|
||||
switchToPeer,
|
||||
markAllPeerEventsAsProcessed,
|
||||
expectNoEventsToBeProcessed,
|
||||
} = createP2PAndMultiplayerManagersMock();
|
||||
markAllPeerMessagesAsProcessed,
|
||||
expectNoMessagesToBeProcessed,
|
||||
} = createMultiplayerManagersMock();
|
||||
|
||||
switchToPeer({
|
||||
peerId: 'player-1',
|
||||
@@ -566,7 +554,7 @@ describe('Multiplayer', () => {
|
||||
p2RuntimeScene.getVariables().add('MyOtherVariable', variable);
|
||||
}
|
||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
expect(
|
||||
p2RuntimeScene.getVariables().get('MyVariable').getAsString()
|
||||
).to.be('Hello from remote world');
|
||||
@@ -574,7 +562,7 @@ describe('Multiplayer', () => {
|
||||
p2RuntimeScene.getVariables().get('MyOtherVariable').getAsString()
|
||||
).to.be('Something else');
|
||||
|
||||
expectNoEventsToBeProcessed();
|
||||
expectNoMessagesToBeProcessed();
|
||||
|
||||
// Check the host sends again the variable, even if not changed, for reliability
|
||||
// (allows to work around a dropped message, without using a real acknowledgement).
|
||||
@@ -605,16 +593,16 @@ describe('Multiplayer', () => {
|
||||
p2RuntimeScene.getVariables().get('MyOtherVariable').getAsString()
|
||||
).to.be('Something else');
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
expectNoEventsToBeProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
expectNoMessagesToBeProcessed();
|
||||
});
|
||||
|
||||
it('synchronizes a scene/global variable from a player to the host to other players', () => {
|
||||
const {
|
||||
switchToPeer,
|
||||
markAllPeerEventsAsProcessed,
|
||||
expectNoEventsToBeProcessed,
|
||||
} = createP2PAndMultiplayerManagersMock();
|
||||
markAllPeerMessagesAsProcessed,
|
||||
expectNoMessagesToBeProcessed,
|
||||
} = createMultiplayerManagersMock();
|
||||
|
||||
switchToPeer({
|
||||
peerId: 'player-1',
|
||||
@@ -678,8 +666,8 @@ describe('Multiplayer', () => {
|
||||
p3GlobalVariable.setPlayerOwnership(3); // Ownership is given to player 3.
|
||||
p3RuntimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
expectNoEventsToBeProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
expectNoMessagesToBeProcessed();
|
||||
|
||||
// Change the variables on player 3.
|
||||
{
|
||||
@@ -747,9 +735,9 @@ describe('Multiplayer', () => {
|
||||
it('does not synchronize a scene/global variable from players if defined as not synchronized', () => {
|
||||
const {
|
||||
switchToPeer,
|
||||
markAllPeerEventsAsProcessed,
|
||||
expectNoEventsToBeProcessed,
|
||||
} = createP2PAndMultiplayerManagersMock();
|
||||
markAllPeerMessagesAsProcessed,
|
||||
expectNoMessagesToBeProcessed,
|
||||
} = createMultiplayerManagersMock();
|
||||
|
||||
switchToPeer({
|
||||
peerId: 'player-1',
|
||||
@@ -813,8 +801,8 @@ describe('Multiplayer', () => {
|
||||
p3GlobalVariable.disableSynchronization(); // Disable synchronization.
|
||||
p3RuntimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
expectNoEventsToBeProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
expectNoMessagesToBeProcessed();
|
||||
|
||||
// Change the variables on player 3.
|
||||
{
|
||||
@@ -945,8 +933,8 @@ describe('Multiplayer', () => {
|
||||
it('synchronizes objects from the host to other players', () => {
|
||||
const {
|
||||
switchToPeer,
|
||||
markAllPeerEventsAsProcessed,
|
||||
} = createP2PAndMultiplayerManagersMock();
|
||||
markAllPeerMessagesAsProcessed,
|
||||
} = createMultiplayerManagersMock();
|
||||
|
||||
// Create an instance on the host's game:
|
||||
switchToPeer({
|
||||
@@ -980,7 +968,7 @@ describe('Multiplayer', () => {
|
||||
if (!p2Objects) throw new Error('No objects found');
|
||||
expect(p2Objects.length).to.be(0);
|
||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
|
||||
const {
|
||||
object: p2SpriteObject,
|
||||
@@ -1021,7 +1009,7 @@ describe('Multiplayer', () => {
|
||||
playerNumber: 2,
|
||||
});
|
||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
|
||||
const {
|
||||
object: p2SpriteObject,
|
||||
@@ -1061,7 +1049,7 @@ describe('Multiplayer', () => {
|
||||
playerNumber: 2,
|
||||
});
|
||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
|
||||
const p2Objects = p2RuntimeScene.getObjects('MySpriteObject');
|
||||
if (!p2Objects) throw new Error('No objects found');
|
||||
@@ -1073,8 +1061,8 @@ describe('Multiplayer', () => {
|
||||
it('synchronizes objects from a player to the host to other players', () => {
|
||||
const {
|
||||
switchToPeer,
|
||||
markAllPeerEventsAsProcessed,
|
||||
} = createP2PAndMultiplayerManagersMock();
|
||||
markAllPeerMessagesAsProcessed,
|
||||
} = createMultiplayerManagersMock();
|
||||
|
||||
// Create an instance on a player:
|
||||
switchToPeer({
|
||||
@@ -1144,7 +1132,7 @@ describe('Multiplayer', () => {
|
||||
expect(p3SpriteObject.getX()).to.be(142);
|
||||
expect(p3SpriteObject.getY()).to.be(143);
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
|
||||
// Move the object on the player:
|
||||
{
|
||||
@@ -1196,7 +1184,7 @@ describe('Multiplayer', () => {
|
||||
playerNumber: 3,
|
||||
});
|
||||
p3RuntimeScene.renderAndStep(1000 / 60);
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
|
||||
const {
|
||||
object: p3SpriteObject,
|
||||
@@ -1270,15 +1258,15 @@ describe('Multiplayer', () => {
|
||||
expect(p3ObjectsAndBehaviorsUpdated.length).to.be(0);
|
||||
}
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
});
|
||||
|
||||
it('allows ownership to change from host to a player to another player', () => {
|
||||
const {
|
||||
switchToPeer,
|
||||
markAllPeerEventsAsProcessed,
|
||||
expectNoEventsToBeProcessed,
|
||||
} = createP2PAndMultiplayerManagersMock();
|
||||
markAllPeerMessagesAsProcessed,
|
||||
expectNoMessagesToBeProcessed,
|
||||
} = createMultiplayerManagersMock();
|
||||
|
||||
// Create an instance on the host's game:
|
||||
switchToPeer({
|
||||
@@ -1335,8 +1323,8 @@ describe('Multiplayer', () => {
|
||||
expect(p3SpriteObjectOriginal.getX()).to.be(142);
|
||||
expect(p3SpriteObjectOriginal.getY()).to.be(143);
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
expectNoEventsToBeProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
expectNoMessagesToBeProcessed();
|
||||
|
||||
// Check player 3 can get ownership (and can directly move the instance, without waiting for the
|
||||
// host to acknowledge the change).
|
||||
@@ -1406,9 +1394,9 @@ describe('Multiplayer', () => {
|
||||
expect(p2SpriteObject.getX()).to.be(342);
|
||||
expect(p2SpriteObject.getY()).to.be(343);
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerEventsAsProcessed();
|
||||
expectNoEventsToBeProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
expectNoMessagesToBeProcessed();
|
||||
}
|
||||
|
||||
// Check player 2 can get ownership.
|
||||
@@ -1492,7 +1480,7 @@ describe('Multiplayer', () => {
|
||||
).to.be(2);
|
||||
}
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
|
||||
// Check that the position given by player 2 is updated on the host and player 3.
|
||||
{
|
||||
@@ -1545,15 +1533,15 @@ describe('Multiplayer', () => {
|
||||
expect(p3SpriteObject.getY()).to.be(243);
|
||||
}
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
expectNoEventsToBeProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
expectNoMessagesToBeProcessed();
|
||||
});
|
||||
|
||||
it('reconciles an instance owned by a player with a "ghost" instance created on other peers without a network ID (as not owned by them)', () => {
|
||||
const {
|
||||
switchToPeer,
|
||||
markAllPeerEventsAsProcessed,
|
||||
} = createP2PAndMultiplayerManagersMock();
|
||||
markAllPeerMessagesAsProcessed,
|
||||
} = createMultiplayerManagersMock();
|
||||
|
||||
// Create an instance on a player:
|
||||
switchToPeer({
|
||||
@@ -1625,7 +1613,7 @@ describe('Multiplayer', () => {
|
||||
expect(p3SpriteObject.getX()).to.be(142);
|
||||
expect(p3SpriteObject.getY()).to.be(143);
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
|
||||
// Now, create a new instance on the host and player 3, but owned by player 2.
|
||||
// We call this in this test a "ghost" instance as it would be deleted if not "reconcilied".
|
||||
@@ -1756,12 +1744,12 @@ describe('Multiplayer', () => {
|
||||
expect(p3Object2.getX()).to.be(42);
|
||||
expect(p3Object2.getY()).to.be(43);
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
}
|
||||
});
|
||||
|
||||
it('deletes an instance owned by another player after a bit (if not "reconciled" in the meantime)', async () => {
|
||||
const { switchToPeer } = createP2PAndMultiplayerManagersMock();
|
||||
const { switchToPeer } = createMultiplayerManagersMock();
|
||||
|
||||
// Create an instance on a player (2), owned by another player (3).
|
||||
// We can assume it's because there is some common logic running for all players
|
||||
@@ -1813,8 +1801,8 @@ describe('Multiplayer', () => {
|
||||
it('gives priority to the first ownership change and revert the wrong one', async () => {
|
||||
const {
|
||||
switchToPeer,
|
||||
markAllPeerEventsAsProcessed,
|
||||
} = createP2PAndMultiplayerManagersMock();
|
||||
markAllPeerMessagesAsProcessed,
|
||||
} = createMultiplayerManagersMock();
|
||||
|
||||
// Create an instance on the host's game:
|
||||
switchToPeer({
|
||||
@@ -1887,7 +1875,7 @@ describe('Multiplayer', () => {
|
||||
expect(p3SpriteObject.getX()).to.be(142);
|
||||
expect(p3SpriteObject.getY()).to.be(143);
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
|
||||
// Now, try to change ownership to player 2 and 3 at the "same time".
|
||||
{
|
||||
@@ -1953,7 +1941,7 @@ describe('Multiplayer', () => {
|
||||
p1SpriteMultiplayerObjectBehaviorUpdated.getPlayerObjectOwnership()
|
||||
).to.be(2);
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
}
|
||||
|
||||
// Wait so that player 3 retries.
|
||||
@@ -1980,7 +1968,7 @@ describe('Multiplayer', () => {
|
||||
p3SpriteMultiplayerObjectBehavior.getPlayerObjectOwnership()
|
||||
).to.be(3);
|
||||
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
|
||||
await delay(210);
|
||||
}
|
||||
@@ -2005,7 +1993,7 @@ describe('Multiplayer', () => {
|
||||
expect(
|
||||
p3SpriteMultiplayerObjectBehavior.getPlayerObjectOwnership()
|
||||
).to.be(0);
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
}
|
||||
|
||||
// Move the object on the player 2:
|
||||
@@ -2072,7 +2060,7 @@ describe('Multiplayer', () => {
|
||||
p3SpriteMultiplayerObjectBehavior.getPlayerObjectOwnership()
|
||||
).to.be(2);
|
||||
}
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2091,7 +2079,7 @@ describe('Multiplayer', () => {
|
||||
};
|
||||
|
||||
it('synchronizes scenes from the host to other players', async () => {
|
||||
const { switchToPeer } = createP2PAndMultiplayerManagersMock();
|
||||
const { switchToPeer } = createMultiplayerManagersMock();
|
||||
|
||||
const gameLayoutData = [
|
||||
getFakeSceneAndExtensionData({ name: 'Scene1' }).sceneData,
|
||||
@@ -2218,8 +2206,8 @@ describe('Multiplayer', () => {
|
||||
it('reconciles a scene launched both by the host and by a player', async () => {
|
||||
const {
|
||||
switchToPeer,
|
||||
markAllPeerEventsAsProcessed,
|
||||
} = createP2PAndMultiplayerManagersMock();
|
||||
markAllPeerMessagesAsProcessed,
|
||||
} = createMultiplayerManagersMock();
|
||||
|
||||
const gameLayoutData = [
|
||||
getFakeSceneAndExtensionData({ name: 'Scene1' }).sceneData,
|
||||
@@ -2263,7 +2251,7 @@ describe('Multiplayer', () => {
|
||||
p2RuntimeGame.getSceneStack().step(1000 / 60);
|
||||
|
||||
checkCurrentSceneIs(p2RuntimeGame, 'Scene1');
|
||||
markAllPeerEventsAsProcessed();
|
||||
markAllPeerMessagesAsProcessed();
|
||||
|
||||
// Launch a second scene, first on the player:
|
||||
p2RuntimeGame.getSceneStack().push('Scene2');
|
||||
|
Reference in New Issue
Block a user