/**1* An object used to configure the Engine instance based on godot export options, and to override those in custom HTML2* templates if needed.3*4* @header Engine configuration5* @summary The Engine configuration object. This is just a typedef, create it like a regular object, e.g.:6*7* ``const MyConfig = { executable: 'godot', unloadAfterInit: false }``8*9* @typedef {Object} EngineConfig10*/11const EngineConfig = {}; // eslint-disable-line no-unused-vars1213/**14* @struct15* @constructor16* @ignore17*/18const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-vars19const cfg = /** @lends {InternalConfig.prototype} */ {20/**21* Whether to unload the engine automatically after the instance is initialized.22*23* @memberof EngineConfig24* @default25* @type {boolean}26*/27unloadAfterInit: true,28/**29* The HTML DOM Canvas object to use.30*31* By default, the first canvas element in the document will be used is none is specified.32*33* @memberof EngineConfig34* @default35* @type {?HTMLCanvasElement}36*/37canvas: null,38/**39* The name of the WASM file without the extension. (Set by Godot Editor export process).40*41* @memberof EngineConfig42* @default43* @type {string}44*/45executable: '',46/**47* An alternative name for the game pck to load. The executable name is used otherwise.48*49* @memberof EngineConfig50* @default51* @type {?string}52*/53mainPack: null,54/**55* Specify a language code to select the proper localization for the game.56*57* The browser locale will be used if none is specified. See complete list of58* :ref:`supported locales <doc_locales>`.59*60* @memberof EngineConfig61* @type {?string}62* @default63*/64locale: null,65/**66* The canvas resize policy determines how the canvas should be resized by Godot.67*68* ``0`` means Godot won't do any resizing. This is useful if you want to control the canvas size from69* javascript code in your template.70*71* ``1`` means Godot will resize the canvas on start, and when changing window size via engine functions.72*73* ``2`` means Godot will adapt the canvas size to match the whole browser window.74*75* @memberof EngineConfig76* @type {number}77* @default78*/79canvasResizePolicy: 2,80/**81* The arguments to be passed as command line arguments on startup.82*83* See :ref:`command line tutorial <doc_command_line_tutorial>`.84*85* **Note**: :js:meth:`startGame <Engine.prototype.startGame>` will always add the ``--main-pack`` argument.86*87* @memberof EngineConfig88* @type {Array<string>}89* @default90*/91args: [],92/**93* When enabled, the game canvas will automatically grab the focus when the engine starts.94*95* @memberof EngineConfig96* @type {boolean}97* @default98*/99focusCanvas: true,100/**101* When enabled, this will turn on experimental virtual keyboard support on mobile.102*103* @memberof EngineConfig104* @type {boolean}105* @default106*/107experimentalVK: false,108/**109* The progressive web app service worker to install.110* @memberof EngineConfig111* @default112* @type {string}113*/114serviceWorker: '',115/**116* @ignore117* @type {Array.<string>}118*/119persistentPaths: ['/userfs'],120/**121* @ignore122* @type {boolean}123*/124persistentDrops: false,125/**126* @ignore127* @type {Array.<string>}128*/129gdextensionLibs: [],130/**131* @ignore132* @type {Array.<string>}133*/134fileSizes: [],135/**136* @ignore137* @type {number}138*/139emscriptenPoolSize: 8,140/**141* @ignore142* @type {number}143*/144godotPoolSize: 4,145/**146* A callback function for handling Godot's ``OS.execute`` calls.147*148* This is for example used in the Web Editor template to switch between project manager and editor, and for running the game.149*150* @callback EngineConfig.onExecute151* @param {string} path The path that Godot's wants executed.152* @param {Array.<string>} args The arguments of the "command" to execute.153*/154/**155* @ignore156* @type {?function(string, Array.<string>)}157*/158onExecute: null,159/**160* A callback function for being notified when the Godot instance quits.161*162* **Note**: This function will not be called if the engine crashes or become unresponsive.163*164* @callback EngineConfig.onExit165* @param {number} status_code The status code returned by Godot on exit.166*/167/**168* @ignore169* @type {?function(number)}170*/171onExit: null,172/**173* A callback function for displaying download progress.174*175* The function is called once per frame while downloading files, so the usage of ``requestAnimationFrame()``176* is not necessary.177*178* If the callback function receives a total amount of bytes as 0, this means that it is impossible to calculate.179* Possible reasons include:180*181* - Files are delivered with server-side chunked compression182* - Files are delivered with server-side compression on Chromium183* - Not all file downloads have started yet (usually on servers without multi-threading)184*185* @callback EngineConfig.onProgress186* @param {number} current The current amount of downloaded bytes so far.187* @param {number} total The total amount of bytes to be downloaded.188*/189/**190* @ignore191* @type {?function(number, number)}192*/193onProgress: null,194/**195* A callback function for handling the standard output stream. This method should usually only be used in debug pages.196*197* By default, ``console.log()`` is used.198*199* @callback EngineConfig.onPrint200* @param {...*} [var_args] A variadic number of arguments to be printed.201*/202/**203* @ignore204* @type {?function(...*)}205*/206onPrint: function () {207console.log.apply(console, Array.from(arguments)); // eslint-disable-line no-console208},209/**210* A callback function for handling the standard error stream. This method should usually only be used in debug pages.211*212* By default, ``console.error()`` is used.213*214* @callback EngineConfig.onPrintError215* @param {...*} [var_args] A variadic number of arguments to be printed as errors.216*/217/**218* @ignore219* @type {?function(...*)}220*/221onPrintError: function (var_args) {222console.error.apply(console, Array.from(arguments)); // eslint-disable-line no-console223},224};225226/**227* @ignore228* @struct229* @constructor230* @param {EngineConfig} opts231*/232function Config(opts) {233this.update(opts);234}235236Config.prototype = cfg;237238/**239* @ignore240* @param {EngineConfig} opts241*/242Config.prototype.update = function (opts) {243const config = opts || {};244// NOTE: We must explicitly pass the default, accessing it via245// the key will fail due to closure compiler renames.246function parse(key, def) {247if (typeof (config[key]) === 'undefined') {248return def;249}250return config[key];251}252// Module config253this.unloadAfterInit = parse('unloadAfterInit', this.unloadAfterInit);254this.onPrintError = parse('onPrintError', this.onPrintError);255this.onPrint = parse('onPrint', this.onPrint);256this.onProgress = parse('onProgress', this.onProgress);257258// Godot config259this.canvas = parse('canvas', this.canvas);260this.executable = parse('executable', this.executable);261this.mainPack = parse('mainPack', this.mainPack);262this.locale = parse('locale', this.locale);263this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy);264this.persistentPaths = parse('persistentPaths', this.persistentPaths);265this.persistentDrops = parse('persistentDrops', this.persistentDrops);266this.experimentalVK = parse('experimentalVK', this.experimentalVK);267this.focusCanvas = parse('focusCanvas', this.focusCanvas);268this.serviceWorker = parse('serviceWorker', this.serviceWorker);269this.gdextensionLibs = parse('gdextensionLibs', this.gdextensionLibs);270this.fileSizes = parse('fileSizes', this.fileSizes);271this.emscriptenPoolSize = parse('emscriptenPoolSize', this.emscriptenPoolSize);272this.godotPoolSize = parse('godotPoolSize', this.godotPoolSize);273this.args = parse('args', this.args);274this.onExecute = parse('onExecute', this.onExecute);275this.onExit = parse('onExit', this.onExit);276};277278/**279* @ignore280* @param {string} loadPath281* @param {Response} response282*/283Config.prototype.getModuleConfig = function (loadPath, response) {284let r = response;285const gdext = this.gdextensionLibs;286return {287'print': this.onPrint,288'printErr': this.onPrintError,289'thisProgram': this.executable,290'noExitRuntime': false,291'dynamicLibraries': [`${loadPath}.side.wasm`].concat(this.gdextensionLibs),292'emscriptenPoolSize': this.emscriptenPoolSize,293'instantiateWasm': function (imports, onSuccess) {294function done(result) {295onSuccess(result['instance'], result['module']);296}297if (typeof (WebAssembly.instantiateStreaming) !== 'undefined') {298WebAssembly.instantiateStreaming(Promise.resolve(r), imports).then(done);299} else {300r.arrayBuffer().then(function (buffer) {301WebAssembly.instantiate(buffer, imports).then(done);302});303}304r = null;305return {};306},307'locateFile': function (path) {308if (!path.startsWith('godot.')) {309return path;310} else if (path.endsWith('.audio.worklet.js')) {311return `${loadPath}.audio.worklet.js`;312} else if (path.endsWith('.audio.position.worklet.js')) {313return `${loadPath}.audio.position.worklet.js`;314} else if (path.endsWith('.js')) {315return `${loadPath}.js`;316} else if (path in gdext) {317return path;318} else if (path.endsWith('.side.wasm')) {319return `${loadPath}.side.wasm`;320} else if (path.endsWith('.wasm')) {321return `${loadPath}.wasm`;322}323return path;324},325};326};327328/**329* @ignore330* @param {function()} cleanup331*/332Config.prototype.getGodotConfig = function (cleanup) {333// Try to find a canvas334if (!(this.canvas instanceof HTMLCanvasElement)) {335const nodes = document.getElementsByTagName('canvas');336if (nodes.length && nodes[0] instanceof HTMLCanvasElement) {337const first = nodes[0];338this.canvas = /** @type {!HTMLCanvasElement} */ (first);339}340if (!this.canvas) {341throw new Error('No canvas found in page');342}343}344// Canvas can grab focus on click, or key events won't work.345if (this.canvas.tabIndex < 0) {346this.canvas.tabIndex = 0;347}348349// Browser locale, or custom one if defined.350let locale = this.locale;351if (!locale) {352locale = navigator.languages ? navigator.languages[0] : navigator.language;353locale = locale.split('.')[0];354}355locale = locale.replace('-', '_');356const onExit = this.onExit;357358// Godot configuration.359return {360'canvas': this.canvas,361'canvasResizePolicy': this.canvasResizePolicy,362'locale': locale,363'persistentDrops': this.persistentDrops,364'virtualKeyboard': this.experimentalVK,365'godotPoolSize': this.godotPoolSize,366'focusCanvas': this.focusCanvas,367'onExecute': this.onExecute,368'onExit': function (p_code) {369cleanup(); // We always need to call the cleanup callback to free memory.370if (typeof (onExit) === 'function') {371onExit(p_code);372}373},374};375};376return new Config(initConfig);377};378379380