From 9b82530f516ea1e9acce99bee8735f416c11a0c9 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Fri, 4 May 2018 12:33:42 -0400 Subject: [PATCH 1/4] Use a constructor to create the execute cache objects --- src/engine/blocks.js | 11 ++++++----- src/engine/execute.js | 31 ++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/engine/blocks.js b/src/engine/blocks.js index 9af98db59..fddd5d71d 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -876,21 +876,22 @@ class Blocks { * @param {string} blockId blockId for the desired execute cache * @return {object} execute cache object */ -BlocksExecuteCache.getCached = function (blocks, blockId) { - const block = blocks.getBlock(blockId); - if (typeof block === 'undefined') return null; +BlocksExecuteCache.getCached = function (blocks, blockId, CacheType = Object) { let cached = blocks._cache._executeCached[blockId]; if (typeof cached !== 'undefined') { return cached; } - cached = { + const block = blocks.getBlock(blockId); + if (typeof block === 'undefined') return null; + + cached = new CacheType({ _initialized: false, opcode: blocks.getOpcode(block), fields: blocks.getFields(block), inputs: blocks.getInputs(block), mutation: blocks.getMutation(block) - }; + }); blocks._cache._executeCached[blockId] = cached; return cached; }; diff --git a/src/engine/execute.js b/src/engine/execute.js index f819b6df2..69468688e 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -118,6 +118,28 @@ const FieldKind = { DYNAMIC: 'DYNAMIC' }; +const BlockCached = function (cached) { + this._initialized = false; + + this.opcode = cached.opcode; + this.fields = cached.fields; + this.inputs = cached.inputs; + this.mutation = cached.mutation; + + this._isHat = false; + this._blockFunction = null; + this._definedBlockFunction = false; + this._isShadowBlock = false; + this._shadowValue = null; + this._fields = null; + this._fieldKind = FieldKind.NONE; + this._fieldVariable = null; + this._fieldList = null; + this._fieldBroadcastOption = null; + this._argValues = null; + this._inputs = null; +}; + /** * Execute a block. * @param {!Sequencer} sequencer Which sequencer is executing. @@ -132,12 +154,12 @@ const execute = function (sequencer, thread, recursiveCall) { const currentStackFrame = thread.peekStackFrame(); let blockContainer = thread.blockContainer; - let block = blockContainer.getBlock(currentBlockId); - if (typeof block === 'undefined') { + let blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);; + if (blockCached === null) { blockContainer = runtime.flyoutBlocks; - block = blockContainer.getBlock(currentBlockId); + blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached); // Stop if block or target no longer exists. - if (typeof block === 'undefined') { + if (blockCached === null) { // No block found: stop the thread; script no longer exists. sequencer.retireThread(thread); return; @@ -158,7 +180,6 @@ const execute = function (sequencer, thread, recursiveCall) { // Blocks is modified in the editor these cached objects will be cleaned up // and new cached copies can be created. This lets us optimize this critical // path while keeping up to date with editor changes to a project. - const blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId); if (blockCached._initialized !== true) { const {opcode, fields, inputs} = blockCached; From 5fd749918f0d9a3e894124ba0a41bc4ac3ec9f90 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Fri, 4 May 2018 12:39:48 -0400 Subject: [PATCH 2/4] Reuse argValues Block args set by fields are static and never change. Inputs that do change are always set onto args. With these two assumptions we can reuse the same objects for each execution of the same block instead of constantly creating them and letting them be garbage collected. --- src/engine/execute.js | 70 +++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/engine/execute.js b/src/engine/execute.js index 69468688e..e01908f58 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -201,21 +201,43 @@ const execute = function (sequencer, thread, recursiveCall) { blockCached._fieldKind = fieldKeys.length > 0 ? FieldKind.DYNAMIC : FieldKind.NONE; if (fieldKeys.length === 1 && fieldKeys.includes('VARIABLE')) { blockCached._fieldKind = FieldKind.VARIABLE; - blockCached._fieldVariable = { - id: fields.VARIABLE.id, - name: fields.VARIABLE.value + blockCached._argValues = { + VARIABLE: { + id: fields.VARIABLE.id, + name: fields.VARIABLE.value + }, + mutation: blockCached.mutation }; } else if (fieldKeys.length === 1 && fieldKeys.includes('LIST')) { blockCached._fieldKind = FieldKind.LIST; - blockCached._fieldList = { - id: fields.LIST.id, - name: fields.LIST.value + blockCached._argValues = { + LIST: { + id: fields.LIST.id, + name: fields.LIST.value + }, + mutation: blockCached.mutation }; } else if (fieldKeys.length === 1 && fieldKeys.includes('BROADCAST_OPTION')) { blockCached._fieldKind = FieldKind.BROADCAST_OPTION; - blockCached._fieldBroadcastOption = { - id: fields.BROADCAST_OPTION.id, - name: fields.BROADCAST_OPTION.value + blockCached._argValues = { + BROADCAST_OPTION: { + id: fields.BROADCAST_OPTION.id, + name: fields.BROADCAST_OPTION.value + }, + mutation: blockCached.mutation + }; + } else if (fieldKeys.length > 0) { + // FieldKind.DYNAMIC + blockCached._argValues = { + mutation: blockCached.mutation + }; + for (const fieldName in fields) { + blockCached._argValues[fieldName] = fields[fieldName].value; + } + } else { + // FieldKind.NONE + blockCached._argValues = { + mutation: blockCached.mutation }; } @@ -228,9 +250,7 @@ const execute = function (sequencer, thread, recursiveCall) { } const opcode = blockCached.opcode; - const fields = blockCached._fields; const inputs = blockCached._inputs; - const mutation = blockCached.mutation; const blockFunction = blockCached._blockFunction; const isHat = blockCached._isHat; @@ -260,29 +280,10 @@ const execute = function (sequencer, thread, recursiveCall) { return; } - // Generate values for arguments (inputs). - const argValues = {}; + // Update values for arguments (inputs). + let argValues = blockCached._argValues; - // Add all fields on this block to the argValues. Some known fields may - // appear by themselves and can be set to argValues quicker by setting them - // explicitly. - if (blockCached._fieldKind !== FieldKind.NONE) { - switch (blockCached._fieldKind) { - case FieldKind.VARIABLE: - argValues.VARIABLE = blockCached._fieldVariable; - break; - case FieldKind.LIST: - argValues.LIST = blockCached._fieldList; - break; - case FieldKind.BROADCAST_OPTION: - argValues.BROADCAST_OPTION = blockCached._fieldBroadcastOption; - break; - default: - for (const fieldName in fields) { - argValues[fieldName] = fields[fieldName].value; - } - } - } + // Fields are set during blockCached initialization. // Recursively evaluate input blocks. for (const inputName in inputs) { @@ -363,9 +364,6 @@ const execute = function (sequencer, thread, recursiveCall) { } } - // Add any mutation to args (e.g., for procedures). - argValues.mutation = mutation; - let primitiveReportedValue = null; blockUtility.sequencer = sequencer; blockUtility.thread = thread; From 2db2287d127f8687a24b228a36573022c5e6f913 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Mon, 7 May 2018 10:31:28 -0400 Subject: [PATCH 3/4] BlockCached instances are always initialized --- src/engine/blocks.js | 26 +++-- src/engine/execute.js | 214 +++++++++++++++++++----------------------- 2 files changed, 114 insertions(+), 126 deletions(-) diff --git a/src/engine/blocks.js b/src/engine/blocks.js index fddd5d71d..04aed2ea3 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -874,9 +874,10 @@ class Blocks { * reset. * @param {Blocks} blocks Blocks containing the expected blockId * @param {string} blockId blockId for the desired execute cache + * @param {function} CacheType constructor for cached block information * @return {object} execute cache object */ -BlocksExecuteCache.getCached = function (blocks, blockId, CacheType = Object) { +BlocksExecuteCache.getCached = function (blocks, blockId, CacheType) { let cached = blocks._cache._executeCached[blockId]; if (typeof cached !== 'undefined') { return cached; @@ -885,13 +886,22 @@ BlocksExecuteCache.getCached = function (blocks, blockId, CacheType = Object) { const block = blocks.getBlock(blockId); if (typeof block === 'undefined') return null; - cached = new CacheType({ - _initialized: false, - opcode: blocks.getOpcode(block), - fields: blocks.getFields(block), - inputs: blocks.getInputs(block), - mutation: blocks.getMutation(block) - }); + if (typeof CacheType === 'undefined') { + cached = { + opcode: blocks.getOpcode(block), + fields: blocks.getFields(block), + inputs: blocks.getInputs(block), + mutation: blocks.getMutation(block) + }; + } else { + cached = new CacheType(blocks, { + opcode: blocks.getOpcode(block), + fields: blocks.getFields(block), + inputs: blocks.getInputs(block), + mutation: blocks.getMutation(block) + }); + } + blocks._cache._executeCached[blockId] = cached; return cached; }; diff --git a/src/engine/execute.js b/src/engine/execute.js index e01908f58..2867da4a6 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -106,21 +106,22 @@ const handleReport = function ( */ const RECURSIVE = true; -/** - * A simple description of the kind of information held in the fields of a block. - * @enum {string} - */ -const FieldKind = { - NONE: 'NONE', - VARIABLE: 'VARIABLE', - LIST: 'LIST', - BROADCAST_OPTION: 'BROADCAST_OPTION', - DYNAMIC: 'DYNAMIC' -}; - -const BlockCached = function (cached) { - this._initialized = false; +// With the help of the Blocks class create a cached copy of values from +// Blocks and the derived values execute needs. These values can be produced +// one time during the first execution of a block so that later executions +// are faster by using these cached values. This helps turn most costly +// javascript operations like testing if the fields for a block has a +// certain key like VARIABLE into a test that done once is saved on the +// cache object to _isFieldVariable. This reduces the cost later in execute +// when that will determine how execute creates the argValues for the +// blockFunction. +// +// With Blocks providing this private function for execute to use, any time +// Blocks is modified in the editor these cached objects will be cleaned up +// and new cached copies can be created. This lets us optimize this critical +// path while keeping up to date with editor changes to a project. +const BlockCached = function (blockContainer, cached) { this.opcode = cached.opcode; this.fields = cached.fields; this.inputs = cached.inputs; @@ -132,12 +133,76 @@ const BlockCached = function (cached) { this._isShadowBlock = false; this._shadowValue = null; this._fields = null; - this._fieldKind = FieldKind.NONE; - this._fieldVariable = null; - this._fieldList = null; - this._fieldBroadcastOption = null; this._argValues = null; this._inputs = null; + + const {runtime} = blockUtility.sequencer; + + const {opcode, fields, inputs} = this; + + // Assign opcode isHat and blockFunction data to avoid dynamic lookups. + this._isHat = runtime.getIsHat(opcode); + this._blockFunction = runtime.getOpcodeFunction(opcode); + this._definedBlockFunction = typeof this._blockFunction !== 'undefined'; + + const fieldKeys = Object.keys(fields); + + // Store the current shadow value if there is a shadow value. + this._isShadowBlock = fieldKeys.length === 1 && Object.keys(inputs).length === 0; + this._shadowValue = this._isShadowBlock && fields[fieldKeys[0]].value; + + // Store a fields copy. + this._fields = Object.assign({}, this.fields); + + // Create a argValues instance for all executions of this block. Fields are + // static, store them on args. + this._argValues = { + mutation: this.mutation + }; + for (const fieldName in fields) { + if (fieldName === 'VARIABLE') { + this._argValues.VARIABLE = { + id: fields.VARIABLE.id, + name: fields.VARIABLE.value + }; + } else if (fieldName === 'LIST') { + this._argValues.LIST = { + id: fields.LIST.id, + name: fields.LIST.value + }; + } else if (fieldName === 'BROADCAST_OPTION') { + this._argValues.BROADCAST_OPTION = { + id: fields.BROADCAST_OPTION.id, + name: fields.BROADCAST_OPTION.value + }; + } else { + this._argValues[fieldName] = fields[fieldName].value; + } + } + + // Store a modified inputs. This assures the keys are its own properties + // and that custom_block will not be evaluated. + this._inputs = Object.assign({}, this.inputs); + delete this._inputs.custom_block; + + if ('BROADCAST_INPUT' in this._inputs) { + this._argValues.BROADCAST_OPTION = { + id: null, + name: null + }; + + const broadcastInput = this._inputs.BROADCAST_INPUT; + if (broadcastInput.block === broadcastInput.shadow) { + // Shadow dropdown menu is being used. + // Get the appropriate information out of it. + const shadow = blockContainer.getBlock(broadcastInput.shadow); + const broadcastField = shadow.fields.BROADCAST_OPTION; + this._argValues.BROADCAST_OPTION.id = broadcastField.id; + this._argValues.BROADCAST_OPTION.name = broadcastField.name; + + delete this._inputs.BROADCAST_INPUT; + } + } }; /** @@ -149,12 +214,19 @@ const BlockCached = function (cached) { const execute = function (sequencer, thread, recursiveCall) { const runtime = sequencer.runtime; + // sequencer and thread are the same objects during a recursive set of + // execute operations. + if (recursiveCall !== RECURSIVE) { + blockUtility.sequencer = sequencer; + blockUtility.thread = thread; + } + // Current block to execute is the one on the top of the stack. const currentBlockId = thread.peekStack(); const currentStackFrame = thread.peekStackFrame(); let blockContainer = thread.blockContainer; - let blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);; + let blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached); if (blockCached === null) { blockContainer = runtime.flyoutBlocks; blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached); @@ -166,89 +238,6 @@ const execute = function (sequencer, thread, recursiveCall) { } } - // With the help of the Blocks class create a cached copy of values from - // Blocks and the derived values execute needs. These values can be produced - // one time during the first execution of a block so that later executions - // are faster by using these cached values. This helps turn most costly - // javascript operations like testing if the fields for a block has a - // certain key like VARIABLE into a test that done once is saved on the - // cache object to _isFieldVariable. This reduces the cost later in execute - // when that will determine how execute creates the argValues for the - // blockFunction. - // - // With Blocks providing this private function for execute to use, any time - // Blocks is modified in the editor these cached objects will be cleaned up - // and new cached copies can be created. This lets us optimize this critical - // path while keeping up to date with editor changes to a project. - if (blockCached._initialized !== true) { - const {opcode, fields, inputs} = blockCached; - - // Assign opcode isHat and blockFunction data to avoid dynamic lookups. - blockCached._isHat = runtime.getIsHat(opcode); - blockCached._blockFunction = runtime.getOpcodeFunction(opcode); - blockCached._definedBlockFunction = typeof blockCached._blockFunction !== 'undefined'; - - const fieldKeys = Object.keys(fields); - - // Store the current shadow value if there is a shadow value. - blockCached._isShadowBlock = fieldKeys.length === 1 && Object.keys(inputs).length === 0; - blockCached._shadowValue = blockCached._isShadowBlock && fields[fieldKeys[0]].value; - - // Store a fields copy. If fields is a VARIABLE, LIST, or - // BROADCAST_OPTION, store the created values so fields assignment to - // argValues does not iterate over fields. - blockCached._fields = Object.assign({}, blockCached.fields); - blockCached._fieldKind = fieldKeys.length > 0 ? FieldKind.DYNAMIC : FieldKind.NONE; - if (fieldKeys.length === 1 && fieldKeys.includes('VARIABLE')) { - blockCached._fieldKind = FieldKind.VARIABLE; - blockCached._argValues = { - VARIABLE: { - id: fields.VARIABLE.id, - name: fields.VARIABLE.value - }, - mutation: blockCached.mutation - }; - } else if (fieldKeys.length === 1 && fieldKeys.includes('LIST')) { - blockCached._fieldKind = FieldKind.LIST; - blockCached._argValues = { - LIST: { - id: fields.LIST.id, - name: fields.LIST.value - }, - mutation: blockCached.mutation - }; - } else if (fieldKeys.length === 1 && fieldKeys.includes('BROADCAST_OPTION')) { - blockCached._fieldKind = FieldKind.BROADCAST_OPTION; - blockCached._argValues = { - BROADCAST_OPTION: { - id: fields.BROADCAST_OPTION.id, - name: fields.BROADCAST_OPTION.value - }, - mutation: blockCached.mutation - }; - } else if (fieldKeys.length > 0) { - // FieldKind.DYNAMIC - blockCached._argValues = { - mutation: blockCached.mutation - }; - for (const fieldName in fields) { - blockCached._argValues[fieldName] = fields[fieldName].value; - } - } else { - // FieldKind.NONE - blockCached._argValues = { - mutation: blockCached.mutation - }; - } - - // Store a modified inputs. This assures the keys are its own properties - // and that custom_block will not be evaluated. - blockCached._inputs = Object.assign({}, blockCached.inputs); - delete blockCached._inputs.custom_block; - - blockCached._initialized = true; - } - const opcode = blockCached.opcode; const inputs = blockCached._inputs; const blockFunction = blockCached._blockFunction; @@ -281,7 +270,7 @@ const execute = function (sequencer, thread, recursiveCall) { } // Update values for arguments (inputs). - let argValues = blockCached._argValues; + const argValues = blockCached._argValues; // Fields are set during blockCached initialization. @@ -339,25 +328,16 @@ const execute = function (sequencer, thread, recursiveCall) { } else if (typeof currentStackFrame.reported[inputName] !== 'undefined') { inputValue = currentStackFrame.reported[inputName]; } + if (inputName === 'BROADCAST_INPUT') { const broadcastInput = inputs[inputName]; // Check if something is plugged into the broadcast block, or // if the shadow dropdown menu is being used. - if (broadcastInput.block === broadcastInput.shadow) { - // Shadow dropdown menu is being used. - // Get the appropriate information out of it. - const shadow = blockContainer.getBlock(broadcastInput.shadow); - const broadcastField = shadow.fields.BROADCAST_OPTION; - argValues.BROADCAST_OPTION = { - id: broadcastField.id, - name: broadcastField.value - }; - } else { + if (broadcastInput.block !== broadcastInput.shadow) { // Something is plugged into the broadcast input. // Cast it to a string. We don't need an id here. - argValues.BROADCAST_OPTION = { - name: cast.toString(inputValue) - }; + argValues.BROADCAST_OPTION.id = null; + argValues.BROADCAST_OPTION.name = cast.toString(inputValue); } } else { argValues[inputName] = inputValue; @@ -365,8 +345,6 @@ const execute = function (sequencer, thread, recursiveCall) { } let primitiveReportedValue = null; - blockUtility.sequencer = sequencer; - blockUtility.thread = thread; if (runtime.profiler !== null) { if (blockFunctionProfilerId === -1) { blockFunctionProfilerId = runtime.profiler.idByName(blockFunctionProfilerFrame); From 8107349bec846e6895b8716bcf6845b350036579 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Mon, 7 May 2018 11:09:45 -0400 Subject: [PATCH 4/4] Comment BlockCached --- src/engine/execute.js | 221 ++++++++++++++++++++++++++---------------- 1 file changed, 139 insertions(+), 82 deletions(-) diff --git a/src/engine/execute.js b/src/engine/execute.js index 2867da4a6..f0fde3b97 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -106,104 +106,161 @@ const handleReport = function ( */ const RECURSIVE = true; -// With the help of the Blocks class create a cached copy of values from -// Blocks and the derived values execute needs. These values can be produced -// one time during the first execution of a block so that later executions -// are faster by using these cached values. This helps turn most costly -// javascript operations like testing if the fields for a block has a -// certain key like VARIABLE into a test that done once is saved on the -// cache object to _isFieldVariable. This reduces the cost later in execute -// when that will determine how execute creates the argValues for the -// blockFunction. -// -// With Blocks providing this private function for execute to use, any time -// Blocks is modified in the editor these cached objects will be cleaned up -// and new cached copies can be created. This lets us optimize this critical -// path while keeping up to date with editor changes to a project. +/** + * A execute.js internal representation of a block to reduce the time spent in + * execute as the same blocks are called the most. + * + * With the help of the Blocks class create a mutable copy of block + * information. The members of BlockCached derived values of block information + * that does not need to be reevaluated until a change in Blocks. Since Blocks + * handles where the cache instance is stored, it drops all cache versions of a + * block when any change happens to it. This way we can quickly execute blocks + * and keep perform the right action according to the current block information + * in the editor. + * + * @param {Blocks} blockContainer the related Blocks instance + * @param {object} cached default set of cached values + */ +class BlockCached { + constructor (blockContainer, cached) { + /** + * Block operation code for this block. + * @type {string} + */ + this.opcode = cached.opcode; -const BlockCached = function (blockContainer, cached) { - this.opcode = cached.opcode; - this.fields = cached.fields; - this.inputs = cached.inputs; - this.mutation = cached.mutation; + /** + * Original block object containing argument values for static fields. + * @type {object} + */ + this.fields = cached.fields; - this._isHat = false; - this._blockFunction = null; - this._definedBlockFunction = false; - this._isShadowBlock = false; - this._shadowValue = null; - this._fields = null; - this._argValues = null; - this._inputs = null; + /** + * Original block object containing argument values for executable inputs. + * @type {object} + */ + this.inputs = cached.inputs; - const {runtime} = blockUtility.sequencer; + /** + * Procedure mutation. + * @type {?object} + */ + this.mutation = cached.mutation; - const {opcode, fields, inputs} = this; + /** + * Is the opcode a hat (event responder) block. + * @type {boolean} + */ + this._isHat = false; - // Assign opcode isHat and blockFunction data to avoid dynamic lookups. - this._isHat = runtime.getIsHat(opcode); - this._blockFunction = runtime.getOpcodeFunction(opcode); - this._definedBlockFunction = typeof this._blockFunction !== 'undefined'; + /** + * The block opcode's implementation function. + * @type {?function} + */ + this._blockFunction = null; - const fieldKeys = Object.keys(fields); + /** + * Is the block function defined for this opcode? + * @type {boolean} + */ + this._definedBlockFunction = false; - // Store the current shadow value if there is a shadow value. - this._isShadowBlock = fieldKeys.length === 1 && Object.keys(inputs).length === 0; - this._shadowValue = this._isShadowBlock && fields[fieldKeys[0]].value; + /** + * Is this block a block with no function but a static value to return. + * @type {boolean} + */ + this._isShadowBlock = false; - // Store a fields copy. - this._fields = Object.assign({}, this.fields); + /** + * The static value of this block if it is a shadow block. + * @type {?any} + */ + this._shadowValue = null; - // Create a argValues instance for all executions of this block. Fields are - // static, store them on args. - this._argValues = { - mutation: this.mutation - }; - for (const fieldName in fields) { - if (fieldName === 'VARIABLE') { - this._argValues.VARIABLE = { - id: fields.VARIABLE.id, - name: fields.VARIABLE.value - }; - } else if (fieldName === 'LIST') { - this._argValues.LIST = { - id: fields.LIST.id, - name: fields.LIST.value - }; - } else if (fieldName === 'BROADCAST_OPTION') { - this._argValues.BROADCAST_OPTION = { - id: fields.BROADCAST_OPTION.id, - name: fields.BROADCAST_OPTION.value - }; - } else { - this._argValues[fieldName] = fields[fieldName].value; - } - } + /** + * A copy of the block's fields that may be modified. + * @type {object} + */ + this._fields = Object.assign({}, this.fields); - // Store a modified inputs. This assures the keys are its own properties - // and that custom_block will not be evaluated. - this._inputs = Object.assign({}, this.inputs); - delete this._inputs.custom_block; + /** + * A copy of the block's inputs that may be modified. + * @type {object} + */ + this._inputs = Object.assign({}, this.inputs); - if ('BROADCAST_INPUT' in this._inputs) { - this._argValues.BROADCAST_OPTION = { - id: null, - name: null + /** + * An arguments object for block implementations. All executions of this + * specific block will use this objecct. + * @type {object} + */ + this._argValues = { + mutation: this.mutation }; - const broadcastInput = this._inputs.BROADCAST_INPUT; - if (broadcastInput.block === broadcastInput.shadow) { - // Shadow dropdown menu is being used. - // Get the appropriate information out of it. - const shadow = blockContainer.getBlock(broadcastInput.shadow); - const broadcastField = shadow.fields.BROADCAST_OPTION; - this._argValues.BROADCAST_OPTION.id = broadcastField.id; - this._argValues.BROADCAST_OPTION.name = broadcastField.name; + const {runtime} = blockUtility.sequencer; - delete this._inputs.BROADCAST_INPUT; + const {opcode, fields, inputs} = this; + + // Assign opcode isHat and blockFunction data to avoid dynamic lookups. + this._isHat = runtime.getIsHat(opcode); + this._blockFunction = runtime.getOpcodeFunction(opcode); + this._definedBlockFunction = typeof this._blockFunction !== 'undefined'; + + // Store the current shadow value if there is a shadow value. + const fieldKeys = Object.keys(fields); + this._isShadowBlock = ( + !this._definedBlockFunction && + fieldKeys.length === 1 && + Object.keys(inputs).length === 0 + ); + this._shadowValue = this._isShadowBlock && fields[fieldKeys[0]].value; + + // Store the static fields onto _argValues. + for (const fieldName in fields) { + if ( + fieldName === 'VARIABLE' || + fieldName === 'LIST' || + fieldName === 'BROADCAST_OPTION' + ) { + this._argValues[fieldName] = { + id: fields[fieldName].id, + name: fields[fieldName].value + }; + } else { + this._argValues[fieldName] = fields[fieldName].value; + } + } + + // Remove custom_block. It is not part of block execution. + delete this._inputs.custom_block; + + if ('BROADCAST_INPUT' in this._inputs) { + // BROADCAST_INPUT is called BROADCAST_OPTION in the args and is an + // object with an unchanging shape. + this._argValues.BROADCAST_OPTION = { + id: null, + name: null + }; + + // We can go ahead and compute BROADCAST_INPUT if it is a shadow + // value. + const broadcastInput = this._inputs.BROADCAST_INPUT; + if (broadcastInput.block === broadcastInput.shadow) { + // Shadow dropdown menu is being used. + // Get the appropriate information out of it. + const shadow = blockContainer.getBlock(broadcastInput.shadow); + const broadcastField = shadow.fields.BROADCAST_OPTION; + this._argValues.BROADCAST_OPTION.id = broadcastField.id; + this._argValues.BROADCAST_OPTION.name = broadcastField.value; + + // Evaluating BROADCAST_INPUT here we do not need to do so + // later. + delete this._inputs.BROADCAST_INPUT; + } } } -}; +} /** * Execute a block.