mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-03-13 17:04:39 -04:00
Merge branch 'develop' of https://github.com/LLK/scratch-vm into storage-no-cache
# Conflicts: # package-lock.json
This commit is contained in:
commit
902aee48ee
16 changed files with 815 additions and 135 deletions
739
package-lock.json
generated
739
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -54,7 +54,7 @@
|
|||
"adm-zip": "0.4.11",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.4",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"copy-webpack-plugin": "^4.5.4",
|
||||
"docdash": "^1.0.0",
|
||||
"eslint": "^5.3.0",
|
||||
"eslint-config-scratch": "^5.0.0",
|
||||
|
|
|
@ -27,20 +27,22 @@ class Scratch3ProcedureBlocks {
|
|||
call (args, util) {
|
||||
if (!util.stackFrame.executed) {
|
||||
const procedureCode = args.mutation.proccode;
|
||||
const paramNamesAndIds = util.getProcedureParamNamesAndIds(procedureCode);
|
||||
const paramNamesIdsAndDefaults = util.getProcedureParamNamesIdsAndDefaults(procedureCode);
|
||||
|
||||
// If null, procedure could not be found, which can happen if custom
|
||||
// block is dragged between sprites without the definition.
|
||||
// Match Scratch 2.0 behavior and noop.
|
||||
if (paramNamesAndIds === null) {
|
||||
if (paramNamesIdsAndDefaults === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [paramNames, paramIds] = paramNamesAndIds;
|
||||
const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults;
|
||||
|
||||
for (let i = 0; i < paramIds.length; i++) {
|
||||
if (args.hasOwnProperty(paramIds[i])) {
|
||||
util.pushParam(paramNames[i], args[paramIds[i]]);
|
||||
} else {
|
||||
util.pushParam(paramNames[i], paramDefaults[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,12 @@ const MathUtil = require('../util/math-util');
|
|||
const Cast = require('../util/cast');
|
||||
const Clone = require('../util/clone');
|
||||
|
||||
/**
|
||||
* Occluded boolean value to make its use more understandable.
|
||||
* @const {boolean}
|
||||
*/
|
||||
const STORE_WAITING = true;
|
||||
|
||||
class Scratch3SoundBlocks {
|
||||
constructor (runtime) {
|
||||
/**
|
||||
|
@ -10,10 +16,16 @@ class Scratch3SoundBlocks {
|
|||
*/
|
||||
this.runtime = runtime;
|
||||
|
||||
this.waitingSounds = {};
|
||||
|
||||
// Clear sound effects on green flag and stop button events.
|
||||
this.stopAllSounds = this.stopAllSounds.bind(this);
|
||||
this._stopWaitingSoundsForTarget = this._stopWaitingSoundsForTarget.bind(this);
|
||||
this._clearEffectsForAllTargets = this._clearEffectsForAllTargets.bind(this);
|
||||
if (this.runtime) {
|
||||
this.runtime.on('PROJECT_STOP_ALL', this.stopAllSounds);
|
||||
this.runtime.on('PROJECT_STOP_ALL', this._clearEffectsForAllTargets);
|
||||
this.runtime.on('STOP_FOR_TARGET', this._stopWaitingSoundsForTarget);
|
||||
this.runtime.on('PROJECT_START', this._clearEffectsForAllTargets);
|
||||
}
|
||||
|
||||
|
@ -141,21 +153,44 @@ class Scratch3SoundBlocks {
|
|||
|
||||
playSound (args, util) {
|
||||
// Don't return the promise, it's the only difference for AndWait
|
||||
this.playSoundAndWait(args, util);
|
||||
this._playSound(args, util);
|
||||
}
|
||||
|
||||
playSoundAndWait (args, util) {
|
||||
return this._playSound(args, util, STORE_WAITING);
|
||||
}
|
||||
|
||||
_playSound (args, util, storeWaiting) {
|
||||
const index = this._getSoundIndex(args.SOUND_MENU, util);
|
||||
if (index >= 0) {
|
||||
const {target} = util;
|
||||
const {sprite} = target;
|
||||
const {soundId} = sprite.sounds[index];
|
||||
if (sprite.soundBank) {
|
||||
if (storeWaiting === STORE_WAITING) {
|
||||
this._addWaitingSound(target.id, soundId);
|
||||
} else {
|
||||
this._removeWaitingSound(target.id, soundId);
|
||||
}
|
||||
return sprite.soundBank.playSound(target, soundId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_addWaitingSound (targetId, soundId) {
|
||||
if (!this.waitingSounds[targetId]) {
|
||||
this.waitingSounds[targetId] = new Set();
|
||||
}
|
||||
this.waitingSounds[targetId].add(soundId);
|
||||
}
|
||||
|
||||
_removeWaitingSound (targetId, soundId) {
|
||||
if (!this.waitingSounds[targetId]) {
|
||||
return;
|
||||
}
|
||||
this.waitingSounds[targetId].delete(soundId);
|
||||
}
|
||||
|
||||
_getSoundIndex (soundName, util) {
|
||||
// if the sprite has no sounds, return -1
|
||||
const len = util.target.sprite.sounds.length;
|
||||
|
@ -201,6 +236,20 @@ class Scratch3SoundBlocks {
|
|||
_stopAllSoundsForTarget (target) {
|
||||
if (target.sprite.soundBank) {
|
||||
target.sprite.soundBank.stopAllSounds(target);
|
||||
if (this.waitingSounds[target.id]) {
|
||||
this.waitingSounds[target.id].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_stopWaitingSoundsForTarget (target) {
|
||||
if (target.sprite.soundBank) {
|
||||
if (this.waitingSounds[target.id]) {
|
||||
for (const soundId of this.waitingSounds[target.id].values()) {
|
||||
target.sprite.soundBank.stop(target, soundId);
|
||||
}
|
||||
this.waitingSounds[target.id].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ class BlockUtility {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get names for parameters for the given procedure.
|
||||
* Get names and ids of parameters for the given procedure.
|
||||
* @param {string} procedureCode Procedure code for procedure to query.
|
||||
* @return {Array.<string>} List of param names for a procedure.
|
||||
*/
|
||||
|
@ -112,6 +112,15 @@ class BlockUtility {
|
|||
return this.thread.target.blocks.getProcedureParamNamesAndIds(procedureCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get names, ids, and defaults of parameters for the given procedure.
|
||||
* @param {string} procedureCode Procedure code for procedure to query.
|
||||
* @return {Array.<string>} List of param names for a procedure.
|
||||
*/
|
||||
getProcedureParamNamesIdsAndDefaults (procedureCode) {
|
||||
return this.thread.target.blocks.getProcedureParamNamesIdsAndDefaults(procedureCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a procedure parameter value by its name.
|
||||
* @param {string} paramName The procedure's parameter name.
|
||||
|
|
|
@ -230,11 +230,20 @@ class Blocks {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get names of parameters for the given procedure.
|
||||
* Get names and ids of parameters for the given procedure.
|
||||
* @param {?string} name Name of procedure to query.
|
||||
* @return {?Array.<string>} List of param names for a procedure.
|
||||
*/
|
||||
getProcedureParamNamesAndIds (name) {
|
||||
return this.getProcedureParamNamesIdsAndDefaults(name).slice(0, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get names, ids, and defaults of parameters for the given procedure.
|
||||
* @param {?string} name Name of procedure to query.
|
||||
* @return {?Array.<string>} List of param names for a procedure.
|
||||
*/
|
||||
getProcedureParamNamesIdsAndDefaults (name) {
|
||||
const cachedNames = this._cache.procedureParamNames[name];
|
||||
if (typeof cachedNames !== 'undefined') {
|
||||
return cachedNames;
|
||||
|
@ -247,7 +256,9 @@ class Blocks {
|
|||
block.mutation.proccode === name) {
|
||||
const names = JSON.parse(block.mutation.argumentnames);
|
||||
const ids = JSON.parse(block.mutation.argumentids);
|
||||
this._cache.procedureParamNames[name] = [names, ids];
|
||||
const defaults = JSON.parse(block.mutation.argumentdefaults);
|
||||
|
||||
this._cache.procedureParamNames[name] = [names, ids, defaults];
|
||||
return this._cache.procedureParamNames[name];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1604,14 +1604,6 @@ class Runtime extends EventEmitter {
|
|||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return {OrderedMap} The current state of monitor blocks.
|
||||
*/
|
||||
getMonitorState () {
|
||||
return this._monitorState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue monitor blocks to sequencer to be run.
|
||||
*/
|
||||
|
|
|
@ -179,8 +179,7 @@ class Scratch3VideoSensingBlocks {
|
|||
if (stage) {
|
||||
return stage.videoState;
|
||||
}
|
||||
// Default to off to prevent a flash of video while the project is loading
|
||||
return VideoState.OFF;
|
||||
return VideoState.ON;
|
||||
}
|
||||
|
||||
set globalVideoState (state) {
|
||||
|
|
|
@ -334,55 +334,6 @@ const parseMonitorObject = (object, runtime, targets, extensions) => {
|
|||
}));
|
||||
};
|
||||
|
||||
const confirmTargetExtensions = function (targets, extensions) {
|
||||
// Ensure only extensions used by blocks or visible monitors are enabled
|
||||
const extensionsInUse = new Set();
|
||||
|
||||
for (const ext of extensions.extensionIDs) {
|
||||
let extensionConfirmed = false;
|
||||
|
||||
for (const target of targets) {
|
||||
const targetBlocks = Object.entries(target.blocks._blocks);
|
||||
|
||||
// Make sure there is a block that uses the currently set extensions
|
||||
extensionConfirmed = targetBlocks.some(block => {
|
||||
const opcode = block[1].opcode;
|
||||
if (opcode && (ext === opcode.split('_')[0])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
if (extensionConfirmed) {
|
||||
extensionsInUse.add(ext);
|
||||
break;
|
||||
}
|
||||
|
||||
const monitorState = target.runtime.getMonitorState();
|
||||
|
||||
// Check if a visible monitor uses this extension
|
||||
extensionConfirmed = monitorState.some(monitor => {
|
||||
if (!monitor.visible) {
|
||||
return false;
|
||||
}
|
||||
if (monitor.opcode && (ext === monitor.opcode.split('_')[0])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
if (extensionConfirmed) {
|
||||
extensionsInUse.add(ext);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extensions.extensionIDs = extensionsInUse;
|
||||
|
||||
return extensions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a single "Scratch object" and create all its in-memory VM objects.
|
||||
* TODO: parse the "info" section, especially "savedExtensions"
|
||||
|
@ -654,8 +605,6 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip)
|
|||
if (object.info.hasOwnProperty('videoOn')) {
|
||||
if (object.info.videoOn) {
|
||||
target.videoState = RenderedTarget.VIDEO_STATE.ON;
|
||||
} else {
|
||||
target.videoState = RenderedTarget.VIDEO_STATE.OFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -744,9 +693,6 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip)
|
|||
for (let n = 0; n < deferredMonitors.length; n++) {
|
||||
parseMonitorObject(deferredMonitors[n], runtime, targets, extensions);
|
||||
}
|
||||
|
||||
extensions = confirmTargetExtensions(targets, extensions);
|
||||
|
||||
return targets;
|
||||
})
|
||||
);
|
||||
|
@ -766,6 +712,7 @@ const reorderParsedTargets = function (targets) {
|
|||
return targets;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Top-level handler. Parse provided JSON,
|
||||
* and process the top-level object (the stage object).
|
||||
|
|
|
@ -1074,9 +1074,6 @@ class RenderedTarget extends Target {
|
|||
*/
|
||||
onStopAll () {
|
||||
this.clearEffects();
|
||||
if (this.sprite.soundBank) {
|
||||
this.sprite.soundBank.stopAllSounds();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -16,7 +16,7 @@ test('calling a custom block with no definition does not throw', t => {
|
|||
}
|
||||
};
|
||||
const util = {
|
||||
getProcedureParamNamesAndIds: () => null,
|
||||
getProcedureParamNamesIdsAndDefaults: () => null,
|
||||
stackFrame: {
|
||||
executed: false
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ test('ask and stop all dismisses question', t => {
|
|||
test('ask and stop other scripts dismisses if it is the last question', t => {
|
||||
const rt = new Runtime();
|
||||
const s = new Sensing(rt);
|
||||
const util = {target: {visible: false}, thread: {}};
|
||||
const util = {target: {visible: false, sprite: {}}, thread: {}};
|
||||
|
||||
const expectedQuestion = 'a question';
|
||||
|
||||
|
@ -94,8 +94,8 @@ test('ask and stop other scripts dismisses if it is the last question', t => {
|
|||
test('ask and stop other scripts asks next question', t => {
|
||||
const rt = new Runtime();
|
||||
const s = new Sensing(rt);
|
||||
const util = {target: {visible: false}, thread: {}};
|
||||
const util2 = {target: {visible: false}, thread: {}};
|
||||
const util = {target: {visible: false, sprite: {}}, thread: {}};
|
||||
const util2 = {target: {visible: false, sprite: {}}, thread: {}};
|
||||
|
||||
const expectedQuestion = 'a question';
|
||||
const nextQuestion = 'a followup';
|
||||
|
|
|
@ -102,50 +102,3 @@ test('Ordering', t => {
|
|||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('Extensions used in blocks serialized', t => {
|
||||
const uri = path.resolve(__dirname, '../fixtures/extensions-in-blocks.sb2');
|
||||
const json = extractProjectJson(uri);
|
||||
const rt = new Runtime();
|
||||
const expectedExtensionIDs = new Set(['videoSensing', 'wedo2', 'pen']);
|
||||
|
||||
// Make sure any extensions loaded in a block are added to the project
|
||||
sb2.deserialize(json, rt).then(({extensions}) => {
|
||||
t.deepEquals(extensions.extensionIDs, expectedExtensionIDs);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('Extensions used in visible monitors serialized', t => {
|
||||
const uri = path.resolve(__dirname, '../fixtures/extensions-in-monitors.sb2');
|
||||
const json = extractProjectJson(uri);
|
||||
const rt = new Runtime();
|
||||
|
||||
sb2.deserialize(json, rt).then(({extensions}) => {
|
||||
const monitorState = rt.getMonitorState();
|
||||
const monitor = monitorState.first();
|
||||
const monitorOpcode = monitor.opcode.split('_')[0];
|
||||
|
||||
t.ok(extensions.extensionIDs.has(monitorOpcode));
|
||||
t.equals(monitor.visible, true);
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('No extensions serialized', t => {
|
||||
// This test project has had the video motion monitor checked, saved,
|
||||
// then unchecked and then saved
|
||||
const uri = path.resolve(__dirname, '../fixtures/extensions-not-serialized.sb2');
|
||||
const json = extractProjectJson(uri);
|
||||
const rt = new Runtime();
|
||||
|
||||
sb2.deserialize(json, rt).then(({extensions}) => {
|
||||
const monitorState = rt.getMonitorState();
|
||||
const monitor = monitorState.first();
|
||||
const monitorOpcode = monitor.opcode.split('_')[0];
|
||||
|
||||
t.equals(monitor.visible, false);
|
||||
t.notOk(extensions.extensionIDs.has(monitorOpcode));
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue