Better shadow evaluation ()

* Better shadow evaluation

* Audit and improve casting for all primitives

* Force repeat times to int

* Remove colorPicker shadow menu
This commit is contained in:
Tim Mickel 2016-09-12 13:14:16 -04:00 committed by GitHub
parent ebbfe881db
commit bbea1af5a3
8 changed files with 132 additions and 134 deletions

View file

@ -1,3 +1,4 @@
var Cast = require('../util/cast');
var Promise = require('promise');
function Scratch3ControlBlocks(runtime) {
@ -25,9 +26,10 @@ Scratch3ControlBlocks.prototype.getPrimitives = function() {
};
Scratch3ControlBlocks.prototype.repeat = function(args, util) {
var times = Math.floor(Cast.toNumber(args.TIMES));
// Initialize loop
if (util.stackFrame.loopCounter === undefined) {
util.stackFrame.loopCounter = parseInt(args.TIMES);
util.stackFrame.loopCounter = times;
}
// Only execute once per frame.
// When the branch finishes, `repeat` will be executed again and
@ -47,13 +49,14 @@ Scratch3ControlBlocks.prototype.repeat = function(args, util) {
};
Scratch3ControlBlocks.prototype.repeatUntil = function(args, util) {
var condition = Cast.toBoolean(args.CONDITION);
// Only execute once per frame.
// When the branch finishes, `repeat` 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;
// If the condition is true, start the branch.
if (!args.CONDITION) {
if (!condition) {
util.startBranch();
}
} else {
@ -76,30 +79,33 @@ Scratch3ControlBlocks.prototype.forever = function(args, util) {
};
Scratch3ControlBlocks.prototype.wait = function(args) {
var duration = Cast.toNumber(args.DURATION);
return new Promise(function(resolve) {
setTimeout(function() {
resolve();
}, 1000 * args.DURATION);
}, 1000 * duration);
});
};
Scratch3ControlBlocks.prototype.if = function(args, util) {
var condition = Cast.toBoolean(args.CONDITION);
// Only execute one time. `if` will be returned to
// when the branch finishes, but it shouldn't execute again.
if (util.stackFrame.executedInFrame === undefined) {
util.stackFrame.executedInFrame = true;
if (args.CONDITION) {
if (condition) {
util.startBranch();
}
}
};
Scratch3ControlBlocks.prototype.ifElse = function(args, util) {
var condition = Cast.toBoolean(args.CONDITION);
// Only execute one time. `ifElse` will be returned to
// when the branch finishes, but it shouldn't execute again.
if (util.stackFrame.executedInFrame === undefined) {
util.stackFrame.executedInFrame = true;
if (args.CONDITION) {
if (condition) {
util.startBranch(1);
} else {
util.startBranch(2);

View file

@ -1,3 +1,5 @@
var Cast = require('../util/cast');
function Scratch3EventBlocks(runtime) {
/**
* The runtime instantiating this block package.
@ -43,26 +45,30 @@ Scratch3EventBlocks.prototype.getHats = function () {
};
Scratch3EventBlocks.prototype.hatGreaterThanPredicate = function (args, util) {
var option = Cast.toString(args.WHENGREATERTHANMENU).toLowerCase();
var value = Cast.toNumber(args.VALUE);
// @todo: Other cases :)
if (args.WHENGREATERTHANMENU == 'TIMER') {
return util.ioQuery('clock', 'projectTimer') > args.VALUE;
if (option == 'timer') {
return util.ioQuery('clock', 'projectTimer') > value;
}
return false;
};
Scratch3EventBlocks.prototype.broadcast = function(args, util) {
var broadcastOption = Cast.toString(args.BROADCAST_OPTION);
util.startHats('event_whenbroadcastreceived', {
'BROADCAST_OPTION': args.BROADCAST_OPTION
'BROADCAST_OPTION': broadcastOption
});
};
Scratch3EventBlocks.prototype.broadcastAndWait = function (args, util) {
var broadcastOption = Cast.toString(args.BROADCAST_OPTION);
// Have we run before, starting threads?
if (!util.stackFrame.startedThreads) {
// No - start hats for this broadcast.
util.stackFrame.startedThreads = util.startHats(
'event_whenbroadcastreceived', {
'BROADCAST_OPTION': args.BROADCAST_OPTION
'BROADCAST_OPTION': broadcastOption
}
);
if (util.stackFrame.startedThreads.length == 0) {

View file

@ -20,14 +20,11 @@ Scratch3LooksBlocks.prototype.getPrimitives = function() {
'looks_thinkforsecs': this.sayforsecs,
'looks_show': this.show,
'looks_hide': this.hide,
'looks_backdrops': this.backdropMenu,
'looks_costume': this.costumeMenu,
'looks_switchcostumeto': this.switchCostume,
'looks_switchbackdropto': this.switchBackdrop,
'looks_switchbackdroptoandwait': this.switchBackdropAndWait,
'looks_nextcostume': this.nextCostume,
'looks_nextbackdrop': this.nextBackdrop,
'looks_effectmenu': this.effectMenu,
'looks_changeeffectby': this.changeEffect,
'looks_seteffectto': this.setEffect,
'looks_cleargraphiceffects': this.clearEffects,
@ -119,11 +116,6 @@ Scratch3LooksBlocks.prototype._setCostumeOrBackdrop = function (target,
return [];
};
// @todo(GH-146): Remove.
Scratch3LooksBlocks.prototype.costumeMenu = function (args) {
return args.COSTUME;
};
Scratch3LooksBlocks.prototype.switchCostume = function (args, util) {
this._setCostumeOrBackdrop(util.target, args.COSTUME);
};
@ -134,11 +126,6 @@ Scratch3LooksBlocks.prototype.nextCostume = function (args, util) {
);
};
// @todo(GH-146): Remove.
Scratch3LooksBlocks.prototype.backdropMenu = function (args) {
return args.BACKDROP;
};
Scratch3LooksBlocks.prototype.switchBackdrop = function (args) {
this._setCostumeOrBackdrop(this.runtime.getTargetForStage(), args.BACKDROP);
};
@ -175,17 +162,18 @@ Scratch3LooksBlocks.prototype.nextBackdrop = function () {
);
};
Scratch3LooksBlocks.prototype.effectMenu = function (args) {
return args.EFFECT.toLowerCase();
};
Scratch3LooksBlocks.prototype.changeEffect = function (args, util) {
var newValue = args.CHANGE + util.target.effects[args.EFFECT];
util.target.setEffect(args.EFFECT, newValue);
var effect = Cast.toString(args.EFFECT).toLowerCase();
var change = Cast.toNumber(args.CHANGE);
if (!util.target.effects.hasOwnProperty(effect)) return;
var newValue = change + util.target.effects[effect];
util.target.setEffect(effect, newValue);
};
Scratch3LooksBlocks.prototype.setEffect = function (args, util) {
util.target.setEffect(args.EFFECT, args.VALUE);
var effect = Cast.toString(args.EFFECT).toLowerCase();
var value = Cast.toNumber(args.VALUE);
util.target.setEffect(effect, value);
};
Scratch3LooksBlocks.prototype.clearEffects = function (args, util) {
@ -193,11 +181,13 @@ Scratch3LooksBlocks.prototype.clearEffects = function (args, util) {
};
Scratch3LooksBlocks.prototype.changeSize = function (args, util) {
util.target.setSize(util.target.size + args.CHANGE);
var change = Cast.toNumber(args.CHANGE);
util.target.setSize(util.target.size + change);
};
Scratch3LooksBlocks.prototype.setSize = function (args, util) {
util.target.setSize(args.SIZE);
var size = Cast.toNumber(args.SIZE);
util.target.setSize(size);
};
Scratch3LooksBlocks.prototype.getSize = function (args, util) {

View file

@ -1,3 +1,4 @@
var Cast = require('../util/cast');
var MathUtil = require('../util/math-util');
function Scratch3MotionBlocks(runtime) {
@ -30,42 +31,52 @@ Scratch3MotionBlocks.prototype.getPrimitives = function() {
};
Scratch3MotionBlocks.prototype.moveSteps = function (args, util) {
var steps = Cast.toNumber(args.STEPS);
var radians = MathUtil.degToRad(util.target.direction);
var dx = args.STEPS * Math.cos(radians);
var dy = args.STEPS * Math.sin(radians);
var dx = steps * Math.cos(radians);
var dy = steps * Math.sin(radians);
util.target.setXY(util.target.x + dx, util.target.y + dy);
};
Scratch3MotionBlocks.prototype.goToXY = function (args, util) {
util.target.setXY(args.X, args.Y);
var x = Cast.toNumber(args.X);
var y = Cast.toNumber(args.Y);
util.target.setXY(x, y);
};
Scratch3MotionBlocks.prototype.turnRight = function (args, util) {
util.target.setDirection(util.target.direction + args.DEGREES);
var degrees = Cast.toNumber(args.DEGREES);
util.target.setDirection(util.target.direction + degrees);
};
Scratch3MotionBlocks.prototype.turnLeft = function (args, util) {
util.target.setDirection(util.target.direction - args.DEGREES);
var degrees = Cast.toNumber(args.DEGREES);
util.target.setDirection(util.target.direction - degrees);
};
Scratch3MotionBlocks.prototype.pointInDirection = function (args, util) {
util.target.setDirection(args.DIRECTION);
var direction = Cast.toNumber(args.DIRECTION);
util.target.setDirection(direction);
};
Scratch3MotionBlocks.prototype.changeX = function (args, util) {
util.target.setXY(util.target.x + args.DX, util.target.y);
var dx = Cast.toNumber(args.DX);
util.target.setXY(util.target.x + dx, util.target.y);
};
Scratch3MotionBlocks.prototype.setX = function (args, util) {
util.target.setXY(args.X, util.target.y);
var x = Cast.toNumber(args.X);
util.target.setXY(x, util.target.y);
};
Scratch3MotionBlocks.prototype.changeY = function (args, util) {
util.target.setXY(util.target.x, util.target.y + args.DY);
var dy = Cast.toNumber(args.DY);
util.target.setXY(util.target.x, util.target.y + dy);
};
Scratch3MotionBlocks.prototype.setY = function (args, util) {
util.target.setXY(util.target.x, args.Y);
var y = Cast.toNumber(args.Y);
util.target.setXY(util.target.x, y);
};
Scratch3MotionBlocks.prototype.getX = function (args, util) {

View file

@ -14,11 +14,6 @@ function Scratch3OperatorsBlocks(runtime) {
*/
Scratch3OperatorsBlocks.prototype.getPrimitives = function() {
return {
'math_number': this.number,
'math_positive_number': this.number,
'math_whole_number': this.number,
'math_angle': this.number,
'text': this.text,
'operator_add': this.add,
'operator_subtract': this.subtract,
'operator_multiply': this.multiply,
@ -35,19 +30,10 @@ Scratch3OperatorsBlocks.prototype.getPrimitives = function() {
'operator_length': this.length,
'operator_mod': this.mod,
'operator_round': this.round,
'operator_mathop_menu': this.mathopMenu,
'operator_mathop': this.mathop
};
};
Scratch3OperatorsBlocks.prototype.number = function (args) {
return Cast.toNumber(args.NUM);
};
Scratch3OperatorsBlocks.prototype.text = function (args) {
return Cast.toString(args.TEXT);
};
Scratch3OperatorsBlocks.prototype.add = function (args) {
return Cast.toNumber(args.NUM1) + Cast.toNumber(args.NUM2);
};
@ -77,20 +63,22 @@ Scratch3OperatorsBlocks.prototype.gt = function (args) {
};
Scratch3OperatorsBlocks.prototype.and = function (args) {
return Cast.toBoolean(args.OPERAND1 && args.OPERAND2);
return Cast.toBoolean(args.OPERAND1) && Cast.toBoolean(args.OPERAND2);
};
Scratch3OperatorsBlocks.prototype.or = function (args) {
return Cast.toBoolean(args.OPERAND1 || args.OPERAND2);
return Cast.toBoolean(args.OPERAND1) || Cast.toBoolean(args.OPERAND2);
};
Scratch3OperatorsBlocks.prototype.not = function (args) {
return Cast.toBoolean(!args.OPERAND);
return !Cast.toBoolean(args.OPERAND);
};
Scratch3OperatorsBlocks.prototype.random = function (args) {
var low = args.FROM <= args.TO ? args.FROM : args.TO;
var high = args.FROM <= args.TO ? args.TO : args.FROM;
var nFrom = Cast.toNumber(args.FROM);
var nTo = Cast.toNumber(args.TO);
var low = nFrom <= nTo ? nFrom : nTo;
var high = nFrom <= nTo ? nTo : nFrom;
if (low == high) return low;
// If both low and high are ints, truncate the result to an int.
var lowInt = low == parseInt(low);
@ -132,10 +120,6 @@ Scratch3OperatorsBlocks.prototype.round = function (args) {
return Math.round(Cast.toNumber(args.NUM));
};
Scratch3OperatorsBlocks.prototype.mathopMenu = function (args) {
return args.OPERATOR;
};
Scratch3OperatorsBlocks.prototype.mathop = function (args) {
var operator = Cast.toString(args.OPERATOR).toLowerCase();
var n = Cast.toNumber(args.NUM);

View file

@ -14,7 +14,6 @@ function Scratch3SensingBlocks(runtime) {
*/
Scratch3SensingBlocks.prototype.getPrimitives = function() {
return {
'colour_picker': this.colorPicker,
'sensing_touchingcolor': this.touchingColor,
'sensing_coloristouchingcolor': this.colorTouchingColor,
'sensing_timer': this.getTimer,
@ -22,17 +21,11 @@ Scratch3SensingBlocks.prototype.getPrimitives = function() {
'sensing_mousex': this.getMouseX,
'sensing_mousey': this.getMouseY,
'sensing_mousedown': this.getMouseDown,
'sensing_keyoptions': this.keyOptions,
'sensing_keypressed': this.getKeyPressed,
'sensing_current': this.current,
'sensing_currentmenu': this.currentMenu
'sensing_current': this.current
};
};
Scratch3SensingBlocks.prototype.colorPicker = function (args) {
return args.COLOUR;
};
Scratch3SensingBlocks.prototype.touchingColor = function (args, util) {
var color = Cast.toRgbColorList(args.COLOR);
return util.target.isTouchingColor(color);
@ -65,8 +58,9 @@ Scratch3SensingBlocks.prototype.getMouseDown = function (args, util) {
};
Scratch3SensingBlocks.prototype.current = function (args) {
var menuOption = Cast.toString(args.CURRENTMENU).toLowerCase();
var date = new Date();
switch (args.CURRENTMENU) {
switch (menuOption) {
case 'year': return date.getFullYear();
case 'month': return date.getMonth() + 1; // getMonth is zero-based
case 'date': return date.getDate();
@ -75,14 +69,7 @@ Scratch3SensingBlocks.prototype.current = function (args) {
case 'minute': return date.getMinutes();
case 'second': return date.getSeconds();
}
};
Scratch3SensingBlocks.prototype.currentMenu = function (args) {
return args.CURRENTMENU.toLowerCase();
};
Scratch3SensingBlocks.prototype.keyOptions = function (args) {
return args.KEY_OPTION.toLowerCase();
return 0;
};
Scratch3SensingBlocks.prototype.getKeyPressed = function (args, util) {

View file

@ -22,40 +22,91 @@ var execute = function (sequencer, thread) {
var currentBlockId = thread.peekStack();
var currentStackFrame = thread.peekStackFrame();
// Query info about the block.
var opcode = target.blocks.getOpcode(currentBlockId);
var blockFunction = runtime.getOpcodeFunction(opcode);
var isHat = runtime.getIsHat(opcode);
var fields = target.blocks.getFields(currentBlockId);
var inputs = target.blocks.getInputs(currentBlockId);
if (!opcode) {
console.warn('Could not get opcode for block: ' + currentBlockId);
return;
}
var blockFunction = runtime.getOpcodeFunction(opcode);
var isHat = runtime.getIsHat(opcode);
// Hats are implemented slightly differently from regular blocks.
// If they have an associated block function, it's treated as a predicate;
// if not, execution will proceed right through it (as a no-op).
if (!blockFunction) {
if (!isHat) {
console.warn('Could not get implementation for opcode: ' + opcode);
/**
* Handle any reported value from the primitive, either directly returned
* or after a promise resolves.
* @param {*} resolvedValue Value eventually returned from the primitive.
*/
var handleReport = function (resolvedValue) {
thread.pushReportedValue(resolvedValue);
if (isHat) {
// Hat predicate was evaluated.
if (runtime.getIsEdgeActivatedHat(opcode)) {
// If this is an edge-activated hat, only proceed if
// the value is true and used to be false.
var oldEdgeValue = runtime.updateEdgeActivatedValue(
currentBlockId,
resolvedValue
);
var edgeWasActivated = !oldEdgeValue && resolvedValue;
if (!edgeWasActivated) {
sequencer.retireThread(thread);
}
} else {
// Not an edge-activated hat: retire the thread
// if predicate was false.
if (!resolvedValue) {
sequencer.retireThread(thread);
}
}
} else {
// In a non-hat, report the value visually if necessary if
// at the top of the thread stack.
if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
runtime.visualReport(currentBlockId, resolvedValue);
}
// Finished any yields.
thread.setStatus(Thread.STATUS_RUNNING);
}
};
// Hats and single-field shadows are implemented slightly differently
// from regular blocks.
// For hats: if they have an associated block function,
// it's treated as a predicate; if not, execution will proceed as a no-op.
// For single-field shadows: If the block has a single field, and no inputs,
// immediately return the value of the field.
if (!blockFunction) {
if (isHat) {
// Skip through the block (hat with no predicate).
return;
} else {
if (Object.keys(fields).length == 1 &&
Object.keys(inputs).length == 0) {
// One field and no inputs - treat as arg.
for (var fieldKey in fields) { // One iteration.
handleReport(fields[fieldKey].value);
}
} else {
console.warn('Could not get implementation for opcode: ' +
opcode);
}
thread.requestScriptGlowInFrame = true;
return;
}
// Skip through the block.
// (either hat with no predicate, or missing op).
thread.requestScriptGlowInFrame = true;
return;
}
// Generate values for arguments (inputs).
var argValues = {};
// Add all fields on this block to the argValues.
var fields = target.blocks.getFields(currentBlockId);
for (var fieldName in fields) {
argValues[fieldName] = fields[fieldName].value;
}
// Recursively evaluate input blocks.
var inputs = target.blocks.getInputs(currentBlockId);
for (var inputName in inputs) {
var input = inputs[inputName];
var inputBlockId = input.block;
@ -117,44 +168,6 @@ var execute = function (sequencer, thread) {
thread.requestScriptGlowInFrame = true;
}
/**
* Handle any reported value from the primitive, either directly returned
* or after a promise resolves.
* @param {*} resolvedValue Value eventually returned from the primitive.
*/
var handleReport = function (resolvedValue) {
thread.pushReportedValue(resolvedValue);
if (isHat) {
// Hat predicate was evaluated.
if (runtime.getIsEdgeActivatedHat(opcode)) {
// If this is an edge-activated hat, only proceed if
// the value is true and used to be false.
var oldEdgeValue = runtime.updateEdgeActivatedValue(
currentBlockId,
resolvedValue
);
var edgeWasActivated = !oldEdgeValue && resolvedValue;
if (!edgeWasActivated) {
sequencer.retireThread(thread);
}
} else {
// Not an edge-activated hat: retire the thread
// if predicate was false.
if (!resolvedValue) {
sequencer.retireThread(thread);
}
}
} else {
// In a non-hat, report the value visually if necessary if
// at the top of the thread stack.
if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
runtime.visualReport(currentBlockId, resolvedValue);
}
// Finished any yields.
thread.setStatus(Thread.STATUS_RUNNING);
}
};
// If it's a promise, wait until promise resolves.
if (isPromise(primitiveReportedValue)) {
if (thread.status === Thread.STATUS_RUNNING) {

View file

@ -198,6 +198,7 @@ Clone.prototype.setSize = function (size) {
* @param {!number} value Numerical magnitude of effect.
*/
Clone.prototype.setEffect = function (effectName, value) {
if (!this.effects.hasOwnProperty(effectName)) return;
this.effects[effectName] = value;
if (this.renderer) {
var props = {};