From 9864403bc977ae212b227ae55d1b384fa07949b6 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Tue, 7 Nov 2017 14:15:53 -0500 Subject: [PATCH 1/3] A thread in the runtime but with the DONE status is not active return false from isActiveThread if thread's status is done even when its contained in the runtime threads list. As it is done, the thread will not be run until either its replaced by a new copy of the thread or the thread is removed from the list and another is added at a later time. --- src/engine/runtime.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 39be44db8..bd1e4a90b 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -825,7 +825,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); } /** From 3d9255345986209c8b885b55b983bbb39224d9ea Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Tue, 7 Nov 2017 14:22:20 -0500 Subject: [PATCH 2/3] Return restarted threads in list from startHats Add restarted threads to the list of newThreads returned by startHats. --- src/engine/runtime.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/runtime.js b/src/engine/runtime.js index bd1e4a90b..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; } /** @@ -976,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; } } From d173326290e592c79f006a97f36db5a62c441a26 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Thu, 16 Nov 2017 11:21:51 -0500 Subject: [PATCH 3/3] Add broadcastAndWait regression test --- test/unit/blocks_event.js | 94 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 test/unit/blocks_event.js 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(); +});