mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-06-04 09:33:57 -04:00
Stack management, stack frames, forever and repeat
This commit is contained in:
parent
4c6f08f665
commit
379b2dec79
6 changed files with 135 additions and 55 deletions
|
@ -22,12 +22,22 @@ Scratch3Blocks.prototype.getPrimitives = function() {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3Blocks.prototype.repeat = function() {
|
Scratch3Blocks.prototype.repeat = function(argValues, util) {
|
||||||
console.log('Running: control_repeat');
|
// Initialize loop
|
||||||
|
if (util.stackFrame.loopCounter === undefined) {
|
||||||
|
util.stackFrame.loopCounter = 4; // @todo arg
|
||||||
|
}
|
||||||
|
// Decrease counter
|
||||||
|
util.stackFrame.loopCounter--;
|
||||||
|
// If we still have some left, start the substack
|
||||||
|
if (util.stackFrame.loopCounter >= 0) {
|
||||||
|
util.startSubstack();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3Blocks.prototype.forever = function() {
|
Scratch3Blocks.prototype.forever = function(argValues, util) {
|
||||||
console.log('Running: control_forever');
|
console.log('Running: control_forever');
|
||||||
|
util.startSubstack();
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3Blocks.prototype.wait = function(argValues, util) {
|
Scratch3Blocks.prototype.wait = function(argValues, util) {
|
||||||
|
|
|
@ -386,24 +386,4 @@ Runtime.prototype._getOpcode = function (id) {
|
||||||
return this.blocks[id].opcode;
|
return this.blocks[id].opcode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Set block execution data
|
|
||||||
* @param {!string} id Block ID
|
|
||||||
* @param {!Any} key Data key
|
|
||||||
* @param {?Any} value Data value
|
|
||||||
*/
|
|
||||||
Runtime.prototype.setBlockExecutionData = function (id, key, value) {
|
|
||||||
this.blockExecutionData[id][key] = value;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get block execution data
|
|
||||||
* @param {!string} id Block ID
|
|
||||||
* @param {!Any} key Data key
|
|
||||||
* @return {?Any} Data value
|
|
||||||
*/
|
|
||||||
Runtime.prototype.getBlockExecutionData = function (id, key) {
|
|
||||||
return this.blockExecutionData[id][key];
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Runtime;
|
module.exports = Runtime;
|
||||||
|
|
|
@ -59,8 +59,18 @@ Sequencer.prototype.stepThreads = function (threads) {
|
||||||
}
|
}
|
||||||
if (activeThread.nextBlock === null &&
|
if (activeThread.nextBlock === null &&
|
||||||
activeThread.status === Thread.STATUS_DONE) {
|
activeThread.status === Thread.STATUS_DONE) {
|
||||||
// Finished with this thread - tell the runtime to clean it up.
|
// First attempt to pop from the stack
|
||||||
inactiveThreads.push(activeThread);
|
if (activeThread.stack.length > 0
|
||||||
|
&& activeThread.nextBlock === null) {
|
||||||
|
activeThread.nextBlock = activeThread.stack.pop();
|
||||||
|
// Don't pop stack frame - we need the data.
|
||||||
|
// A new one won't be created when we execute.
|
||||||
|
}
|
||||||
|
if (activeThread.nextBlock === null) {
|
||||||
|
// No more on the stack
|
||||||
|
// Finished with this thread - tell runtime to clean it up.
|
||||||
|
inactiveThreads.push(activeThread);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Keep this thead in the loop.
|
// Keep this thead in the loop.
|
||||||
newThreads.push(activeThread);
|
newThreads.push(activeThread);
|
||||||
|
@ -90,6 +100,15 @@ Sequencer.prototype.stepThread = function (thread) {
|
||||||
|
|
||||||
var opcode = this.runtime._getOpcode(currentBlock);
|
var opcode = this.runtime._getOpcode(currentBlock);
|
||||||
|
|
||||||
|
// Push the current block to the stack
|
||||||
|
thread.stack.push(currentBlock);
|
||||||
|
// Push an empty stack frame, if we need one.
|
||||||
|
// Might not, if we just popped the stack.
|
||||||
|
if (thread.stack.length > thread.stackFrames.length) {
|
||||||
|
thread.stackFrames.push({});
|
||||||
|
}
|
||||||
|
var currentStackFrame = thread.stackFrames[thread.stackFrames.length - 1];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback for the primitive to indicate its thread should yield.
|
* A callback for the primitive to indicate its thread should yield.
|
||||||
* @type {Function}
|
* @type {Function}
|
||||||
|
@ -105,25 +124,29 @@ Sequencer.prototype.stepThread = function (thread) {
|
||||||
var instance = this;
|
var instance = this;
|
||||||
var threadDoneCallback = function () {
|
var threadDoneCallback = function () {
|
||||||
thread.status = Thread.STATUS_DONE;
|
thread.status = Thread.STATUS_DONE;
|
||||||
// Refresh nextBlock in case it has changed during the yield.
|
// Refresh nextBlock in case it has changed during a yield.
|
||||||
thread.nextBlock = instance.runtime._getNextBlock(currentBlock);
|
thread.nextBlock = instance.runtime._getNextBlock(currentBlock);
|
||||||
instance.runtime.glowBlock(currentBlock, false);
|
instance.runtime.glowBlock(currentBlock, false);
|
||||||
|
// Pop the stack and stack frame
|
||||||
|
thread.stack.pop();
|
||||||
|
thread.stackFrames.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback for the primitive to set data on the block level.
|
* Record whether we have switched stack,
|
||||||
* @type {Function}
|
* to avoid proceeding the thread automatically.
|
||||||
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
var blockDataSetCallback = function (key, value) {
|
var switchedStack = false;
|
||||||
instance.runtime.setBlockExecutionData(currentBlock, key, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback for the primitive to get data on the block level.
|
* A callback for a primitive to start a substack.
|
||||||
* @type {Function}
|
* @type {Function}
|
||||||
*/
|
*/
|
||||||
var blockDataGetCallback = function (key) {
|
var threadStartSubstack = function () {
|
||||||
return instance.runtime.getBlockExecutionData(currentBlock, key);
|
// Set nextBlock to the start of the substack
|
||||||
|
thread.nextBlock = instance.runtime._getSubstack(currentBlock).value;
|
||||||
|
instance.runtime.glowBlock(currentBlock, false);
|
||||||
|
switchedStack = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @todo
|
// @todo
|
||||||
|
@ -145,8 +168,8 @@ Sequencer.prototype.stepThread = function (thread) {
|
||||||
yield: threadYieldCallback,
|
yield: threadYieldCallback,
|
||||||
done: threadDoneCallback,
|
done: threadDoneCallback,
|
||||||
timeout: YieldTimers.timeout,
|
timeout: YieldTimers.timeout,
|
||||||
setData: blockDataSetCallback,
|
stackFrame: currentStackFrame,
|
||||||
getData: blockDataGetCallback
|
startSubstack: threadStartSubstack
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
|
@ -158,10 +181,9 @@ Sequencer.prototype.stepThread = function (thread) {
|
||||||
if (YieldTimers.timerId > oldYieldTimerId) {
|
if (YieldTimers.timerId > oldYieldTimerId) {
|
||||||
thread.yieldTimerId = YieldTimers.timerId;
|
thread.yieldTimerId = YieldTimers.timerId;
|
||||||
}
|
}
|
||||||
if (thread.status === Thread.STATUS_RUNNING) {
|
if (thread.status === Thread.STATUS_RUNNING && !switchedStack) {
|
||||||
// Thread executed without yielding - move to done
|
// Thread executed without yielding - move to done
|
||||||
thread.status = Thread.STATUS_DONE;
|
threadDoneCallback();
|
||||||
this.runtime.glowBlock(currentBlock, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,12 @@ function Thread (firstBlock) {
|
||||||
*/
|
*/
|
||||||
this.stack = [];
|
this.stack = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack frames for the thread. Store metadata for the executing blocks.
|
||||||
|
* @type {Array.<Object>}
|
||||||
|
*/
|
||||||
|
this.stackFrames = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status of the thread, one of three states (below)
|
* Status of the thread, one of three states (below)
|
||||||
* @type {number}
|
* @type {number}
|
||||||
|
|
82
vm.js
82
vm.js
|
@ -1201,6 +1201,12 @@
|
||||||
*/
|
*/
|
||||||
this.blocks = {};
|
this.blocks = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive-accessible execution metadata for each block.
|
||||||
|
* @type {Object.<string, Object>}
|
||||||
|
*/
|
||||||
|
this.blockExecutionData = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All stacks in the workspace.
|
* All stacks in the workspace.
|
||||||
* A list of block IDs that represent stacks (first block in stack).
|
* A list of block IDs that represent stacks (first block in stack).
|
||||||
|
@ -1268,6 +1274,7 @@
|
||||||
Runtime.prototype.createBlock = function (block, opt_isFlyoutBlock) {
|
Runtime.prototype.createBlock = function (block, opt_isFlyoutBlock) {
|
||||||
// Create new block
|
// Create new block
|
||||||
this.blocks[block.id] = block;
|
this.blocks[block.id] = block;
|
||||||
|
this.blockExecutionData[block.id] = {};
|
||||||
|
|
||||||
// Walk each field and add any shadow blocks
|
// Walk each field and add any shadow blocks
|
||||||
// @todo Expand this to cover vertical / nested blocks
|
// @todo Expand this to cover vertical / nested blocks
|
||||||
|
@ -1276,6 +1283,7 @@
|
||||||
for (var y in shadows) {
|
for (var y in shadows) {
|
||||||
var shadow = shadows[y];
|
var shadow = shadows[y];
|
||||||
this.blocks[shadow.id] = shadow;
|
this.blocks[shadow.id] = shadow;
|
||||||
|
this.blockExecutionData[shadow.id] = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1372,6 +1380,7 @@
|
||||||
|
|
||||||
// Delete block
|
// Delete block
|
||||||
delete this.blocks[e.id];
|
delete this.blocks[e.id];
|
||||||
|
delete this.blockExecutionData[e.id];
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
@ -1623,8 +1632,16 @@
|
||||||
}
|
}
|
||||||
if (activeThread.nextBlock === null &&
|
if (activeThread.nextBlock === null &&
|
||||||
activeThread.status === Thread.STATUS_DONE) {
|
activeThread.status === Thread.STATUS_DONE) {
|
||||||
// Finished with this thread - tell the runtime to clean it up.
|
// First attempt to pop from the stack
|
||||||
inactiveThreads.push(activeThread);
|
if (activeThread.stack.length > 0
|
||||||
|
&& activeThread.nextBlock === null) {
|
||||||
|
activeThread.nextBlock = activeThread.stack.pop();
|
||||||
|
}
|
||||||
|
if (activeThread.nextBlock === null) {
|
||||||
|
// No more on the stack
|
||||||
|
// Finished with this thread - tell runtime to clean it up.
|
||||||
|
inactiveThreads.push(activeThread);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Keep this thead in the loop.
|
// Keep this thead in the loop.
|
||||||
newThreads.push(activeThread);
|
newThreads.push(activeThread);
|
||||||
|
@ -1654,6 +1671,13 @@
|
||||||
|
|
||||||
var opcode = this.runtime._getOpcode(currentBlock);
|
var opcode = this.runtime._getOpcode(currentBlock);
|
||||||
|
|
||||||
|
// Push the current block to the stack
|
||||||
|
thread.stack.push(currentBlock);
|
||||||
|
// Push an empty stack frame, if we need one
|
||||||
|
if (thread.stack.length > thread.stackFrames.length) {
|
||||||
|
thread.stackFrames.push({});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback for the primitive to indicate its thread should yield.
|
* A callback for the primitive to indicate its thread should yield.
|
||||||
* @type {Function}
|
* @type {Function}
|
||||||
|
@ -1669,13 +1693,34 @@
|
||||||
var instance = this;
|
var instance = this;
|
||||||
var threadDoneCallback = function () {
|
var threadDoneCallback = function () {
|
||||||
thread.status = Thread.STATUS_DONE;
|
thread.status = Thread.STATUS_DONE;
|
||||||
// Refresh nextBlock in case it has changed during the yield.
|
// Refresh nextBlock in case it has changed during a yield.
|
||||||
thread.nextBlock = instance.runtime._getNextBlock(currentBlock);
|
thread.nextBlock = instance.runtime._getNextBlock(currentBlock);
|
||||||
instance.runtime.glowBlock(currentBlock, false);
|
instance.runtime.glowBlock(currentBlock, false);
|
||||||
|
// Pop the stack and stack frame
|
||||||
|
thread.stack.pop();
|
||||||
|
thread.stackFrames.pop();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record whether we have switched stack,
|
||||||
|
* to avoid proceeding the thread automatically.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
var switchedStack = false;
|
||||||
|
/**
|
||||||
|
* A callback for a primitive to start a substack.
|
||||||
|
* @type {Function}
|
||||||
|
*/
|
||||||
|
var threadStartSubstack = function () {
|
||||||
|
// Set nextBlock to the start of the substack
|
||||||
|
thread.nextBlock = instance.runtime._getSubstack(currentBlock).value;
|
||||||
|
instance.runtime.glowBlock(currentBlock, false);
|
||||||
|
switchedStack = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @todo
|
// @todo
|
||||||
var argValues = [];
|
var argValues = [];
|
||||||
|
var currentStackFrame = thread.stackFrames[thread.stackFrames.length - 1];
|
||||||
|
|
||||||
if (!opcode) {
|
if (!opcode) {
|
||||||
console.warn('Could not get opcode for block: ' + currentBlock);
|
console.warn('Could not get opcode for block: ' + currentBlock);
|
||||||
|
@ -1692,7 +1737,9 @@
|
||||||
blockFunction(argValues, {
|
blockFunction(argValues, {
|
||||||
yield: threadYieldCallback,
|
yield: threadYieldCallback,
|
||||||
done: threadDoneCallback,
|
done: threadDoneCallback,
|
||||||
timeout: YieldTimers.timeout
|
timeout: YieldTimers.timeout,
|
||||||
|
stackFrame: currentStackFrame,
|
||||||
|
startSubstack: threadStartSubstack
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch(e) {
|
catch(e) {
|
||||||
|
@ -1704,10 +1751,9 @@
|
||||||
if (YieldTimers.timerId > oldYieldTimerId) {
|
if (YieldTimers.timerId > oldYieldTimerId) {
|
||||||
thread.yieldTimerId = YieldTimers.timerId;
|
thread.yieldTimerId = YieldTimers.timerId;
|
||||||
}
|
}
|
||||||
if (thread.status === Thread.STATUS_RUNNING) {
|
if (thread.status === Thread.STATUS_RUNNING && !switchedStack) {
|
||||||
// Thread executed without yielding - move to done
|
// Thread executed without yielding - move to done
|
||||||
thread.status = Thread.STATUS_DONE;
|
threadDoneCallback();
|
||||||
this.runtime.glowBlock(currentBlock, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1771,6 +1817,12 @@
|
||||||
*/
|
*/
|
||||||
this.stack = [];
|
this.stack = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack frames for the thread. Store metadata for the executing blocks.
|
||||||
|
* @type {Array.<Object>}
|
||||||
|
*/
|
||||||
|
this.stackFrames = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status of the thread, one of three states (below)
|
* Status of the thread, one of three states (below)
|
||||||
* @type {number}
|
* @type {number}
|
||||||
|
@ -1933,12 +1985,22 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3Blocks.prototype.repeat = function() {
|
Scratch3Blocks.prototype.repeat = function(argValues, util) {
|
||||||
console.log('Running: control_repeat');
|
// Initialize loop
|
||||||
|
if (util.stackFrame.loopCounter === undefined) {
|
||||||
|
util.stackFrame.loopCounter = 4; // @todo arg
|
||||||
|
}
|
||||||
|
// Decrease counter
|
||||||
|
util.stackFrame.loopCounter--;
|
||||||
|
// If we still have some left, start the substack
|
||||||
|
if (util.stackFrame.loopCounter >= 0) {
|
||||||
|
util.startSubstack();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3Blocks.prototype.forever = function() {
|
Scratch3Blocks.prototype.forever = function(argValues, util) {
|
||||||
console.log('Running: control_forever');
|
console.log('Running: control_forever');
|
||||||
|
util.startSubstack();
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3Blocks.prototype.wait = function(argValues, util) {
|
Scratch3Blocks.prototype.wait = function(argValues, util) {
|
||||||
|
|
10
vm.min.js
vendored
10
vm.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue