diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 39be44db8..2f34cd8d0 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -804,6 +804,7 @@ class Runtime extends EventEmitter { * This is used by `startHats` to and is necessary to ensure 2.0-like execution order. * Test project: https://scratch.mit.edu/projects/130183108/ * @param {!Thread} thread Thread object to restart. + * @return {Thread} The restarted thread. */ _restartThread (thread) { const newThread = new Thread(thread.topBlock); @@ -814,9 +815,10 @@ class Runtime extends EventEmitter { const i = this.threads.indexOf(thread); if (i > -1) { this.threads[i] = newThread; - } else { - this.threads.push(thread); + return newThread; } + this.threads.push(thread); + return thread; } /** @@ -825,7 +827,11 @@ class Runtime extends EventEmitter { * @return {boolean} True if the thread is active/running. */ isActiveThread (thread) { - return this.threads.indexOf(thread) > -1; + return ( + ( + thread.stack.length > 0 && + thread.status !== Thread.STATUS_DONE) && + this.threads.indexOf(thread) > -1); } /** @@ -972,7 +978,7 @@ class Runtime extends EventEmitter { if (instance.threads[i].topBlock === topBlockId && !instance.threads[i].stackClick && // stack click threads and hat threads can coexist instance.threads[i].target === target) { - instance._restartThread(instance.threads[i]); + newThreads.push(instance._restartThread(instance.threads[i])); return; } } diff --git a/test/unit/blocks_event.js b/test/unit/blocks_event.js new file mode 100644 index 000000000..2e62125b6 --- /dev/null +++ b/test/unit/blocks_event.js @@ -0,0 +1,94 @@ +const test = require('tap').test; + +const Blocks = require('../../src/engine/blocks'); +const BlockUtility = require('../../src/engine/block-utility'); +const Event = require('../../src/blocks/scratch3_event'); +const Runtime = require('../../src/engine/runtime'); +const Target = require('../../src/engine/target'); +const Thread = require('../../src/engine/thread'); + +test('#760 - broadcastAndWait', t => { + const broadcastAndWaitBlock = { + id: 'broadcastAndWaitBlock', + fields: { + BROADCAST_OPTION: { + id: 'BROADCAST_OPTION', + value: 'message' + } + }, + inputs: Object, + block: 'fakeBlock', + opcode: 'event_broadcastandwait', + next: null, + parent: null, + shadow: false, + topLevel: true, + x: 0, + y: 0 + }; + const receiveMessageBlock = { + id: 'receiveMessageBlock', + fields: { + BROADCAST_OPTION: { + id: 'BROADCAST_OPTION', + value: 'message' + } + }, + inputs: Object, + block: 'fakeBlock', + opcode: 'event_whenbroadcastreceived', + next: null, + parent: null, + shadow: false, + topLevel: true, + x: 0, + y: 0 + }; + + const rt = new Runtime(); + const e = new Event(rt); + const b = new Blocks(); + b.createBlock(broadcastAndWaitBlock); + b.createBlock(receiveMessageBlock); + const tgt = new Target(rt, b); + rt.targets.push(tgt); + + let th = rt._pushThread('broadcastAndWaitBlock', t); + const util = new BlockUtility(); + util.sequencer = rt.sequencer; + util.thread = th; + + // creates threads + e.broadcastAndWait({BROADCAST_OPTION: 'message'}, util); + t.strictEqual(rt.threads.length, 2); + t.strictEqual(rt.threads[1].topBlock, 'receiveMessageBlock'); + // yields when some thread is active + t.strictEqual(th.status, Thread.STATUS_YIELD); + th.status = Thread.STATUS_RUNNING; + e.broadcastAndWait({BROADCAST_OPTION: 'message'}, util); + t.strictEqual(th.status, Thread.STATUS_YIELD); + // does not yield once all threads are done + th.status = Thread.STATUS_RUNNING; + rt.threads[1].status = Thread.STATUS_DONE; + e.broadcastAndWait({BROADCAST_OPTION: 'message'}, util); + t.strictEqual(th.status, Thread.STATUS_RUNNING); + + // restarts done threads that are in runtime threads + th = rt._pushThread('broadcastAndWaitBlock', tgt); + util.thread = th; + e.broadcastAndWait({BROADCAST_OPTION: 'message'}, util); + t.strictEqual(rt.threads.length, 3); + t.strictEqual(rt.threads[1].status, Thread.STATUS_RUNNING); + t.strictEqual(th.status, Thread.STATUS_YIELD); + // yields when some restarted thread is active + th.status = Thread.STATUS_RUNNING; + e.broadcastAndWait({BROADCAST_OPTION: 'message'}, util); + t.strictEqual(th.status, Thread.STATUS_YIELD); + // does not yield once all threads are done + th.status = Thread.STATUS_RUNNING; + rt.threads[1].status = Thread.STATUS_DONE; + e.broadcastAndWait({BROADCAST_OPTION: 'message'}, util); + t.strictEqual(th.status, Thread.STATUS_RUNNING); + + t.end(); +});