diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 791de1f09..44c1a7f19 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -3,7 +3,7 @@ var Sequencer = require('./sequencer'); var util = require('util'); /** - * Manages blocks, stacks, threads, and the sequencer. + * Manages blocks, stacks, and the sequencer. */ function Runtime () { // Bind event emitter @@ -14,8 +14,6 @@ function Runtime () { this.blocks = {}; /** @type {Array.} */ this.stacks = []; - /** @type {Array.} */ - this.threads = []; /** @type {!Sequencer} */ this.sequencer = new Sequencer(); diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 919f105fa..4e783c7a6 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -1,5 +1,59 @@ +var Timer = require('../util/Timer'); + function Sequencer () { - // @todo + /** + * A list of threads that are currently running in the VM. + * Threads are added when execution starts and pruned when execution ends. + * @type {Array.} + */ + this.threads = []; + + /** + * A utility timer for timing thread sequencing. + * @type {!Timer} + */ + this.timer = new Timer(); } +/** + * The sequencer does as much work as it can within WORK_TIME milliseconds, + * then yields. This is essentially a rate-limiter for blocks. + * In Scratch 2.0, this is set to 75% of the target stage frame-rate (30fps). + * @const {!number} + */ +Sequencer.WORK_TIME = 1000 / 60; + +/** + * Step through all threads in `this.threads`, running them in order. + */ +Sequencer.prototype.stepThreads = function () { + // Start counting toward WORK_TIME + this.timer.start(); + // While there are still threads to run and we are within WORK_TIME, + // continue executing threads. + while (this.threads.length > 0 && + this.timer.timeElapsed() < Sequencer.WORK_TIME) { + // New threads at the end of the iteration. + var newThreads = []; + // Attempt to run each thread one time + for (var i = 0; i < this.threads.length; i++) { + var activeThread = this.threads[i]; + this.stepThread(activeThread); + if (activeThread.nextBlock !== null) { + newThreads.push(activeThread); + } + } + // Effectively filters out threads that have stopped. + this.threads = newThreads; + } +}; + +/** + * Step the requested thread + * @param {!Thread} thread Thread object to step + */ +Sequencer.protoype.stepThread = function (thread) { + // @todo +}; + module.exports = Sequencer; diff --git a/src/engine/thread.js b/src/engine/thread.js index 42dd09587..33b48433a 100644 --- a/src/engine/thread.js +++ b/src/engine/thread.js @@ -1,5 +1,15 @@ function Thread () { - // @todo + /** + * Next block that the thread will execute. + * @type {string} + */ + this.nextBlock = null; + /** + * Stack for the thread. When the sequencer enters a control structure, + * the block is pushed onto the stack so we know where to exit. + * @type {Array.} + */ + this.stack = []; } module.exports = Thread; diff --git a/src/util/timer.js b/src/util/timer.js index b16585205..10a5b3241 100644 --- a/src/util/timer.js +++ b/src/util/timer.js @@ -13,8 +13,8 @@ Timer.prototype.start = function () { this.startTime = this.time(); }; -Timer.prototype.stop = function () { - return this.startTime - this.time(); +Timer.prototype.timeElapsed = function () { + return this.time() - this.startTime; }; module.exports = Timer;