Multiple fixes for the P2P feature (#1967)

* Fix "Send variable to all peers" action
* Multiple disconnection from remote instances can now be tracked using events
* Add a condition to detect when another instance connects remotely to the current instance
This commit is contained in:
Arthur Pacaud
2020-09-08 09:24:18 +02:00
committed by GitHub
parent 2a62f71f08
commit d08f4dc059
12 changed files with 177 additions and 72 deletions

View File

@@ -8,6 +8,7 @@
gdjs.evtTools.p2p = {
/**
* The peer to peer configuration.
* @type {Peer.PeerJSOption}
*/
peerConfig: { debug: 1 }, // Enable logging of critical errors
@@ -18,52 +19,75 @@ gdjs.evtTools.p2p = {
peer: null,
/**
* All connected p2p clients, keyed by their id.
* All connected p2p clients, keyed by their ID.
* @type {Object<string, Peer.DataConnection>}
*/
connections: {},
/**
* Contains a list of events triggered by other p2p clients.
* Maps an event name (string) to a boolean:
* true if the event has been triggered, otherwise false.
* @note This is ignored if the event is in no dataloss mode.
* @type {Object<string, boolean>}
*/
triggeredEvents: {},
/**
* Contains the latest data sent with each event.
* If the event is in dataloss mode, maps an event name (string)
* to the string sent with that event.
* If the event is in no dataloss mode, maps an event name (string)
* to an array containing the data of each call of that event.
* @type {Object<string, string | Array>}
*/
lastEventData: {},
/**
* Tells how to handle an event (with or without data loss)
* Tells how to handle an event (with or without data loss).
* Maps the event name (string) to a boolean:
* true for dataloss, false for no dataloss.
* @type {Object<string, string>}
*/
eventHandling: {},
/**
* True if PeerJS is initialized and ready.
* @type {boolean}
*/
ready: false,
/**
* True if an error occured.
* @type {boolean}
*/
error: false,
/**
* Last error's message.
* @type {string}
*/
lastError: '',
/**
* True if a peer diconnected.
* List of IDs of peers that just disconnected.
* @type {Array<string>}
*/
peerJustDisconnected: false,
disconnectedPeers: [],
/**
* The last peer that has disconnected.
* List of IDs of peers that just remotely initiated a connection.
* @type {Array<string>}
*/
lastDisconnectedPeerId: '',
connectedPeers: [],
};
gdjs.evtTools.p2p.loadPeerJS = function () {
/**
* Internal function called to initialize PeerJS after its
* broker server has been configured.
* @private
*/
gdjs.evtTools.p2p._loadPeerJS = function () {
if (gdjs.evtTools.p2p.peer != null) return;
gdjs.evtTools.p2p.peer = new Peer(gdjs.evtTools.p2p.peerConfig);
gdjs.evtTools.p2p.peer.on('open', function () {
@@ -73,14 +97,24 @@ gdjs.evtTools.p2p.loadPeerJS = function () {
gdjs.evtTools.p2p.error = true;
gdjs.evtTools.p2p.lastError = errorMessage;
});
gdjs.evtTools.p2p.peer.on('connection', gdjs.evtTools.p2p._onConnection);
gdjs.evtTools.p2p.peer.on('connection', function (connection) {
connection.on('open', function () {
gdjs.evtTools.p2p._onConnection(connection);
gdjs.evtTools.p2p.connectedPeers.push(connection.peer);
});
});
gdjs.evtTools.p2p.peer.on('close', function () {
gdjs.evtTools.p2p.peer = null;
gdjs.evtTools.p2p.loadPeerJS();
gdjs.evtTools.p2p._loadPeerJS();
});
gdjs.evtTools.p2p.peer.on('disconnected', gdjs.evtTools.p2p.peer.reconnect);
};
/**
* Internal function called when a connection with a remote peer is initiated.
* @private
* @param {Peer.DataConnection} connection The DataConnection of the peer
*/
gdjs.evtTools.p2p._onConnection = function (connection) {
gdjs.evtTools.p2p.connections[connection.peer] = connection;
connection.on('data', function (data) {
@@ -117,15 +151,19 @@ gdjs.evtTools.p2p._onConnection = function (connection) {
disconnectChecker();
};
/**
* Internal function called when a remote client disconnects.
* @private
* @param {string} connectionID The ID of the peer that disconnected.
*/
gdjs.evtTools.p2p._onDisconnect = function (connectionID) {
gdjs.evtTools.p2p.peerJustDisconnected = true;
gdjs.evtTools.p2p.lastDisconnectedPeerId = connectionID;
gdjs.evtTools.p2p.disconnectedPeers.push(connectionID);
delete gdjs.evtTools.p2p.connections[connectionID];
};
/**
* Connects to another p2p client.
* @param {string} id - The other client's id.
* @param {string} id - The other client's ID.
*/
gdjs.evtTools.p2p.connect = function (id) {
var connection = gdjs.evtTools.p2p.peer.connect(id);
@@ -137,14 +175,14 @@ gdjs.evtTools.p2p.connect = function (id) {
/**
* Returns true when the event got triggered by another p2p client.
* @param {string} eventName
* @param {boolean} _dataLoss Is data loss allowed (accelerates event handling when true)?
* @param {boolean} defaultDataLoss Is data loss allowed (accelerates event handling when true)?
* @returns {boolean}
*/
gdjs.evtTools.p2p.onEvent = function (eventName, _dataLoss) {
gdjs.evtTools.p2p.onEvent = function (eventName, defaultDataLoss) {
var dataLoss = gdjs.evtTools.p2p.eventHandling[eventName];
if (dataLoss == undefined) {
gdjs.evtTools.p2p.eventHandling[eventName] = _dataLoss;
return gdjs.evtTools.p2p.onEvent(eventName, _dataLoss);
gdjs.evtTools.p2p.eventHandling[eventName] = defaultDataLoss;
return gdjs.evtTools.p2p.onEvent(eventName, defaultDataLoss);
}
if (dataLoss) {
var returnValue = gdjs.evtTools.p2p.triggeredEvents[eventName];
@@ -160,7 +198,7 @@ gdjs.evtTools.p2p.onEvent = function (eventName, _dataLoss) {
/**
* Send an event to one specific connected client.
* @param {string} id - The id of the client to send the event to.
* @param {string} id - The ID of the client to send the event to.
* @param {string} eventName - The event to trigger.
* @param {string} [eventData] - Additional data to send with the event.
*/
@@ -188,16 +226,16 @@ gdjs.evtTools.p2p.sendDataToAll = function (eventName, eventData) {
/**
* Send an event to one specific connected client.
* @param {string} id - The id of the client to send the event to.
* @param {string} id - The ID of the client to send the event to.
* @param {string} eventName - The event to trigger.
* @param {gdjs.Variable} variable - Additional variable to send with the event.
*/
gdjs.evtTools.p2p.sendVariableTo = function (id, eventName, variable) {
if (gdjs.evtTools.p2p.connections[id])
gdjs.evtTools.p2p.connections[id].send({
eventName: eventName,
data: gdjs.evtTools.network.variableStructureToJSON(variable),
});
gdjs.evtTools.p2p.sendDataTo(
id,
eventName,
gdjs.evtTools.network.variableStructureToJSON(variable)
);
};
/**
@@ -206,12 +244,10 @@ gdjs.evtTools.p2p.sendVariableTo = function (id, eventName, variable) {
* @param {gdjs.Variable} variable - Additional variable to send with the event.
*/
gdjs.evtTools.p2p.sendVariableToAll = function (eventName, variable) {
for (var id in gdjs.evtTools.p2p.connections) {
gdjs.evtTools.p2p.connections[id].send({
eventName: eventName,
data: gdjs.evtTools.network.variableStructureToJSON(variable),
});
}
gdjs.evtTools.p2p.sendDataToAll(
eventName,
gdjs.evtTools.network.variableStructureToJSON(variable)
);
};
/**
@@ -265,7 +301,7 @@ gdjs.evtTools.p2p.useCustomBrokerServer = function (
secure: ssl,
key,
};
gdjs.evtTools.p2p.loadPeerJS();
gdjs.evtTools.p2p._loadPeerJS();
};
/**
@@ -274,11 +310,11 @@ gdjs.evtTools.p2p.useCustomBrokerServer = function (
* this server should only be used for quick testing in development.
*/
gdjs.evtTools.p2p.useDefaultBrokerServer = function () {
gdjs.evtTools.p2p.loadPeerJS();
gdjs.evtTools.p2p._loadPeerJS();
};
/**
* Returns the own current peer ID
* Returns the own current peer ID.
* @see Peer.id
* @returns {string}
*/
@@ -288,7 +324,7 @@ gdjs.evtTools.p2p.getCurrentId = function () {
};
/**
* Returns true once PeerJS is initialized
* Returns true once PeerJS finished initialization.
* @see gdjs.evtTools.p2p.ready
* @returns {boolean}
*/
@@ -319,13 +355,39 @@ gdjs.evtTools.p2p.getLastError = function () {
* @returns {boolean}
*/
gdjs.evtTools.p2p.onDisconnect = function () {
var returnValue = gdjs.evtTools.p2p.peerJustDisconnected;
gdjs.evtTools.p2p.peerJustDisconnected = false;
return returnValue;
return gdjs.evtTools.p2p.disconnectedPeers.length > 0;
};
/**
* Get the ID of the peer that triggered onDisconnect.
* @returns {string}
*/
gdjs.evtTools.p2p.getDisconnectedPeer = function () {
return gdjs.evtTools.p2p.lastDisconnectedPeerId;
return (
gdjs.evtTools.p2p.disconnectedPeers[
gdjs.evtTools.p2p.disconnectedPeers.length - 1
] || ''
);
};
/**
* Returns true once if a remote peer just initiated a connection.
* @returns {boolean}
*/
gdjs.evtTools.p2p.onConnection = function () {
return gdjs.evtTools.p2p.connectedPeers.length > 0;
};
/**
* Get the ID of the peer that triggered onConnection.
* @returns {string}
*/
gdjs.evtTools.p2p.getConnectedPeer = function () {
return (
gdjs.evtTools.p2p.connectedPeers[
gdjs.evtTools.p2p.connectedPeers.length - 1
] || ''
);
};
gdjs.callbacksRuntimeScenePostEvents.push(function () {
@@ -336,4 +398,8 @@ gdjs.callbacksRuntimeScenePostEvents.push(function () {
)
gdjs.evtTools.p2p.lastEventData[i].pop();
}
if (gdjs.evtTools.p2p.disconnectedPeers.length > 0)
gdjs.evtTools.p2p.disconnectedPeers.pop();
if (gdjs.evtTools.p2p.connectedPeers.length > 0)
gdjs.evtTools.p2p.connectedPeers.pop();
});

View File

@@ -105,6 +105,21 @@ module.exports = {
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.onDisconnect');
extension
.addCondition(
'OnConnection',
_('Peer Connected'),
_('Triggers once when a remote peer initiates a connection.'),
_('P2P peer connected'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg',
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.onConnection');
extension
.addAction(
'Connect',
@@ -215,7 +230,7 @@ module.exports = {
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.sendDataToAll');
.setFunctionName('gdjs.evtTools.p2p.sendVariableToAll');
extension
.addAction(
@@ -313,7 +328,7 @@ module.exports = {
.addStrExpression(
'GetLastDisconnectedPeer',
_('Get last disconnected peer'),
_('Gets the id of the latest peer that has disconnected.'),
_('Gets the ID of the latest peer that has disconnected.'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg'
)
@@ -322,6 +337,19 @@ module.exports = {
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.getDisconnectedPeer');
extension
.addStrExpression(
'GetLastConnectedPeer',
_('Get ID of the connected peer'),
_('Gets the ID of the newly connected peer.'),
_('P2P (experimental)'),
'JsPlatform/Extensions/p2picon.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/P2P/A_peer.js')
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
.setFunctionName('gdjs.evtTools.p2p.getConnectedPeer');
return extension;
},
runExtensionSanityTests: function (

View File

@@ -1 +0,0 @@
A showcase of some multiplayer possibilities using the P2P extension.

View File

Before

Width:  |  Height:  |  Size: 629 B

After

Width:  |  Height:  |  Size: 629 B

View File

Before

Width:  |  Height:  |  Size: 382 B

After

Width:  |  Height:  |  Size: 382 B

View File

Before

Width:  |  Height:  |  Size: 351 B

After

Width:  |  Height:  |  Size: 351 B

View File

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 377 B

View File

Before

Width:  |  Height:  |  Size: 189 B

After

Width:  |  Height:  |  Size: 189 B

View File

@@ -0,0 +1 @@
A showcase of some multiplayer/networking possibilities using the P2P extension.

View File

@@ -7,7 +7,6 @@
"revision": 0
},
"properties": {
"adMobAppId": "",
"adaptGameResolutionAtRuntime": true,
"folderProject": false,
"linuxExecutableFilename": "",
@@ -21,7 +20,7 @@
"version": "1.0.0",
"winExecutableFilename": "",
"winExecutableIconFile": "",
"name": "Multiplayer Game Example",
"name": "P2P Networking Example",
"author": "Arthur Pacaud (arthuro555)",
"windowWidth": 800,
"windowHeight": 600,
@@ -33,6 +32,7 @@
"loadingScreen": {
"showGDevelopSplash": false
},
"extensionProperties": [],
"extensions": [
{
"name": "BuiltinObject"
@@ -1079,7 +1079,7 @@
"textG": 0,
"textR": 0
},
"comment": "Try to verify if connection has happened every second",
"comment": "When a peer is connected, confirm connection and switch to the game",
"comment2": ""
},
{
@@ -1090,28 +1090,13 @@
{
"type": {
"inverted": false,
"value": "Timer"
"value": "P2P::OnConnection"
},
"parameters": [
"",
"1",
"\"verifyConnection\""
],
"parameters": [],
"subInstructions": []
}
],
"actions": [
{
"type": {
"inverted": false,
"value": "ResetTimer"
},
"parameters": [
"",
"\"verifyConnection\""
],
"subInstructions": []
},
{
"type": {
"inverted": false,
@@ -1122,6 +1107,18 @@
"\"\""
],
"subInstructions": []
},
{
"type": {
"inverted": false,
"value": "Scene"
},
"parameters": [
"",
"\"Menu\"",
"yes"
],
"subInstructions": []
}
],
"events": []
@@ -1138,7 +1135,7 @@
"textG": 0,
"textR": 0
},
"comment": "Switch to the game once connected and notify other instance that the connection has happened",
"comment": "Switch to the game once connection is confirmed",
"comment2": ""
},
{
@@ -1159,17 +1156,6 @@
}
],
"actions": [
{
"type": {
"inverted": false,
"value": "P2P::SendToAll"
},
"parameters": [
"\"connected\"",
"\"\""
],
"subInstructions": []
},
{
"type": {
"inverted": false,
@@ -1357,6 +1343,11 @@
],
"layers": [
{
"ambientLightColorB": 200,
"ambientLightColorG": 200,
"ambientLightColorR": 200,
"followBaseLayerCamera": false,
"isLightingLayer": false,
"name": "",
"visibility": true,
"cameras": [
@@ -2036,6 +2027,11 @@
],
"layers": [
{
"ambientLightColorB": 200,
"ambientLightColorG": 200,
"ambientLightColorR": 200,
"followBaseLayerCamera": false,
"isLightingLayer": false,
"name": "",
"visibility": true,
"cameras": [
@@ -2626,6 +2622,11 @@
],
"layers": [
{
"ambientLightColorB": 200,
"ambientLightColorG": 200,
"ambientLightColorR": 200,
"followBaseLayerCamera": false,
"isLightingLayer": false,
"name": "",
"visibility": true,
"cameras": [
@@ -3334,6 +3335,11 @@
],
"layers": [
{
"ambientLightColorB": 200,
"ambientLightColorG": 200,
"ambientLightColorR": 200,
"followBaseLayerCamera": false,
"isLightingLayer": false,
"name": "",
"visibility": true,
"cameras": [
@@ -3491,6 +3497,11 @@
],
"layers": [
{
"ambientLightColorB": 200,
"ambientLightColorG": 200,
"ambientLightColorR": 200,
"followBaseLayerCamera": false,
"isLightingLayer": false,
"name": "",
"visibility": true,
"cameras": [