From 80da989f0145d1d4ce8ef2cc2addcd40ab7a5035 Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Fri, 1 Dec 2017 10:29:32 -0500 Subject: [PATCH] SB2 Import for Broadcast Blocks (not including the input field functionality for 'broadcast' and 'broadcast and wait '). Currently we also cannot import projects where messages and variables share names. --- src/serialization/sb2.js | 59 +++++++++++++++++++++++++++----- src/serialization/sb2_specmap.js | 15 ++++---- test/integration/data.js | 4 +-- test/integration/event.js | 4 +-- test/integration/motion.js | 4 +-- 5 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index f6f03bd1..ae501bda 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -88,17 +88,18 @@ const flatten = function (blocks) { * a list of blocks in a branch (e.g., in forever), * or a list of blocks in an argument (e.g., move [pick random...]). * @param {Array.} blockList SB2 JSON-format block list. + * @param {Function} addBroadcastMsg function to update broadcast message name map * @param {Function} getVariableId function to retreive a variable's ID based on name * @param {ImportedExtensionsInfo} extensions - (in/out) parsed extension information will be stored here. * @return {Array.} Scratch VM-format block list. */ -const parseBlockList = function (blockList, getVariableId, extensions) { +const parseBlockList = function (blockList, addBroadcastMsg, getVariableId, extensions) { const resultingList = []; let previousBlock = null; // For setting next. for (let i = 0; i < blockList.length; i++) { const block = blockList[i]; // eslint-disable-next-line no-use-before-define - const parsedBlock = parseBlock(block, getVariableId, extensions); + const parsedBlock = parseBlock(block, addBroadcastMsg, getVariableId, extensions); if (typeof parsedBlock === 'undefined') continue; if (previousBlock) { parsedBlock.parent = previousBlock.id; @@ -115,16 +116,17 @@ const parseBlockList = function (blockList, getVariableId, extensions) { * This should only handle top-level scripts that include X, Y coordinates. * @param {!object} scripts Scripts object from SB2 JSON. * @param {!Blocks} blocks Blocks object to load parsed blocks into. + * @param {Function} addBroadcastMsg function to update broadcast message name map * @param {Function} getVariableId function to retreive a variable's ID based on name * @param {ImportedExtensionsInfo} extensions - (in/out) parsed extension information will be stored here. */ -const parseScripts = function (scripts, blocks, getVariableId, extensions) { +const parseScripts = function (scripts, blocks, addBroadcastMsg, getVariableId, extensions) { for (let i = 0; i < scripts.length; i++) { const script = scripts[i]; const scriptX = script[0]; const scriptY = script[1]; const blockList = script[2]; - const parsedBlockList = parseBlockList(blockList, getVariableId, extensions); + const parsedBlockList = parseBlockList(blockList, addBroadcastMsg, getVariableId, extensions); if (parsedBlockList[0]) { // Adjust script coordinates to account for // larger block size in scratch-blocks. @@ -166,6 +168,23 @@ const generateVariableIdGetter = (function () { }; }()); +const generateBroadcastMsgId = function (name) { + return `broadcastMsgId-${name}`; +}; + +const globalBroadcastMsgStateGenerator = (function () { + let broadcastMsgNameMap = {}; + return function (topLevel) { + if (topLevel) broadcastMsgNameMap = {}; + return { + broadcastMsgMapUpdater: function (name) { + broadcastMsgNameMap[name] = generateBroadcastMsgId(name); + }, + globalBroadcastMsgs: broadcastMsgNameMap + }; + }; +}()); + /** * Parse a single "Scratch object" and create all its in-memory VM objects. * TODO: parse the "info" section, especially "savedExtensions" @@ -227,6 +246,9 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) { const getVariableId = generateVariableIdGetter(target.id, topLevel); + const globalBroadcastMsgObj = globalBroadcastMsgStateGenerator(topLevel); + const addBroadcastMsg = globalBroadcastMsgObj.broadcastMsgMapUpdater; + // Load target properties from JSON. if (object.hasOwnProperty('variables')) { for (let j = 0; j < object.variables.length; j++) { @@ -244,7 +266,7 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) { // If included, parse any and all scripts/blocks on the object. if (object.hasOwnProperty('scripts')) { - parseScripts(object.scripts, blocks, getVariableId, extensions); + parseScripts(object.scripts, blocks, addBroadcastMsg, getVariableId, extensions); } if (object.hasOwnProperty('lists')) { @@ -317,6 +339,21 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) { Promise.all( childrenPromises ).then(children => { + // Need create broadcast msgs as variables after + // all other targets have finished processing. + if (target.isStage) { + const allBroadcastMsgs = globalBroadcastMsgObj.globalBroadcastMsgs; + for (const msgName in allBroadcastMsgs) { + const msgId = allBroadcastMsgs[msgName]; + const newMsg = new Variable( + msgId, + msgName, + Variable.BROADCAST_MESSAGE_TYPE, + false + ); + target.variables[newMsg.id] = newMsg; + } + } let targets = [target]; for (let n = 0; n < children.length; n++) { targets = targets.concat(children[n]); @@ -349,11 +386,12 @@ const sb2import = function (json, runtime, optForceSprite) { /** * Parse a single SB2 JSON-formatted block and its children. * @param {!object} sb2block SB2 JSON-formatted block. + * @param {Function} addBroadcastMsg function to update broadcast message name map * @param {Function} getVariableId function to retrieve a variable's ID based on name * @param {ImportedExtensionsInfo} extensions - (in/out) parsed extension information will be stored here. * @return {object} Scratch VM format block, or null if unsupported object. */ -const parseBlock = function (sb2block, getVariableId, extensions) { +const parseBlock = function (sb2block, addBroadcastMsg, getVariableId, extensions) { // First item in block object is the old opcode (e.g., 'forward:'). const oldOpcode = sb2block[0]; // Convert the block using the specMap. See sb2specmap.js. @@ -404,10 +442,10 @@ const parseBlock = function (sb2block, getVariableId, extensions) { let innerBlocks; if (typeof providedArg[0] === 'object' && providedArg[0]) { // Block list occupies the input. - innerBlocks = parseBlockList(providedArg, getVariableId, extensions); + innerBlocks = parseBlockList(providedArg, addBroadcastMsg, getVariableId, extensions); } else { // Single block occupies the input. - innerBlocks = [parseBlock(providedArg, getVariableId, extensions)]; + innerBlocks = [parseBlock(providedArg, addBroadcastMsg, getVariableId, extensions)]; } let previousBlock = null; for (let j = 0; j < innerBlocks.length; j++) { @@ -493,6 +531,11 @@ const parseBlock = function (sb2block, getVariableId, extensions) { if (expectedArg.fieldName === 'VARIABLE' || expectedArg.fieldName === 'LIST') { // Add `id` property to variable fields activeBlock.fields[expectedArg.fieldName].id = getVariableId(providedArg); + } else if (expectedArg.fieldName === 'BROADCAST_OPTION') { + // add the name in this field to the broadcast msg name map + addBroadcastMsg(providedArg); + const broadcastId = generateBroadcastMsgId(providedArg); + activeBlock.fields[expectedArg.fieldName].id = broadcastId; } const varType = expectedArg.variableType; if (typeof varType === 'string') { diff --git a/src/serialization/sb2_specmap.js b/src/serialization/sb2_specmap.js index 9e1034bf..cc0a5696 100644 --- a/src/serialization/sb2_specmap.js +++ b/src/serialization/sb2_specmap.js @@ -656,7 +656,8 @@ const specMap = { argMap: [ { type: 'field', - fieldName: 'BROADCAST_OPTION' + fieldName: 'BROADCAST_OPTION', + variableType: Variable.BROADCAST_MESSAGE_TYPE } ] }, @@ -664,9 +665,9 @@ const specMap = { opcode: 'event_broadcast', argMap: [ { - type: 'input', - inputOp: 'event_broadcast_menu', - inputName: 'BROADCAST_OPTION' + type: 'field', + fieldName: 'BROADCAST_OPTION', + variableType: Variable.BROADCAST_MESSAGE_TYPE } ] }, @@ -674,9 +675,9 @@ const specMap = { opcode: 'event_broadcastandwait', argMap: [ { - type: 'input', - inputOp: 'event_broadcast_menu', - inputName: 'BROADCAST_OPTION' + type: 'field', + fieldName: 'BROADCAST_OPTION', + variableType: Variable.BROADCAST_MESSAGE_TYPE } ] }, diff --git a/test/integration/data.js b/test/integration/data.js index 63048fc7..c6cd71d1 100644 --- a/test/integration/data.js +++ b/test/integration/data.js @@ -7,9 +7,7 @@ const VirtualMachine = require('../../src/index'); const uri = path.resolve(__dirname, '../fixtures/data.sb2'); const project = extract(uri); -// TODO (#828) Need to re-activate this test after sb2 import works for broadcast -// blocks -test('data', {todo: true}, t => { +test('data', t => { const vm = new VirtualMachine(); vm.attachStorage(makeTestStorage()); diff --git a/test/integration/event.js b/test/integration/event.js index 648f8cdd..c4f3ee58 100644 --- a/test/integration/event.js +++ b/test/integration/event.js @@ -7,9 +7,7 @@ const VirtualMachine = require('../../src/index'); const uri = path.resolve(__dirname, '../fixtures/event.sb2'); const project = extract(uri); -// TODO (#828) Need to re-activate this test after sb2 import works for broadcast -// blocks -test('event', {todo: true}, t => { +test('event', t => { const vm = new VirtualMachine(); vm.attachStorage(makeTestStorage()); diff --git a/test/integration/motion.js b/test/integration/motion.js index dbb24a11..689b2187 100644 --- a/test/integration/motion.js +++ b/test/integration/motion.js @@ -7,9 +7,7 @@ const VirtualMachine = require('../../src/index'); const uri = path.resolve(__dirname, '../fixtures/motion.sb2'); const project = extract(uri); -// TODO (#828) Need to re-activate this test after sb2 import works for broadcast -// blocks -test('motion', {todo: true}, t => { +test('motion', t => { const vm = new VirtualMachine(); vm.attachStorage(makeTestStorage());