mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 06:23:37 -05:00
Merge branch 'develop' of https://github.com/LLK/scratch-vm into develop
This commit is contained in:
commit
7c91565408
29 changed files with 1444 additions and 17552 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -8,3 +8,10 @@ npm-*
|
|||
# Testing
|
||||
/.nyc_output
|
||||
/coverage
|
||||
/dist.js
|
||||
/vm.js
|
||||
/vm.min.js
|
||||
/playground/media
|
||||
/playground/vendor.js
|
||||
/playground/vm.js
|
||||
/playground/zenburn.css
|
||||
|
|
2
.npmignore
Normal file
2
.npmignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
/.nyc_output
|
||||
/coverage
|
24
.travis.yml
24
.travis.yml
|
@ -6,3 +6,27 @@ sudo: false
|
|||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
after_script:
|
||||
- |
|
||||
# RELEASE_BRANCHES and NPM_TOKEN defined in Travis settings panel
|
||||
declare exitCode
|
||||
$(npm bin)/travis-after-all
|
||||
exitCode=$?
|
||||
if [[
|
||||
# Execute after all jobs finish successfully
|
||||
$exitCode = 0 &&
|
||||
# Only release on release branches
|
||||
$RELEASE_BRANCHES =~ $TRAVIS_BRANCH &&
|
||||
# Don't release on PR builds
|
||||
$TRAVIS_PULL_REQUEST = "false"
|
||||
]]; then
|
||||
# Authenticate NPM
|
||||
echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc
|
||||
# Set version to timestamp
|
||||
npm --no-git-tag-version version $($(npm bin)/json -f package.json version)-prerelease.$(date +%s)
|
||||
npm publish
|
||||
# Publish to gh-pages as most recent committer
|
||||
git config --global user.email $(git log --pretty=format:"%ce" -n1)
|
||||
git config --global user.name $(git log --pretty=format:"%cn" -n1)
|
||||
./node_modules/.bin/gh-pages -x -r https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git -d playground -m "Build for $(git log --pretty=format:%H)"
|
||||
fi
|
||||
|
|
2
Makefile
2
Makefile
|
@ -13,7 +13,7 @@ watch:
|
|||
$(WEBPACK) --watch
|
||||
|
||||
serve:
|
||||
$(WEBPACK_DEV_SERVER) --host 0.0.0.0 --content-base ./
|
||||
$(WEBPACK_DEV_SERVER)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ npm install https://github.com/LLK/scratch-vm.git
|
|||
```
|
||||
If you want to edit/play yourself:
|
||||
```bash
|
||||
git clone git@github.com:LLK/scratch-vm.git
|
||||
git clone https://github.com/LLK/scratch-vm.git
|
||||
cd scratch-vm
|
||||
npm install
|
||||
```
|
||||
|
@ -35,7 +35,7 @@ StartServerWindows.bat
|
|||
```
|
||||
|
||||
## Playground
|
||||
To run the Playground, make sure the dev server's running and go to [http://localhost:8080/](http://localhost:8080/) - you will be redirected to the playground, which demonstrates various tools and internal state.
|
||||
To run the Playground, make sure the dev server's running and go to [http://localhost:8080/](http://localhost:8080/) - you will be directed to the playground, which demonstrates various tools and internal state.
|
||||
|
||||
![VM Playground Screenshot](https://i.imgur.com/nOCNqEc.gif)
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
@echo off
|
||||
node_modules\.bin\webpack-dev-server --host 0.0.0.0 --content-base .
|
||||
@echo off
|
||||
node_modules\.bin\webpack-dev-server --host 0.0.0.0 --content-base .\playground
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="refresh" content="0; URL='/playground'" />
|
||||
<title>Redirect to playground</title>
|
||||
</head>
|
||||
</html>
|
23
package.json
23
package.json
|
@ -9,23 +9,30 @@
|
|||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/LLK/scratch-vm.git"
|
||||
},
|
||||
"main": "./src/index.js",
|
||||
"main": "./dist.js",
|
||||
"scripts": {
|
||||
"prepublish": "./node_modules/.bin/webpack --bail",
|
||||
"start": "make serve",
|
||||
"test": "make test",
|
||||
"start": "webpack-dev-server --host 0.0.0.0 --content-base ."
|
||||
},
|
||||
"dependencies": {
|
||||
"htmlparser2": "3.9.0",
|
||||
"promise": "7.1.1"
|
||||
"version": "./node_modules/.bin/json -f package.json -I -e \"this.repository.sha = '$(git log -n1 --pretty=format:%H)'\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "3.0.1",
|
||||
"eslint": "2.7.0",
|
||||
"expose-loader": "0.7.1",
|
||||
"gh-pages": "0.11.0",
|
||||
"highlightjs": "8.7.0",
|
||||
"htmlparser2": "3.9.0",
|
||||
"json": "9.0.4",
|
||||
"json-loader": "0.5.4",
|
||||
"scratch-blocks": "git+https://git@github.com/LLK/scratch-blocks.git#develop",
|
||||
"scratch-render": "git+https://git@github.com/LLK/scratch-render.git#develop",
|
||||
"lodash.defaultsdeep": "4.6.0",
|
||||
"promise": "7.1.1",
|
||||
"scratch-blocks": "^0.1.0-prepublish",
|
||||
"scratch-render": "^0.1.0-prepublish",
|
||||
"script-loader": "0.7.0",
|
||||
"stats.js": "0.16.0",
|
||||
"tap": "5.7.1",
|
||||
"travis-after-all": "1.4.4",
|
||||
"webpack": "1.13.0",
|
||||
"webpack-dev-server": "1.14.1"
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,7 +21,7 @@ var loadProject = function () {
|
|||
|
||||
window.onload = function() {
|
||||
// Lots of global variables to make debugging easier
|
||||
// Instantiate the VM worker.
|
||||
// Instantiate the VM.
|
||||
var vm = new window.VirtualMachine();
|
||||
window.vm = vm;
|
||||
|
||||
|
@ -30,7 +30,8 @@ window.onload = function() {
|
|||
document.location = '#' + document.getElementById('projectId').value;
|
||||
location.reload();
|
||||
};
|
||||
document.getElementById('createEmptyProject').addEventListener('click', function() {
|
||||
document.getElementById('createEmptyProject').addEventListener('click',
|
||||
function() {
|
||||
document.location = '#' + 'createEmptyProject';
|
||||
location.reload();
|
||||
});
|
||||
|
@ -38,7 +39,9 @@ window.onload = function() {
|
|||
|
||||
// Instantiate the renderer and connect it to the VM.
|
||||
var canvas = document.getElementById('scratch-stage');
|
||||
window.renderer = new window.RenderWebGLLocal(canvas);
|
||||
var renderer = new window.RenderWebGL(canvas);
|
||||
window.renderer = renderer;
|
||||
vm.attachRenderer(renderer);
|
||||
|
||||
// Instantiate audio engine
|
||||
window.audioEngine = new window.AudioEngine();
|
||||
|
@ -47,7 +50,7 @@ window.onload = function() {
|
|||
var toolbox = document.getElementById('toolbox');
|
||||
var workspace = window.Blockly.inject('blocks', {
|
||||
toolbox: toolbox,
|
||||
media: '../node_modules/scratch-blocks/media/',
|
||||
media: './media/',
|
||||
zoom: {
|
||||
controls: true,
|
||||
wheel: true,
|
||||
|
@ -87,8 +90,7 @@ window.onload = function() {
|
|||
// Thread representation tab.
|
||||
var threadexplorer = document.getElementById('threadexplorer');
|
||||
var cachedThreadJSON = '';
|
||||
var updateThreadExplorer = function (threads) {
|
||||
var newJSON = JSON.stringify(threads, null, 2);
|
||||
var updateThreadExplorer = function (newJSON) {
|
||||
if (newJSON != cachedThreadJSON) {
|
||||
cachedThreadJSON = newJSON;
|
||||
threadexplorer.innerHTML = cachedThreadJSON;
|
||||
|
|
|
@ -19,9 +19,21 @@ Scratch3ControlBlocks.prototype.getPrimitives = function() {
|
|||
'control_repeat_until': this.repeatUntil,
|
||||
'control_forever': this.forever,
|
||||
'control_wait': this.wait,
|
||||
'control_wait_until': this.waitUntil,
|
||||
'control_if': this.if,
|
||||
'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_delete_this_clone': this.deleteClone
|
||||
};
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.getHats = function () {
|
||||
return {
|
||||
'control_start_as_clone': {
|
||||
restartExistingThreads: false
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -65,6 +77,14 @@ Scratch3ControlBlocks.prototype.repeatUntil = function(args, util) {
|
|||
}
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.waitUntil = function(args, util) {
|
||||
var condition = Cast.toBoolean(args.CONDITION);
|
||||
// Only execute once per frame.
|
||||
if (!condition) {
|
||||
util.yieldFrame();
|
||||
}
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.forever = function(args, util) {
|
||||
// Only execute once per frame.
|
||||
// When the branch finishes, `forever` will be executed again and
|
||||
|
@ -118,4 +138,30 @@ Scratch3ControlBlocks.prototype.stop = function() {
|
|||
this.runtime.stopAll();
|
||||
};
|
||||
|
||||
// @todo (GH-146): remove.
|
||||
Scratch3ControlBlocks.prototype.createCloneMenu = function (args) {
|
||||
return args.CLONE_OPTION;
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.createClone = function (args, util) {
|
||||
var cloneTarget;
|
||||
if (args.CLONE_OPTION == '_myself_') {
|
||||
cloneTarget = util.target;
|
||||
} else {
|
||||
cloneTarget = this.runtime.getSpriteTargetByName(args.CLONE_OPTION);
|
||||
}
|
||||
if (!cloneTarget) {
|
||||
return;
|
||||
}
|
||||
var newClone = cloneTarget.makeClone();
|
||||
if (newClone) {
|
||||
this.runtime.targets.push(newClone);
|
||||
}
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.deleteClone = function (args, util) {
|
||||
this.runtime.disposeTarget(util.target);
|
||||
this.runtime.stopForTarget(util.target);
|
||||
};
|
||||
|
||||
module.exports = Scratch3ControlBlocks;
|
||||
|
|
136
src/blocks/scratch3_data.js
Normal file
136
src/blocks/scratch3_data.js
Normal file
|
@ -0,0 +1,136 @@
|
|||
var Cast = require('../util/cast');
|
||||
|
||||
function Scratch3DataBlocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
* @type {Runtime}
|
||||
*/
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the block primitives implemented by this package.
|
||||
* @return {Object.<string, Function>} Mapping of opcode to Function.
|
||||
*/
|
||||
Scratch3DataBlocks.prototype.getPrimitives = function () {
|
||||
return {
|
||||
'data_variable': this.getVariable,
|
||||
'data_setvariableto': this.setVariableTo,
|
||||
'data_changevariableby': this.changeVariableBy,
|
||||
'data_list': this.getListContents,
|
||||
'data_addtolist': this.addToList,
|
||||
'data_deleteoflist': this.deleteOfList,
|
||||
'data_insertatlist': this.insertAtList,
|
||||
'data_replaceitemoflist': this.replaceItemOfList,
|
||||
'data_itemoflist': this.getItemOfList,
|
||||
'data_lengthoflist': this.lengthOfList,
|
||||
'data_listcontainsitem': this.listContainsItem
|
||||
};
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.getVariable = function (args, util) {
|
||||
var variable = util.target.lookupOrCreateVariable(args.VARIABLE);
|
||||
return variable.value;
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.setVariableTo = function (args, util) {
|
||||
var variable = util.target.lookupOrCreateVariable(args.VARIABLE);
|
||||
variable.value = args.VALUE;
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.changeVariableBy = function (args, util) {
|
||||
var variable = util.target.lookupOrCreateVariable(args.VARIABLE);
|
||||
var castedValue = Cast.toNumber(variable.value);
|
||||
var dValue = Cast.toNumber(args.VALUE);
|
||||
variable.value = castedValue + dValue;
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.getListContents = function (args, util) {
|
||||
var list = util.target.lookupOrCreateList(args.LIST);
|
||||
// Determine if the list is all single letters.
|
||||
// If it is, report contents joined together with no separator.
|
||||
// If it's not, report contents joined together with a space.
|
||||
var allSingleLetters = true;
|
||||
for (var i = 0; i < list.contents.length; i++) {
|
||||
var listItem = list.contents[i];
|
||||
if (!((typeof listItem === 'string') &&
|
||||
(listItem.length == 1))) {
|
||||
allSingleLetters = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allSingleLetters) {
|
||||
return list.contents.join('');
|
||||
} else {
|
||||
return list.contents.join(' ');
|
||||
}
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.addToList = function (args, util) {
|
||||
var list = util.target.lookupOrCreateList(args.LIST);
|
||||
list.contents.push(args.ITEM);
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.deleteOfList = function (args, util) {
|
||||
var list = util.target.lookupOrCreateList(args.LIST);
|
||||
var index = Cast.toListIndex(args.INDEX, list.contents.length);
|
||||
if (index === Cast.LIST_INVALID) {
|
||||
return;
|
||||
} else if (index === Cast.LIST_ALL) {
|
||||
list.contents = [];
|
||||
return;
|
||||
}
|
||||
list.contents.splice(index - 1, 1);
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.insertAtList = function (args, util) {
|
||||
var item = args.ITEM;
|
||||
var list = util.target.lookupOrCreateList(args.LIST);
|
||||
var index = Cast.toListIndex(args.INDEX, list.contents.length + 1);
|
||||
if (index === Cast.LIST_INVALID) {
|
||||
return;
|
||||
}
|
||||
list.contents.splice(index - 1, 0, item);
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.replaceItemOfList = function (args, util) {
|
||||
var item = args.ITEM;
|
||||
var list = util.target.lookupOrCreateList(args.LIST);
|
||||
var index = Cast.toListIndex(args.INDEX, list.contents.length);
|
||||
if (index === Cast.LIST_INVALID) {
|
||||
return;
|
||||
}
|
||||
list.contents.splice(index - 1, 1, item);
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.getItemOfList = function (args, util) {
|
||||
var list = util.target.lookupOrCreateList(args.LIST);
|
||||
var index = Cast.toListIndex(args.INDEX, list.contents.length);
|
||||
if (index === Cast.LIST_INVALID) {
|
||||
return '';
|
||||
}
|
||||
return list.contents[index - 1];
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.lengthOfList = function (args, util) {
|
||||
var list = util.target.lookupOrCreateList(args.LIST);
|
||||
return list.contents.length;
|
||||
};
|
||||
|
||||
Scratch3DataBlocks.prototype.listContainsItem = function (args, util) {
|
||||
var item = args.ITEM;
|
||||
var list = util.target.lookupOrCreateList(args.LIST);
|
||||
if (list.contents.indexOf(item) >= 0) {
|
||||
return true;
|
||||
}
|
||||
// Try using Scratch comparison operator on each item.
|
||||
// (Scratch considers the string '123' equal to the number 123).
|
||||
for (var i = 0; i < list.contents.length; i++) {
|
||||
if (Cast.compare(list.contents[i], item) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
module.exports = Scratch3DataBlocks;
|
|
@ -16,7 +16,7 @@ var isPromise = function (value) {
|
|||
*/
|
||||
var execute = function (sequencer, thread) {
|
||||
var runtime = sequencer.runtime;
|
||||
var target = runtime.targetForThread(thread);
|
||||
var target = thread.target;
|
||||
|
||||
// Current block to execute is the one on the top of the stack.
|
||||
var currentBlockId = thread.peekStack();
|
||||
|
|
16
src/engine/list.js
Normal file
16
src/engine/list.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* @fileoverview
|
||||
* Object representing a Scratch list.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {!string} name Name of the list.
|
||||
* @param {Array} contents Contents of the list, as an array.
|
||||
* @constructor
|
||||
*/
|
||||
function List (name, contents) {
|
||||
this.name = name;
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
module.exports = List;
|
|
@ -15,7 +15,8 @@ var defaultBlockPackages = {
|
|||
'scratch3_motion': require('../blocks/scratch3_motion'),
|
||||
'scratch3_operators': require('../blocks/scratch3_operators'),
|
||||
'scratch3_sound': require('../blocks/scratch3_sound'),
|
||||
'scratch3_sensing': require('../blocks/scratch3_sensing')
|
||||
'scratch3_sensing': require('../blocks/scratch3_sensing'),
|
||||
'scratch3_data': require('../blocks/scratch3_data')
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -61,6 +62,11 @@ function Runtime () {
|
|||
|
||||
this._scriptGlowsPreviousFrame = [];
|
||||
this._editingTarget = null;
|
||||
/**
|
||||
* Currently known number of clones.
|
||||
* @type {number}
|
||||
*/
|
||||
this._cloneCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,6 +109,11 @@ util.inherits(Runtime, EventEmitter);
|
|||
*/
|
||||
Runtime.THREAD_STEP_INTERVAL = 1000 / 60;
|
||||
|
||||
/**
|
||||
* How many clones can be created at a time.
|
||||
* @const {number}
|
||||
*/
|
||||
Runtime.MAX_CLONES = 300;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -190,16 +201,26 @@ Runtime.prototype.clearEdgeActivatedValues = function () {
|
|||
this._edgeActivatedHatValues = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach the renderer
|
||||
* @param {!RenderWebGL} renderer The renderer to attach
|
||||
*/
|
||||
Runtime.prototype.attachRenderer = function (renderer) {
|
||||
this.renderer = renderer;
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Create a thread and push it to the list of threads.
|
||||
* @param {!string} id ID of block that starts the stack
|
||||
* @param {!string} id ID of block that starts the stack.
|
||||
* @param {!Target} target Target to run thread on.
|
||||
* @return {!Thread} The newly created thread.
|
||||
*/
|
||||
Runtime.prototype._pushThread = function (id) {
|
||||
Runtime.prototype._pushThread = function (id, target) {
|
||||
var thread = new Thread(id);
|
||||
thread.setTarget(target);
|
||||
thread.pushStack(id);
|
||||
this.threads.push(thread);
|
||||
return thread;
|
||||
|
@ -238,7 +259,7 @@ Runtime.prototype.toggleScript = function (topBlockId) {
|
|||
}
|
||||
}
|
||||
// Otherwise add it.
|
||||
this._pushThread(topBlockId);
|
||||
this._pushThread(topBlockId, this._editingTarget);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -307,7 +328,8 @@ Runtime.prototype.startHats = function (requestedHatOpcode,
|
|||
// If `restartExistingThreads` is true, we should stop
|
||||
// any existing threads starting with the top block.
|
||||
for (var i = 0; i < instance.threads.length; i++) {
|
||||
if (instance.threads[i].topBlock === topBlockId) {
|
||||
if (instance.threads[i].topBlock === topBlockId &&
|
||||
(!opt_target || instance.threads[i].target == opt_target)) {
|
||||
instance._removeThread(instance.threads[i]);
|
||||
}
|
||||
}
|
||||
|
@ -315,31 +337,76 @@ Runtime.prototype.startHats = function (requestedHatOpcode,
|
|||
// If `restartExistingThreads` is false, we should
|
||||
// give up if any threads with the top block are running.
|
||||
for (var j = 0; j < instance.threads.length; j++) {
|
||||
if (instance.threads[j].topBlock === topBlockId) {
|
||||
if (instance.threads[j].topBlock === topBlockId &&
|
||||
(!opt_target || instance.threads[j].target == opt_target)) {
|
||||
// Some thread is already running.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Start the thread with this top block.
|
||||
newThreads.push(instance._pushThread(topBlockId));
|
||||
newThreads.push(instance._pushThread(topBlockId, target));
|
||||
}, opt_target);
|
||||
return newThreads;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of a target.
|
||||
* @param {!Target} target Target to dispose of.
|
||||
*/
|
||||
Runtime.prototype.disposeTarget = function (target) {
|
||||
// Allow target to do dispose actions.
|
||||
target.dispose();
|
||||
// Remove from list of targets.
|
||||
var index = this.targets.indexOf(target);
|
||||
if (index > -1) {
|
||||
this.targets.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop any threads acting on the target.
|
||||
* @param {!Target} target Target to stop threads for.
|
||||
*/
|
||||
Runtime.prototype.stopForTarget = function (target) {
|
||||
// Stop any threads on the target.
|
||||
for (var i = 0; i < this.threads.length; i++) {
|
||||
if (this.threads[i].target == target) {
|
||||
this._removeThread(this.threads[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Start all threads that start with the green flag.
|
||||
*/
|
||||
Runtime.prototype.greenFlag = function () {
|
||||
this.stopAll();
|
||||
this.ioDevices.clock.resetProjectTimer();
|
||||
this.clearEdgeActivatedValues();
|
||||
// Inform all targets of the green flag.
|
||||
for (var i = 0; i < this.targets.length; i++) {
|
||||
this.targets[i].onGreenFlag();
|
||||
}
|
||||
this.startHats('event_whenflagclicked');
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop "everything"
|
||||
* Stop "everything."
|
||||
*/
|
||||
Runtime.prototype.stopAll = function () {
|
||||
// Dispose all clones.
|
||||
var newTargets = [];
|
||||
for (var i = 0; i < this.targets.length; i++) {
|
||||
if (this.targets[i].hasOwnProperty('isOriginal') &&
|
||||
!this.targets[i].isOriginal) {
|
||||
this.targets[i].dispose();
|
||||
} else {
|
||||
newTargets.push(this.targets[i]);
|
||||
}
|
||||
}
|
||||
this.targets = newTargets;
|
||||
// Dispose all threads.
|
||||
var threadsCopy = this.threads.slice();
|
||||
while (threadsCopy.length > 0) {
|
||||
var poppedThread = threadsCopy.pop();
|
||||
|
@ -380,7 +447,7 @@ Runtime.prototype._updateScriptGlows = function () {
|
|||
// Find all scripts that should be glowing.
|
||||
for (var i = 0; i < this.threads.length; i++) {
|
||||
var thread = this.threads[i];
|
||||
var target = this.targetForThread(thread);
|
||||
var target = thread.target;
|
||||
if (thread.requestScriptGlowInFrame && target == this._editingTarget) {
|
||||
var blockForThread = thread.peekStack() || thread.topBlock;
|
||||
var script = target.blocks.getTopLevelScript(blockForThread);
|
||||
|
@ -459,23 +526,6 @@ Runtime.prototype.visualReport = function (blockId, value) {
|
|||
this.emit(Runtime.VISUAL_REPORT, blockId, String(value));
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the Target for a particular thread.
|
||||
* @param {!Thread} thread Thread to determine target for.
|
||||
* @return {?Target} Target object, if one exists.
|
||||
*/
|
||||
Runtime.prototype.targetForThread = function (thread) {
|
||||
// @todo This is a messy solution,
|
||||
// but prevents having circular data references.
|
||||
// Have a map or some other way to associate target with threads.
|
||||
for (var t = 0; t < this.targets.length; t++) {
|
||||
var target = this.targets[t];
|
||||
if (target.blocks.getBlock(thread.topBlock)) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a target by its id.
|
||||
* @param {string} targetId Id of target to find.
|
||||
|
@ -490,6 +540,36 @@ Runtime.prototype.getTargetById = function (targetId) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the first original (non-clone-block-created) sprite given a name.
|
||||
* @param {string} spriteName Name of sprite to look for.
|
||||
* @return {?Target} Target representing a sprite of the given name.
|
||||
*/
|
||||
Runtime.prototype.getSpriteTargetByName = function (spriteName) {
|
||||
for (var i = 0; i < this.targets.length; i++) {
|
||||
var target = this.targets[i];
|
||||
if (target.sprite && target.sprite.name == spriteName) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the clone counter to track how many clones are created.
|
||||
* @param {number} changeAmount How many clones have been created/destroyed.
|
||||
*/
|
||||
Runtime.prototype.changeCloneCounter = function (changeAmount) {
|
||||
this._cloneCounter += changeAmount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return whether there are clones available.
|
||||
* @return {boolean} True until the number of clones hits Runtime.MAX_CLONES.
|
||||
*/
|
||||
Runtime.prototype.clonesAvailable = function () {
|
||||
return this._cloneCounter < Runtime.MAX_CLONES;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a target representing the Scratch stage, if one exists.
|
||||
* @return {?Target} The target, if found.
|
||||
|
@ -507,8 +587,8 @@ Runtime.prototype.getTargetForStage = function () {
|
|||
* Handle an animation frame from the main thread.
|
||||
*/
|
||||
Runtime.prototype.animationFrame = function () {
|
||||
if (self.renderer) {
|
||||
self.renderer.draw();
|
||||
if (this.renderer) {
|
||||
this.renderer.draw();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ Sequencer.prototype.stepToBranch = function (thread, branchNum) {
|
|||
branchNum = 1;
|
||||
}
|
||||
var currentBlockId = thread.peekStack();
|
||||
var branchId = this.runtime.targetForThread(thread).blocks.getBranch(
|
||||
var branchId = thread.target.blocks.getBranch(
|
||||
currentBlockId,
|
||||
branchNum
|
||||
);
|
||||
|
@ -155,8 +155,7 @@ Sequencer.prototype.proceedThread = function (thread) {
|
|||
// Pop from the stack - finished this level of execution.
|
||||
thread.popStack();
|
||||
// Push next connected block, if there is one.
|
||||
var nextBlockId = (this.runtime.targetForThread(thread).
|
||||
blocks.getNextBlock(currentBlockId));
|
||||
var nextBlockId = thread.target.blocks.getNextBlock(currentBlockId);
|
||||
if (nextBlockId) {
|
||||
thread.pushStack(nextBlockId);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
var Blocks = require('./blocks');
|
||||
var Variable = require('../engine/variable');
|
||||
var List = require('../engine/list');
|
||||
var uid = require('../util/uid');
|
||||
|
||||
/**
|
||||
|
@ -25,8 +27,26 @@ function Target (blocks) {
|
|||
* @type {!Blocks}
|
||||
*/
|
||||
this.blocks = blocks;
|
||||
/**
|
||||
* Dictionary of variables and their values for this target.
|
||||
* Key is the variable name.
|
||||
* @type {Object.<string,*>}
|
||||
*/
|
||||
this.variables = {};
|
||||
/**
|
||||
* Dictionary of lists and their contents for this target.
|
||||
* Key is the list name.
|
||||
* @type {Object.<string,*>}
|
||||
*/
|
||||
this.lists = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the project receives a "green flag."
|
||||
* @abstract
|
||||
*/
|
||||
Target.prototype.onGreenFlag = function () {};
|
||||
|
||||
/**
|
||||
* Return a human-readable name for this target.
|
||||
* Target implementations should override this.
|
||||
|
@ -37,4 +57,60 @@ Target.prototype.getName = function () {
|
|||
return this.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Look up a variable object, and create it if one doesn't exist.
|
||||
* Search begins for local variables; then look for globals.
|
||||
* @param {!string} name Name of the variable.
|
||||
* @return {!Variable} Variable object.
|
||||
*/
|
||||
Target.prototype.lookupOrCreateVariable = function (name) {
|
||||
// If we have a local copy, return it.
|
||||
if (this.variables.hasOwnProperty(name)) {
|
||||
return this.variables[name];
|
||||
}
|
||||
// If the stage has a global copy, return it.
|
||||
if (this.runtime && !this.isStage) {
|
||||
var stage = this.runtime.getTargetForStage();
|
||||
if (stage.variables.hasOwnProperty(name)) {
|
||||
return stage.variables[name];
|
||||
}
|
||||
}
|
||||
// No variable with this name exists - create it locally.
|
||||
var newVariable = new Variable(name, 0, false);
|
||||
this.variables[name] = newVariable;
|
||||
return newVariable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Look up a list object for this target, and create it if one doesn't exist.
|
||||
* Search begins for local lists; then look for globals.
|
||||
* @param {!string} name Name of the list.
|
||||
* @return {!List} List object.
|
||||
*/
|
||||
Target.prototype.lookupOrCreateList = function (name) {
|
||||
// If we have a local copy, return it.
|
||||
if (this.lists.hasOwnProperty(name)) {
|
||||
return this.lists[name];
|
||||
}
|
||||
// If the stage has a global copy, return it.
|
||||
if (this.runtime && !this.isStage) {
|
||||
var stage = this.runtime.getTargetForStage();
|
||||
if (stage.lists.hasOwnProperty(name)) {
|
||||
return stage.lists[name];
|
||||
}
|
||||
}
|
||||
// No list with this name exists - create it locally.
|
||||
var newList = new List(name, []);
|
||||
this.lists[name] = newList;
|
||||
return newList;
|
||||
};
|
||||
|
||||
/**
|
||||
* Call to destroy a target.
|
||||
* @abstract
|
||||
*/
|
||||
Target.prototype.dispose = function () {
|
||||
|
||||
};
|
||||
|
||||
module.exports = Target;
|
||||
|
|
|
@ -29,6 +29,12 @@ function Thread (firstBlock) {
|
|||
*/
|
||||
this.status = 0; /* Thread.STATUS_RUNNING */
|
||||
|
||||
/**
|
||||
* Target of this thread.
|
||||
* @type {?Target}
|
||||
*/
|
||||
this.target = null;
|
||||
|
||||
/**
|
||||
* Whether the thread requests its script to glow during this frame.
|
||||
* @type {boolean}
|
||||
|
@ -145,4 +151,20 @@ Thread.prototype.setStatus = function (status) {
|
|||
this.status = status;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set thread target.
|
||||
* @param {?Target} target Target for this thread.
|
||||
*/
|
||||
Thread.prototype.setTarget = function (target) {
|
||||
this.target = target;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get thread target.
|
||||
* @return {?Target} Target for this thread, if available.
|
||||
*/
|
||||
Thread.prototype.getTarget = function () {
|
||||
return this.target;
|
||||
};
|
||||
|
||||
module.exports = Thread;
|
||||
|
|
18
src/engine/variable.js
Normal file
18
src/engine/variable.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* @fileoverview
|
||||
* Object representing a Scratch variable.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {!string} name Name of the variable.
|
||||
* @param {(string|Number)} value Value of the variable.
|
||||
* @param {boolean} isCloud Whether the variable is stored in the cloud.
|
||||
* @constructor
|
||||
*/
|
||||
function Variable (name, value, isCloud) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.isCloud = isCloud;
|
||||
}
|
||||
|
||||
module.exports = Variable;
|
|
@ -10,6 +10,8 @@ var Sprite = require('../sprites/sprite');
|
|||
var Color = require('../util/color.js');
|
||||
var uid = require('../util/uid');
|
||||
var specMap = require('./sb2specmap');
|
||||
var Variable = require('../engine/variable');
|
||||
var List = require('../engine/list');
|
||||
|
||||
/**
|
||||
* Top-level handler. Parse provided JSON,
|
||||
|
@ -40,7 +42,7 @@ function parseScratchObject (object, runtime, topLevel) {
|
|||
// Blocks container for this object.
|
||||
var blocks = new Blocks();
|
||||
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
|
||||
var sprite = new Sprite(blocks);
|
||||
var sprite = new Sprite(blocks, runtime);
|
||||
// Sprite/stage name from JSON.
|
||||
if (object.hasOwnProperty('objName')) {
|
||||
sprite.name = object.objName;
|
||||
|
@ -51,7 +53,7 @@ function parseScratchObject (object, runtime, topLevel) {
|
|||
var costume = object.costumes[i];
|
||||
// @todo: Make sure all the relevant metadata is being pulled out.
|
||||
sprite.costumes.push({
|
||||
skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/'
|
||||
skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/'
|
||||
+ costume.baseLayerMD5 + '/get/',
|
||||
name: costume.costumeName,
|
||||
bitmapResolution: costume.bitmapResolution,
|
||||
|
@ -68,30 +70,52 @@ function parseScratchObject (object, runtime, topLevel) {
|
|||
var target = sprite.createClone();
|
||||
// Add it to the runtime's list of targets.
|
||||
runtime.targets.push(target);
|
||||
if (object.scratchX) {
|
||||
// Load target properties from JSON.
|
||||
if (object.hasOwnProperty('variables')) {
|
||||
for (var j = 0; j < object.variables.length; j++) {
|
||||
var variable = object.variables[j];
|
||||
target.variables[variable.name] = new Variable(
|
||||
variable.name,
|
||||
variable.value,
|
||||
variable.isPersistent
|
||||
);
|
||||
}
|
||||
}
|
||||
if (object.hasOwnProperty('lists')) {
|
||||
for (var k = 0; k < object.lists.length; k++) {
|
||||
var list = object.lists[k];
|
||||
// @todo: monitor properties.
|
||||
target.lists[list.listName] = new List(
|
||||
list.listName,
|
||||
list.contents
|
||||
);
|
||||
}
|
||||
}
|
||||
if (object.hasOwnProperty('scratchX')) {
|
||||
target.x = object.scratchX;
|
||||
}
|
||||
if (object.scratchY) {
|
||||
if (object.hasOwnProperty('scratchY')) {
|
||||
target.y = object.scratchY;
|
||||
}
|
||||
if (object.direction) {
|
||||
if (object.hasOwnProperty('direction')) {
|
||||
target.direction = object.direction;
|
||||
}
|
||||
if (object.scale) {
|
||||
if (object.hasOwnProperty('scale')) {
|
||||
// SB2 stores as 1.0 = 100%; we use % in the VM.
|
||||
target.size = object.scale * 100;
|
||||
}
|
||||
if (object.visible) {
|
||||
if (object.hasOwnProperty('visible')) {
|
||||
target.visible = object.visible;
|
||||
}
|
||||
if (object.currentCostumeIndex) {
|
||||
if (object.hasOwnProperty('currentCostumeIndex')) {
|
||||
target.currentCostume = object.currentCostumeIndex;
|
||||
}
|
||||
target.isStage = topLevel;
|
||||
target.updateAllDrawableProperties();
|
||||
// The stage will have child objects; recursively process them.
|
||||
if (object.children) {
|
||||
for (var j = 0; j < object.children.length; j++) {
|
||||
parseScratchObject(object.children[j], runtime, false);
|
||||
for (var m = 0; m < object.children.length; m++) {
|
||||
parseScratchObject(object.children[m], runtime, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,22 @@
|
|||
* the SB2 JSON format and the data we need to run a project
|
||||
* in the Scratch 3.0 VM.
|
||||
* Notably:
|
||||
* - Map 2.0-format opcodes (forward:) into 3.0-format (motion_movesteps).
|
||||
* - Map 2.0 and 1.4 opcodes (forward:) into 3.0-format (motion_movesteps).
|
||||
* - Map ordered, unnamed args to unordered, named inputs and fields.
|
||||
* Keep this up-to-date as 3.0 blocks are renamed, changed, etc.
|
||||
* Originally this was generated largely by a hand-guided scripting process.
|
||||
* The relevant data lives here:
|
||||
* https://github.com/LLK/scratch-flash/blob/master/src/Specs.as
|
||||
* (for the old opcode and argument order).
|
||||
* and here:
|
||||
* https://github.com/LLK/scratch-blocks/tree/develop/blocks_vertical
|
||||
* (for the new opcodes and argument names).
|
||||
* and here:
|
||||
* https://github.com/LLK/scratch-blocks/blob/develop/tests/
|
||||
* (for the shadow blocks created for each block).
|
||||
* I started with the `commands` array in Specs.as, and discarded irrelevant
|
||||
* properties. By hand, I matched the opcode name to the 3.0 opcode.
|
||||
* Finally, I filled in the expected arguments as below.
|
||||
*/
|
||||
var specMap = {
|
||||
'forward:':{
|
||||
|
@ -905,12 +918,12 @@ var specMap = {
|
|||
'argMap':[
|
||||
{
|
||||
'type':'input',
|
||||
'inputOp':'sensing_ofattributemenu',
|
||||
'inputName':'ATTRIBUTE'
|
||||
'inputOp':'sensing_of_property_menu',
|
||||
'inputName':'PROPERTY'
|
||||
},
|
||||
{
|
||||
'type':'input',
|
||||
'inputOp':'sensing_ofobjectmenu',
|
||||
'inputOp':'sensing_of_object_menu',
|
||||
'inputName':'OBJECT'
|
||||
}
|
||||
]
|
||||
|
@ -1230,13 +1243,22 @@ var specMap = {
|
|||
}
|
||||
]
|
||||
},
|
||||
'contentsOfList:':{
|
||||
'opcode':'data_list',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'field',
|
||||
'fieldName':'LIST'
|
||||
}
|
||||
]
|
||||
},
|
||||
'append:toList:':{
|
||||
'opcode':'data_listadd',
|
||||
'opcode':'data_addtolist',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'input',
|
||||
'inputOp':'text',
|
||||
'inputName':'VALUE'
|
||||
'inputName':'ITEM'
|
||||
},
|
||||
{
|
||||
'type':'field',
|
||||
|
@ -1245,12 +1267,12 @@ var specMap = {
|
|||
]
|
||||
},
|
||||
'deleteLine:ofList:':{
|
||||
'opcode':'data_listdelete',
|
||||
'opcode':'data_deleteoflist',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'input',
|
||||
'inputOp':'text',
|
||||
'inputName':'LINE'
|
||||
'inputOp':'math_integer',
|
||||
'inputName':'INDEX'
|
||||
},
|
||||
{
|
||||
'type':'field',
|
||||
|
@ -1259,17 +1281,17 @@ var specMap = {
|
|||
]
|
||||
},
|
||||
'insert:at:ofList:':{
|
||||
'opcode':'data_listinsert',
|
||||
'opcode':'data_insertatlist',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'input',
|
||||
'inputOp':'text',
|
||||
'inputName':'VALUE'
|
||||
'inputName':'ITEM'
|
||||
},
|
||||
{
|
||||
'type':'input',
|
||||
'inputOp':'text',
|
||||
'inputName':'LINE'
|
||||
'inputOp':'math_integer',
|
||||
'inputName':'INDEX'
|
||||
},
|
||||
{
|
||||
'type':'field',
|
||||
|
@ -1278,12 +1300,12 @@ var specMap = {
|
|||
]
|
||||
},
|
||||
'setLine:ofList:to:':{
|
||||
'opcode':'data_listreplace',
|
||||
'opcode':'data_replaceitemoflist',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'input',
|
||||
'inputOp':'text',
|
||||
'inputName':'LINE'
|
||||
'inputOp':'math_integer',
|
||||
'inputName':'INDEX'
|
||||
},
|
||||
{
|
||||
'type':'field',
|
||||
|
@ -1292,17 +1314,17 @@ var specMap = {
|
|||
{
|
||||
'type':'input',
|
||||
'inputOp':'text',
|
||||
'inputName':'VALUE'
|
||||
'inputName':'ITEM'
|
||||
}
|
||||
]
|
||||
},
|
||||
'getLine:ofList:':{
|
||||
'opcode':'data_listitem',
|
||||
'opcode':'data_itemoflist',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'input',
|
||||
'inputOp':'text',
|
||||
'inputName':'LINE'
|
||||
'inputOp':'math_integer',
|
||||
'inputName':'INDEX'
|
||||
},
|
||||
{
|
||||
'type':'field',
|
||||
|
@ -1311,7 +1333,7 @@ var specMap = {
|
|||
]
|
||||
},
|
||||
'lineCountOfList:':{
|
||||
'opcode':'data_listlength',
|
||||
'opcode':'data_lengthoflist',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'field',
|
||||
|
@ -1320,7 +1342,7 @@ var specMap = {
|
|||
]
|
||||
},
|
||||
'list:contains:':{
|
||||
'opcode':'data_listcontains',
|
||||
'opcode':'data_listcontainsitem',
|
||||
'argMap':[
|
||||
{
|
||||
'type':'field',
|
||||
|
@ -1329,7 +1351,7 @@ var specMap = {
|
|||
{
|
||||
'type':'input',
|
||||
'inputOp':'text',
|
||||
'inputName':'VALUE'
|
||||
'inputName':'ITEM'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
30
src/index.js
30
src/index.js
|
@ -76,9 +76,19 @@ VirtualMachine.prototype.stopAll = function () {
|
|||
* Get data for playground. Data comes back in an emitted event.
|
||||
*/
|
||||
VirtualMachine.prototype.getPlaygroundData = function () {
|
||||
var instance = this;
|
||||
// Only send back thread data for the current editingTarget.
|
||||
var threadData = this.runtime.threads.filter(function(thread) {
|
||||
return thread.target == instance.editingTarget;
|
||||
});
|
||||
// Remove the target key, since it's a circular reference.
|
||||
var filteredThreadData = JSON.stringify(threadData, function(key, value) {
|
||||
if (key == 'target') return undefined;
|
||||
return value;
|
||||
}, 2);
|
||||
this.emit('playgroundData', {
|
||||
blocks: this.editingTarget.blocks,
|
||||
threads: this.runtime.threads
|
||||
threads: filteredThreadData
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -162,6 +172,14 @@ VirtualMachine.prototype.createEmptyProject = function () {
|
|||
this.emitWorkspaceUpdate();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the renderer for the VM/runtime
|
||||
* @param {!RenderWebGL} renderer The renderer to attach
|
||||
*/
|
||||
VirtualMachine.prototype.attachRenderer = function (renderer) {
|
||||
this.runtime.attachRenderer(renderer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a Blockly event for the current editing target.
|
||||
* @param {!Blockly.Event} e Any Blockly event.
|
||||
|
@ -207,7 +225,10 @@ VirtualMachine.prototype.setEditingTarget = function (targetId) {
|
|||
VirtualMachine.prototype.emitTargetsUpdate = function () {
|
||||
this.emit('targetsUpdate', {
|
||||
// [[target id, human readable target name], ...].
|
||||
targetList: this.runtime.targets.map(function(target) {
|
||||
targetList: this.runtime.targets.filter(function (target) {
|
||||
// Don't report clones.
|
||||
return !target.hasOwnProperty('isOriginal') || target.isOriginal;
|
||||
}).map(function(target) {
|
||||
return [target.id, target.getName()];
|
||||
}),
|
||||
// Currently editing target id.
|
||||
|
@ -224,8 +245,5 @@ VirtualMachine.prototype.emitWorkspaceUpdate = function () {
|
|||
'xml': this.editingTarget.blocks.toXML()
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Export and bind to `window`
|
||||
*/
|
||||
|
||||
module.exports = VirtualMachine;
|
||||
if (typeof window !== 'undefined') window.VirtualMachine = module.exports;
|
||||
|
|
|
@ -28,20 +28,17 @@ Mouse.prototype.postData = function(data) {
|
|||
};
|
||||
|
||||
Mouse.prototype._activateClickHats = function (x, y) {
|
||||
if (self.renderer) {
|
||||
var pickPromise = self.renderer.pick(x, y);
|
||||
var instance = this;
|
||||
pickPromise.then(function(drawableID) {
|
||||
for (var i = 0; i < instance.runtime.targets.length; i++) {
|
||||
var target = instance.runtime.targets[i];
|
||||
if (target.hasOwnProperty('drawableID') &&
|
||||
target.drawableID == drawableID) {
|
||||
instance.runtime.startHats('event_whenthisspriteclicked',
|
||||
null, target);
|
||||
return;
|
||||
}
|
||||
if (this.runtime.renderer) {
|
||||
var drawableID = this.runtime.renderer.pick(x, y);
|
||||
for (var i = 0; i < this.runtime.targets.length; i++) {
|
||||
var target = this.runtime.targets[i];
|
||||
if (target.hasOwnProperty('drawableID') &&
|
||||
target.drawableID == drawableID) {
|
||||
this.runtime.startHats('event_whenthisspriteclicked',
|
||||
null, target);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -5,10 +5,12 @@ var Target = require('../engine/target');
|
|||
/**
|
||||
* Clone (instance) of a sprite.
|
||||
* @param {!Sprite} sprite Reference to the sprite.
|
||||
* @param {Runtime} runtime Reference to the runtime.
|
||||
* @constructor
|
||||
*/
|
||||
function Clone(sprite) {
|
||||
function Clone(sprite, runtime) {
|
||||
Target.call(this, sprite.blocks);
|
||||
this.runtime = runtime;
|
||||
/**
|
||||
* Reference to the sprite that this is a clone of.
|
||||
* @type {!Sprite}
|
||||
|
@ -19,19 +21,14 @@ function Clone(sprite) {
|
|||
* @type {?RenderWebGLWorker}
|
||||
*/
|
||||
this.renderer = null;
|
||||
// If this is not true, there is no renderer (e.g., running in a test env).
|
||||
if (typeof self !== 'undefined' && self.renderer) {
|
||||
// Pull from `self.renderer`.
|
||||
this.renderer = self.renderer;
|
||||
if (this.runtime) {
|
||||
this.renderer = this.runtime.renderer;
|
||||
}
|
||||
/**
|
||||
* ID of the drawable for this clone returned by the renderer, if rendered.
|
||||
* @type {?Number}
|
||||
*/
|
||||
this.drawableID = null;
|
||||
|
||||
this.initDrawable();
|
||||
|
||||
}
|
||||
util.inherits(Clone, Target);
|
||||
|
||||
|
@ -40,17 +37,25 @@ util.inherits(Clone, Target);
|
|||
*/
|
||||
Clone.prototype.initDrawable = function () {
|
||||
if (this.renderer) {
|
||||
var createPromise = this.renderer.createDrawable();
|
||||
var instance = this;
|
||||
createPromise.then(function (id) {
|
||||
instance.drawableID = id;
|
||||
// Once the drawable is created, send our current set of properties.
|
||||
instance.updateAllDrawableProperties();
|
||||
});
|
||||
this.drawableID = this.renderer.createDrawable();
|
||||
this.updateAllDrawableProperties();
|
||||
}
|
||||
// If we're a clone, start the hats.
|
||||
if (!this.isOriginal) {
|
||||
this.runtime.startHats(
|
||||
'control_start_as_clone', null, this
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Clone-level properties.
|
||||
/**
|
||||
* Whether this represents an "original" clone, i.e., created by the editor
|
||||
* and not clone blocks. In interface terms, this true for a "sprite."
|
||||
* @type {boolean}
|
||||
*/
|
||||
Clone.prototype.isOriginal = true;
|
||||
|
||||
/**
|
||||
* Whether this clone represents the Scratch stage.
|
||||
* @type {boolean}
|
||||
|
@ -304,4 +309,50 @@ Clone.prototype.colorIsTouchingColor = function (targetRgb, maskRgb) {
|
|||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a clone of this clone, copying any run-time properties.
|
||||
* If we've hit the global clone limit, returns null.
|
||||
* @return {!Clone} New clone object.
|
||||
*/
|
||||
Clone.prototype.makeClone = function () {
|
||||
if (!this.runtime.clonesAvailable()) {
|
||||
return; // Hit max clone limit.
|
||||
}
|
||||
this.runtime.changeCloneCounter(1);
|
||||
var newClone = this.sprite.createClone();
|
||||
newClone.x = this.x;
|
||||
newClone.y = this.y;
|
||||
newClone.direction = this.direction;
|
||||
newClone.visible = this.visible;
|
||||
newClone.size = this.size;
|
||||
newClone.currentCostume = this.currentCostume;
|
||||
newClone.effects = JSON.parse(JSON.stringify(this.effects));
|
||||
newClone.variables = JSON.parse(JSON.stringify(this.variables));
|
||||
newClone.lists = JSON.parse(JSON.stringify(this.lists));
|
||||
newClone.initDrawable();
|
||||
newClone.updateAllDrawableProperties();
|
||||
return newClone;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the project receives a "green flag."
|
||||
* For a clone, this clears graphic effects.
|
||||
*/
|
||||
Clone.prototype.onGreenFlag = function () {
|
||||
this.clearEffects();
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this clone, destroying any run-time properties.
|
||||
*/
|
||||
Clone.prototype.dispose = function () {
|
||||
if (this.isOriginal) { // Don't allow a non-clone to delete itself.
|
||||
return;
|
||||
}
|
||||
this.runtime.changeCloneCounter(-1);
|
||||
if (this.renderer && this.drawableID !== null) {
|
||||
this.renderer.destroyDrawable(this.drawableID);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Clone;
|
||||
|
|
|
@ -5,9 +5,11 @@ var Blocks = require('../engine/blocks');
|
|||
* Sprite to be used on the Scratch stage.
|
||||
* All clones of a sprite have shared blocks, shared costumes, shared variables.
|
||||
* @param {?Blocks} blocks Shared blocks object for all clones of sprite.
|
||||
* @param {Runtime} runtime Reference to the runtime.
|
||||
* @constructor
|
||||
*/
|
||||
function Sprite (blocks) {
|
||||
function Sprite (blocks, runtime) {
|
||||
this.runtime = runtime;
|
||||
if (!blocks) {
|
||||
// Shared set of blocks for all clones.
|
||||
blocks = new Blocks();
|
||||
|
@ -43,8 +45,13 @@ function Sprite (blocks) {
|
|||
* @returns {!Clone} Newly created clone.
|
||||
*/
|
||||
Sprite.prototype.createClone = function () {
|
||||
var newClone = new Clone(this);
|
||||
var newClone = new Clone(this, this.runtime);
|
||||
newClone.isOriginal = this.clones.length == 0;
|
||||
this.clones.push(newClone);
|
||||
if (newClone.isOriginal) {
|
||||
newClone.initDrawable();
|
||||
newClone.updateAllDrawableProperties();
|
||||
}
|
||||
return newClone;
|
||||
};
|
||||
|
||||
|
|
|
@ -125,4 +125,39 @@ Cast.isInt = function (val) {
|
|||
return false;
|
||||
};
|
||||
|
||||
Cast.LIST_INVALID = 'INVALID';
|
||||
Cast.LIST_ALL = 'ALL';
|
||||
/**
|
||||
* Compute a 1-based index into a list, based on a Scratch argument.
|
||||
* Two special cases may be returned:
|
||||
* LIST_ALL: if the block is referring to all of the items in the list.
|
||||
* LIST_INVALID: if the index was invalid in any way.
|
||||
* @param {*} index Scratch arg, including 1-based numbers or special cases.
|
||||
* @param {number} length Length of the list.
|
||||
* @return {(number|string)} 1-based index for list, LIST_ALL, or LIST_INVALID.
|
||||
*/
|
||||
Cast.toListIndex = function (index, length) {
|
||||
if (typeof index !== 'number') {
|
||||
if (index == 'all') {
|
||||
return Cast.LIST_ALL;
|
||||
}
|
||||
if (index == 'last') {
|
||||
if (length > 0) {
|
||||
return length;
|
||||
}
|
||||
return Cast.LIST_INVALID;
|
||||
} else if (index == 'random' || index == 'any') {
|
||||
if (length > 0) {
|
||||
return 1 + Math.floor(Math.random() * length);
|
||||
}
|
||||
return Cast.LIST_INVALID;
|
||||
}
|
||||
}
|
||||
index = Math.floor(Cast.toNumber(index));
|
||||
if (index < 1 || index > length) {
|
||||
return Cast.LIST_INVALID;
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
module.exports = Cast;
|
||||
|
|
11
vm.min.js
vendored
11
vm.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,13 +1,12 @@
|
|||
var CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
var defaultsDeep = require('lodash.defaultsdeep');
|
||||
var path = require('path');
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
'vm': './src/index.js',
|
||||
'vm.min': './src/index.js'
|
||||
},
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename: '[name].js'
|
||||
var base = {
|
||||
devServer: {
|
||||
contentBase: path.resolve(__dirname, 'playground'),
|
||||
host: '0.0.0.0'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
|
@ -27,3 +26,89 @@ module.exports = {
|
|||
})
|
||||
]
|
||||
};
|
||||
|
||||
module.exports = [
|
||||
// Web-compatible, playground
|
||||
defaultsDeep({}, base, {
|
||||
entry: {
|
||||
'vm': './src/index.js',
|
||||
'vm.min': './src/index.js'
|
||||
},
|
||||
output: {
|
||||
path: __dirname,
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
loaders: base.module.loaders.concat([
|
||||
{
|
||||
test: require.resolve('./src/index.js'),
|
||||
loader: 'expose?VirtualMachine'
|
||||
}
|
||||
])
|
||||
}
|
||||
}),
|
||||
// Webpack-compatible
|
||||
defaultsDeep({}, base, {
|
||||
entry: {
|
||||
'dist': './src/index.js'
|
||||
},
|
||||
output: {
|
||||
library: 'VirtualMachine',
|
||||
libraryTarget: 'commonjs2',
|
||||
path: __dirname,
|
||||
filename: '[name].js'
|
||||
}
|
||||
}),
|
||||
// Playground
|
||||
defaultsDeep({}, base, {
|
||||
entry: {
|
||||
'vm': './src/index.js',
|
||||
'vendor': [
|
||||
// FPS counter
|
||||
'stats.js/build/stats.min.js',
|
||||
// Syntax highlighter
|
||||
'highlightjs/highlight.pack.min.js',
|
||||
// Scratch Blocks
|
||||
'scratch-blocks/dist/vertical.js',
|
||||
// Renderer
|
||||
'scratch-render'
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'playground'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
loaders: base.module.loaders.concat([
|
||||
{
|
||||
test: require.resolve('./src/index.js'),
|
||||
loader: 'expose?VirtualMachine'
|
||||
},
|
||||
{
|
||||
test: require.resolve('stats.js/build/stats.min.js'),
|
||||
loader: 'script'
|
||||
},
|
||||
{
|
||||
test: require.resolve('highlightjs/highlight.pack.min.js'),
|
||||
loader: 'script'
|
||||
},
|
||||
{
|
||||
test: require.resolve('scratch-blocks/dist/vertical.js'),
|
||||
loader: 'expose?Blockly'
|
||||
},
|
||||
{
|
||||
test: require.resolve('scratch-render'),
|
||||
loader: 'expose?RenderWebGL'
|
||||
}
|
||||
])
|
||||
},
|
||||
plugins: base.plugins.concat([
|
||||
new CopyWebpackPlugin([{
|
||||
from: 'node_modules/scratch-blocks/media',
|
||||
to: 'media'
|
||||
}, {
|
||||
from: 'node_modules/highlightjs/styles/zenburn.css'
|
||||
}])
|
||||
])
|
||||
})
|
||||
];
|
||||
|
|
Loading…
Reference in a new issue