@scratch/scratch-vm 11.0.0-UEPR-176

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +120 -0
  3. package/dist/node/extension-worker.js +2 -0
  4. package/dist/node/extension-worker.js.map +1 -0
  5. package/dist/node/scratch-vm.js +3 -0
  6. package/dist/node/scratch-vm.js.LICENSE.txt +205 -0
  7. package/dist/node/scratch-vm.js.map +1 -0
  8. package/dist/web/extension-worker.js +2 -0
  9. package/dist/web/extension-worker.js.map +1 -0
  10. package/dist/web/scratch-vm.js +3 -0
  11. package/dist/web/scratch-vm.js.LICENSE.txt +42 -0
  12. package/dist/web/scratch-vm.js.map +1 -0
  13. package/package.json +108 -0
  14. package/src/.eslintrc.js +7 -0
  15. package/src/blocks/scratch3_control.js +206 -0
  16. package/src/blocks/scratch3_core_example.js +69 -0
  17. package/src/blocks/scratch3_data.js +257 -0
  18. package/src/blocks/scratch3_event.js +137 -0
  19. package/src/blocks/scratch3_looks.js +612 -0
  20. package/src/blocks/scratch3_motion.js +288 -0
  21. package/src/blocks/scratch3_operators.js +154 -0
  22. package/src/blocks/scratch3_procedures.js +79 -0
  23. package/src/blocks/scratch3_sensing.js +336 -0
  24. package/src/blocks/scratch3_sound.js +350 -0
  25. package/src/dispatch/central-dispatch.js +143 -0
  26. package/src/dispatch/shared-dispatch.js +235 -0
  27. package/src/dispatch/worker-dispatch.js +110 -0
  28. package/src/engine/adapter.js +176 -0
  29. package/src/engine/block-utility.js +242 -0
  30. package/src/engine/blocks-execute-cache.js +19 -0
  31. package/src/engine/blocks-runtime-cache.js +78 -0
  32. package/src/engine/blocks.js +1299 -0
  33. package/src/engine/comment.js +56 -0
  34. package/src/engine/execute.js +580 -0
  35. package/src/engine/monitor-record.js +23 -0
  36. package/src/engine/mutation-adapter.js +48 -0
  37. package/src/engine/profiler.js +390 -0
  38. package/src/engine/runtime.js +2686 -0
  39. package/src/engine/scratch-blocks-constants.js +27 -0
  40. package/src/engine/sequencer.js +361 -0
  41. package/src/engine/stage-layering.js +29 -0
  42. package/src/engine/target.js +799 -0
  43. package/src/engine/thread.js +404 -0
  44. package/src/engine/variable.js +70 -0
  45. package/src/extension-support/argument-type.js +47 -0
  46. package/src/extension-support/block-type.js +50 -0
  47. package/src/extension-support/define-messages.js +18 -0
  48. package/src/extension-support/extension-manager.js +440 -0
  49. package/src/extension-support/extension-metadata.js +64 -0
  50. package/src/extension-support/extension-worker.js +59 -0
  51. package/src/extension-support/reporter-scope.js +18 -0
  52. package/src/extension-support/target-type.js +17 -0
  53. package/src/extensions/scratch3_boost/index.js +2113 -0
  54. package/src/extensions/scratch3_ev3/index.js +1355 -0
  55. package/src/extensions/scratch3_gdx_for/index.js +981 -0
  56. package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
  57. package/src/extensions/scratch3_makeymakey/index.js +396 -0
  58. package/src/extensions/scratch3_microbit/index.js +984 -0
  59. package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
  60. package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
  61. package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
  62. package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
  63. package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
  64. package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
  65. package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
  66. package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
  67. package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
  68. package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
  69. package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
  70. package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
  71. package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
  72. package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
  73. package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
  74. package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
  75. package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
  76. package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
  77. package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
  78. package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
  79. package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
  80. package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
  81. package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
  82. package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
  83. package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
  84. package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
  85. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
  86. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
  87. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
  88. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
  89. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
  90. package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
  91. package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
  92. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
  93. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
  94. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
  95. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
  96. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
  97. package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
  98. package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
  99. package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
  100. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
  101. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
  102. package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
  103. package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
  104. package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
  105. package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
  106. package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
  107. package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
  108. package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
  109. package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
  110. package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
  111. package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
  112. package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
  113. package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
  114. package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
  115. package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
  116. package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
  117. package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
  118. package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
  119. package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
  120. package/src/extensions/scratch3_music/index.js +1333 -0
  121. package/src/extensions/scratch3_music/manifest.js +63 -0
  122. package/src/extensions/scratch3_pen/index.js +770 -0
  123. package/src/extensions/scratch3_speech2text/index.js +700 -0
  124. package/src/extensions/scratch3_text2speech/index.js +766 -0
  125. package/src/extensions/scratch3_translate/index.js +286 -0
  126. package/src/extensions/scratch3_video_sensing/debug.js +13 -0
  127. package/src/extensions/scratch3_video_sensing/index.js +594 -0
  128. package/src/extensions/scratch3_video_sensing/library.js +384 -0
  129. package/src/extensions/scratch3_video_sensing/math.js +76 -0
  130. package/src/extensions/scratch3_video_sensing/view.js +509 -0
  131. package/src/extensions/scratch3_wedo2/index.js +1616 -0
  132. package/src/import/load-costume.js +416 -0
  133. package/src/import/load-sound.js +116 -0
  134. package/src/index.js +10 -0
  135. package/src/io/ble.js +256 -0
  136. package/src/io/bt.js +202 -0
  137. package/src/io/clock.js +39 -0
  138. package/src/io/cloud.js +169 -0
  139. package/src/io/keyboard.js +146 -0
  140. package/src/io/mouse.js +145 -0
  141. package/src/io/mouseWheel.js +28 -0
  142. package/src/io/userData.js +24 -0
  143. package/src/io/video.js +210 -0
  144. package/src/playground/benchmark.css +83 -0
  145. package/src/playground/benchmark.js +753 -0
  146. package/src/playground/index.html +111 -0
  147. package/src/playground/suite.css +78 -0
  148. package/src/playground/suite.html +25 -0
  149. package/src/playground/suite.js +544 -0
  150. package/src/playground/video-sensing.html +18 -0
  151. package/src/playground/video-sensing.js +133 -0
  152. package/src/serialization/deserialize-assets.js +178 -0
  153. package/src/serialization/sb2.js +1295 -0
  154. package/src/serialization/sb2_specmap.js +1818 -0
  155. package/src/serialization/sb3.js +1320 -0
  156. package/src/serialization/serialize-assets.js +60 -0
  157. package/src/sprites/rendered-target.js +1115 -0
  158. package/src/sprites/sprite.js +177 -0
  159. package/src/util/base64-util.js +48 -0
  160. package/src/util/cast.js +218 -0
  161. package/src/util/clone.js +17 -0
  162. package/src/util/color.js +204 -0
  163. package/src/util/fetch-with-timeout.js +53 -0
  164. package/src/util/get-monitor-id.js +33 -0
  165. package/src/util/jsonrpc.js +114 -0
  166. package/src/util/log.js +4 -0
  167. package/src/util/math-util.js +122 -0
  168. package/src/util/maybe-format-message.js +18 -0
  169. package/src/util/new-block-ids.js +33 -0
  170. package/src/util/rateLimiter.js +73 -0
  171. package/src/util/scratch-link-websocket.js +133 -0
  172. package/src/util/string-util.js +94 -0
  173. package/src/util/task-queue.js +203 -0
  174. package/src/util/timer.js +103 -0
  175. package/src/util/uid.js +29 -0
  176. package/src/util/variable-util.js +48 -0
  177. package/src/util/xml-escape.js +33 -0
  178. package/src/virtual-machine.js +1586 -0
