thomwolf HF staff commited on
Commit
669cd52
1 Parent(s): 06dfbb8
godot.javascript.opt.tools.threads.engine.js ADDED
@@ -0,0 +1,773 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars
2
+ function getTrackedResponse(response, load_status) {
3
+ function onloadprogress(reader, controller) {
4
+ return reader.read().then(function (result) {
5
+ if (load_status.done) {
6
+ return Promise.resolve();
7
+ }
8
+ if (result.value) {
9
+ controller.enqueue(result.value);
10
+ load_status.loaded += result.value.length;
11
+ }
12
+ if (!result.done) {
13
+ return onloadprogress(reader, controller);
14
+ }
15
+ load_status.done = true;
16
+ return Promise.resolve();
17
+ });
18
+ }
19
+ const reader = response.body.getReader();
20
+ return new Response(new ReadableStream({
21
+ start: function (controller) {
22
+ onloadprogress(reader, controller).then(function () {
23
+ controller.close();
24
+ });
25
+ },
26
+ }), { headers: response.headers });
27
+ }
28
+
29
+ function loadFetch(file, tracker, fileSize, raw) {
30
+ tracker[file] = {
31
+ total: fileSize || 0,
32
+ loaded: 0,
33
+ done: false,
34
+ };
35
+ return fetch(file).then(function (response) {
36
+ if (!response.ok) {
37
+ return Promise.reject(new Error(`Failed loading file '${file}'`));
38
+ }
39
+ const tr = getTrackedResponse(response, tracker[file]);
40
+ if (raw) {
41
+ return Promise.resolve(tr);
42
+ }
43
+ return tr.arrayBuffer();
44
+ });
45
+ }
46
+
47
+ function retry(func, attempts = 1) {
48
+ function onerror(err) {
49
+ if (attempts <= 1) {
50
+ return Promise.reject(err);
51
+ }
52
+ return new Promise(function (resolve, reject) {
53
+ setTimeout(function () {
54
+ retry(func, attempts - 1).then(resolve).catch(reject);
55
+ }, 1000);
56
+ });
57
+ }
58
+ return func().catch(onerror);
59
+ }
60
+
61
+ const DOWNLOAD_ATTEMPTS_MAX = 4;
62
+ const loadingFiles = {};
63
+ const lastProgress = { loaded: 0, total: 0 };
64
+ let progressFunc = null;
65
+
66
+ const animateProgress = function () {
67
+ let loaded = 0;
68
+ let total = 0;
69
+ let totalIsValid = true;
70
+ let progressIsFinal = true;
71
+
72
+ Object.keys(loadingFiles).forEach(function (file) {
73
+ const stat = loadingFiles[file];
74
+ if (!stat.done) {
75
+ progressIsFinal = false;
76
+ }
77
+ if (!totalIsValid || stat.total === 0) {
78
+ totalIsValid = false;
79
+ total = 0;
80
+ } else {
81
+ total += stat.total;
82
+ }
83
+ loaded += stat.loaded;
84
+ });
85
+ if (loaded !== lastProgress.loaded || total !== lastProgress.total) {
86
+ lastProgress.loaded = loaded;
87
+ lastProgress.total = total;
88
+ if (typeof progressFunc === 'function') {
89
+ progressFunc(loaded, total);
90
+ }
91
+ }
92
+ if (!progressIsFinal) {
93
+ requestAnimationFrame(animateProgress);
94
+ }
95
+ };
96
+
97
+ this.animateProgress = animateProgress;
98
+
99
+ this.setProgressFunc = function (callback) {
100
+ progressFunc = callback;
101
+ };
102
+
103
+ this.loadPromise = function (file, fileSize, raw = false) {
104
+ return retry(loadFetch.bind(null, file, loadingFiles, fileSize, raw), DOWNLOAD_ATTEMPTS_MAX);
105
+ };
106
+
107
+ this.preloadedFiles = [];
108
+ this.preload = function (pathOrBuffer, destPath, fileSize) {
109
+ let buffer = null;
110
+ if (typeof pathOrBuffer === 'string') {
111
+ const me = this;
112
+ return this.loadPromise(pathOrBuffer, fileSize).then(function (buf) {
113
+ me.preloadedFiles.push({
114
+ path: destPath || pathOrBuffer,
115
+ buffer: buf,
116
+ });
117
+ return Promise.resolve();
118
+ });
119
+ } else if (pathOrBuffer instanceof ArrayBuffer) {
120
+ buffer = new Uint8Array(pathOrBuffer);
121
+ } else if (ArrayBuffer.isView(pathOrBuffer)) {
122
+ buffer = new Uint8Array(pathOrBuffer.buffer);
123
+ }
124
+ if (buffer) {
125
+ this.preloadedFiles.push({
126
+ path: destPath,
127
+ buffer: pathOrBuffer,
128
+ });
129
+ return Promise.resolve();
130
+ }
131
+ return Promise.reject(new Error('Invalid object for preloading'));
132
+ };
133
+ };
134
+
135
+ /**
136
+ * An object used to configure the Engine instance based on godot export options, and to override those in custom HTML
137
+ * templates if needed.
138
+ *
139
+ * @header Engine configuration
140
+ * @summary The Engine configuration object. This is just a typedef, create it like a regular object, e.g.:
141
+ *
142
+ * ``const MyConfig = { executable: 'godot', unloadAfterInit: false }``
143
+ *
144
+ * @typedef {Object} EngineConfig
145
+ */
146
+ const EngineConfig = {}; // eslint-disable-line no-unused-vars
147
+
148
+ /**
149
+ * @struct
150
+ * @constructor
151
+ * @ignore
152
+ */
153
+ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-vars
154
+ const cfg = /** @lends {InternalConfig.prototype} */ {
155
+ /**
156
+ * Whether the unload the engine automatically after the instance is initialized.
157
+ *
158
+ * @memberof EngineConfig
159
+ * @default
160
+ * @type {boolean}
161
+ */
162
+ unloadAfterInit: true,
163
+ /**
164
+ * The HTML DOM Canvas object to use.
165
+ *
166
+ * By default, the first canvas element in the document will be used is none is specified.
167
+ *
168
+ * @memberof EngineConfig
169
+ * @default
170
+ * @type {?HTMLCanvasElement}
171
+ */
172
+ canvas: null,
173
+ /**
174
+ * The name of the WASM file without the extension. (Set by Godot Editor export process).
175
+ *
176
+ * @memberof EngineConfig
177
+ * @default
178
+ * @type {string}
179
+ */
180
+ executable: '',
181
+ /**
182
+ * An alternative name for the game pck to load. The executable name is used otherwise.
183
+ *
184
+ * @memberof EngineConfig
185
+ * @default
186
+ * @type {?string}
187
+ */
188
+ mainPack: null,
189
+ /**
190
+ * Specify a language code to select the proper localization for the game.
191
+ *
192
+ * The browser locale will be used if none is specified. See complete list of
193
+ * :ref:`supported locales <doc_locales>`.
194
+ *
195
+ * @memberof EngineConfig
196
+ * @type {?string}
197
+ * @default
198
+ */
199
+ locale: null,
200
+ /**
201
+ * The canvas resize policy determines how the canvas should be resized by Godot.
202
+ *
203
+ * ``0`` means Godot won't do any resizing. This is useful if you want to control the canvas size from
204
+ * javascript code in your template.
205
+ *
206
+ * ``1`` means Godot will resize the canvas on start, and when changing window size via engine functions.
207
+ *
208
+ * ``2`` means Godot will adapt the canvas size to match the whole browser window.
209
+ *
210
+ * @memberof EngineConfig
211
+ * @type {number}
212
+ * @default
213
+ */
214
+ canvasResizePolicy: 2,
215
+ /**
216
+ * The arguments to be passed as command line arguments on startup.
217
+ *
218
+ * See :ref:`command line tutorial <doc_command_line_tutorial>`.
219
+ *
220
+ * **Note**: :js:meth:`startGame <Engine.prototype.startGame>` will always add the ``--main-pack`` argument.
221
+ *
222
+ * @memberof EngineConfig
223
+ * @type {Array<string>}
224
+ * @default
225
+ */
226
+ args: [],
227
+ /**
228
+ * When enabled, the game canvas will automatically grab the focus when the engine starts.
229
+ *
230
+ * @memberof EngineConfig
231
+ * @type {boolean}
232
+ * @default
233
+ */
234
+ focusCanvas: true,
235
+ /**
236
+ * When enabled, this will turn on experimental virtual keyboard support on mobile.
237
+ *
238
+ * @memberof EngineConfig
239
+ * @type {boolean}
240
+ * @default
241
+ */
242
+ experimentalVK: false,
243
+ /**
244
+ * The progressive web app service worker to install.
245
+ * @memberof EngineConfig
246
+ * @default
247
+ * @type {string}
248
+ */
249
+ serviceWorker: '',
250
+ /**
251
+ * @ignore
252
+ * @type {Array.<string>}
253
+ */
254
+ persistentPaths: ['/userfs'],
255
+ /**
256
+ * @ignore
257
+ * @type {boolean}
258
+ */
259
+ persistentDrops: false,
260
+ /**
261
+ * @ignore
262
+ * @type {Array.<string>}
263
+ */
264
+ gdnativeLibs: [],
265
+ /**
266
+ * @ignore
267
+ * @type {Array.<string>}
268
+ */
269
+ fileSizes: [],
270
+ /**
271
+ * A callback function for handling Godot's ``OS.execute`` calls.
272
+ *
273
+ * This is for example used in the Web Editor template to switch between project manager and editor, and for running the game.
274
+ *
275
+ * @callback EngineConfig.onExecute
276
+ * @param {string} path The path that Godot's wants executed.
277
+ * @param {Array.<string>} args The arguments of the "command" to execute.
278
+ */
279
+ /**
280
+ * @ignore
281
+ * @type {?function(string, Array.<string>)}
282
+ */
283
+ onExecute: null,
284
+ /**
285
+ * A callback function for being notified when the Godot instance quits.
286
+ *
287
+ * **Note**: This function will not be called if the engine crashes or become unresponsive.
288
+ *
289
+ * @callback EngineConfig.onExit
290
+ * @param {number} status_code The status code returned by Godot on exit.
291
+ */
292
+ /**
293
+ * @ignore
294
+ * @type {?function(number)}
295
+ */
296
+ onExit: null,
297
+ /**
298
+ * A callback function for displaying download progress.
299
+ *
300
+ * The function is called once per frame while downloading files, so the usage of ``requestAnimationFrame()``
301
+ * is not necessary.
302
+ *
303
+ * If the callback function receives a total amount of bytes as 0, this means that it is impossible to calculate.
304
+ * Possible reasons include:
305
+ *
306
+ * - Files are delivered with server-side chunked compression
307
+ * - Files are delivered with server-side compression on Chromium
308
+ * - Not all file downloads have started yet (usually on servers without multi-threading)
309
+ *
310
+ * @callback EngineConfig.onProgress
311
+ * @param {number} current The current amount of downloaded bytes so far.
312
+ * @param {number} total The total amount of bytes to be downloaded.
313
+ */
314
+ /**
315
+ * @ignore
316
+ * @type {?function(number, number)}
317
+ */
318
+ onProgress: null,
319
+ /**
320
+ * A callback function for handling the standard output stream. This method should usually only be used in debug pages.
321
+ *
322
+ * By default, ``console.log()`` is used.
323
+ *
324
+ * @callback EngineConfig.onPrint
325
+ * @param {...*} [var_args] A variadic number of arguments to be printed.
326
+ */
327
+ /**
328
+ * @ignore
329
+ * @type {?function(...*)}
330
+ */
331
+ onPrint: function () {
332
+ console.log.apply(console, Array.from(arguments)); // eslint-disable-line no-console
333
+ },
334
+ /**
335
+ * A callback function for handling the standard error stream. This method should usually only be used in debug pages.
336
+ *
337
+ * By default, ``console.error()`` is used.
338
+ *
339
+ * @callback EngineConfig.onPrintError
340
+ * @param {...*} [var_args] A variadic number of arguments to be printed as errors.
341
+ */
342
+ /**
343
+ * @ignore
344
+ * @type {?function(...*)}
345
+ */
346
+ onPrintError: function (var_args) {
347
+ console.error.apply(console, Array.from(arguments)); // eslint-disable-line no-console
348
+ },
349
+ };
350
+
351
+ /**
352
+ * @ignore
353
+ * @struct
354
+ * @constructor
355
+ * @param {EngineConfig} opts
356
+ */
357
+ function Config(opts) {
358
+ this.update(opts);
359
+ }
360
+
361
+ Config.prototype = cfg;
362
+
363
+ /**
364
+ * @ignore
365
+ * @param {EngineConfig} opts
366
+ */
367
+ Config.prototype.update = function (opts) {
368
+ const config = opts || {};
369
+ // NOTE: We must explicitly pass the default, accessing it via
370
+ // the key will fail due to closure compiler renames.
371
+ function parse(key, def) {
372
+ if (typeof (config[key]) === 'undefined') {
373
+ return def;
374
+ }
375
+ return config[key];
376
+ }
377
+ // Module config
378
+ this.unloadAfterInit = parse('unloadAfterInit', this.unloadAfterInit);
379
+ this.onPrintError = parse('onPrintError', this.onPrintError);
380
+ this.onPrint = parse('onPrint', this.onPrint);
381
+ this.onProgress = parse('onProgress', this.onProgress);
382
+
383
+ // Godot config
384
+ this.canvas = parse('canvas', this.canvas);
385
+ this.executable = parse('executable', this.executable);
386
+ this.mainPack = parse('mainPack', this.mainPack);
387
+ this.locale = parse('locale', this.locale);
388
+ this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy);
389
+ this.persistentPaths = parse('persistentPaths', this.persistentPaths);
390
+ this.persistentDrops = parse('persistentDrops', this.persistentDrops);
391
+ this.experimentalVK = parse('experimentalVK', this.experimentalVK);
392
+ this.focusCanvas = parse('focusCanvas', this.focusCanvas);
393
+ this.serviceWorker = parse('serviceWorker', this.serviceWorker);
394
+ this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs);
395
+ this.fileSizes = parse('fileSizes', this.fileSizes);
396
+ this.args = parse('args', this.args);
397
+ this.onExecute = parse('onExecute', this.onExecute);
398
+ this.onExit = parse('onExit', this.onExit);
399
+ };
400
+
401
+ /**
402
+ * @ignore
403
+ * @param {string} loadPath
404
+ * @param {Response} response
405
+ */
406
+ Config.prototype.getModuleConfig = function (loadPath, response) {
407
+ let r = response;
408
+ return {
409
+ 'print': this.onPrint,
410
+ 'printErr': this.onPrintError,
411
+ 'thisProgram': this.executable,
412
+ 'noExitRuntime': true,
413
+ 'dynamicLibraries': [`${loadPath}.side.wasm`],
414
+ 'instantiateWasm': function (imports, onSuccess) {
415
+ function done(result) {
416
+ onSuccess(result['instance'], result['module']);
417
+ }
418
+ if (typeof (WebAssembly.instantiateStreaming) !== 'undefined') {
419
+ WebAssembly.instantiateStreaming(Promise.resolve(r), imports).then(done);
420
+ } else {
421
+ r.arrayBuffer().then(function (buffer) {
422
+ WebAssembly.instantiate(buffer, imports).then(done);
423
+ });
424
+ }
425
+ r = null;
426
+ return {};
427
+ },
428
+ 'locateFile': function (path) {
429
+ if (path.endsWith('.worker.js')) {
430
+ return `${loadPath}.worker.js`;
431
+ } else if (path.endsWith('.audio.worklet.js')) {
432
+ return `${loadPath}.audio.worklet.js`;
433
+ } else if (path.endsWith('.js')) {
434
+ return `${loadPath}.js`;
435
+ } else if (path.endsWith('.side.wasm')) {
436
+ return `${loadPath}.side.wasm`;
437
+ } else if (path.endsWith('.wasm')) {
438
+ return `${loadPath}.wasm`;
439
+ }
440
+ return path;
441
+ },
442
+ };
443
+ };
444
+
445
+ /**
446
+ * @ignore
447
+ * @param {function()} cleanup
448
+ */
449
+ Config.prototype.getGodotConfig = function (cleanup) {
450
+ // Try to find a canvas
451
+ if (!(this.canvas instanceof HTMLCanvasElement)) {
452
+ const nodes = document.getElementsByTagName('canvas');
453
+ if (nodes.length && nodes[0] instanceof HTMLCanvasElement) {
454
+ this.canvas = nodes[0];
455
+ }
456
+ if (!this.canvas) {
457
+ throw new Error('No canvas found in page');
458
+ }
459
+ }
460
+ // Canvas can grab focus on click, or key events won't work.
461
+ if (this.canvas.tabIndex < 0) {
462
+ this.canvas.tabIndex = 0;
463
+ }
464
+
465
+ // Browser locale, or custom one if defined.
466
+ let locale = this.locale;
467
+ if (!locale) {
468
+ locale = navigator.languages ? navigator.languages[0] : navigator.language;
469
+ locale = locale.split('.')[0];
470
+ }
471
+ const onExit = this.onExit;
472
+
473
+ // Godot configuration.
474
+ return {
475
+ 'canvas': this.canvas,
476
+ 'canvasResizePolicy': this.canvasResizePolicy,
477
+ 'locale': locale,
478
+ 'persistentDrops': this.persistentDrops,
479
+ 'virtualKeyboard': this.experimentalVK,
480
+ 'focusCanvas': this.focusCanvas,
481
+ 'onExecute': this.onExecute,
482
+ 'onExit': function (p_code) {
483
+ cleanup(); // We always need to call the cleanup callback to free memory.
484
+ if (typeof (onExit) === 'function') {
485
+ onExit(p_code);
486
+ }
487
+ },
488
+ };
489
+ };
490
+ return new Config(initConfig);
491
+ };
492
+
493
+ /**
494
+ * Projects exported for the Web expose the :js:class:`Engine` class to the JavaScript environment, that allows
495
+ * fine control over the engine's start-up process.
496
+ *
497
+ * This API is built in an asynchronous manner and requires basic understanding
498
+ * of `Promises <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises>`__.
499
+ *
500
+ * @module Engine
501
+ * @header HTML5 shell class reference
502
+ */
503
+ const Engine = (function () {
504
+ const preloader = new Preloader();
505
+
506
+ let loadPromise = null;
507
+ let loadPath = '';
508
+ let initPromise = null;
509
+
510
+ /**
511
+ * @classdesc The ``Engine`` class provides methods for loading and starting exported projects on the Web. For default export
512
+ * settings, this is already part of the exported HTML page. To understand practical use of the ``Engine`` class,
513
+ * see :ref:`Custom HTML page for Web export <doc_customizing_html5_shell>`.
514
+ *
515
+ * @description Create a new Engine instance with the given configuration.
516
+ *
517
+ * @global
518
+ * @constructor
519
+ * @param {EngineConfig} initConfig The initial config for this instance.
520
+ */
521
+ function Engine(initConfig) { // eslint-disable-line no-shadow
522
+ this.config = new InternalConfig(initConfig);
523
+ this.rtenv = null;
524
+ }
525
+
526
+ /**
527
+ * Load the engine from the specified base path.
528
+ *
529
+ * @param {string} basePath Base path of the engine to load.
530
+ * @param {number=} [size=0] The file size if known.
531
+ * @returns {Promise} A Promise that resolves once the engine is loaded.
532
+ *
533
+ * @function Engine.load
534
+ */
535
+ Engine.load = function (basePath, size) {
536
+ if (loadPromise == null) {
537
+ loadPath = basePath;
538
+ loadPromise = preloader.loadPromise(`${loadPath}.wasm`, size, true);
539
+ requestAnimationFrame(preloader.animateProgress);
540
+ }
541
+ return loadPromise;
542
+ };
543
+
544
+ /**
545
+ * Unload the engine to free memory.
546
+ *
547
+ * This method will be called automatically depending on the configuration. See :js:attr:`unloadAfterInit`.
548
+ *
549
+ * @function Engine.unload
550
+ */
551
+ Engine.unload = function () {
552
+ loadPromise = null;
553
+ };
554
+
555
+ /**
556
+ * Check whether WebGL is available. Optionally, specify a particular version of WebGL to check for.
557
+ *
558
+ * @param {number=} [majorVersion=1] The major WebGL version to check for.
559
+ * @returns {boolean} If the given major version of WebGL is available.
560
+ * @function Engine.isWebGLAvailable
561
+ */
562
+ Engine.isWebGLAvailable = function (majorVersion = 1) {
563
+ try {
564
+ return !!document.createElement('canvas').getContext(['webgl', 'webgl2'][majorVersion - 1]);
565
+ } catch (e) { /* Not available */ }
566
+ return false;
567
+ };
568
+
569
+ /**
570
+ * Safe Engine constructor, creates a new prototype for every new instance to avoid prototype pollution.
571
+ * @ignore
572
+ * @constructor
573
+ */
574
+ function SafeEngine(initConfig) {
575
+ const proto = /** @lends Engine.prototype */ {
576
+ /**
577
+ * Initialize the engine instance. Optionally, pass the base path to the engine to load it,
578
+ * if it hasn't been loaded yet. See :js:meth:`Engine.load`.
579
+ *
580
+ * @param {string=} basePath Base path of the engine to load.
581
+ * @return {Promise} A ``Promise`` that resolves once the engine is loaded and initialized.
582
+ */
583
+ init: function (basePath) {
584
+ if (initPromise) {
585
+ return initPromise;
586
+ }
587
+ if (loadPromise == null) {
588
+ if (!basePath) {
589
+ initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.'));
590
+ return initPromise;
591
+ }
592
+ Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]);
593
+ }
594
+ const me = this;
595
+ function doInit(promise) {
596
+ // Care! Promise chaining is bogus with old emscripten versions.
597
+ // This caused a regression with the Mono build (which uses an older emscripten version).
598
+ // Make sure to test that when refactoring.
599
+ return new Promise(function (resolve, reject) {
600
+ promise.then(function (response) {
601
+ const cloned = new Response(response.clone().body, { 'headers': [['content-type', 'application/wasm']] });
602
+ Godot(me.config.getModuleConfig(loadPath, cloned)).then(function (module) {
603
+ const paths = me.config.persistentPaths;
604
+ module['initFS'](paths).then(function (err) {
605
+ me.rtenv = module;
606
+ if (me.config.unloadAfterInit) {
607
+ Engine.unload();
608
+ }
609
+ resolve();
610
+ });
611
+ });
612
+ });
613
+ });
614
+ }
615
+ preloader.setProgressFunc(this.config.onProgress);
616
+ initPromise = doInit(loadPromise);
617
+ return initPromise;
618
+ },
619
+
620
+ /**
621
+ * Load a file so it is available in the instance's file system once it runs. Must be called **before** starting the
622
+ * instance.
623
+ *
624
+ * If not provided, the ``path`` is derived from the URL of the loaded file.
625
+ *
626
+ * @param {string|ArrayBuffer} file The file to preload.
627
+ *
628
+ * If a ``string`` the file will be loaded from that path.
629
+ *
630
+ * If an ``ArrayBuffer`` or a view on one, the buffer will used as the content of the file.
631
+ *
632
+ * @param {string=} path Path by which the file will be accessible. Required, if ``file`` is not a string.
633
+ *
634
+ * @returns {Promise} A Promise that resolves once the file is loaded.
635
+ */
636
+ preloadFile: function (file, path) {
637
+ return preloader.preload(file, path, this.config.fileSizes[file]);
638
+ },
639
+
640
+ /**
641
+ * Start the engine instance using the given override configuration (if any).
642
+ * :js:meth:`startGame <Engine.prototype.startGame>` can be used in typical cases instead.
643
+ *
644
+ * This will initialize the instance if it is not initialized. For manual initialization, see :js:meth:`init <Engine.prototype.init>`.
645
+ * The engine must be loaded beforehand.
646
+ *
647
+ * Fails if a canvas cannot be found on the page, or not specified in the configuration.
648
+ *
649
+ * @param {EngineConfig} override An optional configuration override.
650
+ * @return {Promise} Promise that resolves once the engine started.
651
+ */
652
+ start: function (override) {
653
+ this.config.update(override);
654
+ const me = this;
655
+ return me.init().then(function () {
656
+ if (!me.rtenv) {
657
+ return Promise.reject(new Error('The engine must be initialized before it can be started'));
658
+ }
659
+
660
+ let config = {};
661
+ try {
662
+ config = me.config.getGodotConfig(function () {
663
+ me.rtenv = null;
664
+ });
665
+ } catch (e) {
666
+ return Promise.reject(e);
667
+ }
668
+ // Godot configuration.
669
+ me.rtenv['initConfig'](config);
670
+
671
+ // Preload GDNative libraries.
672
+ const libs = [];
673
+ me.config.gdnativeLibs.forEach(function (lib) {
674
+ libs.push(me.rtenv['loadDynamicLibrary'](lib, { 'loadAsync': true }));
675
+ });
676
+ return Promise.all(libs).then(function () {
677
+ return new Promise(function (resolve, reject) {
678
+ preloader.preloadedFiles.forEach(function (file) {
679
+ me.rtenv['copyToFS'](file.path, file.buffer);
680
+ });
681
+ preloader.preloadedFiles.length = 0; // Clear memory
682
+ me.rtenv['callMain'](me.config.args);
683
+ initPromise = null;
684
+ if (me.config.serviceWorker && 'serviceWorker' in navigator) {
685
+ navigator.serviceWorker.register(me.config.serviceWorker);
686
+ }
687
+ resolve();
688
+ });
689
+ });
690
+ });
691
+ },
692
+
693
+ /**
694
+ * Start the game instance using the given configuration override (if any).
695
+ *
696
+ * This will initialize the instance if it is not initialized. For manual initialization, see :js:meth:`init <Engine.prototype.init>`.
697
+ *
698
+ * This will load the engine if it is not loaded, and preload the main pck.
699
+ *
700
+ * This method expects the initial config (or the override) to have both the :js:attr:`executable` and :js:attr:`mainPack`
701
+ * properties set (normally done by the editor during export).
702
+ *
703
+ * @param {EngineConfig} override An optional configuration override.
704
+ * @return {Promise} Promise that resolves once the game started.
705
+ */
706
+ startGame: function (override) {
707
+ this.config.update(override);
708
+ // Add main-pack argument.
709
+ const exe = this.config.executable;
710
+ const pack = this.config.mainPack || `${exe}.pck`;
711
+ this.config.args = ['--main-pack', pack].concat(this.config.args);
712
+ // Start and init with execName as loadPath if not inited.
713
+ const me = this;
714
+ return Promise.all([
715
+ this.init(exe),
716
+ this.preloadFile(pack, pack),
717
+ ]).then(function () {
718
+ return me.start.apply(me);
719
+ });
720
+ },
721
+
722
+ /**
723
+ * Create a file at the specified ``path`` with the passed as ``buffer`` in the instance's file system.
724
+ *
725
+ * @param {string} path The location where the file will be created.
726
+ * @param {ArrayBuffer} buffer The content of the file.
727
+ */
728
+ copyToFS: function (path, buffer) {
729
+ if (this.rtenv == null) {
730
+ throw new Error('Engine must be inited before copying files');
731
+ }
732
+ this.rtenv['copyToFS'](path, buffer);
733
+ },
734
+
735
+ /**
736
+ * Request that the current instance quit.
737
+ *
738
+ * This is akin the user pressing the close button in the window manager, and will
739
+ * have no effect if the engine has crashed, or is stuck in a loop.
740
+ *
741
+ */
742
+ requestQuit: function () {
743
+ if (this.rtenv) {
744
+ this.rtenv['request_quit']();
745
+ }
746
+ },
747
+ };
748
+
749
+ Engine.prototype = proto;
750
+ // Closure compiler exported instance methods.
751
+ Engine.prototype['init'] = Engine.prototype.init;
752
+ Engine.prototype['preloadFile'] = Engine.prototype.preloadFile;
753
+ Engine.prototype['start'] = Engine.prototype.start;
754
+ Engine.prototype['startGame'] = Engine.prototype.startGame;
755
+ Engine.prototype['copyToFS'] = Engine.prototype.copyToFS;
756
+ Engine.prototype['requestQuit'] = Engine.prototype.requestQuit;
757
+ // Also expose static methods as instance methods
758
+ Engine.prototype['load'] = Engine.load;
759
+ Engine.prototype['unload'] = Engine.unload;
760
+ Engine.prototype['isWebGLAvailable'] = Engine.isWebGLAvailable;
761
+ return new Engine(initConfig);
762
+ }
763
+
764
+ // Closure compiler exported static methods.
765
+ SafeEngine['load'] = Engine.load;
766
+ SafeEngine['unload'] = Engine.unload;
767
+ SafeEngine['isWebGLAvailable'] = Engine.isWebGLAvailable;
768
+
769
+ return SafeEngine;
770
+ }());
771
+ if (typeof window !== 'undefined') {
772
+ window['Engine'] = Engine;
773
+ }
godot.javascript.opt.tools.threads.js ADDED
The diff for this file is too large to render. See raw diff
 
