Path: blob/master/platform/web/js/libs/library_godot_os.js
10279 views
/**************************************************************************/1/* library_godot_os.js */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930const IDHandler = {31$IDHandler: {32_last_id: 0,33_references: {},3435get: function (p_id) {36return IDHandler._references[p_id];37},3839add: function (p_data) {40const id = ++IDHandler._last_id;41IDHandler._references[id] = p_data;42return id;43},4445remove: function (p_id) {46delete IDHandler._references[p_id];47},48},49};5051autoAddDeps(IDHandler, '$IDHandler');52mergeInto(LibraryManager.library, IDHandler);5354const GodotConfig = {55$GodotConfig__postset: 'Module["initConfig"] = GodotConfig.init_config;',56$GodotConfig__deps: ['$GodotRuntime'],57$GodotConfig: {58canvas: null,59locale: 'en',60canvas_resize_policy: 2, // Adaptive61virtual_keyboard: false,62persistent_drops: false,63godot_pool_size: 4,64on_execute: null,65on_exit: null,6667init_config: function (p_opts) {68GodotConfig.canvas_resize_policy = p_opts['canvasResizePolicy'];69GodotConfig.canvas = p_opts['canvas'];70GodotConfig.locale = p_opts['locale'] || GodotConfig.locale;71GodotConfig.virtual_keyboard = p_opts['virtualKeyboard'];72GodotConfig.persistent_drops = !!p_opts['persistentDrops'];73GodotConfig.godot_pool_size = p_opts['godotPoolSize'];74GodotConfig.on_execute = p_opts['onExecute'];75GodotConfig.on_exit = p_opts['onExit'];76if (p_opts['focusCanvas']) {77GodotConfig.canvas.focus();78}79},8081locate_file: function (file) {82return Module['locateFile'](file);83},84clear: function () {85GodotConfig.canvas = null;86GodotConfig.locale = 'en';87GodotConfig.canvas_resize_policy = 2;88GodotConfig.virtual_keyboard = false;89GodotConfig.persistent_drops = false;90GodotConfig.on_execute = null;91GodotConfig.on_exit = null;92},93},9495godot_js_config_canvas_id_get__proxy: 'sync',96godot_js_config_canvas_id_get__sig: 'vii',97godot_js_config_canvas_id_get: function (p_ptr, p_ptr_max) {98GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`, p_ptr, p_ptr_max);99},100101godot_js_config_locale_get__proxy: 'sync',102godot_js_config_locale_get__sig: 'vii',103godot_js_config_locale_get: function (p_ptr, p_ptr_max) {104GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max);105},106};107108autoAddDeps(GodotConfig, '$GodotConfig');109mergeInto(LibraryManager.library, GodotConfig);110111const GodotFS = {112$GodotFS__deps: ['$FS', '$IDBFS', '$GodotRuntime'],113$GodotFS__postset: [114'Module["initFS"] = GodotFS.init;',115'Module["copyToFS"] = GodotFS.copy_to_fs;',116].join(''),117$GodotFS: {118// ERRNO_CODES works every odd version of emscripten, but this will break too eventually.119ENOENT: 44,120_idbfs: false,121_syncing: false,122_mount_points: [],123124is_persistent: function () {125return GodotFS._idbfs ? 1 : 0;126},127128// Initialize godot file system, setting up persistent paths.129// Returns a promise that resolves when the FS is ready.130// We keep track of mount_points, so that we can properly close the IDBFS131// since emscripten is not doing it by itself. (emscripten GH#12516).132init: function (persistentPaths) {133GodotFS._idbfs = false;134if (!Array.isArray(persistentPaths)) {135return Promise.reject(new Error('Persistent paths must be an array'));136}137if (!persistentPaths.length) {138return Promise.resolve();139}140GodotFS._mount_points = persistentPaths.slice();141142function createRecursive(dir) {143try {144FS.stat(dir);145} catch (e) {146if (e.errno !== GodotFS.ENOENT) {147// Let mkdirTree throw in case, we cannot trust the above check.148GodotRuntime.error(e);149}150FS.mkdirTree(dir);151}152}153154GodotFS._mount_points.forEach(function (path) {155createRecursive(path);156FS.mount(IDBFS, {}, path);157});158return new Promise(function (resolve, reject) {159FS.syncfs(true, function (err) {160if (err) {161GodotFS._mount_points = [];162GodotFS._idbfs = false;163GodotRuntime.print(`IndexedDB not available: ${err.message}`);164} else {165GodotFS._idbfs = true;166}167resolve(err);168});169});170},171172// Deinit godot file system, making sure to unmount file systems, and close IDBFS(s).173deinit: function () {174GodotFS._mount_points.forEach(function (path) {175try {176FS.unmount(path);177} catch (e) {178GodotRuntime.print('Already unmounted', e);179}180if (GodotFS._idbfs && IDBFS.dbs[path]) {181IDBFS.dbs[path].close();182delete IDBFS.dbs[path];183}184});185GodotFS._mount_points = [];186GodotFS._idbfs = false;187GodotFS._syncing = false;188},189190sync: function () {191if (GodotFS._syncing) {192GodotRuntime.error('Already syncing!');193return Promise.resolve();194}195GodotFS._syncing = true;196return new Promise(function (resolve, reject) {197FS.syncfs(false, function (error) {198if (error) {199GodotRuntime.error(`Failed to save IDB file system: ${error.message}`);200}201GodotFS._syncing = false;202resolve(error);203});204});205},206207// Copies a buffer to the internal file system. Creating directories recursively.208copy_to_fs: function (path, buffer) {209const idx = path.lastIndexOf('/');210let dir = '/';211if (idx > 0) {212dir = path.slice(0, idx);213}214try {215FS.stat(dir);216} catch (e) {217if (e.errno !== GodotFS.ENOENT) {218// Let mkdirTree throw in case, we cannot trust the above check.219GodotRuntime.error(e);220}221FS.mkdirTree(dir);222}223FS.writeFile(path, new Uint8Array(buffer));224},225},226};227mergeInto(LibraryManager.library, GodotFS);228229const GodotOS = {230$GodotOS__deps: ['$GodotRuntime', '$GodotConfig', '$GodotFS'],231$GodotOS__postset: [232'Module["request_quit"] = function() { GodotOS.request_quit() };',233'Module["onExit"] = GodotOS.cleanup;',234'GodotOS._fs_sync_promise = Promise.resolve();',235].join(''),236$GodotOS: {237request_quit: function () {},238_async_cbs: [],239_fs_sync_promise: null,240241atexit: function (p_promise_cb) {242GodotOS._async_cbs.push(p_promise_cb);243},244245cleanup: function (exit_code) {246const cb = GodotConfig.on_exit;247GodotFS.deinit();248GodotConfig.clear();249if (cb) {250cb(exit_code);251}252},253254finish_async: function (callback) {255GodotOS._fs_sync_promise.then(function (err) {256const promises = [];257GodotOS._async_cbs.forEach(function (cb) {258promises.push(new Promise(cb));259});260return Promise.all(promises);261}).then(function () {262return GodotFS.sync(); // Final FS sync.263}).then(function (err) {264// Always deferred.265setTimeout(function () {266callback();267}, 0);268});269},270},271272godot_js_os_finish_async__proxy: 'sync',273godot_js_os_finish_async__sig: 'vi',274godot_js_os_finish_async: function (p_callback) {275const func = GodotRuntime.get_func(p_callback);276GodotOS.finish_async(func);277},278279godot_js_os_request_quit_cb__proxy: 'sync',280godot_js_os_request_quit_cb__sig: 'vi',281godot_js_os_request_quit_cb: function (p_callback) {282GodotOS.request_quit = GodotRuntime.get_func(p_callback);283},284285godot_js_os_fs_is_persistent__proxy: 'sync',286godot_js_os_fs_is_persistent__sig: 'i',287godot_js_os_fs_is_persistent: function () {288return GodotFS.is_persistent();289},290291godot_js_os_fs_sync__proxy: 'sync',292godot_js_os_fs_sync__sig: 'vi',293godot_js_os_fs_sync: function (callback) {294const func = GodotRuntime.get_func(callback);295GodotOS._fs_sync_promise = GodotFS.sync();296GodotOS._fs_sync_promise.then(function (err) {297func();298});299},300301godot_js_os_has_feature__proxy: 'sync',302godot_js_os_has_feature__sig: 'ii',303godot_js_os_has_feature: function (p_ftr) {304const ftr = GodotRuntime.parseString(p_ftr);305const ua = navigator.userAgent;306if (ftr === 'web_macos') {307return (ua.indexOf('Mac') !== -1) ? 1 : 0;308}309if (ftr === 'web_windows') {310return (ua.indexOf('Windows') !== -1) ? 1 : 0;311}312if (ftr === 'web_android') {313return (ua.indexOf('Android') !== -1) ? 1 : 0;314}315if (ftr === 'web_ios') {316return ((ua.indexOf('iPhone') !== -1) || (ua.indexOf('iPad') !== -1) || (ua.indexOf('iPod') !== -1)) ? 1 : 0;317}318if (ftr === 'web_linuxbsd') {319return ((ua.indexOf('CrOS') !== -1) || (ua.indexOf('BSD') !== -1) || (ua.indexOf('Linux') !== -1) || (ua.indexOf('X11') !== -1)) ? 1 : 0;320}321return 0;322},323324godot_js_os_execute__proxy: 'sync',325godot_js_os_execute__sig: 'ii',326godot_js_os_execute: function (p_json) {327const json_args = GodotRuntime.parseString(p_json);328const args = JSON.parse(json_args);329if (GodotConfig.on_execute) {330GodotConfig.on_execute(args);331return 0;332}333return 1;334},335336godot_js_os_shell_open__proxy: 'sync',337godot_js_os_shell_open__sig: 'vi',338godot_js_os_shell_open: function (p_uri) {339window.open(GodotRuntime.parseString(p_uri), '_blank');340},341342godot_js_os_hw_concurrency_get__proxy: 'sync',343godot_js_os_hw_concurrency_get__sig: 'i',344godot_js_os_hw_concurrency_get: function () {345// TODO Godot core needs fixing to avoid spawning too many threads (> 24).346const concurrency = navigator.hardwareConcurrency || 1;347return concurrency < 2 ? concurrency : 2;348},349350godot_js_os_thread_pool_size_get__proxy: 'sync',351godot_js_os_thread_pool_size_get__sig: 'i',352godot_js_os_thread_pool_size_get: function () {353if (typeof PThread === 'undefined') {354// Threads aren't supported, so default to `1`.355return 1;356}357358return GodotConfig.godot_pool_size;359},360361godot_js_os_download_buffer__proxy: 'sync',362godot_js_os_download_buffer__sig: 'viiii',363godot_js_os_download_buffer: function (p_ptr, p_size, p_name, p_mime) {364const buf = GodotRuntime.heapSlice(HEAP8, p_ptr, p_size);365const name = GodotRuntime.parseString(p_name);366const mime = GodotRuntime.parseString(p_mime);367const blob = new Blob([buf], { type: mime });368const url = window.URL.createObjectURL(blob);369const a = document.createElement('a');370a.href = url;371a.download = name;372a.style.display = 'none';373document.body.appendChild(a);374a.click();375a.remove();376window.URL.revokeObjectURL(url);377},378};379380autoAddDeps(GodotOS, '$GodotOS');381mergeInto(LibraryManager.library, GodotOS);382383/*384* Godot event listeners.385* Keeps track of registered event listeners so it can remove them on shutdown.386*/387const GodotEventListeners = {388$GodotEventListeners__deps: ['$GodotOS'],389$GodotEventListeners__postset: 'GodotOS.atexit(function(resolve, reject) { GodotEventListeners.clear(); resolve(); });',390$GodotEventListeners: {391handlers: [],392393has: function (target, event, method, capture) {394return GodotEventListeners.handlers.findIndex(function (e) {395return e.target === target && e.event === event && e.method === method && e.capture === capture;396}) !== -1;397},398399add: function (target, event, method, capture) {400if (GodotEventListeners.has(target, event, method, capture)) {401return;402}403function Handler(p_target, p_event, p_method, p_capture) {404this.target = p_target;405this.event = p_event;406this.method = p_method;407this.capture = p_capture;408}409GodotEventListeners.handlers.push(new Handler(target, event, method, capture));410target.addEventListener(event, method, capture);411},412413clear: function () {414GodotEventListeners.handlers.forEach(function (h) {415h.target.removeEventListener(h.event, h.method, h.capture);416});417GodotEventListeners.handlers.length = 0;418},419},420};421mergeInto(LibraryManager.library, GodotEventListeners);422423const GodotPWA = {424425$GodotPWA__deps: ['$GodotRuntime', '$GodotEventListeners'],426$GodotPWA: {427hasUpdate: false,428429updateState: function (cb, reg) {430if (!reg) {431return;432}433if (!reg.active) {434return;435}436if (reg.waiting) {437GodotPWA.hasUpdate = true;438cb();439}440GodotEventListeners.add(reg, 'updatefound', function () {441const installing = reg.installing;442GodotEventListeners.add(installing, 'statechange', function () {443if (installing.state === 'installed') {444GodotPWA.hasUpdate = true;445cb();446}447});448});449},450},451452godot_js_pwa_cb__proxy: 'sync',453godot_js_pwa_cb__sig: 'vi',454godot_js_pwa_cb: function (p_update_cb) {455if ('serviceWorker' in navigator) {456try {457const cb = GodotRuntime.get_func(p_update_cb);458navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb));459} catch (e) {460GodotRuntime.error('Failed to assign PWA callback', e);461}462}463},464465godot_js_pwa_update__proxy: 'sync',466godot_js_pwa_update__sig: 'i',467godot_js_pwa_update: function () {468if ('serviceWorker' in navigator && GodotPWA.hasUpdate) {469try {470navigator.serviceWorker.getRegistration().then(function (reg) {471if (!reg || !reg.waiting) {472return;473}474reg.waiting.postMessage('update');475});476} catch (e) {477GodotRuntime.error(e);478return 1;479}480return 0;481}482return 1;483},484};485486autoAddDeps(GodotPWA, '$GodotPWA');487mergeInto(LibraryManager.library, GodotPWA);488489490