mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -05:00
BlockCached instances are always initialized
This commit is contained in:
parent
5fd749918f
commit
2db2287d12
2 changed files with 114 additions and 126 deletions
|
@ -874,9 +874,10 @@ class Blocks {
|
||||||
* reset.
|
* reset.
|
||||||
* @param {Blocks} blocks Blocks containing the expected blockId
|
* @param {Blocks} blocks Blocks containing the expected blockId
|
||||||
* @param {string} blockId blockId for the desired execute cache
|
* @param {string} blockId blockId for the desired execute cache
|
||||||
|
* @param {function} CacheType constructor for cached block information
|
||||||
* @return {object} execute cache object
|
* @return {object} execute cache object
|
||||||
*/
|
*/
|
||||||
BlocksExecuteCache.getCached = function (blocks, blockId, CacheType = Object) {
|
BlocksExecuteCache.getCached = function (blocks, blockId, CacheType) {
|
||||||
let cached = blocks._cache._executeCached[blockId];
|
let cached = blocks._cache._executeCached[blockId];
|
||||||
if (typeof cached !== 'undefined') {
|
if (typeof cached !== 'undefined') {
|
||||||
return cached;
|
return cached;
|
||||||
|
@ -885,13 +886,22 @@ BlocksExecuteCache.getCached = function (blocks, blockId, CacheType = Object) {
|
||||||
const block = blocks.getBlock(blockId);
|
const block = blocks.getBlock(blockId);
|
||||||
if (typeof block === 'undefined') return null;
|
if (typeof block === 'undefined') return null;
|
||||||
|
|
||||||
cached = new CacheType({
|
if (typeof CacheType === 'undefined') {
|
||||||
_initialized: false,
|
cached = {
|
||||||
opcode: blocks.getOpcode(block),
|
opcode: blocks.getOpcode(block),
|
||||||
fields: blocks.getFields(block),
|
fields: blocks.getFields(block),
|
||||||
inputs: blocks.getInputs(block),
|
inputs: blocks.getInputs(block),
|
||||||
mutation: blocks.getMutation(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;
|
blocks._cache._executeCached[blockId] = cached;
|
||||||
return cached;
|
return cached;
|
||||||
};
|
};
|
||||||
|
|
|
@ -106,21 +106,22 @@ const handleReport = function (
|
||||||
*/
|
*/
|
||||||
const RECURSIVE = true;
|
const RECURSIVE = true;
|
||||||
|
|
||||||
/**
|
// With the help of the Blocks class create a cached copy of values from
|
||||||
* A simple description of the kind of information held in the fields of a block.
|
// Blocks and the derived values execute needs. These values can be produced
|
||||||
* @enum {string}
|
// 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
|
||||||
const FieldKind = {
|
// javascript operations like testing if the fields for a block has a
|
||||||
NONE: 'NONE',
|
// certain key like VARIABLE into a test that done once is saved on the
|
||||||
VARIABLE: 'VARIABLE',
|
// cache object to _isFieldVariable. This reduces the cost later in execute
|
||||||
LIST: 'LIST',
|
// when that will determine how execute creates the argValues for the
|
||||||
BROADCAST_OPTION: 'BROADCAST_OPTION',
|
// blockFunction.
|
||||||
DYNAMIC: 'DYNAMIC'
|
//
|
||||||
};
|
// 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
|
||||||
const BlockCached = function (cached) {
|
// and new cached copies can be created. This lets us optimize this critical
|
||||||
this._initialized = false;
|
// path while keeping up to date with editor changes to a project.
|
||||||
|
|
||||||
|
const BlockCached = function (blockContainer, cached) {
|
||||||
this.opcode = cached.opcode;
|
this.opcode = cached.opcode;
|
||||||
this.fields = cached.fields;
|
this.fields = cached.fields;
|
||||||
this.inputs = cached.inputs;
|
this.inputs = cached.inputs;
|
||||||
|
@ -132,12 +133,76 @@ const BlockCached = function (cached) {
|
||||||
this._isShadowBlock = false;
|
this._isShadowBlock = false;
|
||||||
this._shadowValue = null;
|
this._shadowValue = null;
|
||||||
this._fields = null;
|
this._fields = null;
|
||||||
this._fieldKind = FieldKind.NONE;
|
|
||||||
this._fieldVariable = null;
|
|
||||||
this._fieldList = null;
|
|
||||||
this._fieldBroadcastOption = null;
|
|
||||||
this._argValues = null;
|
this._argValues = null;
|
||||||
this._inputs = 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 execute = function (sequencer, thread, recursiveCall) {
|
||||||
const runtime = sequencer.runtime;
|
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.
|
// Current block to execute is the one on the top of the stack.
|
||||||
const currentBlockId = thread.peekStack();
|
const currentBlockId = thread.peekStack();
|
||||||
const currentStackFrame = thread.peekStackFrame();
|
const currentStackFrame = thread.peekStackFrame();
|
||||||
|
|
||||||
let blockContainer = thread.blockContainer;
|
let blockContainer = thread.blockContainer;
|
||||||
let blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);;
|
let blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);
|
||||||
if (blockCached === null) {
|
if (blockCached === null) {
|
||||||
blockContainer = runtime.flyoutBlocks;
|
blockContainer = runtime.flyoutBlocks;
|
||||||
blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);
|
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 opcode = blockCached.opcode;
|
||||||
const inputs = blockCached._inputs;
|
const inputs = blockCached._inputs;
|
||||||
const blockFunction = blockCached._blockFunction;
|
const blockFunction = blockCached._blockFunction;
|
||||||
|
@ -281,7 +270,7 @@ const execute = function (sequencer, thread, recursiveCall) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update values for arguments (inputs).
|
// Update values for arguments (inputs).
|
||||||
let argValues = blockCached._argValues;
|
const argValues = blockCached._argValues;
|
||||||
|
|
||||||
// Fields are set during blockCached initialization.
|
// Fields are set during blockCached initialization.
|
||||||
|
|
||||||
|
@ -339,25 +328,16 @@ const execute = function (sequencer, thread, recursiveCall) {
|
||||||
} else if (typeof currentStackFrame.reported[inputName] !== 'undefined') {
|
} else if (typeof currentStackFrame.reported[inputName] !== 'undefined') {
|
||||||
inputValue = currentStackFrame.reported[inputName];
|
inputValue = currentStackFrame.reported[inputName];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputName === 'BROADCAST_INPUT') {
|
if (inputName === 'BROADCAST_INPUT') {
|
||||||
const broadcastInput = inputs[inputName];
|
const broadcastInput = inputs[inputName];
|
||||||
// Check if something is plugged into the broadcast block, or
|
// Check if something is plugged into the broadcast block, or
|
||||||
// if the shadow dropdown menu is being used.
|
// if the shadow dropdown menu is being used.
|
||||||
if (broadcastInput.block === broadcastInput.shadow) {
|
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 {
|
|
||||||
// Something is plugged into the broadcast input.
|
// Something is plugged into the broadcast input.
|
||||||
// Cast it to a string. We don't need an id here.
|
// Cast it to a string. We don't need an id here.
|
||||||
argValues.BROADCAST_OPTION = {
|
argValues.BROADCAST_OPTION.id = null;
|
||||||
name: cast.toString(inputValue)
|
argValues.BROADCAST_OPTION.name = cast.toString(inputValue);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
argValues[inputName] = inputValue;
|
argValues[inputName] = inputValue;
|
||||||
|
@ -365,8 +345,6 @@ const execute = function (sequencer, thread, recursiveCall) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let primitiveReportedValue = null;
|
let primitiveReportedValue = null;
|
||||||
blockUtility.sequencer = sequencer;
|
|
||||||
blockUtility.thread = thread;
|
|
||||||
if (runtime.profiler !== null) {
|
if (runtime.profiler !== null) {
|
||||||
if (blockFunctionProfilerId === -1) {
|
if (blockFunctionProfilerId === -1) {
|
||||||
blockFunctionProfilerId = runtime.profiler.idByName(blockFunctionProfilerFrame);
|
blockFunctionProfilerId = runtime.profiler.idByName(blockFunctionProfilerFrame);
|
||||||
|
|
Loading…
Reference in a new issue