Skip to content

Commit 6f9bb17

Browse files
committed
feat: warped threads' events inherit warp state
1 parent df87a72 commit 6f9bb17

File tree

3 files changed

+100
-74
lines changed

3 files changed

+100
-74
lines changed

‎src/blocks/scratch3_event.js

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -96,40 +96,12 @@ class Scratch3EventBlocks {
9696
}
9797
if (util.stackFrame.broadcastVar) {
9898
const broadcastOption = util.stackFrame.broadcastVar.name;
99-
// Have we run before, starting threads?
100-
if (!util.stackFrame.startedThreads) {
101-
// No - start hats for this broadcast.
102-
util.stackFrame.startedThreads = util.startHats(
103-
'event_whenbroadcastreceived', {
104-
BROADCAST_OPTION: broadcastOption
105-
}
106-
);
107-
if (util.stackFrame.startedThreads.length === 0) {
108-
// Nothing was started.
109-
return;
110-
}
111-
}
112-
// We've run before; check if the wait is still going on.
113-
const instance = this;
114-
// Scratch 2 considers threads to be waiting if they are still in
115-
// runtime.threads. Threads that have run all their blocks, or are
116-
// marked done but still in runtime.threads are still considered to
117-
// be waiting.
118-
const waiting = util.stackFrame.startedThreads
119-
.some(thread => instance.runtime.threads.indexOf(thread) !== -1);
120-
if (waiting) {
121-
// If all threads are waiting for the next tick or later yield
122-
// for a tick as well. Otherwise yield until the next loop of
123-
// the threads.
124-
if (
125-
util.stackFrame.startedThreads
126-
.every(thread => instance.runtime.isWaitingThread(thread))
127-
) {
128-
util.yieldTick();
129-
} else {
130-
util.yield();
99+
100+
util.startHatsAndWait(
101+
'event_whenbroadcastreceived', {
102+
BROADCAST_OPTION: broadcastOption
131103
}
132-
}
104+
);
133105
}
134106
}
135107
}

‎src/blocks/scratch3_looks.js

