@scratch/scratch-vm 11.0.0-beta.1
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.
- package/.jsdoc.json +20 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +7004 -0
- package/LICENSE +12 -0
- package/README.md +120 -0
- package/TRADEMARK +1 -0
- package/commitlint.config.js +4 -0
- package/docs/extensions.md +527 -0
- package/package.json +103 -0
- package/release.config.js +13 -0
- package/src/blocks/scratch3_control.js +206 -0
- package/src/blocks/scratch3_core_example.js +69 -0
- package/src/blocks/scratch3_data.js +257 -0
- package/src/blocks/scratch3_event.js +137 -0
- package/src/blocks/scratch3_looks.js +612 -0
- package/src/blocks/scratch3_motion.js +288 -0
- package/src/blocks/scratch3_operators.js +154 -0
- package/src/blocks/scratch3_procedures.js +79 -0
- package/src/blocks/scratch3_sensing.js +336 -0
- package/src/blocks/scratch3_sound.js +350 -0
- package/src/dispatch/central-dispatch.js +143 -0
- package/src/dispatch/shared-dispatch.js +235 -0
- package/src/dispatch/worker-dispatch.js +110 -0
- package/src/engine/adapter.js +176 -0
- package/src/engine/block-utility.js +242 -0
- package/src/engine/blocks-execute-cache.js +19 -0
- package/src/engine/blocks-runtime-cache.js +78 -0
- package/src/engine/blocks.js +1299 -0
- package/src/engine/comment.js +56 -0
- package/src/engine/execute.js +580 -0
- package/src/engine/monitor-record.js +23 -0
- package/src/engine/mutation-adapter.js +48 -0
- package/src/engine/profiler.js +390 -0
- package/src/engine/runtime.js +2686 -0
- package/src/engine/scratch-blocks-constants.js +27 -0
- package/src/engine/sequencer.js +361 -0
- package/src/engine/stage-layering.js +29 -0
- package/src/engine/target.js +799 -0
- package/src/engine/thread.js +404 -0
- package/src/engine/variable.js +70 -0
- package/src/extension-support/argument-type.js +47 -0
- package/src/extension-support/block-type.js +50 -0
- package/src/extension-support/define-messages.js +18 -0
- package/src/extension-support/extension-manager.js +440 -0
- package/src/extension-support/extension-metadata.js +64 -0
- package/src/extension-support/extension-worker.js +59 -0
- package/src/extension-support/reporter-scope.js +18 -0
- package/src/extension-support/target-type.js +17 -0
- package/src/extensions/scratch3_boost/index.js +2113 -0
- package/src/extensions/scratch3_ev3/index.js +1355 -0
- package/src/extensions/scratch3_gdx_for/index.js +981 -0
- package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
- package/src/extensions/scratch3_makeymakey/index.js +396 -0
- package/src/extensions/scratch3_microbit/index.js +984 -0
- package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/index.js +1333 -0
- package/src/extensions/scratch3_music/manifest.js +63 -0
- package/src/extensions/scratch3_pen/index.js +770 -0
- package/src/extensions/scratch3_speech2text/index.js +700 -0
- package/src/extensions/scratch3_text2speech/index.js +766 -0
- package/src/extensions/scratch3_translate/index.js +286 -0
- package/src/extensions/scratch3_video_sensing/debug.js +13 -0
- package/src/extensions/scratch3_video_sensing/index.js +594 -0
- package/src/extensions/scratch3_video_sensing/library.js +384 -0
- package/src/extensions/scratch3_video_sensing/math.js +76 -0
- package/src/extensions/scratch3_video_sensing/view.js +509 -0
- package/src/extensions/scratch3_wedo2/index.js +1616 -0
- package/src/import/load-costume.js +416 -0
- package/src/import/load-sound.js +116 -0
- package/src/index.js +10 -0
- package/src/io/ble.js +256 -0
- package/src/io/bt.js +202 -0
- package/src/io/clock.js +39 -0
- package/src/io/cloud.js +169 -0
- package/src/io/keyboard.js +146 -0
- package/src/io/mouse.js +145 -0
- package/src/io/mouseWheel.js +28 -0
- package/src/io/userData.js +24 -0
- package/src/io/video.js +210 -0
- package/src/playground/benchmark.css +83 -0
- package/src/playground/benchmark.js +753 -0
- package/src/playground/index.html +111 -0
- package/src/playground/suite.css +78 -0
- package/src/playground/suite.html +25 -0
- package/src/playground/suite.js +544 -0
- package/src/playground/video-sensing.html +18 -0
- package/src/playground/video-sensing.js +133 -0
- package/src/serialization/deserialize-assets.js +178 -0
- package/src/serialization/sb2.js +1295 -0
- package/src/serialization/sb2_specmap.js +1818 -0
- package/src/serialization/sb3.js +1320 -0
- package/src/serialization/serialize-assets.js +60 -0
- package/src/sprites/rendered-target.js +1115 -0
- package/src/sprites/sprite.js +177 -0
- package/src/util/base64-util.js +48 -0
- package/src/util/cast.js +218 -0
- package/src/util/clone.js +17 -0
- package/src/util/color.js +204 -0
- package/src/util/fetch-with-timeout.js +53 -0
- package/src/util/get-monitor-id.js +33 -0
- package/src/util/jsonrpc.js +114 -0
- package/src/util/log.js +4 -0
- package/src/util/math-util.js +122 -0
- package/src/util/maybe-format-message.js +18 -0
- package/src/util/new-block-ids.js +33 -0
- package/src/util/rateLimiter.js +73 -0
- package/src/util/scratch-link-websocket.js +133 -0
- package/src/util/string-util.js +94 -0
- package/src/util/task-queue.js +203 -0
- package/src/util/timer.js +103 -0
- package/src/util/uid.js +29 -0
- package/src/util/variable-util.js +48 -0
- package/src/util/xml-escape.js +33 -0
- package/src/virtual-machine.js +1586 -0
- package/webpack.config.js +134 -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> </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
|
+
}());
|