godot.javascript.opt.tools.threads.service.worker.js ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // This service worker is required to expose an exported Godot project as a
2
+ // Progressive Web App. It provides an offline fallback page telling the user
3
+ // that they need an Internet connection to run the project if desired.
4
+ // Incrementing CACHE_VERSION will kick off the install event and force
5
+ // previously cached resources to be updated from the network.
6
+ const CACHE_VERSION = "4.0.alpha.custom_build";
7
+ const CACHE_PREFIX = "GodotEngine-sw-cache-";
8
+ const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION;
9
+ const OFFLINE_URL = "offline.html";
10
+ // Files that will be cached on load.
11
+ const CACHED_FILES = ["godot.tools.html", "offline.html", "godot.tools.js", "godot.tools.worker.js", "godot.tools.audio.worklet.js", "logo.svg", "favicon.png"];
12
+ // Files that we might not want the user to preload, and will only be cached on first load.
13
+ const CACHABLE_FILES = ["godot.tools.wasm"];
14
+ const FULL_CACHE = CACHED_FILES.concat(CACHABLE_FILES);
15
+
16
+ self.addEventListener("install", (event) => {
17
+ event.waitUntil(caches.open(CACHE_NAME).then(cache => cache.addAll(CACHED_FILES)));
18
+ });
19
+
20
+ self.addEventListener("activate", (event) => {
21
+ event.waitUntil(caches.keys().then(
22
+ function (keys) {
23
+ // Remove old caches.
24
+ return Promise.all(keys.filter(key => key.startsWith(CACHE_PREFIX) && key != CACHE_NAME).map(key => caches.delete(key)));
25
+ }).then(function() {
26
+ // Enable navigation preload if available.
27
+ return ("navigationPreload" in self.registration) ? self.registration.navigationPreload.enable() : Promise.resolve();
28
+ })
29
+ );
30
+ });
31
+
32
+ async function fetchAndCache(event, cache, isCachable) {
33
+ // Use the preloaded response, if it's there
34
+ let response = await event.preloadResponse;
35
+ if (!response) {
36
+ // Or, go over network.
37
+ response = await self.fetch(event.request);
38
+ }
39
+ if (isCachable) {
40
+ // And update the cache
41
+ cache.put(event.request, response.clone());
42
+ }
43
+ return response;
44
+ }
45
+
46
+ self.addEventListener("fetch", (event) => {
47
+ const isNavigate = event.request.mode === "navigate";
48
+ const url = event.request.url || "";
49
+ const referrer = event.request.referrer || "";
50
+ const base = referrer.slice(0, referrer.lastIndexOf("/") + 1);
51
+ const local = url.startsWith(base) ? url.replace(base, "") : "";
52
+ const isCachable = FULL_CACHE.some(v => v === local) || (base === referrer && base.endsWith(CACHED_FILES[0]));
53
+ if (isNavigate || isCachable) {
54
+ event.respondWith(async function () {
55
+ // Try to use cache first
56
+ const cache = await caches.open(CACHE_NAME);
57
+ if (event.request.mode === "navigate") {
58
+ // Check if we have full cache during HTML page request.
59
+ const fullCache = await Promise.all(FULL_CACHE.map(name => cache.match(name)));
60
+ const missing = fullCache.some(v => v === undefined);
61
+ if (missing) {
62
+ try {
63
+ // Try network if some cached file is missing (so we can display offline page in case).
64
+ return await fetchAndCache(event, cache, isCachable);
65
+ } catch (e) {
66
+ // And return the hopefully always cached offline page in case of network failure.
67
+ console.error("Network error: ", e);
68
+ return await caches.match(OFFLINE_URL);
69
+ }
70
+ }
71
+ }
72
+ const cached = await cache.match(event.request);
73
+ if (cached) {
74
+ return cached;
75
+ } else {
76
+ // Try network if don't have it in cache.
77
+ return await fetchAndCache(event, cache, isCachable);
78
+ }
79
+ }());
80
+ }
81
+ });
82
+
83
+ self.addEventListener("message", (event) => {
84
+ // No cross origin
85
+ if (event.origin != self.origin) {
86
+ return;
87
+ }
88
+ const id = event.source.id || "";
89
+ const msg = event.data || "";
90
+ // Ensure it's one of our clients.
91
+ self.clients.get(id).then(function (client) {
92
+ if (!client) {
93
+ return; // Not a valid client.
94
+ }
95
+ if (msg === "claim") {
96
+ self.skipWaiting().then(() => self.clients.claim());
97
+ } else if (msg === "clear") {
98
+ caches.delete(CACHE_NAME);
99
+ } else if (msg === "update") {
100
+ self.skipWaiting().then(() => self.clients.claim()).then(() => self.clients.matchAll()).then(all => all.forEach(c => c.navigate(c.url)));
101
+ } else {
102
+ onClientMessage(event);
103
+ }
104
+ });
105
+ });
godot.javascript.opt.tools.threads.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b78c77d76f67d77ed74206ab81993952200f85c344c59e1403d69535783fd5a7
3
+ size 57390540
godot.javascript.opt.tools.threads.worker.js ADDED
@@ -0,0 +1 @@
 
 
1
+ "use strict";var Module={};function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=((info,receiveInstance)=>{var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports});self.onmessage=(e=>{try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob=="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}Godot(Module).then(function(instance){Module=instance})}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0,1);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["__emscripten_thread_exit"](result)}}catch(ex){if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["__emscripten_thread_exit"](ex.status)}}else{throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else if(e.data.cmd==="processProxyingQueue"){if(Module["_pthread_self"]()){Module["_emscripten_proxy_execute_queue"](e.data.queue)}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);if(Module["__emscripten_thread_crashed"]){Module["__emscripten_thread_crashed"]()}throw ex}});
godot.javascript.opt.tools.threads.wrapped.js ADDED
The diff for this file is too large to render. See raw diff
 
godot.javascript.opt.tools.threads.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:138d96d977649424c38c3ca82b869c6e1f187afbc86606cdf2b680ab1e453680
3
+ size 23833082
index.html CHANGED
@@ -1,24 +1,714 @@
1
  <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>
13
- You can modify this app directly by editing <i>index.html</i> in the
14
- Files and versions tab.
15
- </p>
16
- <p>
17
- Also don't forget to check the
18
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank"
19
- >Spaces documentation</a
20
- >.
21
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  </div>
23
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  </html>
 
1
  <!DOCTYPE html>
2
+ <html xmlns="https://www.w3.org/1999/xhtml" lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
6
+ <meta name="author" content="Godot Engine" />
7
+ <meta name="description" content="Use the Godot Engine editor directly in your web browser, without having to install anything." />
8
+ <meta name="mobile-web-app-capable" content="yes" />
9
+ <meta name="apple-mobile-web-app-capable" content="yes" />
10
+ <meta name="application-name" content="Godot" />
11
+ <meta name="apple-mobile-web-app-title" content="Godot" />
12
+ <meta name="theme-color" content="#202531" />
13
+ <meta name="msapplication-navbutton-color" content="#202531" />
14
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
15
+ <meta name="msapplication-starturl" content="/latest" />
16
+ <meta property="og:site_name" content="Godot Engine Web Editor" />
17
+ <meta property="og:url" name="twitter:url" content="https://editor.godotengine.org/releases/latest/" />
18
+ <meta property="og:title" name="twitter:title" content="Free and open source 2D and 3D game engine" />
19
+ <meta property="og:description" name="twitter:description" content="Use the Godot Engine editor directly in your web browser, without having to install anything." />
20
+ <meta property="og:image" name="twitter:image" content="https://godotengine.org/themes/godotengine/assets/og_image.png" />
21
+ <meta property="og:type" content="website" />
22
+ <meta name="twitter:card" content="summary" />
23
+ <link id="-gd-engine-icon" rel="icon" type="image/png" href="favicon.png" />
24
+ <link rel="apple-touch-icon" type="image/png" href="favicon.png" />
25
+ <link rel="manifest" href="manifest.json" />
26
+ <title>Godot Engine Web Editor (4.0.alpha.custom_build)</title>
27
+ <style>
28
+ *:focus {
29
+ /* More visible outline for better keyboard navigation. */
30
+ outline: 0.125rem solid hsl(220, 100%, 62.5%);
31
+ /* Make the outline always appear above other elements. */
32
+ /* Otherwise, one of its sides can be hidden by tabs in the Download and More layouts. */
33
+ position: relative;
34
+ }
35
+
36
+ body {
37
+ touch-action: none;
38
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
39
+ margin: 0;
40
+ border: 0 none;
41
+ padding: 0;
42
+ text-align: center;
43
+ background-color: #333b4f;
44
+ overflow: hidden;
45
+ }
46
+
47
+ a {
48
+ color: hsl(205, 100%, 75%);
49
+ text-decoration-color: hsla(205, 100%, 75%, 0.3);
50
+ text-decoration-thickness: 0.125rem;
51
+ }
52
+
53
+ a:hover {
54
+ filter: brightness(117.5%);
55
+ }
56
+
57
+ a:active {
58
+ filter: brightness(82.5%);
59
+ }
60
+
61
+ .welcome-modal {
62
+ display: none;
63
+ position: fixed;
64
+ z-index: 1;
65
+ left: 0;
66
+ top: 0;
67
+ width: 100%;
68
+ height: 100%;
69
+ overflow: auto;
70
+ background-color: hsla(0, 0%, 0%, 0.5);
71
+ }
72
+
73
+ .welcome-modal-content {
74
+ background-color: #333b4f;
75
+ box-shadow: 0 0.25rem 0.25rem hsla(0, 0%, 0%, 0.5);
76
+ line-height: 1.5;
77
+ max-width: 38rem;
78
+ margin: 4rem auto 0 auto;
79
+ color: white;
80
+ border-radius: 0.5rem;
81
+ padding: 1rem 1rem 2rem 1rem;
82
+ }
83
+
84
+ #tabs-buttons {
85
+ /* Match the default background color of the editor window for a seamless appearance. */
86
+ background-color: #202531;
87
+ }
88
+
89
+ #tab-game {
90
+ /* Use a pure black background to better distinguish the running project */
91
+ /* from the editor window, and to use a more neutral background color (no tint). */
92
+ background-color: black;
93
+ /* Make the background span the entire page height. */
94
+ min-height: 100vh;
95
+ }
96
+
97
+ #canvas, #gameCanvas {
98
+ display: block;
99
+ margin: 0;
100
+ color: white;
101
+ }
102
+
103
+ /* Don't show distracting focus outlines for the main tabs' contents. */
104
+ #tab-editor canvas:focus,
105
+ #tab-game canvas:focus,
106
+ #canvas:focus,
107
+ #gameCanvas:focus {
108
+ outline: none;
109
+ }
110
+
111
+ .godot {
112
+ color: #e0e0e0;
113
+ background-color: #3b3943;
114
+ background-image: linear-gradient(to bottom, #403e48, #35333c);
115
+ border: 1px solid #45434e;
116
+ box-shadow: 0 0 1px 1px #2f2d35;
117
+ }
118
+
119
+ .btn {
120
+ appearance: none;
121
+ color: #e0e0e0;
122
+ background-color: #262c3b;
123
+ border: 1px solid #202531;
124
+ padding: 0.5rem 1rem;
125
+ margin: 0 0.5rem;
126
+ }
127
+
128
+ .btn:not(:disabled):hover {
129
+ color: #e0e1e5;
130
+ border-color: #666c7b;
131
+ }
132
+
133
+ .btn:active {
134
+ border-color: #699ce8;
135
+ color: #699ce8;
136
+ }
137
+
138
+ .btn:disabled {
139
+ color: #aaa;
140
+ border-color: #242937;
141
+ }
142
+
143
+ .btn.tab-btn {
144
+ padding: 0.3rem 1rem;
145
+ }
146
+
147
+ .btn.close-btn {
148
+ padding: 0.3rem 1rem;
149
+ margin-left: -0.75rem;
150
+ font-weight: 700;
151
+ }
152
+
153
+
154
+ /* Status display
155
+ * ============== */
156
+
157
+ #status {
158
+ position: absolute;
159
+ left: 0;
160
+ top: 0;
161
+ right: 0;
162
+ bottom: 0;
163
+ display: flex;
164
+ justify-content: center;
165
+ align-items: center;
166
+ /* don't consume click events - make children visible explicitly */
167
+ visibility: hidden;
168
+ }
169
+
170
+ #status-progress {
171
+ width: 366px;
172
+ height: 7px;
173
+ background-color: #38363A;
174
+ border: 1px solid #444246;
175
+ padding: 1px;
176
+ box-shadow: 0 0 2px 1px #1B1C22;
177
+ border-radius: 2px;
178
+ visibility: visible;
179
+ }
180
+
181
+ @media only screen and (orientation:portrait) {
182
+ #status-progress {
183
+ width: 61.8%;
184
+ }
185
+ }
186
+
187
+ #status-progress-inner {
188
+ height: 100%;
189
+ width: 0;
190
+ box-sizing: border-box;
191
+ transition: width 0.5s linear;
192
+ background-color: #202020;
193
+ border: 1px solid #222223;
194
+ box-shadow: 0 0 1px 1px #27282E;
195
+ border-radius: 3px;
196
+ }
197
+
198
+ #status-indeterminate {
199
+ visibility: visible;
200
+ position: relative;
201
+ }
202
+
203
+ #status-indeterminate > div {
204
+ width: 4.5px;
205
+ height: 0;
206
+ border-style: solid;
207
+ border-width: 9px 3px 0 3px;
208
+ border-color: #2b2b2b transparent transparent transparent;
209
+ transform-origin: center 21px;
210
+ position: absolute;
211
+ }
212
+
213
+ #status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); }
214
+ #status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); }
215
+ #status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); }
216
+ #status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); }
217
+ #status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); }
218
+ #status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); }
219
+ #status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); }
220
+ #status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); }
221
+
222
+ #status-notice {
223
+ margin: 0 100px;
224
+ line-height: 1.3;
225
+ visibility: visible;
226
+ padding: 4px 6px;
227
+ visibility: visible;
228
+ }
229
+ </style>
230
+ </head>
231
+ <body>
232
+ <div
233
+ id="welcome-modal"
234
+ class="welcome-modal"
235
+ role="dialog"
236
+ aria-labelledby="welcome-modal-title"
237
+ aria-describedby="welcome-modal-description"
238
+ onclick="if (event.target === this) closeWelcomeModal(false)"
239
+ >
240
+ <div class="welcome-modal-content">
241
+ <h2 id="welcome-modal-title">Important - Please read before continuing</h2>
242
+ <div id="welcome-modal-description">
243
+ <p>
244
+ The Godot Web Editor has some limitations compared to the native version.
245
+ Its main focus is education and experimentation;
246
+ <strong>it is not recommended for production</strong>.
247
+ </p>
248
+ <p>
249
+ Refer to the
250
+ <a
251
+ href="https://docs.godotengine.org/en/latest/tutorials/editor/using_the_web_editor.html"
252
+ target="_blank"
253
+ rel="noopener"
254
+ >Web editor documentation</a> for usage instructions and limitations.
255
+ </p>
256
+ </div>
257
+ <button id="welcome-modal-dismiss" class="btn" type="button" onclick="closeWelcomeModal(true)" style="margin-top: 1rem">
258
+ OK, don't show again
259
+ </button>
260
  </div>
