Merge pull request #1703 from kchadha/cloud-io

Cloud IO
This commit is contained in:
Karishma Chadha 2018-10-30 11:57:20 -04:00 committed by GitHub
commit fd5e178d3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 2746 additions and 2698 deletions

5212
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -45,6 +45,10 @@ class Scratch3DataBlocks {
const variable = util.target.lookupOrCreateVariable( const variable = util.target.lookupOrCreateVariable(
args.VARIABLE.id, args.VARIABLE.name); args.VARIABLE.id, args.VARIABLE.name);
variable.value = args.VALUE; variable.value = args.VALUE;
if (variable.isCloud) {
util.ioQuery('cloud', 'requestUpdateVariable', [variable.name, args.VALUE]);
}
} }
changeVariableBy (args, util) { changeVariableBy (args, util) {
@ -52,7 +56,12 @@ class Scratch3DataBlocks {
args.VARIABLE.id, args.VARIABLE.name); args.VARIABLE.id, args.VARIABLE.name);
const castedValue = Cast.toNumber(variable.value); const castedValue = Cast.toNumber(variable.value);
const dValue = Cast.toNumber(args.VALUE); const dValue = Cast.toNumber(args.VALUE);
variable.value = castedValue + dValue; const newValue = castedValue + dValue;
variable.value = newValue;
if (variable.isCloud) {
util.ioQuery('cloud', 'requestUpdateVariable', [variable.name, newValue]);
}
} }
changeMonitorVisibility (id, visible) { changeMonitorVisibility (id, visible) {

View file

@ -17,6 +17,7 @@ const Variable = require('./variable');
// Virtual I/O devices. // Virtual I/O devices.
const Clock = require('../io/clock'); const Clock = require('../io/clock');
const Cloud = require('../io/cloud');
const DeviceManager = require('../io/deviceManager'); const DeviceManager = require('../io/deviceManager');
const Keyboard = require('../io/keyboard'); const Keyboard = require('../io/keyboard');
const Mouse = require('../io/mouse'); const Mouse = require('../io/mouse');
@ -262,6 +263,7 @@ class Runtime extends EventEmitter {
/** @type {Object.<string, Object>} */ /** @type {Object.<string, Object>} */
this.ioDevices = { this.ioDevices = {
clock: new Clock(), clock: new Clock(),
cloud: new Cloud(),
deviceManager: new DeviceManager(), deviceManager: new DeviceManager(),
keyboard: new Keyboard(this), keyboard: new Keyboard(this),
mouse: new Mouse(this), mouse: new Mouse(this),
@ -1383,6 +1385,7 @@ class Runtime extends EventEmitter {
this.targets.map(this.disposeTarget, this); this.targets.map(this.disposeTarget, this);
this._monitorState = OrderedMap({}); this._monitorState = OrderedMap({});
// @todo clear out extensions? turboMode? etc. // @todo clear out extensions? turboMode? etc.
this.ioDevices.cloud.clear();
} }
/** /**

129
src/io/cloud.js Normal file
View file

@ -0,0 +1,129 @@
const Variable = require('../engine/variable');
const log = require('../util/log');
class Cloud {
/**
* @typedef updateVariable
* @param {string} name The name of the cloud variable to update on the server
* @param {(string | number)} value The value to update the cloud variable with.
*/
/**
* A cloud data provider, responsible for managing the connection to the
* cloud data server and for posting data about cloud data activity to
* this IO device.
* @typedef {object} CloudProvider
* @property {updateVariable} updateVariable A function which sends a cloud variable
* update to the cloud data server.
* @property {Function} requestCloseConnection A function which closes
* the connection to the cloud data server.
*/
/**
* Part of a cloud io data post indicating a cloud variable update.
* @typedef {object} VarUpdateData
* @property {string} name The name of the variable to update
* @property {(number | string)} value The scalar value to update the variable with
*/
/**
* A cloud io data post message.
* @typedef {object} CloudIOData
* @property {VarUpdateData} varUpdate A {@link VarUpdateData} message indicating
* a cloud variable update
*/
/**
* Cloud IO Device responsible for sending and receiving messages from
* cloud provider (mananging the cloud server connection) and interacting
* with cloud variables in the current project.
*/
constructor () {
/**
* Reference to the cloud data provider, responsible for mananging
* the web socket connection to the cloud data server.
* @type {?CloudProvider}
*/
this.provider = null;
/**
* Reference to the stage target which owns the cloud variables
* in the project.
* @type {?Target}
*/
this.stage = null;
}
/**
* Set a reference to the cloud data provider.
* @param {CloudProvider} provider The cloud data provider
*/
setProvider (provider) {
this.provider = provider;
}
/**
* Set a reference to the stage target which owns the
* cloud variables in the project.
* @param {Target} stage The stage target
*/
setStage (stage) {
this.stage = stage;
}
/**
* Handle incoming data to this io device.
* @param {CloudIOData} data The {@link CloudIOData} object to process
*/
postData (data) {
if (data.varUpdate) {
this.updateCloudVariable(data.varUpdate);
}
}
/**
* Request the cloud data provider to update the given variable with
* the given value. Does nothing if this io device does not have a provider set.
* @param {string} name The name of the variable to update
* @param {string | number} value The value to update the variable with
*/
requestUpdateVariable (name, value) {
if (this.provider) {
this.provider.updateVariable(name, value);
}
}
/**
* Update a cloud variable in the runtime based on the message received
* from the cloud provider.
* @param {VarUpdateData} varUpdate A {@link VarUpdateData} object describing
* a cloud variable update received from the cloud data provider.
*/
updateCloudVariable (varUpdate) {
const varName = varUpdate.name;
const variable = this.stage.lookupVariableByNameAndType(varName, Variable.SCALAR_TYPE);
if (!variable || !variable.isCloud) {
log.warn(`Received an update for a cloud variable that does not exist: ${varName}`);
return;
}
variable.value = varUpdate.value;
}
/**
* Request the cloud data provider to close the web socket connection and
* clear this io device of references to the cloud data provider and the
* stage.
*/
clear () {
if (!this.provider) return;
this.provider.requestCloseConnection();
this.provider = null;
this.stage = null;
}
}
module.exports = Cloud;

View file

@ -218,6 +218,10 @@ class VirtualMachine extends EventEmitter {
this.runtime.ioDevices.video.setProvider(videoProvider); this.runtime.ioDevices.video.setProvider(videoProvider);
} }
setCloudProvider (cloudProvider) {
this.runtime.ioDevices.cloud.setProvider(cloudProvider);
}
/** /**
* Tell the specified extension to scan for a peripheral. * Tell the specified extension to scan for a peripheral.
* @param {string} extensionId - the id of the extension. * @param {string} extensionId - the id of the extension.
@ -463,6 +467,7 @@ class VirtualMachine extends EventEmitter {
this.emitTargetsUpdate(); this.emitTargetsUpdate();
this.emitWorkspaceUpdate(); this.emitWorkspaceUpdate();
this.runtime.setEditingTarget(this.editingTarget); this.runtime.setEditingTarget(this.editingTarget);
this.runtime.ioDevices.cloud.setStage(this.runtime.getTargetForStage());
}); });
} }

84
test/unit/io_cloud.js Normal file
View file

@ -0,0 +1,84 @@
const test = require('tap').test;
const Cloud = require('../../src/io/cloud');
const Target = require('../../src/engine/target');
const Variable = require('../../src/engine/variable');
test('spec', t => {
const cloud = new Cloud();
t.type(cloud, 'object');
t.type(cloud.postData, 'function');
t.type(cloud.requestUpdateVariable, 'function');
t.type(cloud.updateCloudVariable, 'function');
t.type(cloud.setProvider, 'function');
t.type(cloud.setStage, 'function');
t.type(cloud.clear, 'function');
t.end();
});
test('stage and provider are null initially', t => {
const cloud = new Cloud();
t.strictEquals(cloud.provider, null);
t.strictEquals(cloud.stage, null);
t.end();
});
test('setProvider sets the provider', t => {
const cloud = new Cloud();
const provider = {
foo: 'a fake provider'
};
cloud.setProvider(provider);
t.strictEquals(cloud.provider, provider);
t.end();
});
test('postData updates the variable', t => {
const stage = new Target();
const fooVar = new Variable(
'a fake var id',
'foo',
Variable.SCALAR_TYPE,
true /* isCloud */
);
stage.variables[fooVar.id] = fooVar;
t.strictEquals(fooVar.value, 0);
const cloud = new Cloud();
cloud.setStage(stage);
cloud.postData({varUpdate: {
name: 'foo',
value: 3
}});
t.strictEquals(fooVar.value, 3);
t.end();
});
test('requestUpdateVariable calls provider\'s updateVariable function', t => {
let updateVariableCalled = false;
let mockVarName = '';
let mockVarValue = '';
const mockUpdateVariable = (name, value) => {
updateVariableCalled = true;
mockVarName = name;
mockVarValue = value;
return;
};
const provider = {
updateVariable: mockUpdateVariable
};
const cloud = new Cloud();
cloud.setProvider(provider);
cloud.requestUpdateVariable('foo', 3);
t.equals(updateVariableCalled, true);
t.strictEquals(mockVarName, 'foo');
t.strictEquals(mockVarValue, 3);
t.end();
});