@@ -0,0 +1,544 @@
1
+ const soon = (() => {
2
+ let _soon;
3
+ return () => {
4
+ if (!_soon) {
5
+ _soon = Promise.resolve()
6
+ .then(() => {
7
+ _soon = null;
8
+ });
9
+ }
10
+ return _soon;
11
+ };
12
+ })();
13
+
14
+ class Emitter {
15
+ constructor () {
16
+ Object.defineProperty(this, '_listeners', {
17
+ value: {},
18
+ enumerable: false
19
+ });
20
+ }
21
+ on (name, listener, context) {
22
+ if (!this._listeners[name]) {
23
+ this._listeners[name] = [];
24
+ }
25
+
26
+ this._listeners[name].push(listener, context);
27
+ }
28
+ off (name, listener, context) {
29
+ if (this._listeners[name]) {
30
+ if (listener) {
31
+ for (let i = 0; i < this._listeners[name].length; i += 2) {
32
+ if (
33
+ this._listeners[name][i] === listener &&
34
+ this._listeners[name][i + 1] === context) {
35
+ this._listeners[name].splice(i, 2);
36
+ i -= 2;
37
+ }
38
+ }
39
+ } else {
40
+ for (let i = 0; i < this._listeners[name].length; i += 2) {
41
+ if (this._listeners[name][i + 1] === context) {
42
+ this._listeners[name].splice(i, 2);
43
+ i -= 2;
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ emit (name, ...args) {
50
+ if (this._listeners[name]) {
51
+ for (let i = 0; i < this._listeners[name].length; i += 2) {
52
+ this._listeners[name][i].call(this._listeners[name][i + 1] || this, ...args);
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ class BenchFrameStream extends Emitter {
59
+ constructor (frame) {
60
+ super();
61
+
62
+ this.frame = frame;
63
+ window.addEventListener('message', message => {
64
+ this.emit('message', message.data);
65
+ });
66
+ }
67
+
68
+ send (message) {
69
+ this.frame.send(message);
70
+ }
71
+ }
72
+
73
+ const benchmarkUrlArgs = args => (
74
+ [
75
+ args.projectId,
76
+ args.warmUpTime,
77
+ args.recordingTime
78
+ ].join(',')
79
+ );
80
+
81
+ const BENCH_MESSAGE_TYPE = {
82
+ INACTIVE: 'BENCH_MESSAGE_INACTIVE',
83
+ LOAD: 'BENCH_MESSAGE_LOAD',
84
+ LOADING: 'BENCH_MESSAGE_LOADING',
85
+ WARMING_UP: 'BENCH_MESSAGE_WARMING_UP',
86
+ ACTIVE: 'BENCH_MESSAGE_ACTIVE',
87
+ COMPLETE: 'BENCH_MESSAGE_COMPLETE'
88
+ };
89
+
90
+ class BenchUtil {
91
+ constructor (frame) {
92
+ this.frame = frame;
93
+ this.benchStream = new BenchFrameStream(frame);
94
+ }
95
+
96
+ setFrameLocation (url) {
97
+ this.frame.contentWindow.location.assign(url);
98
+ }
99
+
100
+ startBench (args) {
101
+ this.benchArgs = args;
102
+ this.setFrameLocation(`index.html#${benchmarkUrlArgs(args)}`);
103
+ }
104
+
105
+ pauseBench () {
106
+ new Promise(resolve => setTimeout(resolve, 1000))
107
+ .then(() => {
108
+ this.benchStream.emit('message', {
109
+ type: BENCH_MESSAGE_TYPE.INACTIVE
110
+ });
111
+ });
112
+ }
113
+
114
+ resumeBench () {
115
+ this.startBench(this.benchArgs);
116
+ }
117
+
118
+ renderResults (results) {
119
+ this.setFrameLocation(
120
+ `index.html#view/${btoa(JSON.stringify(results))}`
121
+ );
122
+ }
123
+ }
124
+
125
+ const BENCH_STATUS = {
126
+ INACTIVE: 'BENCH_STATUS_INACTIVE',
127
+ RESUME: 'BENCH_STATUS_RESUME',
128
+ STARTING: 'BENCH_STATUS_STARTING',
129
+ LOADING: 'BENCH_STATUS_LOADING',
130
+ WARMING_UP: 'BENCH_STATUS_WARMING_UP',
131
+ ACTIVE: 'BENCH_STATUS_ACTIVE',
132
+ COMPLETE: 'BENCH_STATUS_COMPLETE'
133
+ };
134
+
135
+ class BenchResult {
136
+ constructor ({fixture, status = BENCH_STATUS.INACTIVE, frames = null, opcodes = null}) {
137
+ this.fixture = fixture;
138
+ this.status = status;
139
+ this.frames = frames;
140
+ this.opcodes = opcodes;
141
+ }
142
+ }
143
+
144
+ class BenchFixture extends Emitter {
145
+ constructor ({
146
+ projectId,
147
+ warmUpTime = 4000,
148
+ recordingTime = 6000
149
+ }) {
150
+ super();
151
+
152
+ this.projectId = projectId;
153
+ this.warmUpTime = warmUpTime;
154
+ this.recordingTime = recordingTime;
155
+ }
156
+
157
+ get id () {
158
+ return `${this.projectId}-${this.warmUpTime}-${this.recordingTime}`;
159
+ }
160
+
161
+ run (util) {
162
+ return new Promise(resolve => {
163
+ util.benchStream.on('message', message => {
164
+ const result = {
165
+ fixture: this,
166
+ status: BENCH_STATUS.STARTING,
167
+ frames: null,
168
+ opcodes: null
169
+ };
170
+ if (message.type === BENCH_MESSAGE_TYPE.INACTIVE) {
171
+ result.status = BENCH_STATUS.RESUME;
172
+ } else if (message.type === BENCH_MESSAGE_TYPE.LOADING) {
173
+ result.status = BENCH_STATUS.LOADING;
174
+ } else if (message.type === BENCH_MESSAGE_TYPE.WARMING_UP) {
175
+ result.status = BENCH_STATUS.WARMING_UP;
176
+ } else if (message.type === BENCH_MESSAGE_TYPE.ACTIVE) {
177
+ result.status = BENCH_STATUS.ACTIVE;
178
+ } else if (message.type === BENCH_MESSAGE_TYPE.COMPLETE) {
179
+ result.status = BENCH_STATUS.COMPLETE;
180
+ result.frames = message.frames;
181
+ result.opcodes = message.opcodes;
182
+ resolve(new BenchResult(result));
183
+ util.benchStream.off('message', null, this);
184
+ }
185
+ this.emit('result', new BenchResult(result));
186
+ }, this);
187
+ util.startBench(this);
188
+ });
189
+ }
190
+ }
191
+
192
+ class BenchSuiteResult extends Emitter {
193
+ constructor ({suite, results = []}) {
194
+ super();
195
+
196
+ this.suite = suite;
197
+ this.results = results;
198
+
199
+ if (suite) {
200
+ suite.on('result', result => {
201
+ if (result.status === BENCH_STATUS.COMPLETE) {
202
+ this.results.push(results);
203
+ this.emit('add', this);
204
+ }
205
+ });
206
+ }
207
+ }
208
+ }
209
+
210
+ class BenchSuite extends Emitter {
211
+ constructor (fixtures = []) {
212
+ super();
213
+
214
+ this.fixtures = fixtures;
215
+ }
216
+
217
+ add (fixture) {
218
+ this.fixtures.push(fixture);
219
+ }
220
+
221
+ run (util) {
222
+ return new Promise(resolve => {
223
+ const fixtures = this.fixtures.slice();
224
+ const results = [];
225
+ const push = result => {
226
+ result.fixture.off('result', null, this);
227
+ results.push(result);
228
+ };
229
+ const emitResult = this.emit.bind(this, 'result');
230
+ const pop = () => {
231
+ const fixture = fixtures.shift();
232
+ if (fixture) {
233
+ fixture.on('result', emitResult, this);
234
+ fixture.run(util)
235
+ .then(push)
236
+ .then(pop);
237
+ } else {
238
+ resolve(new BenchSuiteResult({suite: this, results}));
239
+ }
240
+ };
241
+ pop();
242
+ });
243
+ }
244
+ }
245
+
246
+ class BenchRunner extends Emitter {
247
+ constructor ({frame, suite}) {
248
+ super();
249
+
250
+ this.frame = frame;
251
+ this.suite = suite;
252
+ this.util = new BenchUtil(frame);
253
+ }
254
+
255
+ run () {
256
+ return this.suite.run(this.util);
257
+ }
258
+ }
259
+
260
+ const viewNames = {
261
+ [BENCH_STATUS.INACTIVE]: 'Inactive',
262
+ [BENCH_STATUS.RESUME]: 'Resume',
263
+ [BENCH_STATUS.STARTING]: 'Starting',
264
+ [BENCH_STATUS.LOADING]: 'Loading',
265
+ [BENCH_STATUS.WARMING_UP]: 'Warming Up',
266
+ [BENCH_STATUS.ACTIVE]: 'Active',
267
+ [BENCH_STATUS.COMPLETE]: 'Complete'
268
+ };
269
+
270
+ class BenchResultView {
271
+ constructor ({result, benchUtil}) {
272
+ this.result = result;
273
+ this.compare = null;
274
+ this.benchUtil = benchUtil;
275
+ this.dom = document.createElement('div');
276
+ }
277
+
278
+ update (result) {
279
+ soon().then(() => this.render(result));
280
+ }
281
+
282
+ resume () {
283
+ this.benchUtil.resumeBench();
284
+ }
285
+
286
+ setFrameLocation (loc) {
287
+ this.benchUtil.pauseBench();
288
+ this.benchUtil.setFrameLocation(loc);
289
+ }
290
+
291
+ act (ev) {
292
+ if (
293
+ ev.type === 'click' &&
294
+ ev.button === 0 &&
295
+ !(ev.altKey || ev.ctrlKey || ev.shiftKey || ev.metaKey)
296
+ ) {
297
+ let target = ev.target;
298
+ while (target && target.tagName.toLowerCase() !== 'a') {
299
+ target = target.parentElement;
300
+ }
301
+ if (target && target.tagName.toLowerCase() === 'a') {
302
+ if (target.href) {
303
+ this.setFrameLocation(target.href);
304
+ ev.preventDefault();
305
+ }
306
+ } else if (ev.currentTarget.classList.contains('resume')) {
307
+ this.resume();
308
+ }
309
+ }
310
+ }
311
+
312
+ render (newResult = this.result, compareResult = this.compare) {
313
+ const newResultFrames = (newResult.frames ? newResult.frames : []);
314
+ const blockFunctionFrame = newResultFrames
315
+ .find(frame => frame.name === 'blockFunction');
316
+ const stepThreadsInnerFrame = newResultFrames
317
+ .find(frame => frame.name === 'Sequencer.stepThreads#inner');
318
+
319
+ const blocksPerSecond = blockFunctionFrame ?
320
+ (blockFunctionFrame.executions /
321
+ (stepThreadsInnerFrame.totalTime / 1000)) | 0 :
322
+ 0;
323
+ const stepsPerSecond = stepThreadsInnerFrame ?
324
+ (stepThreadsInnerFrame.executions /
325
+ (stepThreadsInnerFrame.totalTime / 1000)) | 0 :
326
+ 0;
327
+
328
+ const compareResultFrames = (
329
+ compareResult && compareResult.frames ?
330
+ compareResult.frames :
331
+ []
332
+ );
333
+ const blockFunctionCompareFrame = compareResultFrames
334
+ .find(frame => frame.name === 'blockFunction');
335
+ const stepThreadsInnerCompareFrame = compareResultFrames
336
+ .find(frame => frame.name === 'Sequencer.stepThreads#inner');
337
+
338
+ const compareBlocksPerSecond = blockFunctionCompareFrame ?
339
+ (blockFunctionCompareFrame.executions /
340
+ (stepThreadsInnerCompareFrame.totalTime / 1000)) | 0 :
341
+ 0;
342
+ const compareStepsPerSecond = stepThreadsInnerCompareFrame ?
343
+ (stepThreadsInnerCompareFrame.executions /
344
+ (stepThreadsInnerCompareFrame.totalTime / 1000)) | 0 :
345
+ 0;
346
+
347
+ const statusName = viewNames[newResult.status];
348
+
349
+ this.dom.className = `result-view ${
350
+ viewNames[newResult.status].toLowerCase()
351
+ }`;
352
+ this.dom.onclick = this.act.bind(this);
353
+ let url = `index.html#${benchmarkUrlArgs(newResult.fixture)}`;
354
+ if (newResult.status === BENCH_STATUS.COMPLETE) {
355
+ url = `index.html#view/${btoa(JSON.stringify(newResult))}`;
356
+ }
357
+ let compareUrl = url;
358
+ if (compareResult && compareResult) {
359
+ compareUrl =
360
+ `index.html#view/${btoa(JSON.stringify(compareResult))}`;
361
+ }
362
+ let compareHTML = '';
363
+ if (stepThreadsInnerFrame && stepThreadsInnerCompareFrame) {
364
+ compareHTML = `<a href="${compareUrl}" target="_blank">
365
+ <div class="result-status">
366
+ <div>${compareStepsPerSecond}</div>
367
+ <div>${compareBlocksPerSecond}</div>
368
+ </div>
369
+ </a>`;
370
+ }
371
+ this.dom.innerHTML = `
372
+ <div class="fixture-project">
373
+ <a href="${url}" target="bench_frame"
374
+ >${newResult.fixture.projectId}</a>
375
+ </div>
376
+ <div class="result-status">
377
+ <div>${stepThreadsInnerFrame ? `steps/s` : ''}</div>
378
+ <div>${blockFunctionFrame ? `blocks/s` : statusName}</div>
379
+ </div>
380
+ <a href="${stepThreadsInnerFrame ? url : ''}" target="_blank">
381
+ <div class="result-status">
382
+ <div>${stepThreadsInnerFrame ? `${stepsPerSecond}` : ''}</div>
383
+ <div>${blockFunctionFrame ? `${blocksPerSecond}` : ''}</div>
384
+ </div>
385
+ </a>
386
+ ${compareHTML}
387
+ <div class="">
388
+ Run for ${newResult.fixture.recordingTime / 1000} seconds after
389
+ ${newResult.fixture.warmUpTime / 1000} seconds
390
+ </div>
391
+ `;
392
+
393
+ this.result = newResult;
394
+ return this;
395
+ }
396
+ }
397
+
398
+ class BenchSuiteResultView {
399
+ constructor ({runner}) {
400
+ const {suite, util} = runner;
401
+
402
+ this.runner = runner;
403
+ this.suite = suite;
404
+ this.views = {};
405
+ this.dom = document.createElement('div');
406
+
407
+ for (const fixture of suite.fixtures) {
408
+ this.views[fixture.id] = new BenchResultView({
409
+ result: new BenchResult({fixture}),
410
+ benchUtil: util
411
+ });
412
+ }
413
+
414
+ suite.on('result', result => {
415
+ this.views[result.fixture.id].update(result);
416
+ });
417
+ }
418
+
419
+ render () {
420
+ this.dom.innerHTML = `<div class="legend">
421
+ <span>Project ID</span>
422
+ <div class="result-status">
423
+ <div>steps per second</div>
424
+ <div>blocks per second</div>
425
+ </div>
426
+ <div>Description</div>
427
+ </div>
428
+
429
+ <div class="legend">
430
+ <span>&nbsp;</span>
431
+ <div class="result-status">
432
+ <div><a href="#" onclick="window.download(this)">
433
+ Save Reports
434
+ </a></div>
435
+ </div>
436
+ <div class="result-status">
437
+ <a href="#"><label for="compare-file">Compare Reports<input
438
+ id="compare-file" type="file"
439
+ class="compare-file"
440
+ accept="application/json"
441
+ onchange="window.upload(this)" />
442
+ </label></a>
443
+ </div>
444
+ </div>`;
445
+
446
+ for (const fixture of this.suite.fixtures) {
447
+ this.dom.appendChild(this.views[fixture.id].render().dom);
448
+ }
449
+
450
+ return this;
451
+ }
452
+ }
453
+
454
+ let suite;
455
+ let suiteView;
456
+
457
+ window.upload = function (_this) {
458
+ if (!_this.files.length) {
459
+ return;
460
+ }
461
+ const reader = new FileReader();
462
+ reader.onload = function () {
463
+ const report = JSON.parse(reader.result);
464
+ Object.values(suiteView.views)
465
+ .forEach(view => {
466
+ const sameFixture = report.results.find(result => (
467
+ result.fixture.projectId ===
468
+ view.result.fixture.projectId &&
469
+ result.fixture.warmUpTime ===
470
+ view.result.fixture.warmUpTime &&
471
+ result.fixture.recordingTime ===
472
+ view.result.fixture.recordingTime
473
+ ));
474
+
475
+ if (sameFixture) {
476
+ if (
477
+ view.result && view.result.frames &&
478
+ view.result.frames.length > 0
479
+ ) {
480
+ view.render(view.result, sameFixture);
481
+ } else {
482
+ view.compare = sameFixture;
483
+ }
484
+ }
485
+ });
486
+ };
487
+ reader.readAsText(_this.files[0]);
488
+ };
489
+
490
+ window.download = function (_this) {
491
+ const blob = new Blob([JSON.stringify({
492
+ meta: {
493
+ source: 'Scratch VM Benchmark Suite',
494
+ version: 1
495
+ },
496
+ results: Object.values(suiteView.views)
497
+ .map(view => view.result)
498
+ .filter(view => view.status === BENCH_STATUS.COMPLETE)
499
+ })], {type: 'application/json'});
500
+
501
+ _this.download = 'scratch-vm-benchmark.json';
502
+ _this.href = URL.createObjectURL(blob);
503
+ };
504
+
505
+ window.onload = function () {
506
+ suite = new BenchSuite();
507
+
508
+ const add = (projectId, warmUp = 0, recording = 5000) => {
509
+ suite.add(new BenchFixture({
510
+ projectId,
511
+ warmUpTime: warmUp,
512
+ recordingTime: recording
513
+ }));
514
+ };
515
+
516
+ const standard = projectId => {
517
+ add(projectId, 0, 5000);
518
+ add(projectId, 5000, 5000);
519
+ };
520
+
521
+ add(130041250, 0, 2000); // floating blocks
522
+ add(130041250, 4000, 6000);
523
+
524
+ add(14844969, 0, 2000); // scratch cats
525
+ add(14844969, 1000, 6000);
526
+
527
+ standard(173918262); // bouncy heros
528
+ standard(155128646); // stacky build
529
+ standard(89811578); // solar system
530
+ standard(139193539); // pixel art maker
531
+ standard(187694931); // spiralgraph
532
+ standard(219313833); // sensing_touching benchmark
533
+ standard(236115215); // touching color benchmark
534
+ standard(238750909); // bob ross painting (heavy pen stamp)
535
+
536
+ const frame = document.getElementsByTagName('iframe')[0];
537
+ const runner = new BenchRunner({frame, suite});
538
+ const resultsView = suiteView = new BenchSuiteResultView({runner}).render();
539
+
540
+ document.getElementsByClassName('suite-results')[0]
541
+ .appendChild(resultsView.dom);
542
+
543
+ runner.run();
544
+ };
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Video Motion Test Playground</title>
5
+ </head>
6
+ <body>
7
+ <!-- FPS counter, Syntax highlighter, Blocks, Renderer -->
8
+ <script src="./vendor.js"></script>
9
+ <!-- Storage module -->
10
+ <script src="./scratch-storage.js"></script>
11
+ <!-- Stage rendering -->
12
+ <script src="./scratch-render.js"></script>
13
+ <!-- Extension -->
14
+ <script src="./video-sensing-extension-debug.js"></script>
15
+ <!-- Motion -->
16
+ <script src="./video-sensing.js"></script>
17
+ </body>
18
+ </html>
@@ -0,0 +1,133 @@
1
+ (function () {
2
+ const BENCHMARK_THROTTLE = 250;
3
+ const INTERVAL = 33;
4
+
5
+ const video = document.createElement('video');
6
+ navigator.getUserMedia({
7
+ audio: false,
8
+ video: {
9
+ width: {min: 480, ideal: 640},
10
+ height: {min: 360, ideal: 480}
11
+ }
12
+ }, stream => {
13
+ video.autoplay = true;
14
+ video.src = window.URL.createObjectURL(stream);
15
+ // Get the track to hint to the browser the stream needs to be running
16
+ // even though we don't add the video tag to the DOM.
17
+ stream.getTracks();
18
+ video.addEventListener('play', () => {
19
+ video.width = video.videoWidth;
20
+ video.height = video.videoHeight;
21
+ });
22
+ }, err => {
23
+ // eslint-disable-next-line no-console
24
+ console.log(err);
25
+ });
26
+
27
+ const VideoMotion = window.Scratch3VideoSensingDebug.VideoMotion;
28
+ const VideoMotionView = window.Scratch3VideoSensingDebug.VideoMotionView;
29
+
30
+ // Create motion detector
31
+ const motion = new VideoMotion();
32
+
33
+ // Create debug views that will render different slices of how the detector
34
+ // uses a frame of input.
35
+ const OUTPUT = VideoMotionView.OUTPUT;
36
+ const outputKeys = Object.keys(OUTPUT);
37
+ const outputValues = Object.values(OUTPUT);
38
+ const views = outputValues
39
+ .map(output => new VideoMotionView(motion, output));
40
+ const view = views[0];
41
+
42
+ const defaultViews = [OUTPUT.INPUT, OUTPUT.XY_CELL, OUTPUT.T_CELL, OUTPUT.UV_CELL];
43
+
44
+ // Add activation toggles for each debug view.
45
+ const activators = document.createElement('div');
46
+ activators.style.userSelect = 'none';
47
+ outputValues.forEach((output, index) => {
48
+ const checkboxLabel = document.createElement('label');
49
+ const checkbox = document.createElement('input');
50
+ checkbox.type = 'checkbox';
51
+ checkbox.checked = defaultViews.indexOf(output) !== -1;
52
+ const checkboxSpan = document.createElement('span');
53
+ checkboxSpan.innerText = outputKeys[index];
54
+ checkboxLabel.appendChild(checkbox);
55
+ checkboxLabel.appendChild(checkboxSpan);
56
+
57
+ const _view = views[index];
58
+ _view.canvas.style.display = checkbox.checked ? '' : 'none';
59
+ _view.active = checkbox.checked;
60
+ checkbox.onchange = event => {
61
+ _view.canvas.style.display = checkbox.checked ? '' : 'none';
62
+ _view.active = checkbox.checked;
63
+ event.preventDefault();
64
+ return false;
65
+ };
66
+
67
+ activators.appendChild(checkboxLabel);
68
+ });
69
+ document.body.appendChild(activators);
70
+
71
+ // Add a text line to display milliseconds per frame, motion value, and
72
+ // motion direction
73
+ const textContainer = document.createElement('div');
74
+ const textHeader = document.createElement('div');
75
+ textHeader.innerText = 'duration (us) :: motion amount :: motion direction';
76
+ textContainer.appendChild(textHeader);
77
+ const textEl = document.createElement('div');
78
+ textEl.innerText = `0 :: 0 :: 0`;
79
+ textContainer.appendChild(textEl);
80
+ document.body.appendChild(textContainer);
81
+ let textTimer = Date.now();
82
+
83
+ // Add the motion debug views to the dom after the text line, so the text
84
+ // appears first.
85
+ views.forEach(_view => document.body.appendChild(_view.canvas));
86
+
87
+ // Create a temporary canvas the video will be drawn to so the video's
88
+ // bitmap data can be transformed into a TypeArray.
89
+ const tempCanvas = document.createElement('canvas');
90
+ tempCanvas.width = view.canvas.width;
91
+ tempCanvas.height = view.canvas.height;
92
+ const ctx = tempCanvas.getContext('2d');
93
+
94
+ const loop = function () {
95
+ const timeoutId = setTimeout(loop, INTERVAL);
96
+
97
+ try {
98
+ // Get the bitmap data for the video frame
99
+ ctx.scale(-1, 1);
100
+ ctx.drawImage(
101
+ video,
102
+ 0, 0, video.width || video.clientWidth, video.height || video.clientHeight,
103
+ -tempCanvas.width, 0, tempCanvas.width, tempCanvas.height
104
+ );
105
+ ctx.resetTransform();
106
+ const data = ctx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
107
+
108
+ // Analyze the latest frame.
109
+ const b = performance.now();
110
+ motion.addFrame(data.data);
111
+ motion.analyzeFrame();
112
+
113
+ // Every so often update the visible debug numbers with duration in
114
+ // microseconds, the amount of motion and the direction of the
115
+ // motion.
116
+ if (Date.now() - textTimer > BENCHMARK_THROTTLE) {
117
+ const e = performance.now();
118
+ const analyzeDuration = ((e - b) * 1000).toFixed(0);
119
+ const motionAmount = motion.motionAmount.toFixed(1);
120
+ const motionDirection = motion.motionDirection.toFixed(1);
121
+ textEl.innerText = `${analyzeDuration} :: ${motionAmount} :: ${motionDirection}`;
122
+ textTimer = Date.now();
123
+ }
124
+ views.forEach(_view => _view.active && _view.draw());
125
+ } catch (error) {
126
+ // eslint-disable-next-line no-console
127
+ console.error(error.stack || error);
128
+ clearTimeout(timeoutId);
129
+ }
130
+ };
131
+
132
+ loop();
133
+ }());