* Provide property to Clone to distinguish "original" clones

* Provide method to clone a clone's properties

* Don't report clones in the UI target list

* Add target info to Thread

* Allow hats to skip clones (for green flag)

* Green flag skips clones

* Implement "create clone" and hat

* Pass the runtime to sprites and clones (for start hats)

* Clone disposal; trigger hats after drawable initializes.

* Separate stop threads for target; fix handling of stop button

* Remove extraneous `skipClones` property

* Add global clone limit

* Don't allow a non-clone to delete itself.

* Rename `cloneClone` -> `makeClone`

* Variable updates in runtime.js

* Synchronous drawable initialization (until we put it back to promises)
This commit is contained in:
Tim Mickel 2016-09-15 19:37:12 -04:00 committed by GitHub
parent 542899949e
commit 9744bcbb70
10 changed files with 229 additions and 37 deletions
src/engine

View file

@ -60,6 +60,11 @@ function Runtime () {
this._scriptGlowsPreviousFrame = [];
this._editingTarget = null;
/**
* Currently known number of clones.
* @type {number}
*/
this._cloneCounter = 0;
}
/**
@ -102,6 +107,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;
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
@ -194,11 +204,13 @@ Runtime.prototype.clearEdgeActivatedValues = function () {
/**
* 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;
@ -237,7 +249,7 @@ Runtime.prototype.toggleScript = function (topBlockId) {
}
}
// Otherwise add it.
this._pushThread(topBlockId);
this._pushThread(topBlockId, this._editingTarget);
};
/**
@ -306,7 +318,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]);
}
}
@ -314,31 +327,72 @@ 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();
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();
@ -379,7 +433,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);
@ -458,23 +512,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.
@ -489,6 +526,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.