mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-07-09 12:33:58 -04:00
Interpreter fixes, enhancements, features (#280)
* Thread stepping rework; interp.redraw equivalent * Add turbo mode and pause mode * Yielding behavior to match Scratch 2.0 * Implement warp-mode procedure threads * Add check for recursive call * Inline wait block timer * Revert to setInterval and always drawing * Restore yielding in glide * 30TPS compatibility mode * 5-call count recursion limit * Removing dead primitive code * To simplify, access runtime.threads inline in `stepThreads`. * Warp mode/timer fixes; recursive check fixes; clean-up * Add basic single-stepping * Add single-stepping speed slider * Allow yielding threads to run in single-stepping * Restore inactive threads tracking for block glows * Add clock pausing during pause mode * Documentation and clean-up throughout * Don't look for block glows in `thread.topBlock`. * Add null check for block glows; rename `_updateScriptGlows` to reflect block glowing * Use the current executed block for glow, instead of stack * Add more comments to `stepToProcedure`, and re-arrange to match 2.0 * Tweak to Blocks.prototype.getTopLevelScript * Revert previous * Fix threads array to be resilient to changes during `stepThreads` * Restore inactive threads filtering * Fix typo in "procedure" * !! instead of == true
This commit is contained in:
parent
060d1ab2a5
commit
e49f076fa1
14 changed files with 705 additions and 281 deletions
|
@ -15,6 +15,19 @@
|
||||||
<button id="greenflag">Green flag</button>
|
<button id="greenflag">Green flag</button>
|
||||||
<button id="stopall">Stop</button>
|
<button id="stopall">Stop</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
Turbo: <input id='turbomode' type='checkbox' />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Pause: <input id='pausemode' type='checkbox' />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Compatibility (30 TPS): <input id='compatmode' type='checkbox' />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Single stepping: <input id='singlestepmode' type='checkbox' />
|
||||||
|
<input id='singlestepspeed' type='range' min='1' max='20' value='10' />
|
||||||
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<ul id="playgroundLinks">
|
<ul id="playgroundLinks">
|
||||||
<li><a id="renderexplorer-link" href="#">Renderer</a></li>
|
<li><a id="renderexplorer-link" href="#">Renderer</a></li>
|
||||||
|
|
|
@ -245,6 +245,29 @@ window.onload = function() {
|
||||||
document.getElementById('stopall').addEventListener('click', function() {
|
document.getElementById('stopall').addEventListener('click', function() {
|
||||||
vm.stopAll();
|
vm.stopAll();
|
||||||
});
|
});
|
||||||
|
document.getElementById('turbomode').addEventListener('change', function() {
|
||||||
|
var turboOn = document.getElementById('turbomode').checked;
|
||||||
|
vm.setTurboMode(turboOn);
|
||||||
|
});
|
||||||
|
document.getElementById('pausemode').addEventListener('change', function() {
|
||||||
|
var pauseOn = document.getElementById('pausemode').checked;
|
||||||
|
vm.setPauseMode(pauseOn);
|
||||||
|
});
|
||||||
|
document.getElementById('compatmode').addEventListener('change',
|
||||||
|
function() {
|
||||||
|
var compatibilityMode = document.getElementById('compatmode').checked;
|
||||||
|
vm.setCompatibilityMode(compatibilityMode);
|
||||||
|
});
|
||||||
|
document.getElementById('singlestepmode').addEventListener('change',
|
||||||
|
function() {
|
||||||
|
var singleStep = document.getElementById('singlestepmode').checked;
|
||||||
|
vm.setSingleSteppingMode(singleStep);
|
||||||
|
});
|
||||||
|
document.getElementById('singlestepspeed').addEventListener('input',
|
||||||
|
function() {
|
||||||
|
var speed = document.getElementById('singlestepspeed').value;
|
||||||
|
vm.setSingleSteppingSpeed(speed);
|
||||||
|
});
|
||||||
|
|
||||||
var tabBlockExplorer = document.getElementById('tab-blockexplorer');
|
var tabBlockExplorer = document.getElementById('tab-blockexplorer');
|
||||||
var tabThreadExplorer = document.getElementById('tab-threadexplorer');
|
var tabThreadExplorer = document.getElementById('tab-threadexplorer');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
var Cast = require('../util/cast');
|
var Cast = require('../util/cast');
|
||||||
var Promise = require('promise');
|
var Timer = require('../util/timer');
|
||||||
|
|
||||||
function Scratch3ControlBlocks(runtime) {
|
function Scratch3ControlBlocks(runtime) {
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +23,6 @@ Scratch3ControlBlocks.prototype.getPrimitives = function() {
|
||||||
'control_if': this.if,
|
'control_if': this.if,
|
||||||
'control_if_else': this.ifElse,
|
'control_if_else': this.ifElse,
|
||||||
'control_stop': this.stop,
|
'control_stop': this.stop,
|
||||||
'control_create_clone_of_menu': this.createCloneMenu,
|
|
||||||
'control_create_clone_of': this.createClone,
|
'control_create_clone_of': this.createClone,
|
||||||
'control_delete_this_clone': this.deleteClone
|
'control_delete_this_clone': this.deleteClone
|
||||||
};
|
};
|
||||||
|
@ -46,90 +45,60 @@ Scratch3ControlBlocks.prototype.repeat = function(args, util) {
|
||||||
// Only execute once per frame.
|
// Only execute once per frame.
|
||||||
// When the branch finishes, `repeat` will be executed again and
|
// When the branch finishes, `repeat` will be executed again and
|
||||||
// the second branch will be taken, yielding for the rest of the frame.
|
// the second branch will be taken, yielding for the rest of the frame.
|
||||||
if (!util.stackFrame.executedInFrame) {
|
// Decrease counter
|
||||||
util.stackFrame.executedInFrame = true;
|
util.stackFrame.loopCounter--;
|
||||||
// Decrease counter
|
// If we still have some left, start the branch.
|
||||||
util.stackFrame.loopCounter--;
|
if (util.stackFrame.loopCounter >= 0) {
|
||||||
// If we still have some left, start the branch.
|
util.startBranch(1, true);
|
||||||
if (util.stackFrame.loopCounter >= 0) {
|
|
||||||
util.startBranch();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
util.stackFrame.executedInFrame = false;
|
|
||||||
util.yieldFrame();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.repeatUntil = function(args, util) {
|
Scratch3ControlBlocks.prototype.repeatUntil = function(args, util) {
|
||||||
var condition = Cast.toBoolean(args.CONDITION);
|
var condition = Cast.toBoolean(args.CONDITION);
|
||||||
// Only execute once per frame.
|
// If the condition is true, start the branch.
|
||||||
// When the branch finishes, `repeat` will be executed again and
|
if (!condition) {
|
||||||
// the second branch will be taken, yielding for the rest of the frame.
|
util.startBranch(1, true);
|
||||||
if (!util.stackFrame.executedInFrame) {
|
|
||||||
util.stackFrame.executedInFrame = true;
|
|
||||||
// If the condition is true, start the branch.
|
|
||||||
if (!condition) {
|
|
||||||
util.startBranch();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
util.stackFrame.executedInFrame = false;
|
|
||||||
util.yieldFrame();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.waitUntil = function(args, util) {
|
Scratch3ControlBlocks.prototype.waitUntil = function(args, util) {
|
||||||
var condition = Cast.toBoolean(args.CONDITION);
|
var condition = Cast.toBoolean(args.CONDITION);
|
||||||
// Only execute once per frame.
|
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
util.yieldFrame();
|
util.yield();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.forever = function(args, util) {
|
Scratch3ControlBlocks.prototype.forever = function(args, util) {
|
||||||
// Only execute once per frame.
|
util.startBranch(1, true);
|
||||||
// When the branch finishes, `forever` will be executed again and
|
|
||||||
// the second branch will be taken, yielding for the rest of the frame.
|
|
||||||
if (!util.stackFrame.executedInFrame) {
|
|
||||||
util.stackFrame.executedInFrame = true;
|
|
||||||
util.startBranch();
|
|
||||||
} else {
|
|
||||||
util.stackFrame.executedInFrame = false;
|
|
||||||
util.yieldFrame();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.wait = function(args) {
|
Scratch3ControlBlocks.prototype.wait = function(args, util) {
|
||||||
var duration = Cast.toNumber(args.DURATION);
|
if (!util.stackFrame.timer) {
|
||||||
return new Promise(function(resolve) {
|
util.stackFrame.timer = new Timer();
|
||||||
setTimeout(function() {
|
util.stackFrame.timer.start();
|
||||||
resolve();
|
util.yield();
|
||||||
}, 1000 * duration);
|
this.runtime.requestRedraw();
|
||||||
});
|
} else {
|
||||||
|
var duration = Math.max(0, 1000 * Cast.toNumber(args.DURATION));
|
||||||
|
if (util.stackFrame.timer.timeElapsed() < duration) {
|
||||||
|
util.yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.if = function(args, util) {
|
Scratch3ControlBlocks.prototype.if = function(args, util) {
|
||||||
var condition = Cast.toBoolean(args.CONDITION);
|
var condition = Cast.toBoolean(args.CONDITION);
|
||||||
// Only execute one time. `if` will be returned to
|
if (condition) {
|
||||||
// when the branch finishes, but it shouldn't execute again.
|
util.startBranch(1, false);
|
||||||
if (util.stackFrame.executedInFrame === undefined) {
|
|
||||||
util.stackFrame.executedInFrame = true;
|
|
||||||
if (condition) {
|
|
||||||
util.startBranch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.ifElse = function(args, util) {
|
Scratch3ControlBlocks.prototype.ifElse = function(args, util) {
|
||||||
var condition = Cast.toBoolean(args.CONDITION);
|
var condition = Cast.toBoolean(args.CONDITION);
|
||||||
// Only execute one time. `ifElse` will be returned to
|
if (condition) {
|
||||||
// when the branch finishes, but it shouldn't execute again.
|
util.startBranch(1, false);
|
||||||
if (util.stackFrame.executedInFrame === undefined) {
|
} else {
|
||||||
util.stackFrame.executedInFrame = true;
|
util.startBranch(2, false);
|
||||||
if (condition) {
|
|
||||||
util.startBranch(1);
|
|
||||||
} else {
|
|
||||||
util.startBranch(2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -145,11 +114,6 @@ Scratch3ControlBlocks.prototype.stop = function(args, util) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// @todo (GH-146): remove.
|
|
||||||
Scratch3ControlBlocks.prototype.createCloneMenu = function (args) {
|
|
||||||
return args.CLONE_OPTION;
|
|
||||||
};
|
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.createClone = function (args, util) {
|
Scratch3ControlBlocks.prototype.createClone = function (args, util) {
|
||||||
var cloneTarget;
|
var cloneTarget;
|
||||||
if (args.CLONE_OPTION == '_myself_') {
|
if (args.CLONE_OPTION == '_myself_') {
|
||||||
|
|
|
@ -82,7 +82,7 @@ Scratch3EventBlocks.prototype.broadcastAndWait = function (args, util) {
|
||||||
return instance.runtime.isActiveThread(thread);
|
return instance.runtime.isActiveThread(thread);
|
||||||
});
|
});
|
||||||
if (waiting) {
|
if (waiting) {
|
||||||
util.yieldFrame();
|
util.yield();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,7 @@ Scratch3LooksBlocks.prototype.switchBackdropAndWait = function (args, util) {
|
||||||
return instance.runtime.isActiveThread(thread);
|
return instance.runtime.isActiveThread(thread);
|
||||||
});
|
});
|
||||||
if (waiting) {
|
if (waiting) {
|
||||||
util.yieldFrame();
|
util.yield();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,7 @@ Scratch3MotionBlocks.prototype.glide = function (args, util) {
|
||||||
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
util.yieldFrame();
|
util.yield();
|
||||||
} else {
|
} else {
|
||||||
var timeElapsed = util.stackFrame.timer.timeElapsed();
|
var timeElapsed = util.stackFrame.timer.timeElapsed();
|
||||||
if (timeElapsed < util.stackFrame.duration * 1000) {
|
if (timeElapsed < util.stackFrame.duration * 1000) {
|
||||||
|
@ -131,7 +131,7 @@ Scratch3MotionBlocks.prototype.glide = function (args, util) {
|
||||||
util.stackFrame.startX + dx,
|
util.stackFrame.startX + dx,
|
||||||
util.stackFrame.startY + dy
|
util.stackFrame.startY + dy
|
||||||
);
|
);
|
||||||
util.yieldFrame();
|
util.yield();
|
||||||
} else {
|
} else {
|
||||||
// Finished: move to final position.
|
// Finished: move to final position.
|
||||||
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
||||||
|
|
|
@ -24,23 +24,20 @@ Scratch3ProcedureBlocks.prototype.defNoReturn = function () {
|
||||||
|
|
||||||
Scratch3ProcedureBlocks.prototype.callNoReturn = function (args, util) {
|
Scratch3ProcedureBlocks.prototype.callNoReturn = function (args, util) {
|
||||||
if (!util.stackFrame.executed) {
|
if (!util.stackFrame.executed) {
|
||||||
var procedureName = args.mutation.proccode;
|
var procedureCode = args.mutation.proccode;
|
||||||
var paramNames = util.getProcedureParamNames(procedureName);
|
var paramNames = util.getProcedureParamNames(procedureCode);
|
||||||
for (var i = 0; i < paramNames.length; i++) {
|
for (var i = 0; i < paramNames.length; i++) {
|
||||||
if (args.hasOwnProperty('input' + i)) {
|
if (args.hasOwnProperty('input' + i)) {
|
||||||
util.pushParam(paramNames[i], args['input' + i]);
|
util.pushParam(paramNames[i], args['input' + i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
util.stackFrame.executed = true;
|
util.stackFrame.executed = true;
|
||||||
util.startProcedure(procedureName);
|
util.startProcedure(procedureCode);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ProcedureBlocks.prototype.param = function (args, util) {
|
Scratch3ProcedureBlocks.prototype.param = function (args, util) {
|
||||||
var value = util.getParam(args.mutation.paramname);
|
var value = util.getParam(args.mutation.paramname);
|
||||||
if (!value) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ var execute = function (sequencer, thread) {
|
||||||
runtime.visualReport(currentBlockId, resolvedValue);
|
runtime.visualReport(currentBlockId, resolvedValue);
|
||||||
}
|
}
|
||||||
// Finished any yields.
|
// Finished any yields.
|
||||||
thread.setStatus(Thread.STATUS_RUNNING);
|
thread.status = Thread.STATUS_RUNNING;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -133,15 +133,22 @@ var execute = function (sequencer, thread) {
|
||||||
var input = inputs[inputName];
|
var input = inputs[inputName];
|
||||||
var inputBlockId = input.block;
|
var inputBlockId = input.block;
|
||||||
// Is there no value for this input waiting in the stack frame?
|
// Is there no value for this input waiting in the stack frame?
|
||||||
if (typeof currentStackFrame.reported[inputName] === 'undefined') {
|
if (typeof currentStackFrame.reported[inputName] === 'undefined'
|
||||||
|
&& inputBlockId) {
|
||||||
// If there's not, we need to evaluate the block.
|
// If there's not, we need to evaluate the block.
|
||||||
var reporterYielded = (
|
// Push to the stack to evaluate the reporter block.
|
||||||
sequencer.stepToReporter(thread, inputBlockId, inputName)
|
thread.pushStack(inputBlockId);
|
||||||
);
|
// Save name of input for `Thread.pushReportedValue`.
|
||||||
// If the reporter yielded, return immediately;
|
currentStackFrame.waitingReporter = inputName;
|
||||||
// it needs time to finish and report its value.
|
// Actually execute the block.
|
||||||
if (reporterYielded) {
|
execute(sequencer, thread);
|
||||||
|
if (thread.status === Thread.STATUS_PROMISE_WAIT) {
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
// Execution returned immediately,
|
||||||
|
// and presumably a value was reported, so pop the stack.
|
||||||
|
currentStackFrame.waitingReporter = null;
|
||||||
|
thread.popStack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
argValues[inputName] = currentStackFrame.reported[inputName];
|
argValues[inputName] = currentStackFrame.reported[inputName];
|
||||||
|
@ -164,17 +171,10 @@ var execute = function (sequencer, thread) {
|
||||||
stackFrame: currentStackFrame.executionContext,
|
stackFrame: currentStackFrame.executionContext,
|
||||||
target: target,
|
target: target,
|
||||||
yield: function() {
|
yield: function() {
|
||||||
thread.setStatus(Thread.STATUS_YIELD);
|
thread.status = Thread.STATUS_YIELD;
|
||||||
},
|
},
|
||||||
yieldFrame: function() {
|
startBranch: function (branchNum, isLoop) {
|
||||||
thread.setStatus(Thread.STATUS_YIELD_FRAME);
|
sequencer.stepToBranch(thread, branchNum, isLoop);
|
||||||
},
|
|
||||||
done: function() {
|
|
||||||
thread.setStatus(Thread.STATUS_RUNNING);
|
|
||||||
sequencer.proceedThread(thread);
|
|
||||||
},
|
|
||||||
startBranch: function (branchNum) {
|
|
||||||
sequencer.stepToBranch(thread, branchNum);
|
|
||||||
},
|
},
|
||||||
stopAll: function () {
|
stopAll: function () {
|
||||||
runtime.stopAll();
|
runtime.stopAll();
|
||||||
|
@ -185,11 +185,11 @@ var execute = function (sequencer, thread) {
|
||||||
stopThread: function() {
|
stopThread: function() {
|
||||||
sequencer.retireThread(thread);
|
sequencer.retireThread(thread);
|
||||||
},
|
},
|
||||||
startProcedure: function (procedureName) {
|
startProcedure: function (procedureCode) {
|
||||||
sequencer.stepToProcedure(thread, procedureName);
|
sequencer.stepToProcedure(thread, procedureCode);
|
||||||
},
|
},
|
||||||
getProcedureParamNames: function (procedureName) {
|
getProcedureParamNames: function (procedureCode) {
|
||||||
return blockContainer.getProcedureParamNames(procedureName);
|
return blockContainer.getProcedureParamNames(procedureCode);
|
||||||
},
|
},
|
||||||
pushParam: function (paramName, paramValue) {
|
pushParam: function (paramName, paramValue) {
|
||||||
thread.pushParam(paramName, paramValue);
|
thread.pushParam(paramName, paramValue);
|
||||||
|
@ -221,18 +221,24 @@ var execute = function (sequencer, thread) {
|
||||||
if (isPromise(primitiveReportedValue)) {
|
if (isPromise(primitiveReportedValue)) {
|
||||||
if (thread.status === Thread.STATUS_RUNNING) {
|
if (thread.status === Thread.STATUS_RUNNING) {
|
||||||
// Primitive returned a promise; automatically yield thread.
|
// Primitive returned a promise; automatically yield thread.
|
||||||
thread.setStatus(Thread.STATUS_YIELD);
|
thread.status = Thread.STATUS_PROMISE_WAIT;
|
||||||
}
|
}
|
||||||
// Promise handlers
|
// Promise handlers
|
||||||
primitiveReportedValue.then(function(resolvedValue) {
|
primitiveReportedValue.then(function(resolvedValue) {
|
||||||
handleReport(resolvedValue);
|
handleReport(resolvedValue);
|
||||||
sequencer.proceedThread(thread);
|
if (typeof resolvedValue !== 'undefined') {
|
||||||
|
thread.popStack();
|
||||||
|
} else {
|
||||||
|
var popped = thread.popStack();
|
||||||
|
var nextBlockId = thread.target.blocks.getNextBlock(popped);
|
||||||
|
thread.pushStack(nextBlockId);
|
||||||
|
}
|
||||||
}, function(rejectionReason) {
|
}, function(rejectionReason) {
|
||||||
// Promise rejected: the primitive had some error.
|
// Promise rejected: the primitive had some error.
|
||||||
// Log it and proceed.
|
// Log it and proceed.
|
||||||
console.warn('Primitive rejected promise: ', rejectionReason);
|
console.warn('Primitive rejected promise: ', rejectionReason);
|
||||||
thread.setStatus(Thread.STATUS_RUNNING);
|
thread.status = Thread.STATUS_RUNNING;
|
||||||
sequencer.proceedThread(thread);
|
thread.popStack();
|
||||||
});
|
});
|
||||||
} else if (thread.status === Thread.STATUS_RUNNING) {
|
} else if (thread.status === Thread.STATUS_RUNNING) {
|
||||||
handleReport(primitiveReportedValue);
|
handleReport(primitiveReportedValue);
|
||||||
|
|
|
@ -27,8 +27,6 @@ function Runtime () {
|
||||||
// Bind event emitter
|
// Bind event emitter
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
|
|
||||||
// State for the runtime
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Target management and storage.
|
* Target management and storage.
|
||||||
* @type {Array.<!Target>}
|
* @type {Array.<!Target>}
|
||||||
|
@ -45,33 +43,131 @@ function Runtime () {
|
||||||
/** @type {!Sequencer} */
|
/** @type {!Sequencer} */
|
||||||
this.sequencer = new Sequencer(this);
|
this.sequencer = new Sequencer(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage container for flyout blocks.
|
||||||
|
* These will execute on `_editingTarget.`
|
||||||
|
* @type {!Blocks}
|
||||||
|
*/
|
||||||
this.flyoutBlocks = new Blocks();
|
this.flyoutBlocks = new Blocks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently known editing target for the VM.
|
||||||
|
* @type {?Target}
|
||||||
|
*/
|
||||||
|
this._editingTarget = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map to look up a block primitive's implementation function by its opcode.
|
* Map to look up a block primitive's implementation function by its opcode.
|
||||||
* This is a two-step lookup: package name first, then primitive name.
|
* This is a two-step lookup: package name first, then primitive name.
|
||||||
* @type {Object.<string, Function>}
|
* @type {Object.<string, Function>}
|
||||||
*/
|
*/
|
||||||
this._primitives = {};
|
this._primitives = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map to look up hat blocks' metadata.
|
||||||
|
* Keys are opcode for hat, values are metadata objects.
|
||||||
|
* @type {Object.<string, Object>}
|
||||||
|
*/
|
||||||
this._hats = {};
|
this._hats = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently known values for edge-activated hats.
|
||||||
|
* Keys are block ID for the hat; values are the currently known values.
|
||||||
|
* @type {Object.<string, *>}
|
||||||
|
*/
|
||||||
this._edgeActivatedHatValues = {};
|
this._edgeActivatedHatValues = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of script block IDs that were glowing during the previous frame.
|
||||||
|
* @type {!Array.<!string>}
|
||||||
|
*/
|
||||||
|
this._scriptGlowsPreviousFrame = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of block IDs that were glowing during the previous frame.
|
||||||
|
* @type {!Array.<!string>}
|
||||||
|
*/
|
||||||
|
this._blockGlowsPreviousFrame = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently known number of clones, used to enforce clone limit.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this._cloneCounter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the project is in "turbo mode."
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
this.turboMode = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the project is in "pause mode."
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
this.pauseMode = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the project is in "compatibility mode" (30 TPS).
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
this.compatibilityMode = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the project is in "single stepping mode."
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
this.singleStepping = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How fast in ms "single stepping mode" should run, in ms.
|
||||||
|
* Can be updated dynamically.
|
||||||
|
* @type {!number}
|
||||||
|
*/
|
||||||
|
this.singleStepInterval = 1000 / 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the current runtime stepping interval, set
|
||||||
|
* by a `setInterval`.
|
||||||
|
* @type {!number}
|
||||||
|
*/
|
||||||
|
this._steppingInterval = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current length of a step.
|
||||||
|
* Changes as mode switches, and used by the sequencer to calculate
|
||||||
|
* WORK_TIME.
|
||||||
|
* @type {!number}
|
||||||
|
*/
|
||||||
|
this.currentStepTime = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether any primitive has requested a redraw.
|
||||||
|
* Affects whether `Sequencer.stepThreads` will yield
|
||||||
|
* after stepping each thread.
|
||||||
|
* Reset on every frame.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.redrawRequested = false;
|
||||||
|
|
||||||
|
// Register all given block packages.
|
||||||
this._registerBlockPackages();
|
this._registerBlockPackages();
|
||||||
|
|
||||||
|
// Register and initialize "IO devices", containers for processing
|
||||||
|
// I/O related data.
|
||||||
|
/** @type {Object.<string, Object>} */
|
||||||
this.ioDevices = {
|
this.ioDevices = {
|
||||||
'clock': new Clock(),
|
'clock': new Clock(),
|
||||||
'keyboard': new Keyboard(this),
|
'keyboard': new Keyboard(this),
|
||||||
'mouse': new Mouse(this)
|
'mouse': new Mouse(this)
|
||||||
};
|
};
|
||||||
|
|
||||||
this._scriptGlowsPreviousFrame = [];
|
|
||||||
this._editingTarget = null;
|
|
||||||
/**
|
|
||||||
* Currently known number of clones.
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
this._cloneCounter = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherit from EventEmitter
|
||||||
|
*/
|
||||||
|
util.inherits(Runtime, EventEmitter);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Width of the stage, in pixels.
|
* Width of the stage, in pixels.
|
||||||
* @const {number}
|
* @const {number}
|
||||||
|
@ -115,15 +211,15 @@ Runtime.BLOCK_GLOW_OFF = 'BLOCK_GLOW_OFF';
|
||||||
Runtime.VISUAL_REPORT = 'VISUAL_REPORT';
|
Runtime.VISUAL_REPORT = 'VISUAL_REPORT';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inherit from EventEmitter
|
* How rapidly we try to step threads by default, in ms.
|
||||||
*/
|
|
||||||
util.inherits(Runtime, EventEmitter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How rapidly we try to step threads, in ms.
|
|
||||||
*/
|
*/
|
||||||
Runtime.THREAD_STEP_INTERVAL = 1000 / 60;
|
Runtime.THREAD_STEP_INTERVAL = 1000 / 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In compatibility mode, how rapidly we try to step threads, in ms.
|
||||||
|
*/
|
||||||
|
Runtime.THREAD_STEP_INTERVAL_COMPATIBILITY = 1000 / 30;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How many clones can be created at a time.
|
* How many clones can be created at a time.
|
||||||
* @const {number}
|
* @const {number}
|
||||||
|
@ -175,9 +271,6 @@ Runtime.prototype.getOpcodeFunction = function (opcode) {
|
||||||
return this._primitives[opcode];
|
return this._primitives[opcode];
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether an opcode represents a hat block.
|
* Return whether an opcode represents a hat block.
|
||||||
* @param {!string} opcode The opcode to look up.
|
* @param {!string} opcode The opcode to look up.
|
||||||
|
@ -235,7 +328,7 @@ Runtime.prototype.attachRenderer = function (renderer) {
|
||||||
*/
|
*/
|
||||||
Runtime.prototype._pushThread = function (id, target) {
|
Runtime.prototype._pushThread = function (id, target) {
|
||||||
var thread = new Thread(id);
|
var thread = new Thread(id);
|
||||||
thread.setTarget(target);
|
thread.target = target;
|
||||||
thread.pushStack(id);
|
thread.pushStack(id);
|
||||||
this.threads.push(thread);
|
this.threads.push(thread);
|
||||||
return thread;
|
return thread;
|
||||||
|
@ -449,6 +542,10 @@ Runtime.prototype.stopAll = function () {
|
||||||
* inactive threads after each iteration.
|
* inactive threads after each iteration.
|
||||||
*/
|
*/
|
||||||
Runtime.prototype._step = function () {
|
Runtime.prototype._step = function () {
|
||||||
|
if (this.pauseMode) {
|
||||||
|
// Don't do any execution while in pause mode.
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Find all edge-activated hats, and add them to threads to be evaluated.
|
// Find all edge-activated hats, and add them to threads to be evaluated.
|
||||||
for (var hatType in this._hats) {
|
for (var hatType in this._hats) {
|
||||||
var hat = this._hats[hatType];
|
var hat = this._hats[hatType];
|
||||||
|
@ -456,37 +553,113 @@ Runtime.prototype._step = function () {
|
||||||
this.startHats(hatType);
|
this.startHats(hatType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var inactiveThreads = this.sequencer.stepThreads(this.threads);
|
this.redrawRequested = false;
|
||||||
this._updateScriptGlows();
|
var inactiveThreads = this.sequencer.stepThreads();
|
||||||
for (var i = 0; i < inactiveThreads.length; i++) {
|
this._updateGlows(inactiveThreads);
|
||||||
this._removeThread(inactiveThreads[i]);
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current editing target known by the runtime.
|
||||||
|
* @param {!Target} editingTarget New editing target.
|
||||||
|
*/
|
||||||
|
Runtime.prototype.setEditingTarget = function (editingTarget) {
|
||||||
|
this._editingTarget = editingTarget;
|
||||||
|
// Script glows must be cleared.
|
||||||
|
this._scriptGlowsPreviousFrame = [];
|
||||||
|
this._updateGlows();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether we are in pause mode.
|
||||||
|
* @param {boolean} pauseModeOn True iff in pause mode.
|
||||||
|
*/
|
||||||
|
Runtime.prototype.setPauseMode = function (pauseModeOn) {
|
||||||
|
// Inform the project clock/timer to pause/resume its time.
|
||||||
|
if (this.ioDevices.clock) {
|
||||||
|
if (pauseModeOn && !this.pauseMode) {
|
||||||
|
this.ioDevices.clock.pause();
|
||||||
|
}
|
||||||
|
if (!pauseModeOn && this.pauseMode) {
|
||||||
|
this.ioDevices.clock.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.pauseMode = pauseModeOn;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether we are in 30 TPS compatibility mode.
|
||||||
|
* @param {boolean} compatibilityModeOn True iff in compatibility mode.
|
||||||
|
*/
|
||||||
|
Runtime.prototype.setCompatibilityMode = function (compatibilityModeOn) {
|
||||||
|
this.compatibilityMode = compatibilityModeOn;
|
||||||
|
if (this._steppingInterval) {
|
||||||
|
self.clearInterval(this._steppingInterval);
|
||||||
|
this.start();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Runtime.prototype.setEditingTarget = function (editingTarget) {
|
/**
|
||||||
this._scriptGlowsPreviousFrame = [];
|
* Set whether we are in single-stepping mode.
|
||||||
this._editingTarget = editingTarget;
|
* @param {boolean} singleSteppingOn True iff in single-stepping mode.
|
||||||
this._updateScriptGlows();
|
*/
|
||||||
|
Runtime.prototype.setSingleSteppingMode = function (singleSteppingOn) {
|
||||||
|
this.singleStepping = singleSteppingOn;
|
||||||
|
if (this._steppingInterval) {
|
||||||
|
self.clearInterval(this._steppingInterval);
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Runtime.prototype._updateScriptGlows = function () {
|
/**
|
||||||
|
* Set the speed during single-stepping mode.
|
||||||
|
* @param {number} speed Interval length to step threads, in ms.
|
||||||
|
*/
|
||||||
|
Runtime.prototype.setSingleSteppingSpeed = function (speed) {
|
||||||
|
this.singleStepInterval = 1000 / speed;
|
||||||
|
if (this._steppingInterval) {
|
||||||
|
self.clearInterval(this._steppingInterval);
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit glows/glow clears for blocks and scripts after a single tick.
|
||||||
|
* Looks at `this.threads` and notices which have turned on/off new glows.
|
||||||
|
* @param {Array.<Thread>=} opt_extraThreads Optional list of inactive threads.
|
||||||
|
*/
|
||||||
|
Runtime.prototype._updateGlows = function (opt_extraThreads) {
|
||||||
|
var searchThreads = [];
|
||||||
|
searchThreads.push.apply(searchThreads, this.threads);
|
||||||
|
if (opt_extraThreads) {
|
||||||
|
searchThreads.push.apply(searchThreads, opt_extraThreads);
|
||||||
|
}
|
||||||
// Set of scripts that request a glow this frame.
|
// Set of scripts that request a glow this frame.
|
||||||
var requestedGlowsThisFrame = [];
|
var requestedGlowsThisFrame = [];
|
||||||
|
var requestedBlockGlowsThisFrame = [];
|
||||||
// Final set of scripts glowing during this frame.
|
// Final set of scripts glowing during this frame.
|
||||||
var finalScriptGlows = [];
|
var finalScriptGlows = [];
|
||||||
|
var finalBlockGlows = [];
|
||||||
// Find all scripts that should be glowing.
|
// Find all scripts that should be glowing.
|
||||||
for (var i = 0; i < this.threads.length; i++) {
|
for (var i = 0; i < searchThreads.length; i++) {
|
||||||
var thread = this.threads[i];
|
var thread = searchThreads[i];
|
||||||
var target = thread.target;
|
var target = thread.target;
|
||||||
if (thread.requestScriptGlowInFrame && target == this._editingTarget) {
|
if (target == this._editingTarget) {
|
||||||
var blockForThread = thread.peekStack() || thread.topBlock;
|
var blockForThread = thread.blockGlowInFrame;
|
||||||
var script = target.blocks.getTopLevelScript(blockForThread);
|
if (thread.requestScriptGlowInFrame) {
|
||||||
if (!script) {
|
var script = target.blocks.getTopLevelScript(blockForThread);
|
||||||
// Attempt to find in flyout blocks.
|
if (!script) {
|
||||||
script = this.flyoutBlocks.getTopLevelScript(blockForThread);
|
// Attempt to find in flyout blocks.
|
||||||
|
script = this.flyoutBlocks.getTopLevelScript(
|
||||||
|
blockForThread
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (script) {
|
||||||
|
requestedGlowsThisFrame.push(script);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (script) {
|
// Only show block glows in single-stepping mode.
|
||||||
requestedGlowsThisFrame.push(script);
|
if (this.singleStepping && blockForThread) {
|
||||||
|
requestedBlockGlowsThisFrame.push(blockForThread);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -509,7 +682,30 @@ Runtime.prototype._updateScriptGlows = function () {
|
||||||
finalScriptGlows.push(currentFrameGlow);
|
finalScriptGlows.push(currentFrameGlow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (var m = 0; m < this._blockGlowsPreviousFrame.length; m++) {
|
||||||
|
var previousBlockGlow = this._blockGlowsPreviousFrame[m];
|
||||||
|
if (requestedBlockGlowsThisFrame.indexOf(previousBlockGlow) < 0) {
|
||||||
|
// Glow turned off.
|
||||||
|
try {
|
||||||
|
this.glowBlock(previousBlockGlow, false);
|
||||||
|
} catch (e) {
|
||||||
|
// Block has been removed.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Still glowing.
|
||||||
|
finalBlockGlows.push(previousBlockGlow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var p = 0; p < requestedBlockGlowsThisFrame.length; p++) {
|
||||||
|
var currentBlockFrameGlow = requestedBlockGlowsThisFrame[p];
|
||||||
|
if (this._blockGlowsPreviousFrame.indexOf(currentBlockFrameGlow) < 0) {
|
||||||
|
// Glow turned on.
|
||||||
|
this.glowBlock(currentBlockFrameGlow, true);
|
||||||
|
finalBlockGlows.push(currentBlockFrameGlow);
|
||||||
|
}
|
||||||
|
}
|
||||||
this._scriptGlowsPreviousFrame = finalScriptGlows;
|
this._scriptGlowsPreviousFrame = finalScriptGlows;
|
||||||
|
this._blockGlowsPreviousFrame = finalBlockGlows;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -617,22 +813,38 @@ Runtime.prototype.getTargetForStage = function () {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell the runtime to request a redraw.
|
||||||
|
* Use after a clone/sprite has completed some visible operation on the stage.
|
||||||
|
*/
|
||||||
|
Runtime.prototype.requestRedraw = function () {
|
||||||
|
this.redrawRequested = true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an animation frame from the main thread.
|
* Handle an animation frame from the main thread.
|
||||||
*/
|
*/
|
||||||
Runtime.prototype.animationFrame = function () {
|
Runtime.prototype.animationFrame = function () {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
|
// @todo: Only render when this.redrawRequested or clones rendered.
|
||||||
this.renderer.draw();
|
this.renderer.draw();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up timers to repeatedly step in a browser
|
* Set up timers to repeatedly step in a browser.
|
||||||
*/
|
*/
|
||||||
Runtime.prototype.start = function () {
|
Runtime.prototype.start = function () {
|
||||||
self.setInterval(function() {
|
var interval = Runtime.THREAD_STEP_INTERVAL;
|
||||||
|
if (this.singleStepping) {
|
||||||
|
interval = this.singleStepInterval;
|
||||||
|
} else if (this.compatibilityMode) {
|
||||||
|
interval = Runtime.THREAD_STEP_INTERVAL_COMPATIBILITY;
|
||||||
|
}
|
||||||
|
this.currentStepTime = interval;
|
||||||
|
this._steppingInterval = self.setInterval(function() {
|
||||||
this._step();
|
this._step();
|
||||||
}.bind(this), Runtime.THREAD_STEP_INTERVAL);
|
}.bind(this), interval);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Runtime;
|
module.exports = Runtime;
|
||||||
|
|
|
@ -17,87 +17,163 @@ function Sequencer (runtime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The sequencer does as much work as it can within WORK_TIME milliseconds,
|
* Time to run a warp-mode thread, in ms.
|
||||||
* then yields. This is essentially a rate-limiter for blocks.
|
* @type {number}
|
||||||
* In Scratch 2.0, this is set to 75% of the target stage frame-rate (30fps).
|
|
||||||
* @const {!number}
|
|
||||||
*/
|
*/
|
||||||
Sequencer.WORK_TIME = 10;
|
Sequencer.WARP_TIME = 500;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Step through all threads in `this.threads`, running them in order.
|
* Step through all threads in `this.runtime.threads`, running them in order.
|
||||||
* @param {Array.<Thread>} threads List of which threads to step.
|
* @return {Array.<!Thread>} List of inactive threads after stepping.
|
||||||
* @return {Array.<Thread>} All threads which have finished in this iteration.
|
|
||||||
*/
|
*/
|
||||||
Sequencer.prototype.stepThreads = function (threads) {
|
Sequencer.prototype.stepThreads = function () {
|
||||||
// Start counting toward WORK_TIME
|
// Work time is 75% of the thread stepping interval.
|
||||||
|
var WORK_TIME = 0.75 * this.runtime.currentStepTime;
|
||||||
|
// Start counting toward WORK_TIME.
|
||||||
this.timer.start();
|
this.timer.start();
|
||||||
// List of threads which have been killed by this step.
|
// Count of active threads.
|
||||||
|
var numActiveThreads = Infinity;
|
||||||
|
// Whether `stepThreads` has run through a full single tick.
|
||||||
|
var ranFirstTick = false;
|
||||||
var inactiveThreads = [];
|
var inactiveThreads = [];
|
||||||
// If all of the threads are yielding, we should yield.
|
// Conditions for continuing to stepping threads:
|
||||||
var numYieldingThreads = 0;
|
// 1. We must have threads in the list, and some must be active.
|
||||||
// Clear all yield statuses that were for the previous frame.
|
// 2. Time elapsed must be less than WORK_TIME.
|
||||||
for (var t = 0; t < threads.length; t++) {
|
// 3. Either turbo mode, or no redraw has been requested by a primitive.
|
||||||
if (threads[t].status === Thread.STATUS_YIELD_FRAME) {
|
while (this.runtime.threads.length > 0 &&
|
||||||
threads[t].setStatus(Thread.STATUS_RUNNING);
|
numActiveThreads > 0 &&
|
||||||
}
|
this.timer.timeElapsed() < WORK_TIME &&
|
||||||
}
|
(this.runtime.turboMode || !this.runtime.redrawRequested)) {
|
||||||
|
numActiveThreads = 0;
|
||||||
// While there are still threads to run and we are within WORK_TIME,
|
// Inline copy of the threads, updated on each step.
|
||||||
// continue executing threads.
|
var threadsCopy = this.runtime.threads.slice();
|
||||||
while (threads.length > 0 &&
|
// Attempt to run each thread one time.
|
||||||
threads.length > numYieldingThreads &&
|
for (var i = 0; i < threadsCopy.length; i++) {
|
||||||
this.timer.timeElapsed() < Sequencer.WORK_TIME) {
|
var activeThread = threadsCopy[i];
|
||||||
// New threads at the end of the iteration.
|
if (activeThread.stack.length === 0 ||
|
||||||
var newThreads = [];
|
|
||||||
// Reset yielding thread count.
|
|
||||||
numYieldingThreads = 0;
|
|
||||||
// Attempt to run each thread one time
|
|
||||||
for (var i = 0; i < threads.length; i++) {
|
|
||||||
var activeThread = threads[i];
|
|
||||||
if (activeThread.status === Thread.STATUS_RUNNING) {
|
|
||||||
// Normal-mode thread: step.
|
|
||||||
this.startThread(activeThread);
|
|
||||||
} else if (activeThread.status === Thread.STATUS_YIELD ||
|
|
||||||
activeThread.status === Thread.STATUS_YIELD_FRAME) {
|
|
||||||
// Yielding thread: do nothing for this step.
|
|
||||||
numYieldingThreads++;
|
|
||||||
}
|
|
||||||
if (activeThread.stack.length === 0 &&
|
|
||||||
activeThread.status === Thread.STATUS_DONE) {
|
activeThread.status === Thread.STATUS_DONE) {
|
||||||
// Finished with this thread - tell runtime to clean it up.
|
// Finished with this thread.
|
||||||
inactiveThreads.push(activeThread);
|
if (inactiveThreads.indexOf(activeThread) < 0) {
|
||||||
} else {
|
inactiveThreads.push(activeThread);
|
||||||
// Keep this thead in the loop.
|
}
|
||||||
newThreads.push(activeThread);
|
continue;
|
||||||
|
}
|
||||||
|
if (activeThread.status === Thread.STATUS_YIELD_TICK &&
|
||||||
|
!ranFirstTick) {
|
||||||
|
// Clear single-tick yield from the last call of `stepThreads`.
|
||||||
|
activeThread.status = Thread.STATUS_RUNNING;
|
||||||
|
}
|
||||||
|
if (activeThread.status === Thread.STATUS_RUNNING ||
|
||||||
|
activeThread.status === Thread.STATUS_YIELD) {
|
||||||
|
// Normal-mode thread: step.
|
||||||
|
this.stepThread(activeThread);
|
||||||
|
activeThread.warpTimer = null;
|
||||||
|
}
|
||||||
|
if (activeThread.status === Thread.STATUS_RUNNING) {
|
||||||
|
// After stepping, status is still running.
|
||||||
|
// If we're in single-stepping mode, mark the thread as
|
||||||
|
// a single-tick yield so it doesn't re-execute
|
||||||
|
// until the next frame.
|
||||||
|
if (this.runtime.singleStepping) {
|
||||||
|
activeThread.status = Thread.STATUS_YIELD_TICK;
|
||||||
|
}
|
||||||
|
numActiveThreads++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Effectively filters out threads that have stopped.
|
// We successfully ticked once. Prevents running STATUS_YIELD_TICK
|
||||||
threads = newThreads;
|
// threads on the next tick.
|
||||||
|
ranFirstTick = true;
|
||||||
}
|
}
|
||||||
|
// Filter inactive threads from `this.runtime.threads`.
|
||||||
|
this.runtime.threads = this.runtime.threads.filter(function(thread) {
|
||||||
|
if (inactiveThreads.indexOf(thread) > -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
return inactiveThreads;
|
return inactiveThreads;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Step the requested thread
|
* Step the requested thread for as long as necessary.
|
||||||
* @param {!Thread} thread Thread object to step
|
* @param {!Thread} thread Thread object to step.
|
||||||
*/
|
*/
|
||||||
Sequencer.prototype.startThread = function (thread) {
|
Sequencer.prototype.stepThread = function (thread) {
|
||||||
var currentBlockId = thread.peekStack();
|
var currentBlockId = thread.peekStack();
|
||||||
if (!currentBlockId) {
|
if (!currentBlockId) {
|
||||||
// A "null block" - empty branch.
|
// A "null block" - empty branch.
|
||||||
// Yield for the frame.
|
|
||||||
thread.popStack();
|
thread.popStack();
|
||||||
thread.setStatus(Thread.STATUS_YIELD_FRAME);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// Execute the current block
|
while (thread.peekStack()) {
|
||||||
execute(this, thread);
|
var isWarpMode = thread.peekStackFrame().warpMode;
|
||||||
// If the block executed without yielding and without doing control flow,
|
if (isWarpMode && !thread.warpTimer) {
|
||||||
// move to done.
|
// Initialize warp-mode timer if it hasn't been already.
|
||||||
if (thread.status === Thread.STATUS_RUNNING &&
|
// This will start counting the thread toward `Sequencer.WARP_TIME`.
|
||||||
thread.peekStack() === currentBlockId) {
|
thread.warpTimer = new Timer();
|
||||||
this.proceedThread(thread);
|
thread.warpTimer.start();
|
||||||
|
}
|
||||||
|
// Execute the current block.
|
||||||
|
// Save the current block ID to notice if we did control flow.
|
||||||
|
currentBlockId = thread.peekStack();
|
||||||
|
execute(this, thread);
|
||||||
|
thread.blockGlowInFrame = currentBlockId;
|
||||||
|
// If the thread has yielded or is waiting, yield to other threads.
|
||||||
|
if (thread.status === Thread.STATUS_YIELD) {
|
||||||
|
// Mark as running for next iteration.
|
||||||
|
thread.status = Thread.STATUS_RUNNING;
|
||||||
|
// In warp mode, yielded blocks are re-executed immediately.
|
||||||
|
if (isWarpMode &&
|
||||||
|
thread.warpTimer.timeElapsed() <= Sequencer.WARP_TIME) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (thread.status === Thread.STATUS_PROMISE_WAIT) {
|
||||||
|
// A promise was returned by the primitive. Yield the thread
|
||||||
|
// until the promise resolves. Promise resolution should reset
|
||||||
|
// thread.status to Thread.STATUS_RUNNING.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If no control flow has happened, switch to next block.
|
||||||
|
if (thread.peekStack() === currentBlockId) {
|
||||||
|
thread.goToNextBlock();
|
||||||
|
}
|
||||||
|
// If no next block has been found at this point, look on the stack.
|
||||||
|
while (!thread.peekStack()) {
|
||||||
|
thread.popStack();
|
||||||
|
if (thread.stack.length === 0) {
|
||||||
|
// No more stack to run!
|
||||||
|
thread.status = Thread.STATUS_DONE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thread.peekStackFrame().isLoop) {
|
||||||
|
// The current level of the stack is marked as a loop.
|
||||||
|
// Return to yield for the frame/tick in general.
|
||||||
|
// Unless we're in warp mode - then only return if the
|
||||||
|
// warp timer is up.
|
||||||
|
if (!isWarpMode ||
|
||||||
|
thread.warpTimer.timeElapsed() > Sequencer.WARP_TIME) {
|
||||||
|
// Don't do anything to the stack, since loops need
|
||||||
|
// to be re-executed.
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Don't go to the next block for this level of the stack,
|
||||||
|
// since loops need to be re-executed.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (thread.peekStackFrame().waitingReporter) {
|
||||||
|
// This level of the stack was waiting for a value.
|
||||||
|
// This means a reporter has just returned - so don't go
|
||||||
|
// to the next block for this level of the stack.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Get next block of existing block on the stack.
|
||||||
|
thread.goToNextBlock();
|
||||||
|
}
|
||||||
|
// In single-stepping mode, force `stepThread` to only run one block
|
||||||
|
// at a time.
|
||||||
|
if (this.runtime.singleStepping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,8 +181,9 @@ Sequencer.prototype.startThread = function (thread) {
|
||||||
* Step a thread into a block's branch.
|
* Step a thread into a block's branch.
|
||||||
* @param {!Thread} thread Thread object to step to branch.
|
* @param {!Thread} thread Thread object to step to branch.
|
||||||
* @param {Number} branchNum Which branch to step to (i.e., 1, 2).
|
* @param {Number} branchNum Which branch to step to (i.e., 1, 2).
|
||||||
|
* @param {Boolean} isLoop Whether this block is a loop.
|
||||||
*/
|
*/
|
||||||
Sequencer.prototype.stepToBranch = function (thread, branchNum) {
|
Sequencer.prototype.stepToBranch = function (thread, branchNum, isLoop) {
|
||||||
if (!branchNum) {
|
if (!branchNum) {
|
||||||
branchNum = 1;
|
branchNum = 1;
|
||||||
}
|
}
|
||||||
|
@ -115,11 +192,11 @@ Sequencer.prototype.stepToBranch = function (thread, branchNum) {
|
||||||
currentBlockId,
|
currentBlockId,
|
||||||
branchNum
|
branchNum
|
||||||
);
|
);
|
||||||
|
thread.peekStackFrame().isLoop = isLoop;
|
||||||
if (branchId) {
|
if (branchId) {
|
||||||
// Push branch ID to the thread's stack.
|
// Push branch ID to the thread's stack.
|
||||||
thread.pushStack(branchId);
|
thread.pushStack(branchId);
|
||||||
} else {
|
} else {
|
||||||
// Push null, so we come back to the current block.
|
|
||||||
thread.pushStack(null);
|
thread.pushStack(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -127,56 +204,39 @@ Sequencer.prototype.stepToBranch = function (thread, branchNum) {
|
||||||
/**
|
/**
|
||||||
* Step a procedure.
|
* Step a procedure.
|
||||||
* @param {!Thread} thread Thread object to step to procedure.
|
* @param {!Thread} thread Thread object to step to procedure.
|
||||||
* @param {!string} procedureName Name of procedure defined in this target.
|
* @param {!string} procedureCode Procedure code of procedure to step to.
|
||||||
*/
|
*/
|
||||||
Sequencer.prototype.stepToProcedure = function (thread, procedureName) {
|
Sequencer.prototype.stepToProcedure = function (thread, procedureCode) {
|
||||||
var definition = thread.target.blocks.getProcedureDefinition(procedureName);
|
var definition = thread.target.blocks.getProcedureDefinition(procedureCode);
|
||||||
|
if (!definition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if the call is recursive.
|
||||||
|
// If so, set the thread to yield after pushing.
|
||||||
|
var isRecursive = thread.isRecursiveCall(procedureCode);
|
||||||
|
// To step to a procedure, we put its definition on the stack.
|
||||||
|
// Execution for the thread will proceed through the definition hat
|
||||||
|
// and on to the main definition of the procedure.
|
||||||
|
// When that set of blocks finishes executing, it will be popped
|
||||||
|
// from the stack by the sequencer, returning control to the caller.
|
||||||
thread.pushStack(definition);
|
thread.pushStack(definition);
|
||||||
// Check if the call is recursive. If so, yield.
|
// In known warp-mode threads, only yield when time is up.
|
||||||
// @todo: Have behavior match Scratch 2.0.
|
if (thread.peekStackFrame().warpMode &&
|
||||||
if (thread.stack.indexOf(definition) > -1) {
|
thread.warpTimer.timeElapsed() > Sequencer.WARP_TIME) {
|
||||||
thread.setStatus(Thread.STATUS_YIELD_FRAME);
|
thread.status = Thread.STATUS_YIELD;
|
||||||
}
|
} else {
|
||||||
};
|
// Look for warp-mode flag on definition, and set the thread
|
||||||
|
// to warp-mode if needed.
|
||||||
/**
|
var definitionBlock = thread.target.blocks.getBlock(definition);
|
||||||
* Step a thread into an input reporter, and manage its status appropriately.
|
var doWarp = definitionBlock.mutation.warp;
|
||||||
* @param {!Thread} thread Thread object to step to reporter.
|
if (doWarp) {
|
||||||
* @param {!string} blockId ID of reporter block.
|
thread.peekStackFrame().warpMode = true;
|
||||||
* @param {!string} inputName Name of input on parent block.
|
} else {
|
||||||
* @return {boolean} True if yielded, false if it finished immediately.
|
// In normal-mode threads, yield any time we have a recursive call.
|
||||||
*/
|
if (isRecursive) {
|
||||||
Sequencer.prototype.stepToReporter = function (thread, blockId, inputName) {
|
thread.status = Thread.STATUS_YIELD;
|
||||||
var currentStackFrame = thread.peekStackFrame();
|
}
|
||||||
// Push to the stack to evaluate the reporter block.
|
}
|
||||||
thread.pushStack(blockId);
|
|
||||||
// Save name of input for `Thread.pushReportedValue`.
|
|
||||||
currentStackFrame.waitingReporter = inputName;
|
|
||||||
// Actually execute the block.
|
|
||||||
this.startThread(thread);
|
|
||||||
// If a reporter yielded, caller must wait for it to unyield.
|
|
||||||
// The value will be populated once the reporter unyields,
|
|
||||||
// and passed up to the currentStackFrame on next execution.
|
|
||||||
return thread.status === Thread.STATUS_YIELD;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finish stepping a thread and proceed it to the next block.
|
|
||||||
* @param {!Thread} thread Thread object to proceed.
|
|
||||||
*/
|
|
||||||
Sequencer.prototype.proceedThread = function (thread) {
|
|
||||||
var currentBlockId = thread.peekStack();
|
|
||||||
// Mark the status as done and proceed to the next block.
|
|
||||||
// Pop from the stack - finished this level of execution.
|
|
||||||
thread.popStack();
|
|
||||||
// Push next connected block, if there is one.
|
|
||||||
var nextBlockId = thread.target.blocks.getNextBlock(currentBlockId);
|
|
||||||
if (nextBlockId) {
|
|
||||||
thread.pushStack(nextBlockId);
|
|
||||||
}
|
|
||||||
// If we can't find a next block to run, mark the thread as done.
|
|
||||||
if (!thread.peekStack()) {
|
|
||||||
thread.setStatus(Thread.STATUS_DONE);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -188,7 +248,7 @@ Sequencer.prototype.retireThread = function (thread) {
|
||||||
thread.stack = [];
|
thread.stack = [];
|
||||||
thread.stackFrame = [];
|
thread.stackFrame = [];
|
||||||
thread.requestScriptGlowInFrame = false;
|
thread.requestScriptGlowInFrame = false;
|
||||||
thread.setStatus(Thread.STATUS_DONE);
|
thread.status = Thread.STATUS_DONE;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Sequencer;
|
module.exports = Sequencer;
|
||||||
|
|
|
@ -40,6 +40,19 @@ function Thread (firstBlock) {
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
this.requestScriptGlowInFrame = false;
|
this.requestScriptGlowInFrame = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which block ID should glow during this frame, if any.
|
||||||
|
* @type {?string}
|
||||||
|
*/
|
||||||
|
this.blockGlowInFrame = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A timer for when the thread enters warp mode.
|
||||||
|
* Substitutes the sequencer's count toward WORK_TIME on a per-thread basis.
|
||||||
|
* @type {?Timer}
|
||||||
|
*/
|
||||||
|
this.warpTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,25 +64,31 @@ function Thread (firstBlock) {
|
||||||
Thread.STATUS_RUNNING = 0;
|
Thread.STATUS_RUNNING = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thread status for a yielded thread.
|
* Threads are in this state when a primitive is waiting on a promise;
|
||||||
* Threads are in this state when a primitive has yielded; execution is paused
|
* execution is paused until the promise changes thread status.
|
||||||
* until the relevant primitive unyields.
|
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
Thread.STATUS_YIELD = 1;
|
Thread.STATUS_PROMISE_WAIT = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thread status for a single-frame yield.
|
* Thread status for yield.
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
Thread.STATUS_YIELD_FRAME = 2;
|
Thread.STATUS_YIELD = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread status for a single-tick yield. This will be cleared when the
|
||||||
|
* thread is resumed.
|
||||||
|
* @const
|
||||||
|
*/
|
||||||
|
Thread.STATUS_YIELD_TICK = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thread status for a finished/done thread.
|
* Thread status for a finished/done thread.
|
||||||
* Thread is in this state when there are no more blocks to execute.
|
* Thread is in this state when there are no more blocks to execute.
|
||||||
* @const
|
* @const
|
||||||
*/
|
*/
|
||||||
Thread.STATUS_DONE = 3;
|
Thread.STATUS_DONE = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push stack and update stack frames appropriately.
|
* Push stack and update stack frames appropriately.
|
||||||
|
@ -80,7 +99,14 @@ Thread.prototype.pushStack = function (blockId) {
|
||||||
// Push an empty stack frame, if we need one.
|
// Push an empty stack frame, if we need one.
|
||||||
// Might not, if we just popped the stack.
|
// Might not, if we just popped the stack.
|
||||||
if (this.stack.length > this.stackFrames.length) {
|
if (this.stack.length > this.stackFrames.length) {
|
||||||
|
// Copy warp mode from any higher level.
|
||||||
|
var warpMode = false;
|
||||||
|
if (this.stackFrames[this.stackFrames.length - 1]) {
|
||||||
|
warpMode = this.stackFrames[this.stackFrames.length - 1].warpMode;
|
||||||
|
}
|
||||||
this.stackFrames.push({
|
this.stackFrames.push({
|
||||||
|
isLoop: false, // Whether this level of the stack is a loop.
|
||||||
|
warpMode: warpMode, // Whether this level is in warp mode.
|
||||||
reported: {}, // Collects reported input values.
|
reported: {}, // Collects reported input values.
|
||||||
waitingReporter: null, // Name of waiting reporter.
|
waitingReporter: null, // Name of waiting reporter.
|
||||||
params: {}, // Procedure parameters.
|
params: {}, // Procedure parameters.
|
||||||
|
@ -125,22 +151,32 @@ Thread.prototype.peekParentStackFrame = function () {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push a reported value to the parent of the current stack frame.
|
* Push a reported value to the parent of the current stack frame.
|
||||||
* @param {!Any} value Reported value to push.
|
* @param {*} value Reported value to push.
|
||||||
*/
|
*/
|
||||||
Thread.prototype.pushReportedValue = function (value) {
|
Thread.prototype.pushReportedValue = function (value) {
|
||||||
var parentStackFrame = this.peekParentStackFrame();
|
var parentStackFrame = this.peekParentStackFrame();
|
||||||
if (parentStackFrame) {
|
if (parentStackFrame) {
|
||||||
var waitingReporter = parentStackFrame.waitingReporter;
|
var waitingReporter = parentStackFrame.waitingReporter;
|
||||||
parentStackFrame.reported[waitingReporter] = value;
|
parentStackFrame.reported[waitingReporter] = value;
|
||||||
parentStackFrame.waitingReporter = null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a parameter to the stack frame.
|
||||||
|
* Use when calling a procedure with parameter values.
|
||||||
|
* @param {!string} paramName Name of parameter.
|
||||||
|
* @param {*} value Value to set for parameter.
|
||||||
|
*/
|
||||||
Thread.prototype.pushParam = function (paramName, value) {
|
Thread.prototype.pushParam = function (paramName, value) {
|
||||||
var stackFrame = this.peekStackFrame();
|
var stackFrame = this.peekStackFrame();
|
||||||
stackFrame.params[paramName] = value;
|
stackFrame.params[paramName] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a parameter at the lowest possible level of the stack.
|
||||||
|
* @param {!string} paramName Name of parameter.
|
||||||
|
* @return {*} value Value for parameter.
|
||||||
|
*/
|
||||||
Thread.prototype.getParam = function (paramName) {
|
Thread.prototype.getParam = function (paramName) {
|
||||||
for (var i = this.stackFrames.length - 1; i >= 0; i--) {
|
for (var i = this.stackFrames.length - 1; i >= 0; i--) {
|
||||||
var frame = this.stackFrames[i];
|
var frame = this.stackFrames[i];
|
||||||
|
@ -159,28 +195,43 @@ Thread.prototype.atStackTop = function () {
|
||||||
return this.peekStack() === this.topBlock;
|
return this.peekStack() === this.topBlock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set thread status.
|
* Switch the thread to the next block at the current level of the stack.
|
||||||
* @param {number} status Enum representing thread status.
|
* For example, this is used in a standard sequence of blocks,
|
||||||
|
* where execution proceeds from one block to the next.
|
||||||
*/
|
*/
|
||||||
Thread.prototype.setStatus = function (status) {
|
Thread.prototype.goToNextBlock = function () {
|
||||||
this.status = status;
|
var nextBlockId = this.target.blocks.getNextBlock(this.peekStack());
|
||||||
|
// Copy warp mode to next block.
|
||||||
|
var warpMode = this.peekStackFrame().warpMode;
|
||||||
|
// The current block is on the stack - pop it and push the next.
|
||||||
|
// Note that this could push `null` - that is handled by the sequencer.
|
||||||
|
this.popStack();
|
||||||
|
this.pushStack(nextBlockId);
|
||||||
|
if (this.peekStackFrame()) {
|
||||||
|
this.peekStackFrame().warpMode = warpMode;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set thread target.
|
* Attempt to determine whether a procedure call is recursive,
|
||||||
* @param {?Target} target Target for this thread.
|
* by examining the stack.
|
||||||
|
* @param {!string} procedureCode Procedure code of procedure being called.
|
||||||
|
* @return {boolean} True if the call appears recursive.
|
||||||
*/
|
*/
|
||||||
Thread.prototype.setTarget = function (target) {
|
Thread.prototype.isRecursiveCall = function (procedureCode) {
|
||||||
this.target = target;
|
var callCount = 5; // Max number of enclosing procedure calls to examine.
|
||||||
};
|
var sp = this.stack.length - 1;
|
||||||
|
for (var i = sp - 1; i >= 0; i--) {
|
||||||
/**
|
var block = this.target.blocks.getBlock(this.stack[i]);
|
||||||
* Get thread target.
|
if (block.opcode == 'procedures_callnoreturn' &&
|
||||||
* @return {?Target} Target for this thread, if available.
|
block.mutation.proccode == procedureCode) {
|
||||||
*/
|
return true;
|
||||||
Thread.prototype.getTarget = function () {
|
}
|
||||||
return this.target;
|
if (--callCount < 0) return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Thread;
|
module.exports = Thread;
|
||||||
|
|
47
src/index.js
47
src/index.js
|
@ -66,6 +66,53 @@ VirtualMachine.prototype.greenFlag = function () {
|
||||||
this.runtime.greenFlag();
|
this.runtime.greenFlag();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the VM is in "turbo mode."
|
||||||
|
* When true, loops don't yield to redraw.
|
||||||
|
* @param {Boolean} turboModeOn Whether turbo mode should be set.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.setTurboMode = function (turboModeOn) {
|
||||||
|
this.runtime.turboMode = !!turboModeOn;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the VM is in "pause mode."
|
||||||
|
* When true, nothing is stepped.
|
||||||
|
* @param {Boolean} pauseModeOn Whether pause mode should be set.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.setPauseMode = function (pauseModeOn) {
|
||||||
|
this.runtime.setPauseMode(!!pauseModeOn);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the VM is in 2.0 "compatibility mode."
|
||||||
|
* When true, ticks go at 2.0 speed (30 TPS).
|
||||||
|
* @param {Boolean} compatibilityModeOn Whether compatibility mode is set.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.setCompatibilityMode = function (compatibilityModeOn) {
|
||||||
|
this.runtime.setCompatibilityMode(!!compatibilityModeOn);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the VM is in "single stepping mode."
|
||||||
|
* When true, blocks execute slowly and are highlighted visually.
|
||||||
|
* @param {Boolean} singleSteppingOn Whether single-stepping mode is set.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.setSingleSteppingMode = function (singleSteppingOn) {
|
||||||
|
this.runtime.setSingleSteppingMode(!!singleSteppingOn);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set single-stepping mode speed.
|
||||||
|
* When in single-stepping mode, adjusts the speed of execution.
|
||||||
|
* @param {Number} speed Interval length in ms.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.setSingleSteppingSpeed = function (speed) {
|
||||||
|
this.runtime.setSingleSteppingSpeed(speed);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop all threads and running activities.
|
* Stop all threads and running activities.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,14 +1,35 @@
|
||||||
var Timer = require('../util/timer');
|
var Timer = require('../util/timer');
|
||||||
|
|
||||||
function Clock () {
|
function Clock (runtime) {
|
||||||
this._projectTimer = new Timer();
|
this._projectTimer = new Timer();
|
||||||
this._projectTimer.start();
|
this._projectTimer.start();
|
||||||
|
this._pausedTime = null;
|
||||||
|
this._paused = false;
|
||||||
|
/**
|
||||||
|
* Reference to the owning Runtime.
|
||||||
|
* @type{!Runtime}
|
||||||
|
*/
|
||||||
|
this.runtime = runtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
Clock.prototype.projectTimer = function () {
|
Clock.prototype.projectTimer = function () {
|
||||||
|
if (this._paused) {
|
||||||
|
return this._pausedTime / 1000;
|
||||||
|
}
|
||||||
return this._projectTimer.timeElapsed() / 1000;
|
return this._projectTimer.timeElapsed() / 1000;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Clock.prototype.pause = function () {
|
||||||
|
this._paused = true;
|
||||||
|
this._pausedTime = this._projectTimer.timeElapsed();
|
||||||
|
};
|
||||||
|
|
||||||
|
Clock.prototype.resume = function () {
|
||||||
|
this._paused = false;
|
||||||
|
var dt = this._projectTimer.timeElapsed() - this._pausedTime;
|
||||||
|
this._projectTimer.startTime += dt;
|
||||||
|
};
|
||||||
|
|
||||||
Clock.prototype.resetProjectTimer = function () {
|
Clock.prototype.resetProjectTimer = function () {
|
||||||
this._projectTimer.start();
|
this._projectTimer.start();
|
||||||
};
|
};
|
||||||
|
|
|
@ -152,6 +152,9 @@ Clone.prototype.setXY = function (x, y) {
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||||
position: [this.x, this.y]
|
position: [this.x, this.y]
|
||||||
});
|
});
|
||||||
|
if (this.visible) {
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -191,6 +194,9 @@ Clone.prototype.setDirection = function (direction) {
|
||||||
direction: renderedDirectionScale.direction,
|
direction: renderedDirectionScale.direction,
|
||||||
scale: renderedDirectionScale.scale
|
scale: renderedDirectionScale.scale
|
||||||
});
|
});
|
||||||
|
if (this.visible) {
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -224,6 +230,9 @@ Clone.prototype.setVisible = function (visible) {
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||||
visible: this.visible
|
visible: this.visible
|
||||||
});
|
});
|
||||||
|
if (this.visible) {
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -243,6 +252,9 @@ Clone.prototype.setSize = function (size) {
|
||||||
direction: renderedDirectionScale.direction,
|
direction: renderedDirectionScale.direction,
|
||||||
scale: renderedDirectionScale.scale
|
scale: renderedDirectionScale.scale
|
||||||
});
|
});
|
||||||
|
if (this.visible) {
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -258,6 +270,9 @@ Clone.prototype.setEffect = function (effectName, value) {
|
||||||
var props = {};
|
var props = {};
|
||||||
props[effectName] = this.effects[effectName];
|
props[effectName] = this.effects[effectName];
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, props);
|
this.renderer.updateDrawableProperties(this.drawableID, props);
|
||||||
|
if (this.visible) {
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -270,6 +285,9 @@ Clone.prototype.clearEffects = function () {
|
||||||
}
|
}
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, this.effects);
|
this.renderer.updateDrawableProperties(this.drawableID, this.effects);
|
||||||
|
if (this.visible) {
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -287,6 +305,9 @@ Clone.prototype.setCostume = function (index) {
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||||
skin: this.sprite.costumes[this.currentCostume].skin
|
skin: this.sprite.costumes[this.currentCostume].skin
|
||||||
});
|
});
|
||||||
|
if (this.visible) {
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -308,6 +329,9 @@ Clone.prototype.setRotationStyle = function (rotationStyle) {
|
||||||
direction: renderedDirectionScale.direction,
|
direction: renderedDirectionScale.direction,
|
||||||
scale: renderedDirectionScale.scale
|
scale: renderedDirectionScale.scale
|
||||||
});
|
});
|
||||||
|
if (this.visible) {
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -339,6 +363,9 @@ Clone.prototype.updateAllDrawableProperties = function () {
|
||||||
visible: this.visible,
|
visible: this.visible,
|
||||||
skin: this.sprite.costumes[this.currentCostume].skin
|
skin: this.sprite.costumes[this.currentCostume].skin
|
||||||
});
|
});
|
||||||
|
if (this.visible) {
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -552,6 +579,9 @@ Clone.prototype.dispose = function () {
|
||||||
this.runtime.changeCloneCounter(-1);
|
this.runtime.changeCloneCounter(-1);
|
||||||
if (this.renderer && this.drawableID !== null) {
|
if (this.renderer && this.drawableID !== null) {
|
||||||
this.renderer.destroyDrawable(this.drawableID);
|
this.renderer.destroyDrawable(this.drawableID);
|
||||||
|
if (this.visible) {
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue