diff --git a/src/compiler/compat-block-utility.js b/src/compiler/compat-block-utility.js index 6fedba1d9d2..b42c09aa09d 100644 --- a/src/compiler/compat-block-utility.js +++ b/src/compiler/compat-block-utility.js @@ -33,6 +33,7 @@ class CompatibilityLayerBlockUtility extends BlockUtility { this.thread = thread; this.sequencer = thread.target.runtime.sequencer; this._startedBranch = null; + this._forceRevaluationOfArguments = false; thread.stack[0] = fakeBlockId; thread.compatibilityStackFrame = stackFrame; } diff --git a/src/compiler/jsexecute.js b/src/compiler/jsexecute.js index b7bff8956ad..18ac1edabc4 100644 --- a/src/compiler/jsexecute.js +++ b/src/compiler/jsexecute.js @@ -135,6 +135,7 @@ const isPromise = value => ( typeof value.then === 'function' ); const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, useFlags, blockId, branchInfo) { + const inputNames = new Set(Object.keys(inputs)); const thread = globalState.thread; const blockUtility = globalState.blockUtility; const stackFrame = branchInfo ? branchInfo.stackFrame : {}; @@ -151,12 +152,39 @@ const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, use return returnValue; }; - const executeBlock = () => { + const inputCache = {}; + let firstRun = true; + const executeBlock = function*() { + let callInputs, reEvaluate = new Set(); + if (firstRun || blockUtility._forceRevaluationOfArguments) { + if (firstRun) { + blockUtility._forceRevaluationOfArguments = false; + reEvaluate = inputNames; + firstRun = false; + } + if (blockUtility._forceRevaluationOfArguments) { + if (Array.isArray(blockUtility._forceRevaluationOfArguments)) { + reEvaluate = new Set(blockUtility._forceRevaluationOfArguments); + } else { + reEvaluate = inputNames; + } + blockUtility._forceRevaluationOfArguments = false; + callInputs = {}; + } + for (const inputName of reEvaluate) { + if (!inputNames.has(inputName)) continue; + const inputValue = ( + typeof inputs[inputName] === 'function' + ) ? (yield* (inputs[inputName]())) : inputs[inputName]; + inputCache[inputName] = inputValue; + if (callInputs) callInputs[inputName] = inputValue; + } + } blockUtility.init(thread, blockId, stackFrame); - return blockFunction(inputs, blockUtility); + return blockFunction(callInputs || inputCache, blockUtility); }; - let returnValue = executeBlock(); + let returnValue = yield* executeBlock(); if (isPromise(returnValue)) { returnValue = finish(yield* waitPromise(returnValue)); if (useFlags) hasResumedFromPromise = true; @@ -183,7 +211,7 @@ const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, use yield; } - returnValue = executeBlock(); + returnValue = yield* executeBlock(); if (isPromise(returnValue)) { returnValue = finish(yield* waitPromise(returnValue)); if (useFlags) hasResumedFromPromise = true; diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js index 914e5cc72b1..23a9e8be865 100644 --- a/src/compiler/jsgen.js +++ b/src/compiler/jsgen.js @@ -1333,7 +1333,7 @@ class JSGenerator { for (const inputName of Object.keys(node.inputs)) { const input = node.inputs[inputName]; const compiledInput = this.descendInput(input).asSafe(); - result += `"${sanitize(inputName)}":${compiledInput},`; + result += `"${sanitize(inputName)}":(function*(){ return( ${compiledInput} ); }),`; } for (const fieldName of Object.keys(node.fields)) { const field = node.fields[fieldName]; @@ -1445,7 +1445,8 @@ JSGenerator.unstable_exports = { ConstantInput, VariableInput, Frame, - sanitize + sanitize, + jsexecute }; // Test hook used by automated snapshot testing.