diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 2e17500c..a7b10d9b 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -188,6 +188,13 @@ class Runtime extends EventEmitter { */ this._nonMonitorThreadCount = 0; + /** + * All threads that finished running and were removed from this.threads + * by behaviour in Sequencer.stepThreads. + * @type {Array} + */ + this._lastStepDoneThreads = null; + /** * Currently known number of clones, used to enforce clone limit. * @type {number} @@ -1560,6 +1567,9 @@ class Runtime extends EventEmitter { this._emitProjectRunStatus( this.threads.length + doneThreads.length - this._getMonitorThreadCount([...this.threads, ...doneThreads])); + // Store threads that completed this iteration for testing and other + // internal purposes. + this._lastStepDoneThreads = doneThreads; if (this.renderer) { // @todo: Only render when this.redrawRequested or clones rendered. if (this.profiler !== null) { diff --git a/test/integration/hat-threads-run-every-frame.js b/test/integration/hat-threads-run-every-frame.js index 8a80ef76..5d3c3527 100644 --- a/test/integration/hat-threads-run-every-frame.js +++ b/test/integration/hat-threads-run-every-frame.js @@ -45,15 +45,19 @@ test('edge activated hat thread runs once every frame', t => { t.equal(vm.runtime.threads.length, 0); vm.runtime._step(); - t.equal(vm.runtime.threads.length, 1); - checkIsHatThread(t, vm, vm.runtime.threads[0]); - t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); + let threads = vm.runtime._lastStepDoneThreads; + t.equal(vm.runtime.threads.length, 0); + t.equal(threads.length, 1); + checkIsHatThread(t, vm, threads[0]); + t.assert(threads[0].status === Thread.STATUS_DONE); // Check that the hat thread is added again when another step is taken vm.runtime._step(); - t.equal(vm.runtime.threads.length, 1); - checkIsHatThread(t, vm, vm.runtime.threads[0]); - t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); + threads = vm.runtime._lastStepDoneThreads; + t.equal(vm.runtime.threads.length, 0); + t.equal(threads.length, 1); + checkIsHatThread(t, vm, threads[0]); + t.assert(threads[0].status === Thread.STATUS_DONE); t.end(); }); }); @@ -79,15 +83,19 @@ test('edge activated hat thread not added twice', t => { t.equal(vm.runtime.threads.length, 0); vm.runtime._step(); + let doneThreads = vm.runtime._lastStepDoneThreads; t.equal(vm.runtime.threads.length, 1); + t.equal(doneThreads.length, 0); const prevThread = vm.runtime.threads[0]; checkIsHatThread(t, vm, vm.runtime.threads[0]); t.assert(vm.runtime.threads[0].status === Thread.STATUS_RUNNING); // Check that no new threads are added when another step is taken vm.runtime._step(); + doneThreads = vm.runtime._lastStepDoneThreads; // There should now be one done hat thread and one new hat thread to run t.equal(vm.runtime.threads.length, 1); + t.equal(doneThreads.length, 0); checkIsHatThread(t, vm, vm.runtime.threads[0]); t.assert(vm.runtime.threads[0] === prevThread); t.end(); @@ -115,29 +123,33 @@ test('edge activated hat thread does not interrupt stack click thread', t => { t.equal(vm.runtime.threads.length, 0); vm.runtime._step(); - t.equal(vm.runtime.threads.length, 1); - checkIsHatThread(t, vm, vm.runtime.threads[0]); - t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); + let doneThreads = vm.runtime._lastStepDoneThreads; + t.equal(vm.runtime.threads.length, 0); + t.equal(doneThreads.length, 1); + checkIsHatThread(t, vm, doneThreads[0]); + t.assert(doneThreads[0].status === Thread.STATUS_DONE); // Add stack click thread on this hat - vm.runtime.toggleScript(vm.runtime.threads[0].topBlock, {stackClick: true}); + vm.runtime.toggleScript(doneThreads[0].topBlock, {stackClick: true}); // Check that the hat thread is added again when another step is taken vm.runtime._step(); - t.equal(vm.runtime.threads.length, 2); + doneThreads = vm.runtime._lastStepDoneThreads; + t.equal(vm.runtime.threads.length, 0); + t.equal(doneThreads.length, 2); let hatThread; let stackClickThread; - if (vm.runtime.threads[0].stackClick) { - stackClickThread = vm.runtime.threads[0]; - hatThread = vm.runtime.threads[1]; + if (doneThreads[0].stackClick) { + stackClickThread = doneThreads[0]; + hatThread = doneThreads[1]; } else { - stackClickThread = vm.runtime.threads[1]; - hatThread = vm.runtime.threads[0]; + stackClickThread = doneThreads[1]; + hatThread = doneThreads[0]; } checkIsHatThread(t, vm, hatThread); checkIsStackClickThread(t, vm, stackClickThread); - t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); - t.assert(vm.runtime.threads[1].status === Thread.STATUS_DONE); + t.assert(doneThreads[0].status === Thread.STATUS_DONE); + t.assert(doneThreads[1].status === Thread.STATUS_DONE); t.end(); }); }); @@ -163,7 +175,9 @@ test('edge activated hat thread does not interrupt stack click thread', t => { t.equal(vm.runtime.threads.length, 0); vm.runtime._step(); + let doneThreads = vm.runtime._lastStepDoneThreads; t.equal(vm.runtime.threads.length, 1); + t.equal(doneThreads.length, 0); checkIsHatThread(t, vm, vm.runtime.threads[0]); t.assert(vm.runtime.threads[0].status === Thread.STATUS_RUNNING); @@ -174,20 +188,22 @@ test('edge activated hat thread does not interrupt stack click thread', t => { // Check that the hat thread is added again when another step is taken vm.runtime._step(); - t.equal(vm.runtime.threads.length, 2); + doneThreads = vm.runtime._lastStepDoneThreads; + t.equal(vm.runtime.threads.length, 0); + t.equal(doneThreads.length, 2); let hatThread; let stackClickThread; - if (vm.runtime.threads[0].stackClick) { - stackClickThread = vm.runtime.threads[0]; - hatThread = vm.runtime.threads[1]; + if (doneThreads[0].stackClick) { + stackClickThread = doneThreads[0]; + hatThread = doneThreads[1]; } else { - stackClickThread = vm.runtime.threads[1]; - hatThread = vm.runtime.threads[0]; + stackClickThread = doneThreads[1]; + hatThread = doneThreads[0]; } checkIsHatThread(t, vm, hatThread); checkIsStackClickThread(t, vm, stackClickThread); - t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); - t.assert(vm.runtime.threads[1].status === Thread.STATUS_DONE); + t.assert(doneThreads[0].status === Thread.STATUS_DONE); + t.assert(doneThreads[1].status === Thread.STATUS_DONE); t.end(); }); }); diff --git a/test/integration/monitor-threads-run-every-frame.js b/test/integration/monitor-threads-run-every-frame.js index cebd7314..bcfe5daa 100644 --- a/test/integration/monitor-threads-run-every-frame.js +++ b/test/integration/monitor-threads-run-every-frame.js @@ -55,13 +55,19 @@ test('monitor thread runs every frame', t => { t.equal(vm.runtime.threads.length, 0); vm.runtime._step(); - checkMonitorThreadPresent(t, vm.runtime.threads); - t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); + let doneThreads = vm.runtime._lastStepDoneThreads; + t.equal(vm.runtime.threads.length, 0); + t.equal(doneThreads.length, 1); + checkMonitorThreadPresent(t, doneThreads); + t.assert(doneThreads[0].status === Thread.STATUS_DONE); // Check that both are added again when another step is taken vm.runtime._step(); - checkMonitorThreadPresent(t, vm.runtime.threads); - t.assert(vm.runtime.threads[0].status === Thread.STATUS_DONE); + doneThreads = vm.runtime._lastStepDoneThreads; + t.equal(vm.runtime.threads.length, 0); + t.equal(doneThreads.length, 1); + checkMonitorThreadPresent(t, doneThreads); + t.assert(doneThreads[0].status === Thread.STATUS_DONE); t.end(); }); }); @@ -103,12 +109,18 @@ test('monitor thread not added twice', t => { t.equal(vm.runtime.threads.length, 0); vm.runtime._step(); + let doneThreads = vm.runtime._lastStepDoneThreads; + t.equal(vm.runtime.threads.length, 1); + t.equal(doneThreads.length, 0); checkMonitorThreadPresent(t, vm.runtime.threads); t.assert(vm.runtime.threads[0].status === Thread.STATUS_RUNNING); const prevThread = vm.runtime.threads[0]; // Check that both are added again when another step is taken vm.runtime._step(); + doneThreads = vm.runtime._lastStepDoneThreads; + t.equal(vm.runtime.threads.length, 1); + t.equal(doneThreads.length, 0); checkMonitorThreadPresent(t, vm.runtime.threads); t.equal(vm.runtime.threads[0], prevThread); t.end(); diff --git a/test/integration/monitors.js b/test/integration/monitors.js index 52bc5b0f..38e89b43 100644 --- a/test/integration/monitors.js +++ b/test/integration/monitors.js @@ -14,8 +14,10 @@ test('importing sb2 project with monitors', t => { // Evaluate playground data and exit vm.on('playgroundData', e => { const threads = JSON.parse(e.threads); - // All monitors should leave threads running - t.equal(threads.length, 5); + // All monitors should create threads that finish during the step and + // are revoved from runtime.threads. + t.equal(threads.length, 0); + t.equal(vm.runtime._lastStepDoneThreads.length, 5); // There should be one additional hidden monitor that is in the monitorState but // does not start a thread. t.equal(vm.runtime._monitorState.size, 6); diff --git a/test/unit/engine_sequencer.js b/test/unit/engine_sequencer.js index 55faeb69..622dc64b 100644 --- a/test/unit/engine_sequencer.js +++ b/test/unit/engine_sequencer.js @@ -181,8 +181,7 @@ test('stepThreads', t => { t.strictEquals(s.stepThreads().length, 0); generateThread(r); t.strictEquals(r.threads.length, 1); - t.strictEquals(s.stepThreads().length, 0); - r.threads[0].status = Thread.STATUS_RUNNING; + // Threads should be marked DONE and removed in the same step they finish. t.strictEquals(s.stepThreads().length, 1); t.end();