261
+ </div>
262
+ <div id="tabs-buttons">
263
+ <button id="btn-tab-loader" class="btn tab-btn" onclick="showTab('loader')">Loader</button>
264
+ <button id="btn-tab-editor" class="btn tab-btn" disabled="disabled" onclick="showTab('editor')">Editor</button>
265
+ <button id="btn-close-editor" class="btn close-btn" disabled="disabled" onclick="closeEditor()">×</button>
266
+ <button id="btn-tab-game" class="btn tab-btn" disabled="disabled" onclick="showTab('game')">Game</button>
267
+ <button id="btn-close-game" class="btn close-btn" disabled="disabled" onclick="closeGame()">×</button>
268
+ <button id="btn-tab-update" class="btn tab-btn" style="display: none;">Update</button>
269
+ </div>
270
+ <div id="tabs">
271
+ <div id="tab-loader">
272
+ <div style="color: #e0e0e0;" id="persistence">
273
+ <br />
274
+ <img src="logo.svg" alt="Godot Engine logo" width="1024" height="414" style="width: auto; height: auto; max-width: min(85%, 50vh); max-height: 250px" />
275
+ <br />
276
+ 4.0.alpha.custom_build
277
+ <br />
278
+ <a href="releases/">Need an old version?</a>
279
+ <br />
280
+ <br />
281
+ <br />
282
+ <label for="videoMode" style="margin-right: 1rem">Video driver:</label>
283
+ <select id="videoMode">
284
+ <option value="" selected="selected">Auto</option>
285
+ <option value="opengl3">WebGL 2</option>
286
+ </select>
287
+ <br />
288
+ <br />
289
+ <label for="zip-file" style="margin-right: 1rem">Preload project ZIP:</label> <input id="zip-file" type="file" name="files" style="margin-bottom: 1rem"/>
290
+ <br />
291
+ <a href="demo.zip">(Try this for example)</a>
292
+ <br />
293
+ <br />
294
+ <button id="startButton" class="btn" style="margin-bottom: 4rem; font-weight: 700">Start Godot editor</button>
295
+ <br />
296
+ <button class="btn" onclick="clearPersistence()" style="margin-bottom: 1.5rem">Clear persistent data</button>
297
+ <br />
298
+ <a href="https://docs.godotengine.org/en/latest/tutorials/editor/using_the_web_editor.html">Web editor documentation</a>
299
+ </div>
300
+ </div>
301
+ <div id="tab-editor" style="display: none;">
302
+ <canvas id="editor-canvas" tabindex="1">
303
+ HTML5 canvas appears to be unsupported in the current browser.<br />
304
+ Please try updating or use a different browser.
305
+ </canvas>
306
+ </div>
307
+ <div id="tab-game" style="display: none;">
308
+ <canvas id="game-canvas" tabindex="2">
309
+ HTML5 canvas appears to be unsupported in the current browser.<br />
310
+ Please try updating or use a different browser.
311
+ </canvas>
312
+ </div>
313
+ <div id="tab-status" style="display: none;">
314
+ <div id="status-progress" style="display: none;" oncontextmenu="event.preventDefault();"><div id="status-progress-inner"></div></div>
315
+ <div id="status-indeterminate" style="display: none;" oncontextmenu="event.preventDefault();">
316
+ <div></div>
317
+ <div></div>
318
+ <div></div>
319
+ <div></div>
320
+ <div></div>
321
+ <div></div>
322
+ <div></div>
323
+ <div></div>
324
+ </div>
325
+ <div id="status-notice" class="godot" style="display: none;"></div>
326
+ </div>
327
+ </div>
328
+ <script>//<![CDATA[
329
+ window.addEventListener("load", () => {
330
+ function notifyUpdate(sw) {
331
+ const btn = document.getElementById("btn-tab-update");
332
+ btn.onclick = function () {
333
+ if (!window.confirm("Are you sure you want to update?\nClicking \"OK\" will reload all active instances!")) {
334
+ return;
335
+ }
336
+ sw.postMessage("update");
337
+ btn.innerHTML = "Updating...";
338
+ btn.disabled = true;
339
+ };
340
+ btn.style.display = "";
341
+ }
342
+ if ("serviceWorker" in navigator) {
343
+ navigator.serviceWorker.register("service.worker.js").then(function (reg) {
344
+ if (reg.waiting) {
345
+ notifyUpdate(reg.waiting);
346
+ }
347
+ reg.addEventListener("updatefound", function () {
348
+ const update = reg.installing;
349
+ update.addEventListener("statechange", function () {
350
+ if (update.state === "installed") {
351
+ // It's a new install, claim and perform aggressive caching.
352
+ if (!reg.active) {
353
+ update.postMessage("claim");
354
+ } else {
355
+ notifyUpdate(update);
356
+ }
357
+ }
358
+ });
359
+ });
360
+ });
361
+ }
362
+
363
+ if (localStorage.getItem("welcomeModalDismissed") !== 'true') {
364
+ document.getElementById("welcome-modal").style.display = "block";
365
+ document.getElementById("welcome-modal-dismiss").focus();
366
+ }
367
+ });
368
+
369
+ function closeWelcomeModal(dontShowAgain) {
370
+ document.getElementById("welcome-modal").style.display = "none";
371
+ if (dontShowAgain) {
372
+ localStorage.setItem("welcomeModalDismissed", 'true');
373
+ }
374
+ }
375
+ //]]></script>
376
+ <script src="godot.tools.js"></script>
377
+ <script>//<![CDATA[
378
+
379
+ var editor = null;
380
+ var game = null;
381
+ var setStatusMode;
382
+ var setStatusNotice;
383
+ var video_driver = "";
384
+
385
+ function clearPersistence() {
386
+ function deleteDB(path) {
387
+ return new Promise(function(resolve, reject) {
388
+ var req = indexedDB.deleteDatabase(path);
389
+ req.onsuccess = function() {
390
+ resolve();
391
+ };
392
+ req.onerror = function(err) {
393
+ reject(err);
394
+ };
395
+ req.onblocked = function(err) {
396
+ reject(err);
397
+ }
398
+
399
+ });
400
+ }
401
+ if (!window.confirm("Are you sure you want to delete all the locally stored files?\nClicking \"OK\" will permanently remove your projects and editor settings!")) {
402
+ return;
403
+ }
404
+ Promise.all([
405
+ deleteDB("/home/web_user"),
406
+ ]).then(function(results) {
407
+ alert("Done.");
408
+ }).catch(function (err) {
409
+ alert("Error deleting local files. Please retry after reloading the page.");
410
+ });
411
+ }
412
+
413
+ function selectVideoMode() {
414
+ var select = document.getElementById('videoMode');
415
+ video_driver = select.selectedOptions[0].value;
416
+ }
417
+
418
+ var tabs = [
419
+ document.getElementById('tab-loader'),
420
+ document.getElementById('tab-editor'),
421
+ document.getElementById('tab-game')
422
+ ]
423
+ function showTab(name) {
424
+ tabs.forEach(function (elem) {
425
+ if (elem.id == 'tab-' + name) {
426
+ elem.style.display = 'block';
427
+ if (name == 'editor' || name == 'game') {
428
+ const canvas = document.getElementById(name + '-canvas');
429
+ canvas.focus();
430
+ }
431
+ } else {
432
+ elem.style.display = 'none';
433
+ }
434
+ });
435
+ }
436
+
437
+ function setButtonEnabled(id, enabled) {
438
+ if (enabled) {
439
+ document.getElementById(id).disabled = "";
440
+ } else {
441
+ document.getElementById(id).disabled = "disabled";
442
+ }
443
+ }
444
+
445
+ function setLoaderEnabled(enabled) {
446
+ setButtonEnabled('btn-tab-loader', enabled);
447
+ setButtonEnabled('btn-tab-editor', !enabled);
448
+ setButtonEnabled('btn-close-editor', !enabled);
449
+ }
450
+
451
+ function setGameTabEnabled(enabled) {
452
+ setButtonEnabled('btn-tab-game', enabled);
453
+ setButtonEnabled('btn-close-game', enabled);
454
+ }
455
+
456
+ function closeGame() {
457
+ if (game) {
458
+ game.requestQuit();
459
+ }
460
+ }
461
+
462
+ function closeEditor() {
463
+ closeGame();
464
+ if (editor) {
465
+ editor.requestQuit();
466
+ }
467
+ }
468
+
469
+ function startEditor(zip) {
470
+ const INDETERMINATE_STATUS_STEP_MS = 100;
471
+ const persistentPaths = ['/home/web_user'];
472
+
473
+ var editorCanvas = document.getElementById('editor-canvas');
474
+ var gameCanvas = document.getElementById('game-canvas');
475
+ var statusProgress = document.getElementById('status-progress');
476
+ var statusProgressInner = document.getElementById('status-progress-inner');
477
+ var statusIndeterminate = document.getElementById('status-indeterminate');
478
+ var statusNotice = document.getElementById('status-notice');
479
+ var headerDiv = document.getElementById('tabs-buttons');
480
+
481
+ var initializing = true;
482
+ var statusMode = 'hidden';
483
+
484
+ showTab('status');
485
+
486
+ var animationCallbacks = [];
487
+ function animate(time) {
488
+ animationCallbacks.forEach(callback => callback(time));
489
+ requestAnimationFrame(animate);
490
+ }
491
+ requestAnimationFrame(animate);
492
+
493
+ var lastScale = 0;
494
+ var lastWidth = 0;
495
+ var lastHeight = 0;
496
+ function adjustCanvasDimensions() {
497
+ var scale = window.devicePixelRatio || 1;
498
+ var headerHeight = headerDiv.offsetHeight + 1;
499
+ var width = window.innerWidth;
500
+ var height = window.innerHeight - headerHeight;
501
+ if (lastScale !== scale || lastWidth !== width || lastHeight !== height) {
502
+ editorCanvas.width = width * scale;
503
+ editorCanvas.height = height * scale;
504
+ editorCanvas.style.width = width + "px";
505
+ editorCanvas.style.height = height + "px";
506
+ lastScale = scale;
507
+ lastWidth = width;
508
+ lastHeight = height;
509
+ }
510
+ }
511
+ animationCallbacks.push(adjustCanvasDimensions);
512
+ adjustCanvasDimensions();
513
+
514
+ function replaceCanvas(from) {
515
+ const out = document.createElement("canvas");
516
+ out.id = from.id;
517
+ out.tabIndex = from.tabIndex;
518
+ from.parentNode.replaceChild(out, from);
519
+ lastScale = 0;
520
+ return out;
521
+ }
522
+
523
+ setStatusMode = function setStatusMode(mode) {
524
+ if (statusMode === mode || !initializing)
525
+ return;
526
+ [statusProgress, statusIndeterminate, statusNotice].forEach(elem => {
527
+ elem.style.display = 'none';
528
+ });
529
+ animationCallbacks = animationCallbacks.filter(function(value) {
530
+ return (value != animateStatusIndeterminate);
531
+ });
532
+ switch (mode) {
533
+ case 'progress':
534
+ statusProgress.style.display = 'block';
535
+ break;
536
+ case 'indeterminate':
537
+ statusIndeterminate.style.display = 'block';
538
+ animationCallbacks.push(animateStatusIndeterminate);
539
+ break;
540
+ case 'notice':
541
+ statusNotice.style.display = 'block';
542
+ break;
543
+ case 'hidden':
544
+ break;
545
+ default:
546
+ throw new Error('Invalid status mode');
547
+ }
548
+ statusMode = mode;
549
+ };
550
+
551
+ function animateStatusIndeterminate(ms) {
552
+ var i = Math.floor(ms / INDETERMINATE_STATUS_STEP_MS % 8);
553
+ if (statusIndeterminate.children[i].style.borderTopColor == '') {
554
+ Array.prototype.slice.call(statusIndeterminate.children).forEach(child => {
555
+ child.style.borderTopColor = '';
556
+ });
557
+ statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf';
558
+ }
559
+ }
560
+
561
+ setStatusNotice = function setStatusNotice(text) {
562
+ while (statusNotice.lastChild) {
563
+ statusNotice.removeChild(statusNotice.lastChild);
564
+ }
565
+ var lines = text.split('\n');
566
+ lines.forEach((line) => {
567
+ statusNotice.appendChild(document.createTextNode(line));
568
+ statusNotice.appendChild(document.createElement('br'));
569
+ });
570
+ };
571
+
572
+ const gameConfig = {
573
+ 'persistentPaths': persistentPaths,
574
+ 'unloadAfterInit': false,
575
+ 'canvas': gameCanvas,
576
+ 'canvasResizePolicy': 1,
577
+ 'onExit': function () {
578
+ gameCanvas = replaceCanvas(gameCanvas);
579
+ setGameTabEnabled(false);
580
+ showTab('editor');
581
+ game = null;
582
+ },
583
+ };
584
+
585
+ var OnEditorExit = function () {
586
+ showTab('loader');
587
+ setLoaderEnabled(true);
588
+ };
589
+ function Execute(args) {
590
+ const is_editor = args.filter(function(v) { return v == '--editor' || v == '-e' }).length != 0;
591
+ const is_project_manager = args.filter(function(v) { return v == '--project-manager' }).length != 0;
592
+ const is_game = !is_editor && !is_project_manager;
593
+ if (video_driver) {
594
+ args.push('--rendering-driver', video_driver);
595
+ }
596
+
597
+ if (is_game) {
598
+ if (game) {
599
+ console.error("A game is already running. Close it first");
600
+ return;
601
+ }
602
+ setGameTabEnabled(true);
603
+ game = new Engine(gameConfig);
604
+ showTab('game');
605
+ game.init().then(function() {
606
+ requestAnimationFrame(function() {
607
+ game.start({'args': args, 'canvas': gameCanvas}).then(function() {
608
+ gameCanvas.focus();
609
+ });
610
+ });
611
+ });
612
+ } else { // New editor instances will be run in the same canvas. We want to wait for it to exit.
613
+ OnEditorExit = function(code) {
614
+ setLoaderEnabled(true);
615
+ setTimeout(function() {
616
+ editor.init().then(function() {
617
+ setLoaderEnabled(false);
618
+ OnEditorExit = function() {
619
+ showTab('loader');
620
+ setLoaderEnabled(true);
621
+ };
622
+ editor.start({'args': args, 'persistentDrops': is_project_manager, 'canvas': editorCanvas});
623
+ });
624
+ }, 0);
625
+ OnEditorExit = null;
626
+ };
627
+ }
628
+ }
629
+
630
+ const editorConfig = {
631
+ 'unloadAfterInit': false,
632
+ 'onProgress': function progressFunction (current, total) {
633
+ if (total > 0) {
634
+ statusProgressInner.style.width = current/total * 100 + '%';
635
+ setStatusMode('progress');
636
+ if (current === total) {
637
+ // wait for progress bar animation
638
+ setTimeout(() => {
639
+ setStatusMode('indeterminate');
640
+ }, 100);
641
+ }
642
+ } else {
643
+ setStatusMode('indeterminate');
644
+ }
645
+ },
646
+ 'canvas': editorCanvas,
647
+ 'canvasResizePolicy': 0,
648
+ 'onExit': function() {
649
+ editorCanvas = replaceCanvas(editorCanvas);
650
+ if (OnEditorExit) {
651
+ OnEditorExit();
652
+ }
653
+ },
654
+ 'onExecute': Execute,
655
+ 'persistentPaths': persistentPaths,
656
+ };
657
+ editor = new Engine(editorConfig);
658
+
659
+ function displayFailureNotice(err) {
660
+ var msg = err.message || err;
661
+ console.error(msg);
662
+ setStatusNotice(msg);
663
+ setStatusMode('notice');
664
+ initializing = false;
665
+ };
666
+
667
+ if (!Engine.isWebGLAvailable()) {
668
+ displayFailureNotice('WebGL not available');
669
+ } else {
670
+ setStatusMode('indeterminate');
671
+ editor.init('godot.tools').then(function() {
672
+ if (zip) {
673
+ editor.copyToFS("/tmp/preload.zip", zip);
674
+ }
675
+ try {
676
+ // Avoid user creating project in the persistent root folder.
677
+ editor.copyToFS("/home/web_user/keep", new Uint8Array());
678
+ } catch(e) {
679
+ // File exists
680
+ }
681
+ selectVideoMode();
682
+ showTab('editor');
683
+ setLoaderEnabled(false);
684
+ const args = ['--project-manager', '--single-window'];
685
+ if (video_driver) {
686
+ args.push('--rendering-driver', video_driver);
687
+ }
688
+ editor.start({'args': args, 'persistentDrops': true}).then(function() {
689
+ setStatusMode('hidden');
690
+ initializing = false;
691
+ });
692
+ }).catch(displayFailureNotice);
693
+ }
694
+ };
695
+ document.getElementById("startButton").onclick = function() {
696
+ preloadZip(document.getElementById('zip-file')).then(function(zip) {
697
+ startEditor(zip);
698
+ });
699
+ }
700
+
701
+ function preloadZip(target) {
702
+ return new Promise(function(resolve, reject) {
703
+ if (target.files.length > 0) {
704
+ target.files[0].arrayBuffer().then(function(data) {
705
+ resolve(data);
706
+ });
707
+ } else {
708
+ resolve();
709
+ }
710
+ });
711
+ }
712
+ //]]></script>
713
+ </body>
714
  </html>