Lines changed: 21 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -418,9 +418,9 @@ class Scratch3LooksBlocks {
418418
* @param {!Target} stage Target to set backdrop to.
419419
* @param {Any} requestedBackdrop Backdrop requested, e.g., 0, 'name', etc.
420420
* @param {boolean=} optZeroIndex Set to zero-index the requestedBackdrop.
421-
* @return {Array.<!Thread>} Any threads started by this switch.
421+
* @param {boolean=} optSkipEvent Skips firing the backdrop changed event.
422422
*/
423-
_setBackdrop (stage, requestedBackdrop, optZeroIndex) {
423+
_setBackdrop (stage, requestedBackdrop, optZeroIndex, optSkipEvent) {
424424
if (typeof requestedBackdrop === 'number') {
425425
// Numbers should be treated as backdrop indices, always
426426
stage.setCostume(optZeroIndex ? requestedBackdrop : requestedBackdrop - 1);
@@ -455,10 +455,12 @@ class Scratch3LooksBlocks {
455455
}
456456
}
457457

458-
const newName = stage.getCostumes()[stage.currentCostume].name;
459-
return this.runtime.startHats('event_whenbackdropswitchesto', {
460-
BACKDROP: newName
461-
});
458+
if (!optSkipEvent) {
459+
const newName = stage.getCostumes()[stage.currentCostume].name;
460+
this.runtime.startHats('event_whenbackdropswitchesto', {
461+
BACKDROP: newName
462+
});
463+
}
462464
}
463465

464466
switchCostume (args, util) {
@@ -476,41 +478,19 @@ class Scratch3LooksBlocks {
476478
}
477479

478480
switchBackdropAndWait (args, util) {
479-
// Have we run before, starting threads?
480-
if (!util.stackFrame.startedThreads) {
481-
// No - switch the backdrop.
482-
util.stackFrame.startedThreads = (
483-
this._setBackdrop(
484-
this.runtime.getTargetForStage(),
485-
args.BACKDROP
486-
)
487-
);
488-
if (util.stackFrame.startedThreads.length === 0) {
489-
// Nothing was started.
490-
return;
491-
}
492-
}
493-
// We've run before; check if the wait is still going on.
494-
const instance = this;
495-
// Scratch 2 considers threads to be waiting if they are still in
496-
// runtime.threads. Threads that have run all their blocks, or are
497-
// marked done but still in runtime.threads are still considered to
498-
// be waiting.
499-
const waiting = util.stackFrame.startedThreads
500-
.some(thread => instance.runtime.threads.indexOf(thread) !== -1);
501-
if (waiting) {
502-
// If all threads are waiting for the next tick or later yield
503-
// for a tick as well. Otherwise yield until the next loop of
504-
// the threads.
505-
if (
506-
util.stackFrame.startedThreads
507-
.every(thread => instance.runtime.isWaitingThread(thread))
508-
) {
509-
util.yieldTick();
510-
} else {
511-
util.yield();
512-
}
513-
}
481+
const stage = this.runtime.getTargetForStage();
482+
483+
this._setBackdrop(
484+
stage,
485+
args.BACKDROP,
486+
false,
487+
true // Skip firing event so we can wait on it.
488+
);
489+
490+
const newName = stage.getCostumes()[stage.currentCostume].name;
491+
util.startHatsAndWait('event_whenbackdropswitchesto', {
492+
BACKDROP: newName
493+
});
514494
}
515495

516496
nextBackdrop () {

‎src/engine/block-utility.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,80 @@ class BlockUtility {
219219
return result;
220220
}
221221

222+
/**
223+
* Start all relevant hats, and wait for their completion in warp mode.
224+
* @param {!string} requestedHat Opcode of hats to start.
225+
* @param {object=} optMatchFields Optionally, fields to match on the hat.
226+
* @param {Target=} optTarget Optionally, a target to restrict to.
227+
*/
228+
startHatsAndWait(requestedHat, optMatchFields, optTarget) {
229+
// Have we run before, starting threads?
230+
if (!this.stackFrame.startedThreads) {
231+
// No - start threads.
232+
this.stackFrame.startedThreads = this.startHats(
233+
requestedHat, optMatchFields, optTarget
234+
);
235+
if (this.stackFrame.startedThreads.length === 0) {
236+
// Nothing was started.
237+
return;
238+
}
239+
}
240+
241+
// We've run before; check if the wait is still going on.
242+
// Scratch 2 considers threads to be waiting if they are still in
243+
// runtime.threads. Threads that have run all their blocks, or are
244+
// marked done but still in runtime.threads are still considered to
245+
// be waiting.
246+
if (
247+
this.stackFrame.startedThreads
248+
.every(thread => this.runtime.threads.indexOf(thread) === -1)
249+
) {
250+
return;
251+
}
252+
253+
if (this.thread.peekStackFrame().warpMode) {
254+
let threads = this.stackFrame.startedThreads;
255+
256+
// Make threads inherit warpMode and warpTimer if they haven't already.
257+
for (let i = 0; i < threads.length; i++) {
258+
if (!threads[i].warpTimer) {
259+
threads[i].peekStackFrame().warpMode = true;
260+
threads[i].warpTimer = this.thread.warpTimer;
261+
}
262+
}
263+
264+
// Workaround for cyclic dependency chain introduced by importing Sequencer
265+
const warpTime = this.sequencer.constructor.WARP_TIME;
266+
// Don't step the threads once the timer is up.
267+
if (this.thread.warpTimer.timeElapsed() <= warpTime) {
268+
// Step threads one by one in warp mode. They will execute until completion, or until
269+
// the inherited warpTimer is up.
270+
for (let i = 0; i < threads.length; i++) {
271+
this.sequencer.stepThread(threads[i]);
272+
}
273+
274+
// End the call when all threads are completed.
275+
if (threads.every(thread => thread.status === Thread.STATUS_DONE)) {
276+
return;
277+
}
278+
}
279+
280+
// If we still need to execute the threads, and the warp timer is up, yield.
281+
}
282+
283+
// If all threads are waiting for the next tick or later yield
284+
// for a tick as well. Otherwise yield until the next loop of
285+
// the threads.
286+
if (
287+
this.stackFrame.startedThreads
288+
.every(thread => this.runtime.isWaitingThread(thread))
289+
) {
290+
this.yieldTick();
291+
} else {
292+
this.yield();
293+
}
294+
}
295+
222296
/**
223297
* Query a named IO device.
224298
* @param {string} device The name of like the device, like keyboard.

0 commit comments

Comments
 (0)