diff --git a/Unity/SpaceCraft/Assets/Editor/Build.cs b/Unity/SpaceCraft/Assets/Editor/Build.cs index be623417..345d7574 100644 --- a/Unity/SpaceCraft/Assets/Editor/Build.cs +++ b/Unity/SpaceCraft/Assets/Editor/Build.cs @@ -92,8 +92,8 @@ public static void WebGL_Prod() } // --- End Force --- - // Default prod output (existing behavior) - string buildPath = Path.Combine("Builds", "SpaceCraft"); + // Default prod output (match Development build location) + string buildPath = Path.Combine("..", "..", "WebSites", "SpaceCraft"); // Allow CI override via env var or CLI buildPath = ResolveOutputPath(buildPath); Debug.Log($"[Build] Output path: {buildPath}"); diff --git a/Unity/SpaceCraft/Assets/Resources/Prefabs/MagnetView.prefab b/Unity/SpaceCraft/Assets/Resources/Prefabs/MagnetView.prefab index 641e03c9..202c435c 100644 --- a/Unity/SpaceCraft/Assets/Resources/Prefabs/MagnetView.prefab +++ b/Unity/SpaceCraft/Assets/Resources/Prefabs/MagnetView.prefab @@ -118,11 +118,17 @@ MonoBehaviour: magnetSoftness: 0 magnetHoleRadius: 0 magnetHoleStrength: 0 - _scoreMin: 0 + _scoreMin: 0.8 _scoreMax: 1 viewScale: 1 viewScaleInitial: 0.01 magnetMaterial: {fileID: 2100000, guid: 2610998a9e1a94c63a3d4e941db2602e, type: 2} + orbitForce: 0 + orbitWidth: 4 + orbitEjectStrength: 10 + orbitEjectRadius: 10 + orbitDeflectStrength: 2 + orbitDeflectRadius: 7 --- !u!1 &7411782117926916970 GameObject: m_ObjectHideFlags: 0 diff --git a/Unity/SpaceCraft/Assets/Scripts/Core/InputManager.cs b/Unity/SpaceCraft/Assets/Scripts/Core/InputManager.cs index 5b68a8f0..3fa87938 100644 --- a/Unity/SpaceCraft/Assets/Scripts/Core/InputManager.cs +++ b/Unity/SpaceCraft/Assets/Scripts/Core/InputManager.cs @@ -128,11 +128,11 @@ public class InputManager : MonoBehaviour [ExposedParameter( "View Mode", Category = "View", - Description = "Current view mode: magnets, selection, manual, or attract", - Default = "magnets", + Description = "Current view mode (tab id): about, arrange, view, select, inspect", + Default = "arrange", Visible = true )] - public string viewMode = "magnets"; + public string viewMode = "arrange"; [ExposedParameter( "View Seek Position Speed", @@ -391,24 +391,28 @@ void FixedUpdate() ApplyContinuousThrust(); - switch (viewMode) { + // Use tab ids only: about, arrange, view, select, inspect + string _mode = (viewMode ?? "").ToLowerInvariant(); - case "attract": + switch (_mode) { + + case "about": viewSeekEnabled = true; ViewSeekAttract(); break; - case "magnets": + case "arrange": viewSeekEnabled = true; ViewSeekMagnets(); break; - case "selection": + case "select": + case "inspect": viewSeekEnabled = true; ViewSeekSelection(); break; - case "manual": + case "view": default: viewSeekEnabled = false; break; @@ -864,29 +868,40 @@ private void ApplyMagnetForces() ItemView[] allItems = FindObjectsByType(FindObjectsSortMode.None); if (allItems.Length == 0) return; + // Precompute, for each item, whether any magnet (other than the one applying force) + // would attract/affect it. This lets MagnetView modulate ejection using orbitDeflectStrength. + Dictionary itemAffected = new Dictionary(allItems.Length); + foreach (var itemView in allItems) + { + bool affected = false; + Vector3 pos = itemView.transform.position; + foreach (var m in allMagnets) + { + if (m == null || !m.magnetEnabled) continue; + if (m.WouldAttract(itemView, pos)) { affected = true; break; } + } + itemAffected[itemView] = affected; + } + foreach (MagnetView magnet in allMagnets) { if (magnet == null || !magnet.magnetEnabled) continue; - foreach (ItemView itemView in allItems) { if (itemView?.GetComponent() == null) continue; - Rigidbody rb = itemView.GetComponent(); - if (rb.isKinematic) continue; - - Vector3 magneticForce = magnet.CalculateMagneticForce(itemView, itemView.transform.position); - - if (magneticForce.magnitude > 0.001f) - { - rb.AddForce(magneticForce, ForceMode.Force); - - if (rb.IsSleeping() && magneticForce.magnitude > 0.1f) - { - rb.WakeUp(); - } - } - + if (rb.isKinematic) continue; + + bool affectedElsewhere = false; + if (itemAffected.TryGetValue(itemView, out bool v)) affectedElsewhere = v; + // If this magnet alone is affecting, we still pass the global flag; + // MagnetView will choose orbitDeflectStrength only for repulsion. + Vector3 magneticForce = magnet.CalculateMagneticForce(itemView, itemView.transform.position, affectedElsewhere); + if (magneticForce.magnitude > 0.001f) + { + rb.AddForce(magneticForce, ForceMode.Force); + if (rb.IsSleeping() && magneticForce.magnitude > 0.1f) rb.WakeUp(); + } if (rb.linearVelocity.magnitude > maxItemVelocity) { rb.linearVelocity = rb.linearVelocity.normalized * maxItemVelocity; diff --git a/Unity/SpaceCraft/Assets/Scripts/Core/SpaceCraft.cs b/Unity/SpaceCraft/Assets/Scripts/Core/SpaceCraft.cs index 4986cd72..f424e117 100644 --- a/Unity/SpaceCraft/Assets/Scripts/Core/SpaceCraft.cs +++ b/Unity/SpaceCraft/Assets/Scripts/Core/SpaceCraft.cs @@ -1208,4 +1208,19 @@ public void PushCameraVelocity(string controllerId, string controllerName, float { inputManager.PushCameraVelocity(controllerId, controllerName, panXDelta, panYDelta); } + + /// + /// Public API to push an item by id with a small delta in world units. + /// Exposed to JS via bridge.updateObject(this.spaceCraft, { "method:PushItem": [itemId, dx, dy] }) + /// + public void PushItem(string itemId, float dx, float dy) + { + if (string.IsNullOrEmpty(itemId)) return; + var itemView = FindItemSafe(itemId); + if (itemView == null) return; + var rb = itemView.GetComponent(); + if (rb == null) return; + Vector3 newPosition = itemView.transform.position + new Vector3(dx, 0f, dy); + rb.MovePosition(newPosition); + } } diff --git a/Unity/SpaceCraft/Assets/Scripts/Views/MagnetView.cs b/Unity/SpaceCraft/Assets/Scripts/Views/MagnetView.cs index ac6fc76f..6158f110 100644 --- a/Unity/SpaceCraft/Assets/Scripts/Views/MagnetView.cs +++ b/Unity/SpaceCraft/Assets/Scripts/Views/MagnetView.cs @@ -218,17 +218,41 @@ public string searchType )] [SerializeField] public float orbitEjectStrength = 10f; + [ExposedParameter( + "Orbit Deflect Strength", + Category = "Magnetic Field", + Description = "Reduced outward force applied to items already influenced by one or more magnets.", + Default = 2f, + Visible = true, + Min = 0f, + Max = 100f, + Step = 0.1f + )] + [SerializeField] public float orbitDeflectStrength = 2f; + [ExposedParameter( "Orbit Eject Radius", Category = "Magnetic Field", Description = "Outward force applied to items with scores below the minimum threshold.", - Default = 15f, + Default = 10f, + Visible = true, + Min = 0f, + Max = 100f, + Step = 0.1f + )] + [SerializeField] public float orbitEjectRadius= 10f; + + [ExposedParameter( + "Orbit Deflect Radius", + Category = "Magnetic Field", + Description = "Radius for reduced repulsion when item is influenced by other magnets.", + Default = 7f, Visible = true, Min = 0f, Max = 100f, Step = 0.1f )] - [SerializeField] public float orbitEjectRadius= 7f; + [SerializeField] public float orbitDeflectRadius = 7f; [SerializeField] private float _scoreMin = 0.8f; [ExposedParameter( @@ -435,6 +459,16 @@ public bool IsItemEligible(Item item) /// Apply magnetic force to an item based on distance and relevance /// public Vector3 CalculateMagneticForce(ItemView itemView, Vector3 itemPosition) + { + // Backwards-compat overload: default no deflection knowledge + return CalculateMagneticForce(itemView, itemPosition, false); + } + + /// + /// Calculates magnetic force. If itemAffectedByOtherMagnets is true, repulsion inside eject radius + /// will use orbitDeflectStrength instead of orbitEjectStrength. + /// + public Vector3 CalculateMagneticForce(ItemView itemView, Vector3 itemPosition, bool itemAffectedByOtherMagnets) { if (!magnetEnabled || itemView?.Model == null) return Vector3.zero; @@ -454,15 +488,17 @@ public Vector3 CalculateMagneticForce(ItemView itemView, Vector3 itemPosition) // EJECTION LOGIC for items that don't meet the minimum score if (score < scoreMin) { - // Only apply ejection inside the orbitEjectRadius - if (orbitEjectStrength <= 0.001f || distance >= orbitEjectRadius) return Vector3.zero; + // Only apply ejection/deflection inside the appropriate radius + float radius = itemAffectedByOtherMagnets ? orbitDeflectRadius : orbitEjectRadius; + float strengthParam = itemAffectedByOtherMagnets ? orbitDeflectStrength : orbitEjectStrength; + if (strengthParam <= 0.001f || distance >= radius) return Vector3.zero; // Ejection force is strongest for items with score 0, and falls to 0 as score approaches scoreMin float scoreFactor = 1f - (score / Mathf.Max(scoreMin, 0.0001f)); scoreFactor = Mathf.Clamp01(scoreFactor); // Have the ejection force fall off towards the edge of the ejection radius - float ejectU = distance / Mathf.Max(orbitEjectRadius, 0.0001f); // 0 at center, 1 at eject edge + float ejectU = distance / Mathf.Max(radius, 0.0001f); // 0 at center, 1 at edge float s = Mathf.Clamp01(magnetSoftness); float ejectEdgeWidth = 0.5f * s; float ejectEdgeFactor; @@ -477,7 +513,7 @@ public Vector3 CalculateMagneticForce(ItemView itemView, Vector3 itemPosition) ejectEdgeFactor = (ejectU < 1f) ? 1f : 0f; } - float forceStrength = orbitEjectStrength * scoreFactor * ejectEdgeFactor; + float forceStrength = strengthParam * scoreFactor * ejectEdgeFactor; // Return outward force (away from magnet) return -toMagnet.normalized * forceStrength; @@ -584,6 +620,22 @@ public Vector3 CalculateMagneticForce(ItemView itemView, Vector3 itemPosition) return Vector3.zero; } + /// + /// Returns true if this magnet would attract or orbit the item (meets score threshold + /// and lies within the magnet's influence radius). + /// + public bool WouldAttract(ItemView itemView, Vector3 itemPosition) + { + if (!magnetEnabled || itemView?.Model == null) return false; + float score = CalculateItemScore(itemView.Model); + if (score < scoreMin || score > scoreMax) return false; + Vector3 toMagnet = transform.position - itemPosition; + float distance = toMagnet.magnitude; + float innerR = Mathf.Max(0f, magnetHoleRadius); + float outerR = Mathf.Max(innerR, magnetRadius); + return distance < outerR; + } + // Cubic Hermite smoothstep private static float SmoothStep(float edge0, float edge1, float x) { diff --git a/Unity/SpaceCraft/Assets/StreamingAssets/SpaceCraft/spacecraft.js b/Unity/SpaceCraft/Assets/StreamingAssets/SpaceCraft/spacecraft.js index 4416b43a..2a2e6a8f 100644 --- a/Unity/SpaceCraft/Assets/StreamingAssets/SpaceCraft/spacecraft.js +++ b/Unity/SpaceCraft/Assets/StreamingAssets/SpaceCraft/spacecraft.js @@ -1093,6 +1093,47 @@ class SpaceCraftSim { } }) + // Push a single item by ID in simulator world space (from controller Select tab joystick) + .on('broadcast', { event: 'pushItem' }, (data) => { + try { + const payload = data && data.payload || {}; + try { console.log('[Sim] pushItem event received', JSON.parse(JSON.stringify(payload))); } catch {} + + // Respect targeting if provided + if (payload.targetSimulatorId && payload.targetSimulatorId !== this.identity.clientId) { + try { console.log('[Sim] pushItem ignored: targeted for other simulator', { targetSimulatorId: payload.targetSimulatorId, myId: this.identity.clientId }); } catch {} + return; + } + + const itemId = payload.itemId; + const dx = Number(payload.deltaX); + const dy = Number(payload.deltaY); + + if (!itemId || !isFinite(dx) || !isFinite(dy)) { + try { console.warn('[Sim] pushItem invalid payload', { itemId, dx, dy }); } catch {} + return; + } + + if (!this.spaceCraft) { + try { console.warn('[Sim] pushItem aborted: this.spaceCraft is not ready'); } catch {} + return; + } + + // Prefer calling the exposed C# method via bridge updateObject (explicit) + try { console.log('[Sim] pushItem invoking bridge.updateObject → method:PushItem', { itemId, dx, dy }); } catch {} + try { + bridge.updateObject(this.spaceCraft, { + "method:PushItem": [itemId, dx, dy] + }); + try { console.log('[Sim] pushItem dispatched to Unity'); } catch {} + } catch (e) { + try { console.error('[Sim] pushItem bridge call failed', e); } catch {} + } + } catch (outer) { + try { console.error('[Sim] pushItem handler error', outer); } catch {} + } + }) + .on('broadcast', { event: 'setViewMode' }, (data) => { if (data.payload.targetSimulatorId && data.payload.targetSimulatorId !== this.identity.clientId) { return; diff --git a/WebSites/SpaceCraft/Build/SpaceCraft.data b/WebSites/SpaceCraft/Build/SpaceCraft.data index 00d106f5..d4bb48e7 100644 --- a/WebSites/SpaceCraft/Build/SpaceCraft.data +++ b/WebSites/SpaceCraft/Build/SpaceCraft.data @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3483f278d8f9e6dccbba597fafd8a8889d577dbbcf0f97fe25884352d27be1a5 -size 10232378 +oid sha256:b9b71d9c6fcc86c5939f58d2ff22949148cbd4f462044f18ae6c6ce53b9e174d +size 10191020 diff --git a/WebSites/SpaceCraft/Build/SpaceCraft.framework.js b/WebSites/SpaceCraft/Build/SpaceCraft.framework.js index 3f3b80e6..404e27d3 100644 --- a/WebSites/SpaceCraft/Build/SpaceCraft.framework.js +++ b/WebSites/SpaceCraft/Build/SpaceCraft.framework.js @@ -5,21546 +5,8 @@ var unityFramework = (() => { return ( function(unityFramework = {}) { -// include: shell.js -// The Module object: Our interface to the outside world. We import -// and export values on it. There are various ways Module can be used: -// 1. Not defined. We create it here -// 2. A function parameter, function(Module) { ..generated code.. } -// 3. pre-run appended it, var Module = {}; ..generated code.. -// 4. External script tag defines var Module. -// We need to check if Module already exists (e.g. case 3 above). -// Substitution will be replaced with actual code on later stage of the build, -// this way Closure Compiler will not mangle it (e.g. case 4. above). -// Note that if you want to run closure, and also to use Module -// after the generated code, you will need to define var Module = {}; -// before the code. Then that object will be used in the code, and you -// can continue to use Module afterwards as well. -var Module = typeof unityFramework != 'undefined' ? unityFramework : {}; - -// Set up the promise that indicates the Module is initialized -var readyPromiseResolve, readyPromiseReject; -Module['ready'] = new Promise((resolve, reject) => { - readyPromiseResolve = resolve; - readyPromiseReject = reject; -}); -["_main","_ReleaseKeys","_GetCopyBufferAsCStr","_getMetricsInfo","_SendMessageFloat","_SendMessageString","_SendMessage","_SetFullscreen","_InjectProfilerSample","_SendPasteEvent","_fflush","onRuntimeInitialized"].forEach((prop) => { - if (!Object.getOwnPropertyDescriptor(Module['ready'], prop)) { - Object.defineProperty(Module['ready'], prop, { - get: () => abort('You are getting ' + prop + ' on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js'), - set: () => abort('You are setting ' + prop + ' on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js'), - }); - } -}); - -// --pre-jses are emitted after the Module integration code, so that they can -// refer to Module (if they choose; they can also define Module) - - -var stackTraceReference = "(^|\\n)(\\s+at\\s+|)jsStackTrace(\\s+\\(|@)([^\\n]+):\\d+:\\d+(\\)|)(\\n|$)"; -var stackTraceReferenceMatch = jsStackTrace().match(new RegExp(stackTraceReference)); -if (stackTraceReferenceMatch) - Module.stackTraceRegExp = new RegExp(stackTraceReference.replace("([^\\n]+)", stackTraceReferenceMatch[4].replace(/[\\^${}[\]().*+?|]/g,"\\$&")).replace("jsStackTrace", "[^\\n]+")); - -var abort = function (what) { - if (ABORT) - return; - ABORT = true; - EXITSTATUS = 1; - if (typeof ENVIRONMENT_IS_PTHREAD !== "undefined" && ENVIRONMENT_IS_PTHREAD) - console.error("Pthread aborting at " + new Error().stack); - if (what !== undefined) { - out(what); - err(what); - // Convert "what" to a string for the abort handler. - // Use .toString() method for Error objects to get a readable text. - what = (what instanceof Error) ? what.toString() : JSON.stringify(what); - } else { - what = ""; - } - var message = "abort(" + what + ") at " + stackTrace(); - if (Module.abortHandler && Module.abortHandler(message)) - return; - throw message; -} -Module["SetFullscreen"] = function (fullscreen) { - if (typeof runtimeInitialized === 'undefined' || !runtimeInitialized) { - console.log ("Runtime not initialized yet."); - } else if (typeof JSEvents === 'undefined') { - console.log ("Player not loaded yet."); - } else { - var tmp = JSEvents.canPerformEventHandlerRequests; - JSEvents.canPerformEventHandlerRequests = function () { return 1; }; - Module.ccall("SetFullscreen", null, ["number"], [fullscreen]); - JSEvents.canPerformEventHandlerRequests = tmp; - } -}; -if (!Module['ENVIRONMENT_IS_PTHREAD']) { - Module['preRun'].push(function () { - function injectIndexedDBToAutomaticallyPersist() { - // The contents of this function cherry-pick the changes from upstream Emscripten - // PR https://github.com/emscripten-core/emscripten/pull/21938. - // TODO: Once Emscripten is updated the next time, this IDBFS.queuePersist = ... assignment can be removed. - IDBFS.queuePersist = function(mount) { - function onPersistComplete() { - if (mount.idbPersistState === 'again') startPersist(); // If a new sync request has appeared in between, kick off a new sync - else mount.idbPersistState = 0; // Otherwise reset sync state back to idle to wait for a new sync later - } - function startPersist() { - mount.idbPersistState = 'idb'; // Mark that we are currently running a sync operation - IDBFS.syncfs(mount, /*populate:*/false, onPersistComplete); - } - - if (!mount.idbPersistState) { - // Programs typically write/copy/move multiple files in the in-memory - // filesystem within a single app frame, so when a filesystem sync - // command is triggered, do not start it immediately, but only after - // the current frame is finished. This way all the modified files - // inside the main loop tick will be batched up to the same sync. - mount.idbPersistState = setTimeout(startPersist, 0); - } else if (mount.idbPersistState === 'idb') { - // There is an active IndexedDB sync operation in-flight, but we now - // have accumulated more files to sync. We should therefore queue up - // a new sync after the current one finishes so that all writes - // will be properly persisted. - mount.idbPersistState = 'again'; - } - }; - // TODO: Once Emscripten is updated the next time, this IDBFS.mount = ... assignment can be removed. - IDBFS.mount = function(mount) { - // reuse core MEMFS functionality - var mnt = MEMFS.mount(mount); - // If the automatic IDBFS persistence option has been selected, then automatically persist - // all modifications to the filesystem as they occur. - if (typeof mount !== 'undefined' && mount.opts && mount.opts.autoPersist) { - mnt.idbPersistState = 0; // IndexedDB sync starts in idle state - var memfs_node_ops = mnt.node_ops; - mnt.node_ops = Object.assign({}, mnt.node_ops); // Clone node_ops to inject write tracking - mnt.node_ops.mknod = function(parent, name, mode, dev) { - var node = memfs_node_ops.mknod(parent, name, mode, dev); - // Propagate injected node_ops to the newly created child node - node.node_ops = mnt.node_ops; - // Remember for each IDBFS node which IDBFS mount point they came from so we know which mount to persist on modification. - node.idbfs_mount = mnt.mount; - // Remember original MEMFS stream_ops for this node - node.memfs_stream_ops = node.stream_ops; - // Clone stream_ops to inject write tracking - node.stream_ops = Object.assign({}, node.stream_ops); - - // Track all file writes - node.stream_ops.write = function(stream, buffer, offset, length, position, canOwn) { - // This file has been modified, we must persist IndexedDB when this file closes - stream.node.isModified = true; - return node.memfs_stream_ops.write(stream, buffer, offset, length, position, canOwn); - }; - - // Persist IndexedDB on file close - node.stream_ops.close = function(stream) { - var n = stream.node; - if (n.isModified) { - IDBFS.queuePersist(n.idbfs_mount); - n.isModified = false; - } - if (n.memfs_stream_ops.close) return n.memfs_stream_ops.close(stream); - }; - - return node; - }; - // Also kick off persisting the filesystem on other operations that modify the filesystem. - mnt.node_ops.rmdir = function(parent, name) { IDBFS.queuePersist(mnt.mount); return memfs_node_ops.rmdir(parent, name); }; - mnt.node_ops.unlink = function(parent, name) { IDBFS.queuePersist(mnt.mount); return memfs_node_ops.unlink(parent, name); }; - mnt.node_ops.mkdir = function(path, mode) { IDBFS.queuePersist(mnt.mount); return memfs_node_ops.mkdir(path, mode); }; - mnt.node_ops.symlink = function(parent, newname, oldpath) { IDBFS.queuePersist(mnt.mount); return memfs_node_ops.symlink(parent, newname, oldpath); }; - mnt.node_ops.rename = function(old_node, new_dir, new_name) { IDBFS.queuePersist(mnt.mount); return memfs_node_ops.rename(old_node, new_dir, new_name); }; - } - return mnt; - }; - } - // TODO: Once Emscripten is updated the next time, this injectIndexedDBToAutomaticallyPersist() function can be removed. - injectIndexedDBToAutomaticallyPersist(); - // Initialize the IndexedDB based file system. Module['unityFileSystemInit'] allows - // developers to override this with their own function, when they want to do cloud storage - // instead. - var unityFileSystemInit = Module['unityFileSystemInit'] || function () { - FS.mkdir('/idbfs'); - // If user has specified Module.autoSyncPersistentDataPath = true in their JS web template config, then the IndexedDB storage - // will be automatically persisted to the user. - // Save the IDBFS mount point to Module so that JS_FileSystem_Sync() function can have access to it. - Module.__unityIdbfsMount = FS.mount(IDBFS, { autoPersist: !!Module['autoSyncPersistentDataPath'] }, '/idbfs'); - Module.addRunDependency('JS_FileSystem_Mount'); - FS.syncfs(true, function (err) { - if (err) - console.log('IndexedDB is not available. Data will not persist in cache and PlayerPrefs will not be saved.'); - Module.removeRunDependency('JS_FileSystem_Mount'); - }); - }; - unityFileSystemInit(); - }); -} -var videoInputDevices = []; // Set to null to disable video input devices altogether. -// Track whether we have been able to enumerate media devices successfully at least once. Used -// by JS_WebCamVideo_GetNumDevices() to detect if we are clear of the browser spec issue -// https://github.com/w3c/mediacapture-main/issues/905 -var videoInputDevicesSuccessfullyEnumerated = false; - -// Bug/limitation: Chrome does not specify deviceIds for any MediaDeviceInfo input devices at least in Chrome 85 on Windows 10 -// This means that we need to use an awkward heuristic way of matching old video input connections to new ones. -function matchToOldDevice(newDevice) { - var oldDevices = Object.keys(videoInputDevices); - // First match by deviceId - for(var i = 0; i < oldDevices.length; ++i) { - var old = videoInputDevices[oldDevices[i]]; - if (old.deviceId && old.deviceId == newDevice.deviceId) return old; - } - // Then by object identity, in case that is supported. - for(var i = 0; i < oldDevices.length; ++i) { - var old = videoInputDevices[oldDevices[i]]; - if (old == newDevice) return old; - } - // Then by label - for(var i = 0; i < oldDevices.length; ++i) { - var old = videoInputDevices[oldDevices[i]]; - if (old.label && old.label == newDevice.label) return old; - } - // Last, by groupId + kind combination - for(var i = 0; i < oldDevices.length; ++i) { - var old = videoInputDevices[oldDevices[i]]; - if (old.groupId && old.kind && old.groupId == newDevice.groupId && old.kind == newDevice.kind) return old; - } -} - -function assignNewVideoInputId() { - for(var i = 0;; ++i) { - if (!videoInputDevices[i]) return i; - } -} - -function updateVideoInputDevices(devices) { - videoInputDevicesSuccessfullyEnumerated = true; - // we're going to clear the list of videoInputDevices and regenerate it to get more accurate info after being granted camera access - videoInputDevices = []; - var retainedDevices = {}; - var newDevices = []; - - // Find devices that still exist - devices.forEach(function (device) { - if (device.kind === 'videoinput') { // Only interested in WebCam inputs - var oldDevice = matchToOldDevice(device); - if (oldDevice) { - retainedDevices[oldDevice.id] = oldDevice; - } else { - newDevices.push(device); - } - } - }); - videoInputDevices = retainedDevices; - - // Assign IDs to video input devices that are new - newDevices.forEach(function (device) { - if (!device.id) { - device.id = assignNewVideoInputId(); - // Attempt to name the device. In both Firefox and Chrome, label is null. - // In Chrome, deviceId is null. (could use it here, but human-readable - // name is probably better than a long hash) - device.name = device.label || ("Video input #" + (device.id + 1)); - - // Chrome 85 on Android labels camera provides devices with labels like - // "camera2 0, facing back" and "camera2 1, facing front", use that to - // determine whether the device is front facing or not. - // some labels don't provide that info, like the camera on a 2019 MacbookPro: FaceTime HD Camera (Built-in) - // so if there's no "front" or "back" in the label or name, we're going to assume it's front facing - device.isFrontFacing = device.name.toLowerCase().includes('front') || (!(device.name.toLowerCase().includes('front')) && !(device.name.toLowerCase().includes('back'))); - - videoInputDevices[device.id] = device; - } - }); -} - -// Track whether we are currently waiting for enumerateMediaDevices action to complete before -// we'll continue with the rest of the page startup. -var mediaDevicesRunDependencyPending = true; - -function removeEnumerateMediaDevicesRunDependency() { - if (!mediaDevicesRunDependencyPending) return; - mediaDevicesRunDependencyPending = false; - // This is the startup run of media devices enumeration, so remove the "run blocker" - // from the Emscripten runtime, which will cause the Wasm code main() to start as - // result. But If main() throws an exception, we don't want that exception to flow - // into the catch() handler of this Promise below, so detach the execution of - // removeRunDependency() to occur on the next event loop tick. - try { - removeRunDependency('enumerateMediaDevices'); - } catch(e) { - // Raise a startup error that is propagated out to the Promise returned from - // createUnityInstance(). - Module.startupErrorHandler(e); - }; -} - -function enumerateMediaDeviceList() { - if (!videoInputDevices) return; - // Bug/limitation: If there are multiple video or audio devices connected, - // Chrome only lists one of each category (audioinput/videoinput/audioutput) (tested Chrome 85 on Windows 10) - navigator.mediaDevices.enumerateDevices().then(function(devices) { - // Devices enumeration completed: update video input devices list. - updateVideoInputDevices(devices); - removeEnumerateMediaDevicesRunDependency(); - }).catch(function(e) { - console.warn('Unable to enumerate media devices: ' + e + '\nWebcams will not be available.'); - disableAccessToMediaDevices(); - }); - - // Work around Firefox 81 bug on Windows: - // https://bugzilla.mozilla.org/show_bug.cgi?id=1397977, devicechange - // events do not fire, so resort to polling for device changes once every - // 60 seconds. - if (/Firefox/.test(navigator.userAgent)) { - setTimeout(enumerateMediaDeviceList, 60000); - warnOnce('Applying workaround to Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=1397977'); - } -} - -function disableAccessToMediaDevices() { - // Safari 11 has navigator.mediaDevices, but navigator.mediaDevices.add/removeEventListener is undefined - if (navigator.mediaDevices && navigator.mediaDevices.removeEventListener) { - navigator.mediaDevices.removeEventListener('devicechange', enumerateMediaDeviceList); - } - videoInputDevices = null; -} -Module['disableAccessToMediaDevices'] = disableAccessToMediaDevices; - -if (!Module['ENVIRONMENT_IS_PTHREAD']) { - if (!navigator.mediaDevices) { - console.warn('navigator.mediaDevices not supported by this browser. Webcam access will not be available.' + (location.protocol == 'https:' ? '' : ' Try hosting the page over HTTPS, because some browsers disable webcam access when insecure HTTP is being used.')); - disableAccessToMediaDevices(); - } else setTimeout(function() { - try { - // Do not start engine main() until we have completed enumeration. - addRunDependency('enumerateMediaDevices'); - - // Enumerate media devices now.. - enumerateMediaDeviceList(); - - // .. and whenever the connected devices list changes. - - navigator.mediaDevices.addEventListener('devicechange', enumerateMediaDeviceList); - // Firefox won't complete device enumeration if the window isn't in focus causing the startup to hang, so we - // wait a second before removing the dependency and starting with an empty list of devices. Moving forward it's - // likely more browsers will assume this standard. - // See https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices - setTimeout(removeEnumerateMediaDevicesRunDependency, 1000); - } catch(e) { - console.warn('Unable to enumerate media devices: ' + e); - disableAccessToMediaDevices(); - } - }, 0); -} -function SendMessage(gameObject, func, param) { - var func_cstr = stringToNewUTF8(func); - var gameObject_cstr = stringToNewUTF8(gameObject); - var param_cstr = 0; - - try { - if (param === undefined) - _SendMessage(gameObject_cstr, func_cstr); - else if (typeof param === "string") { - param_cstr = stringToNewUTF8(param); - _SendMessageString(gameObject_cstr, func_cstr, param_cstr); - } - else if (typeof param === "number") - _SendMessageFloat(gameObject_cstr, func_cstr, param); - else - throw "" + param + " is does not have a type which is supported by SendMessage."; - - } finally { - _free(param_cstr); - _free(gameObject_cstr); - _free(func_cstr); - } -} -// Export SendMessage out from the JS module via the Module export object -Module["SendMessage"] = SendMessage; -// Create a promise that is resolved later when "RunWebGLPlayer" was run -// We can ignore the reject handler as the UnityLoader registers a global startupErrorHandler -var _resolvePlayerIsInitialized; -var _playerIsInitializedPromise = new Promise(function (resolve) { - _resolvePlayerIsInitialized = resolve; -}); - -// Waits for unity player initialization to finish, i.e., load initial scene -function _WaitForInitialization() { - return _playerIsInitializedPromise; -} - -Module["WebPlayer"] = { - PlayerIsInitialized: _resolvePlayerIsInitialized, - WaitForInitialization: _WaitForInitialization, -}; -var ___cxa_throw = (function() { - var original___cxa_throw = ___cxa_throw; - - return function() { - console.log("Exception at: \n"+stackTrace()); - original___cxa_throw(); - } -})(); - - -// Sometimes an existing Module object exists with properties -// meant to overwrite the default module functionality. Here -// we collect those properties and reapply _after_ we configure -// the current environment's defaults to avoid having to be so -// defensive during initialization. -var moduleOverrides = Object.assign({}, Module); - -var arguments_ = []; -var thisProgram = './this.program'; -var quit_ = (status, toThrow) => { - throw toThrow; -}; - -// Determine the runtime environment we are in. You can customize this by -// setting the ENVIRONMENT setting at compile time (see settings.js). - -var ENVIRONMENT_IS_WEB = true; -var ENVIRONMENT_IS_WORKER = false; -var ENVIRONMENT_IS_NODE = false; -var ENVIRONMENT_IS_SHELL = false; - -if (Module['ENVIRONMENT']) { - throw new Error('Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -sENVIRONMENT=web or -sENVIRONMENT=node)'); -} - -// `/` should be present at the end if `scriptDirectory` is not empty -var scriptDirectory = ''; -function locateFile(path) { - if (Module['locateFile']) { - return Module['locateFile'](path, scriptDirectory); - } - return scriptDirectory + path; -} - -// Hooks that are implemented differently in different runtime environments. -var read_, - readAsync, - readBinary, - setWindowTitle; - -if (ENVIRONMENT_IS_SHELL) { - - if ((typeof process == 'object' && typeof require === 'function') || typeof window == 'object' || typeof importScripts == 'function') throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); - - if (typeof read != 'undefined') { - read_ = (f) => { - return read(f); - }; - } - - readBinary = (f) => { - let data; - if (typeof readbuffer == 'function') { - return new Uint8Array(readbuffer(f)); - } - data = read(f, 'binary'); - assert(typeof data == 'object'); - return data; - }; - - readAsync = (f, onload, onerror) => { - setTimeout(() => onload(readBinary(f)), 0); - }; - - if (typeof clearTimeout == 'undefined') { - globalThis.clearTimeout = (id) => {}; - } - - if (typeof scriptArgs != 'undefined') { - arguments_ = scriptArgs; - } else if (typeof arguments != 'undefined') { - arguments_ = arguments; - } - - if (typeof quit == 'function') { - quit_ = (status, toThrow) => { - // Unlike node which has process.exitCode, d8 has no such mechanism. So we - // have no way to set the exit code and then let the program exit with - // that code when it naturally stops running (say, when all setTimeouts - // have completed). For that reason, we must call `quit` - the only way to - // set the exit code - but quit also halts immediately. To increase - // consistency with node (and the web) we schedule the actual quit call - // using a setTimeout to give the current stack and any exception handlers - // a chance to run. This enables features such as addOnPostRun (which - // expected to be able to run code after main returns). - setTimeout(() => { - if (!(toThrow instanceof ExitStatus)) { - let toLog = toThrow; - if (toThrow && typeof toThrow == 'object' && toThrow.stack) { - toLog = [toThrow, toThrow.stack]; - } - err('exiting due to exception: ' + toLog); - } - quit(status); - }); - throw toThrow; - }; - } - - if (typeof print != 'undefined') { - // Prefer to use print/printErr where they exist, as they usually work better. - if (typeof console == 'undefined') console = /** @type{!Console} */({}); - console.log = /** @type{!function(this:Console, ...*): undefined} */ (print); - console.warn = console.error = /** @type{!function(this:Console, ...*): undefined} */ (typeof printErr != 'undefined' ? printErr : print); - } - -} else - -// Note that this includes Node.js workers when relevant (pthreads is enabled). -// Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and -// ENVIRONMENT_IS_NODE. -if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { - if (ENVIRONMENT_IS_WORKER) { // Check worker, not web, since window could be polyfilled - scriptDirectory = self.location.href; - } else if (typeof document != 'undefined' && document.currentScript) { // web - scriptDirectory = document.currentScript.src; - } - // When MODULARIZE, this JS may be executed later, after document.currentScript - // is gone, so we saved it, and we use it here instead of any other info. - if (_scriptDir) { - scriptDirectory = _scriptDir; - } - // blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them. - // otherwise, slice off the final part of the url to find the script directory. - // if scriptDirectory does not contain a slash, lastIndexOf will return -1, - // and scriptDirectory will correctly be replaced with an empty string. - // If scriptDirectory contains a query (starting with ?) or a fragment (starting with #), - // they are removed because they could contain a slash. - if (scriptDirectory.indexOf('blob:') !== 0) { - scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf('/')+1); - } else { - scriptDirectory = ''; - } - - if (!(typeof window == 'object' || typeof importScripts == 'function')) throw new Error('not compiled for this environment (did you build to HTML and try to run it not on the web, or set ENVIRONMENT to something - like node - and run it someplace else - like on the web?)'); - - // Differentiate the Web Worker from the Node Worker case, as reading must - // be done differently. - { -// include: web_or_worker_shell_read.js -read_ = (url) => { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, false); - xhr.send(null); - return xhr.responseText; - } - - if (ENVIRONMENT_IS_WORKER) { - readBinary = (url) => { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, false); - xhr.responseType = 'arraybuffer'; - xhr.send(null); - return new Uint8Array(/** @type{!ArrayBuffer} */(xhr.response)); - }; - } - - readAsync = (url, onload, onerror) => { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.responseType = 'arraybuffer'; - xhr.onload = () => { - if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { // file URLs can return 0 - onload(xhr.response); - return; - } - onerror(); - }; - xhr.onerror = onerror; - xhr.send(null); - } - -// end include: web_or_worker_shell_read.js - } - - setWindowTitle = (title) => document.title = title; -} else -{ - throw new Error('environment detection error'); -} - -var out = Module['print'] || console.log.bind(console); -var err = Module['printErr'] || console.error.bind(console); - -// Merge back in the overrides -Object.assign(Module, moduleOverrides); -// Free the object hierarchy contained in the overrides, this lets the GC -// reclaim data used e.g. in memoryInitializerRequest, which is a large typed array. -moduleOverrides = null; -checkIncomingModuleAPI(); - -// Emit code to handle expected values on the Module object. This applies Module.x -// to the proper local x. This has two benefits: first, we only emit it if it is -// expected to arrive, and second, by using a local everywhere else that can be -// minified. - -if (Module['arguments']) arguments_ = Module['arguments'];legacyModuleProp('arguments', 'arguments_'); - -if (Module['thisProgram']) thisProgram = Module['thisProgram'];legacyModuleProp('thisProgram', 'thisProgram'); - -if (Module['quit']) quit_ = Module['quit'];legacyModuleProp('quit', 'quit_'); - -// perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message -// Assertions on removed incoming Module JS APIs. -assert(typeof Module['memoryInitializerPrefixURL'] == 'undefined', 'Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead'); -assert(typeof Module['pthreadMainPrefixURL'] == 'undefined', 'Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead'); -assert(typeof Module['cdInitializerPrefixURL'] == 'undefined', 'Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead'); -assert(typeof Module['filePackagePrefixURL'] == 'undefined', 'Module.filePackagePrefixURL option was removed, use Module.locateFile instead'); -assert(typeof Module['read'] == 'undefined', 'Module.read option was removed (modify read_ in JS)'); -assert(typeof Module['readAsync'] == 'undefined', 'Module.readAsync option was removed (modify readAsync in JS)'); -assert(typeof Module['readBinary'] == 'undefined', 'Module.readBinary option was removed (modify readBinary in JS)'); -assert(typeof Module['setWindowTitle'] == 'undefined', 'Module.setWindowTitle option was removed (modify setWindowTitle in JS)'); -assert(typeof Module['TOTAL_MEMORY'] == 'undefined', 'Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY'); -legacyModuleProp('read', 'read_'); -legacyModuleProp('readAsync', 'readAsync'); -legacyModuleProp('readBinary', 'readBinary'); -legacyModuleProp('setWindowTitle', 'setWindowTitle'); - -var PROXYFS = 'PROXYFS is no longer included by default; build with -lproxyfs.js'; -var WORKERFS = 'WORKERFS is no longer included by default; build with -lworkerfs.js'; -var NODEFS = 'NODEFS is no longer included by default; build with -lnodefs.js'; - -assert(!ENVIRONMENT_IS_WORKER, "worker environment detected but not enabled at build time. Add 'worker' to `-sENVIRONMENT` to enable."); - -assert(!ENVIRONMENT_IS_NODE, "node environment detected but not enabled at build time. Add 'node' to `-sENVIRONMENT` to enable."); - -assert(!ENVIRONMENT_IS_SHELL, "shell environment detected but not enabled at build time. Add 'shell' to `-sENVIRONMENT` to enable."); - - -// end include: shell.js -// include: preamble.js -// === Preamble library stuff === - -// Documentation for the public APIs defined in this file must be updated in: -// site/source/docs/api_reference/preamble.js.rst -// A prebuilt local version of the documentation is available at: -// site/build/text/docs/api_reference/preamble.js.txt -// You can also build docs locally as HTML or other formats in site/ -// An online HTML version (which may be of a different version of Emscripten) -// is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html - -var wasmBinary; -if (Module['wasmBinary']) wasmBinary = Module['wasmBinary'];legacyModuleProp('wasmBinary', 'wasmBinary'); -var noExitRuntime = Module['noExitRuntime'] || true;legacyModuleProp('noExitRuntime', 'noExitRuntime'); - -if (typeof WebAssembly != 'object') { - abort('no native wasm support detected'); -} - -// Wasm globals - -var wasmMemory; - -//======================================== -// Runtime essentials -//======================================== - -// whether we are quitting the application. no code should run after this. -// set in exit() and abort() -var ABORT = false; - -// set by exit() and abort(). Passed to 'onExit' handler. -// NOTE: This is also used as the process return code code in shell environments -// but only when noExitRuntime is false. -var EXITSTATUS; - -/** @type {function(*, string=)} */ -function assert(condition, text) { - if (!condition) { - abort('Assertion failed' + (text ? ': ' + text : '')); - } -} - -// We used to include malloc/free by default in the past. Show a helpful error in -// builds with assertions. - -// Memory management - -var HEAP, -/** @type {!Int8Array} */ - HEAP8, -/** @type {!Uint8Array} */ - HEAPU8, -/** @type {!Int16Array} */ - HEAP16, -/** @type {!Uint16Array} */ - HEAPU16, -/** @type {!Int32Array} */ - HEAP32, -/** @type {!Uint32Array} */ - HEAPU32, -/** @type {!Float32Array} */ - HEAPF32, -/** @type {!Float64Array} */ - HEAPF64; - -function updateMemoryViews() { - var b = wasmMemory.buffer; - Module['HEAP8'] = HEAP8 = new Int8Array(b); - Module['HEAP16'] = HEAP16 = new Int16Array(b); - Module['HEAP32'] = HEAP32 = new Int32Array(b); - Module['HEAPU8'] = HEAPU8 = new Uint8Array(b); - Module['HEAPU16'] = HEAPU16 = new Uint16Array(b); - Module['HEAPU32'] = HEAPU32 = new Uint32Array(b); - Module['HEAPF32'] = HEAPF32 = new Float32Array(b); - Module['HEAPF64'] = HEAPF64 = new Float64Array(b); -} - -assert(!Module['STACK_SIZE'], 'STACK_SIZE can no longer be set at runtime. Use -sSTACK_SIZE at link time') - -assert(typeof Int32Array != 'undefined' && typeof Float64Array !== 'undefined' && Int32Array.prototype.subarray != undefined && Int32Array.prototype.set != undefined, - 'JS engine does not provide full typed array support'); - -// If memory is defined in wasm, the user can't provide it, or set INITIAL_MEMORY -assert(!Module['wasmMemory'], 'Use of `wasmMemory` detected. Use -sIMPORTED_MEMORY to define wasmMemory externally'); -assert(!Module['INITIAL_MEMORY'], 'Detected runtime INITIAL_MEMORY setting. Use -sIMPORTED_MEMORY to define wasmMemory dynamically'); - -// include: runtime_init_table.js -// In regular non-RELOCATABLE mode the table is exported -// from the wasm module and this will be assigned once -// the exports are available. -var wasmTable; - -// end include: runtime_init_table.js -// include: runtime_stack_check.js -// Initializes the stack cookie. Called at the startup of main and at the startup of each thread in pthreads mode. -function writeStackCookie() { - var max = _emscripten_stack_get_end(); - assert((max & 3) == 0); - // If the stack ends at address zero we write our cookies 4 bytes into the - // stack. This prevents interference with the (separate) address-zero check - // below. - if (max == 0) { - max += 4; - } - // The stack grow downwards towards _emscripten_stack_get_end. - // We write cookies to the final two words in the stack and detect if they are - // ever overwritten. - HEAPU32[((max)>>2)] = 0x02135467; - HEAPU32[(((max)+(4))>>2)] = 0x89BACDFE; - // Also test the global address 0 for integrity. - HEAPU32[0] = 0x63736d65; /* 'emsc' */ -} - -function checkStackCookie() { - if (ABORT) return; - var max = _emscripten_stack_get_end(); - // See writeStackCookie(). - if (max == 0) { - max += 4; - } - var cookie1 = HEAPU32[((max)>>2)]; - var cookie2 = HEAPU32[(((max)+(4))>>2)]; - if (cookie1 != 0x02135467 || cookie2 != 0x89BACDFE) { - abort('Stack overflow! Stack cookie has been overwritten at ' + ptrToString(max) + ', expected hex dwords 0x89BACDFE and 0x2135467, but received ' + ptrToString(cookie2) + ' ' + ptrToString(cookie1)); - } - // Also test the global address 0 for integrity. - if (HEAPU32[0] !== 0x63736d65 /* 'emsc' */) { - abort('Runtime error: The application has corrupted its heap memory area (address zero)!'); - } -} - -// end include: runtime_stack_check.js -// include: runtime_assertions.js -// Endianness check -(function() { - var h16 = new Int16Array(1); - var h8 = new Int8Array(h16.buffer); - h16[0] = 0x6373; - if (h8[0] !== 0x73 || h8[1] !== 0x63) throw 'Runtime error: expected the system to be little-endian! (Run with -sSUPPORT_BIG_ENDIAN to bypass)'; -})(); - -// end include: runtime_assertions.js -var __ATPRERUN__ = []; // functions called before the runtime is initialized -var __ATINIT__ = []; // functions called during startup -var __ATMAIN__ = []; // functions called when main() is to be run -var __ATEXIT__ = []; // functions called during shutdown -var __ATPOSTRUN__ = []; // functions called after the main() is called - -var runtimeInitialized = false; - -var runtimeKeepaliveCounter = 0; - -function keepRuntimeAlive() { - return noExitRuntime || runtimeKeepaliveCounter > 0; -} - -function preRun() { - if (Module['preRun']) { - if (typeof Module['preRun'] == 'function') Module['preRun'] = [Module['preRun']]; - while (Module['preRun'].length) { - addOnPreRun(Module['preRun'].shift()); - } - } - callRuntimeCallbacks(__ATPRERUN__); -} - -function initRuntime() { - assert(!runtimeInitialized); - runtimeInitialized = true; - - checkStackCookie(); - - -if (!Module["noFSInit"] && !FS.init.initialized) - FS.init(); -FS.ignorePermissions = false; - -TTY.init(); -SOCKFS.root = FS.mount(SOCKFS, {}, null); -PIPEFS.root = FS.mount(PIPEFS, {}, null); - callRuntimeCallbacks(__ATINIT__); -} - -function preMain() { - checkStackCookie(); - - callRuntimeCallbacks(__ATMAIN__); -} - -function postRun() { - checkStackCookie(); - - if (Module['postRun']) { - if (typeof Module['postRun'] == 'function') Module['postRun'] = [Module['postRun']]; - while (Module['postRun'].length) { - addOnPostRun(Module['postRun'].shift()); - } - } - - callRuntimeCallbacks(__ATPOSTRUN__); -} - -function addOnPreRun(cb) { - __ATPRERUN__.unshift(cb); -} - -function addOnInit(cb) { - __ATINIT__.unshift(cb); -} - -function addOnPreMain(cb) { - __ATMAIN__.unshift(cb); -} - -function addOnExit(cb) { -} - -function addOnPostRun(cb) { - __ATPOSTRUN__.unshift(cb); -} - -// include: runtime_math.js -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32 - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc - -assert(Math.imul, 'This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); -assert(Math.fround, 'This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); -assert(Math.clz32, 'This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); -assert(Math.trunc, 'This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill'); - -// end include: runtime_math.js -// A counter of dependencies for calling run(). If we need to -// do asynchronous work before running, increment this and -// decrement it. Incrementing must happen in a place like -// Module.preRun (used by emcc to add file preloading). -// Note that you can add dependencies in preRun, even though -// it happens right before run - run will be postponed until -// the dependencies are met. -var runDependencies = 0; -var runDependencyWatcher = null; -var dependenciesFulfilled = null; // overridden to take different actions when all run dependencies are fulfilled -var runDependencyTracking = {}; - -function getUniqueRunDependency(id) { - var orig = id; - while (1) { - if (!runDependencyTracking[id]) return id; - id = orig + Math.random(); - } -} - -function addRunDependency(id) { - runDependencies++; - - if (Module['monitorRunDependencies']) { - Module['monitorRunDependencies'](runDependencies); - } - - if (id) { - assert(!runDependencyTracking[id]); - runDependencyTracking[id] = 1; - if (runDependencyWatcher === null && typeof setInterval != 'undefined') { - // Check for missing dependencies every few seconds - runDependencyWatcher = setInterval(() => { - if (ABORT) { - clearInterval(runDependencyWatcher); - runDependencyWatcher = null; - return; - } - var shown = false; - for (var dep in runDependencyTracking) { - if (!shown) { - shown = true; - err('still waiting on run dependencies:'); - } - err('dependency: ' + dep); - } - if (shown) { - err('(end of list)'); - } - }, 10000); - } - } else { - err('warning: run dependency added without ID'); - } -} - -function removeRunDependency(id) { - runDependencies--; - - if (Module['monitorRunDependencies']) { - Module['monitorRunDependencies'](runDependencies); - } - - if (id) { - assert(runDependencyTracking[id]); - delete runDependencyTracking[id]; - } else { - err('warning: run dependency removed without ID'); - } - if (runDependencies == 0) { - if (runDependencyWatcher !== null) { - clearInterval(runDependencyWatcher); - runDependencyWatcher = null; - } - if (dependenciesFulfilled) { - var callback = dependenciesFulfilled; - dependenciesFulfilled = null; - callback(); // can add another dependenciesFulfilled - } - } -} - -/** @param {string|number=} what */ -function abort(what) { - if (Module['onAbort']) { - Module['onAbort'](what); - } - - what = 'Aborted(' + what + ')'; - // TODO(sbc): Should we remove printing and leave it up to whoever - // catches the exception? - err(what); - - ABORT = true; - EXITSTATUS = 1; - - // Use a wasm runtime error, because a JS error might be seen as a foreign - // exception, which means we'd run destructors on it. We need the error to - // simply make the program stop. - // FIXME This approach does not work in Wasm EH because it currently does not assume - // all RuntimeErrors are from traps; it decides whether a RuntimeError is from - // a trap or not based on a hidden field within the object. So at the moment - // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that - // allows this in the wasm spec. - - // Suppress closure compiler warning here. Closure compiler's builtin extern - // defintion for WebAssembly.RuntimeError claims it takes no arguments even - // though it can. - // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed. - /** @suppress {checkTypes} */ - var e = new WebAssembly.RuntimeError(what); - - readyPromiseReject(e); - // Throw the error whether or not MODULARIZE is set because abort is used - // in code paths apart from instantiation where an exception is expected - // to be thrown when abort is called. - throw e; -} - -// include: memoryprofiler.js -// end include: memoryprofiler.js -// include: URIUtils.js -// Prefix of data URIs emitted by SINGLE_FILE and related options. -var dataURIPrefix = 'data:application/octet-stream;base64,'; - -// Indicates whether filename is a base64 data URI. -function isDataURI(filename) { - // Prefix of data URIs emitted by SINGLE_FILE and related options. - return filename.startsWith(dataURIPrefix); -} - -// Indicates whether filename is delivered via file protocol (as opposed to http/https) -function isFileURI(filename) { - return filename.startsWith('file://'); -} - -// end include: URIUtils.js -/** @param {boolean=} fixedasm */ -function createExportWrapper(name, fixedasm) { - return function() { - var displayName = name; - var asm = fixedasm; - if (!fixedasm) { - asm = Module['asm']; - } - assert(runtimeInitialized, 'native function `' + displayName + '` called before runtime initialization'); - if (!asm[name]) { - assert(asm[name], 'exported native function `' + displayName + '` not found'); - } - return asm[name].apply(null, arguments); - }; -} - -// include: runtime_exceptions.js -// end include: runtime_exceptions.js -var wasmBinaryFile; - wasmBinaryFile = 'build.wasm'; - if (!isDataURI(wasmBinaryFile)) { - wasmBinaryFile = locateFile(wasmBinaryFile); - } - -function getBinary(file) { - try { - if (file == wasmBinaryFile && wasmBinary) { - return new Uint8Array(wasmBinary); - } - if (readBinary) { - return readBinary(file); - } - throw "both async and sync fetching of the wasm failed"; - } - catch (err) { - abort(err); - } -} - -function getBinaryPromise(binaryFile) { - // If we don't have the binary yet, try to load it asynchronously. - // Fetch has some additional restrictions over XHR, like it can't be used on a file:// url. - // See https://github.com/github/fetch/pull/92#issuecomment-140665932 - // Cordova or Electron apps are typically loaded from a file:// url. - // So use fetch if it is available and the url is not a file, otherwise fall back to XHR. - if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) { - if (typeof fetch == 'function' - ) { - return fetch(binaryFile, { credentials: 'same-origin' }).then((response) => { - if (!response['ok']) { - throw "failed to load wasm binary file at '" + binaryFile + "'"; - } - return response['arrayBuffer'](); - }).catch(() => getBinary(binaryFile)); - } - } - - // Otherwise, getBinary should be able to get it synchronously - return Promise.resolve().then(() => getBinary(binaryFile)); -} - -function instantiateArrayBuffer(binaryFile, imports, receiver) { - return getBinaryPromise(binaryFile).then((binary) => { - return WebAssembly.instantiate(binary, imports); - }).then((instance) => { - return instance; - }).then(receiver, (reason) => { - err('failed to asynchronously prepare wasm: ' + reason); - - // Warn on some common problems. - if (isFileURI(wasmBinaryFile)) { - err('warning: Loading from a file URI (' + wasmBinaryFile + ') is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing'); - } - abort(reason); - }); -} - -function instantiateAsync(binary, binaryFile, imports, callback) { - if (!binary && - typeof WebAssembly.instantiateStreaming == 'function' && - !isDataURI(binaryFile) && - typeof fetch == 'function') { - return fetch(binaryFile, { credentials: 'same-origin' }).then((response) => { - // Suppress closure warning here since the upstream definition for - // instantiateStreaming only allows Promise rather than - // an actual Response. - // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure is fixed. - /** @suppress {checkTypes} */ - var result = WebAssembly.instantiateStreaming(response, imports); - - return result.then( - callback, - function(reason) { - // We expect the most common failure cause to be a bad MIME type for the binary, - // in which case falling back to ArrayBuffer instantiation should work. - err('wasm streaming compile failed: ' + reason); - err('falling back to ArrayBuffer instantiation'); - return instantiateArrayBuffer(binaryFile, imports, callback); - }); - }); - } else { - return instantiateArrayBuffer(binaryFile, imports, callback); - } -} - -// Create the wasm instance. -// Receives the wasm imports, returns the exports. -function createWasm() { - - // prepare imports - var info = { - 'env': wasmImports, - 'wasi_snapshot_preview1': wasmImports, - }; - // Load the wasm module and create an instance of using native support in the JS engine. - // handle a generated wasm instance, receiving its exports and - // performing other necessary setup - /** @param {WebAssembly.Module=} module*/ - function receiveInstance(instance, module) { - var exports = instance.exports; - - Module['asm'] = exports; - - wasmMemory = Module['asm']['memory']; - assert(wasmMemory, "memory not found in wasm exports"); - // This assertion doesn't hold when emscripten is run in --post-link - // mode. - // TODO(sbc): Read INITIAL_MEMORY out of the wasm file in post-link mode. - //assert(wasmMemory.buffer.byteLength === 134217728); - updateMemoryViews(); - - wasmTable = Module['asm']['__indirect_function_table']; - assert(wasmTable, "table not found in wasm exports"); - - addOnInit(Module['asm']['__wasm_call_ctors']); - - removeRunDependency('wasm-instantiate'); - - return exports; - } - // wait for the pthread pool (if any) - addRunDependency('wasm-instantiate'); - - // Prefer streaming instantiation if available. - // Async compilation can be confusing when an error on the page overwrites Module - // (for example, if the order of elements is wrong, and the one defining Module is - // later), so we save Module and check it later. - var trueModule = Module; - function receiveInstantiationResult(result) { - // 'result' is a ResultObject object which has both the module and instance. - // receiveInstance() will swap in the exports (to Module.asm) so they can be called - assert(Module === trueModule, 'the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?'); - trueModule = null; - // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line. - // When the regression is fixed, can restore the above PTHREADS-enabled path. - receiveInstance(result['instance']); - } - - // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback - // to manually instantiate the Wasm module themselves. This allows pages to - // run the instantiation parallel to any other async startup actions they are - // performing. - // Also pthreads and wasm workers initialize the wasm instance through this - // path. - if (Module['instantiateWasm']) { - - try { - return Module['instantiateWasm'](info, receiveInstance); - } catch(e) { - err('Module.instantiateWasm callback failed with error: ' + e); - // If instantiation fails, reject the module ready promise. - readyPromiseReject(e); - } - } - - // If instantiation fails, reject the module ready promise. - instantiateAsync(wasmBinary, wasmBinaryFile, info, receiveInstantiationResult).catch(readyPromiseReject); - return {}; // no exports yet; we'll fill them in later -} - -// Globals used by JS i64 conversions (see makeSetValue) -var tempDouble; -var tempI64; - -// include: runtime_debug.js -function legacyModuleProp(prop, newName) { - if (!Object.getOwnPropertyDescriptor(Module, prop)) { - Object.defineProperty(Module, prop, { - configurable: true, - get: function() { - abort('Module.' + prop + ' has been replaced with plain ' + newName + ' (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)'); - } - }); - } -} - -function ignoredModuleProp(prop) { - if (Object.getOwnPropertyDescriptor(Module, prop)) { - abort('`Module.' + prop + '` was supplied but `' + prop + '` not included in INCOMING_MODULE_JS_API'); - } -} - -// forcing the filesystem exports a few things by default -function isExportedByForceFilesystem(name) { - return name === 'FS_createPath' || - name === 'FS_createDataFile' || - name === 'FS_createPreloadedFile' || - name === 'FS_unlink' || - name === 'addRunDependency' || - // The old FS has some functionality that WasmFS lacks. - name === 'FS_createLazyFile' || - name === 'FS_createDevice' || - name === 'removeRunDependency'; -} - -function missingGlobal(sym, msg) { - if (typeof globalThis !== 'undefined') { - Object.defineProperty(globalThis, sym, { - configurable: true, - get: function() { - warnOnce('`' + sym + '` is not longer defined by emscripten. ' + msg); - return undefined; - } - }); - } -} - -missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer'); - -function missingLibrarySymbol(sym) { - if (typeof globalThis !== 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) { - Object.defineProperty(globalThis, sym, { - configurable: true, - get: function() { - // Can't `abort()` here because it would break code that does runtime - // checks. e.g. `if (typeof SDL === 'undefined')`. - var msg = '`' + sym + '` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line'; - // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in - // library.js, which means $name for a JS name with no prefix, or name - // for a JS name like _name. - var librarySymbol = sym; - if (!librarySymbol.startsWith('_')) { - librarySymbol = '$' + sym; - } - msg += " (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=" + librarySymbol + ")"; - if (isExportedByForceFilesystem(sym)) { - msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; - } - warnOnce(msg); - return undefined; - } - }); - } - // Any symbol that is not included from the JS libary is also (by definition) - // not exported on the Module object. - unexportedRuntimeSymbol(sym); -} - -function unexportedRuntimeSymbol(sym) { - if (!Object.getOwnPropertyDescriptor(Module, sym)) { - Object.defineProperty(Module, sym, { - configurable: true, - get: function() { - var msg = "'" + sym + "' was not exported. add it to EXPORTED_RUNTIME_METHODS (see the FAQ)"; - if (isExportedByForceFilesystem(sym)) { - msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you'; - } - abort(msg); - } - }); - } -} - -// Used by XXXXX_DEBUG settings to output debug messages. -function dbg(text) { - // TODO(sbc): Make this configurable somehow. Its not always convenient for - // logging to show up as warnings. - console.warn.apply(console, arguments); -} - -// end include: runtime_debug.js -// === Body === - -var ASM_CONSTS = { - 5876112: () => { Module['emscripten_get_now_backup'] = performance.now; }, - 5876167: ($0) => { performance.now = function() { return $0; }; }, - 5876215: ($0) => { performance.now = function() { return $0; }; }, - 5876263: () => { performance.now = Module['emscripten_get_now_backup']; } -}; - - - -// end include: preamble.js - - /** @constructor */ - function ExitStatus(status) { - this.name = 'ExitStatus'; - this.message = 'Program terminated with exit(' + status + ')'; - this.status = status; - } - - function callRuntimeCallbacks(callbacks) { - while (callbacks.length > 0) { - // Pass the module as the first argument. - callbacks.shift()(Module); - } - } - - function withStackSave(f) { - var stack = stackSave(); - var ret = f(); - stackRestore(stack); - return ret; - } - - - - function lengthBytesUTF8(str) { - var len = 0; - for (var i = 0; i < str.length; ++i) { - // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code - // unit, not a Unicode code point of the character! So decode - // UTF16->UTF32->UTF8. - // See http://unicode.org/faq/utf_bom.html#utf16-3 - var c = str.charCodeAt(i); // possibly a lead surrogate - if (c <= 0x7F) { - len++; - } else if (c <= 0x7FF) { - len += 2; - } else if (c >= 0xD800 && c <= 0xDFFF) { - len += 4; ++i; - } else { - len += 3; - } - } - return len; - } - - function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) { - assert(typeof str === 'string'); - // Parameter maxBytesToWrite is not optional. Negative values, 0, null, - // undefined and false each don't write out any bytes. - if (!(maxBytesToWrite > 0)) - return 0; - - var startIdx = outIdx; - var endIdx = outIdx + maxBytesToWrite - 1; // -1 for string null terminator. - for (var i = 0; i < str.length; ++i) { - // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code - // unit, not a Unicode code point of the character! So decode - // UTF16->UTF32->UTF8. - // See http://unicode.org/faq/utf_bom.html#utf16-3 - // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description - // and https://www.ietf.org/rfc/rfc2279.txt - // and https://tools.ietf.org/html/rfc3629 - var u = str.charCodeAt(i); // possibly a lead surrogate - if (u >= 0xD800 && u <= 0xDFFF) { - var u1 = str.charCodeAt(++i); - u = 0x10000 + ((u & 0x3FF) << 10) | (u1 & 0x3FF); - } - if (u <= 0x7F) { - if (outIdx >= endIdx) break; - heap[outIdx++] = u; - } else if (u <= 0x7FF) { - if (outIdx + 1 >= endIdx) break; - heap[outIdx++] = 0xC0 | (u >> 6); - heap[outIdx++] = 0x80 | (u & 63); - } else if (u <= 0xFFFF) { - if (outIdx + 2 >= endIdx) break; - heap[outIdx++] = 0xE0 | (u >> 12); - heap[outIdx++] = 0x80 | ((u >> 6) & 63); - heap[outIdx++] = 0x80 | (u & 63); - } else { - if (outIdx + 3 >= endIdx) break; - if (u > 0x10FFFF) warnOnce('Invalid Unicode code point ' + ptrToString(u) + ' encountered when serializing a JS string to a UTF-8 string in wasm memory! (Valid unicode code points should be in range 0-0x10FFFF).'); - heap[outIdx++] = 0xF0 | (u >> 18); - heap[outIdx++] = 0x80 | ((u >> 12) & 63); - heap[outIdx++] = 0x80 | ((u >> 6) & 63); - heap[outIdx++] = 0x80 | (u & 63); - } - } - // Null-terminate the pointer to the buffer. - heap[outIdx] = 0; - return outIdx - startIdx; - } - function stringToUTF8(str, outPtr, maxBytesToWrite) { - assert(typeof maxBytesToWrite == 'number', 'stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!'); - return stringToUTF8Array(str, HEAPU8,outPtr, maxBytesToWrite); - } - function stringToUTF8OnStack(str) { - var size = lengthBytesUTF8(str) + 1; - var ret = stackAlloc(size); - stringToUTF8(str, ret, size); - return ret; - } - - var UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder('utf8') : undefined; - - /** - * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given - * array that contains uint8 values, returns a copy of that string as a - * Javascript String object. - * heapOrArray is either a regular array, or a JavaScript typed array view. - * @param {number} idx - * @param {number=} maxBytesToRead - * @return {string} - */ - function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead) { - var endIdx = idx + maxBytesToRead; - var endPtr = idx; - // TextDecoder needs to know the byte length in advance, it doesn't stop on - // null terminator by itself. Also, use the length info to avoid running tiny - // strings through TextDecoder, since .subarray() allocates garbage. - // (As a tiny code save trick, compare endPtr against endIdx using a negation, - // so that undefined means Infinity) - while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; - - if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) { - return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr)); - } - var str = ''; - // If building with TextDecoder, we have already computed the string length - // above, so test loop end condition against that - while (idx < endPtr) { - // For UTF8 byte structure, see: - // http://en.wikipedia.org/wiki/UTF-8#Description - // https://www.ietf.org/rfc/rfc2279.txt - // https://tools.ietf.org/html/rfc3629 - var u0 = heapOrArray[idx++]; - if (!(u0 & 0x80)) { str += String.fromCharCode(u0); continue; } - var u1 = heapOrArray[idx++] & 63; - if ((u0 & 0xE0) == 0xC0) { str += String.fromCharCode(((u0 & 31) << 6) | u1); continue; } - var u2 = heapOrArray[idx++] & 63; - if ((u0 & 0xF0) == 0xE0) { - u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; - } else { - if ((u0 & 0xF8) != 0xF0) warnOnce('Invalid UTF-8 leading byte ' + ptrToString(u0) + ' encountered when deserializing a UTF-8 string in wasm memory to a JS string!'); - u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63); - } - - if (u0 < 0x10000) { - str += String.fromCharCode(u0); - } else { - var ch = u0 - 0x10000; - str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF)); - } - } - return str; - } - - - /** - * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the - * emscripten HEAP, returns a copy of that string as a Javascript String object. - * - * @param {number} ptr - * @param {number=} maxBytesToRead - An optional length that specifies the - * maximum number of bytes to read. You can omit this parameter to scan the - * string until the first 0 byte. If maxBytesToRead is passed, and the string - * at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the - * string will cut short at that byte index (i.e. maxBytesToRead will not - * produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing - * frequent uses of UTF8ToString() with and without maxBytesToRead may throw - * JS JIT optimizations off, so it is worth to consider consistently using one - * @return {string} - */ - function UTF8ToString(ptr, maxBytesToRead) { - assert(typeof ptr == 'number'); - return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ''; - } - function demangle(func) { - // If demangle has failed before, stop demangling any further function names - // This avoids an infinite recursion with malloc()->abort()->stackTrace()->demangle()->malloc()->... - demangle.recursionGuard = (demangle.recursionGuard|0)+1; - if (demangle.recursionGuard > 1) return func; - return withStackSave(function() { - try { - var s = func; - if (s.startsWith('__Z')) - s = s.substr(1); - var buf = stringToUTF8OnStack(s); - var status = stackAlloc(4); - var ret = ___cxa_demangle(buf, 0, 0, status); - if (HEAP32[((status)>>2)] === 0 && ret) { - return UTF8ToString(ret); - } - // otherwise, libcxxabi failed - } catch(e) { - } finally { - _free(ret); - if (demangle.recursionGuard < 2) --demangle.recursionGuard; - } - // failure when using libcxxabi, don't demangle - return func; - }); - } - - function dynCallLegacy(sig, ptr, args) { - assert(('dynCall_' + sig) in Module, `bad function pointer type - dynCall function not found for sig '${sig}'`); - if (args && args.length) { - // j (64-bit integer) must be passed in as two numbers [low 32, high 32]. - assert(args.length === sig.substring(1).replace(/j/g, '--').length); - } else { - assert(sig.length == 1); - } - var f = Module['dynCall_' + sig]; - return args && args.length ? f.apply(null, [ptr].concat(args)) : f.call(null, ptr); - } - - var wasmTableMirror = []; - - function getWasmTableEntry(funcPtr) { - assert(funcPtr >= 0, "Function pointers must be nonnegative!"); - var func = wasmTableMirror[funcPtr]; - if (!func) { - if (funcPtr >= wasmTableMirror.length) wasmTableMirror.length = funcPtr + 1; - wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr); - } - assert(wasmTable.get(funcPtr) == func, "JavaScript-side Wasm function table mirror is out of date!"); - return func; - } - /** @param {Object=} args */ - function dynCall(sig, ptr, args) { - return dynCallLegacy(sig, ptr, args); - } - - - /** - * @param {number} ptr - * @param {string} type - */ - function getValue(ptr, type = 'i8') { - if (type.endsWith('*')) type = '*'; - switch (type) { - case 'i1': return HEAP8[((ptr)>>0)]; - case 'i8': return HEAP8[((ptr)>>0)]; - case 'i16': return HEAP16[((ptr)>>1)]; - case 'i32': return HEAP32[((ptr)>>2)]; - case 'i64': return HEAP32[((ptr)>>2)]; - case 'float': return HEAPF32[((ptr)>>2)]; - case 'double': return HEAPF64[((ptr)>>3)]; - case '*': return HEAPU32[((ptr)>>2)]; - default: abort('invalid type for getValue: ' + type); - } - } - - function ptrToString(ptr) { - assert(typeof ptr === 'number'); - return '0x' + ptr.toString(16).padStart(8, '0'); - } - - - /** - * @param {number} ptr - * @param {number} value - * @param {string} type - */ - function setValue(ptr, value, type = 'i8') { - if (type.endsWith('*')) type = '*'; - switch (type) { - case 'i1': HEAP8[((ptr)>>0)] = value; break; - case 'i8': HEAP8[((ptr)>>0)] = value; break; - case 'i16': HEAP16[((ptr)>>1)] = value; break; - case 'i32': HEAP32[((ptr)>>2)] = value; break; - case 'i64': (tempI64 = [value>>>0,(tempDouble=value,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[((ptr)>>2)] = tempI64[0],HEAP32[(((ptr)+(4))>>2)] = tempI64[1]); break; - case 'float': HEAPF32[((ptr)>>2)] = value; break; - case 'double': HEAPF64[((ptr)>>3)] = value; break; - case '*': HEAPU32[((ptr)>>2)] = value; break; - default: abort('invalid type for setValue: ' + type); - } - } - - function jsStackTrace() { - var error = new Error(); - if (!error.stack) { - // IE10+ special cases: It does have callstack info, but it is only - // populated if an Error object is thrown, so try that as a special-case. - try { - throw new Error(); - } catch(e) { - error = e; - } - if (!error.stack) { - return '(no stack trace available)'; - } - } - return error.stack.toString(); - } - - function demangleAll(text) { - var regex = - /\b_Z[\w\d_]+/g; - return text.replace(regex, - function(x) { - var y = demangle(x); - return x === y ? x : (y + ' [' + x + ']'); - }); - } - function stackTrace() { - var js = jsStackTrace(); - if (Module['extraStackTrace']) js += '\n' + Module['extraStackTrace'](); - return demangleAll(js); - } - - function warnOnce(text) { - if (!warnOnce.shown) warnOnce.shown = {}; - if (!warnOnce.shown[text]) { - warnOnce.shown[text] = 1; - err(text); - } - } - - function _GetJSLoadTimeInfo(loadTimePtr) { - loadTimePtr = (loadTimePtr >> 2); - HEAPU32[loadTimePtr] = Module.pageStartupTime || 0; - HEAPU32[loadTimePtr + 1] = Module.dataUrlLoadEndTime || 0; - HEAPU32[loadTimePtr + 2] = Module.codeDownloadTimeEnd || 0; - } - - function _GetJSMemoryInfo(totalJSptr, usedJSptr) { - totalJSptr = (totalJSptr >> 3); - usedJSptr = (usedJSptr >> 3); - if (performance.memory) { - HEAPF64[totalJSptr] = performance.memory.totalJSHeapSize; - HEAPF64[usedJSptr] = performance.memory.usedJSHeapSize; - } else { - HEAPF64[totalJSptr] = NaN; - HEAPF64[usedJSptr] = NaN; - } - } - - var JS_Accelerometer = null; - - var JS_Accelerometer_callback = 0; - function _JS_Accelerometer_IsRunning() { - // Sensor is running if there is an activated new JS_Accelerometer; or the JS_Accelerometer_callback is hooked up - return (JS_Accelerometer && JS_Accelerometer.activated) || (JS_Accelerometer_callback != 0); - } - - - - - var JS_Accelerometer_multiplier = 1; - - var JS_Accelerometer_lastValue = {x:0,y:0,z:0}; - function JS_Accelerometer_eventHandler() { - // Record the last value for gravity computation - JS_Accelerometer_lastValue = { - x: JS_Accelerometer.x * JS_Accelerometer_multiplier, - y: JS_Accelerometer.y * JS_Accelerometer_multiplier, - z: JS_Accelerometer.z * JS_Accelerometer_multiplier - }; - if (JS_Accelerometer_callback != 0) - ((a1, a2, a3) => dynCall_vfff.apply(null, [JS_Accelerometer_callback, a1, a2, a3]))(JS_Accelerometer_lastValue.x, JS_Accelerometer_lastValue.y, JS_Accelerometer_lastValue.z); - } - - - var JS_Accelerometer_frequencyRequest = 0; - - var JS_Accelerometer_frequency = 0; - - - var JS_LinearAccelerationSensor_callback = 0; - - var JS_GravitySensor_callback = 0; - - var JS_Gyroscope_callback = 0; - - - function JS_ComputeGravity(accelerometerValue, linearAccelerationValue) { - // On some Android devices, the linear acceleration direction is reversed compared to its accelerometer - // So, compute both the difference and sum (difference of the negative) and return the one that's the smallest in magnitude - var difference = { - x: accelerometerValue.x - linearAccelerationValue.x, - y: accelerometerValue.y - linearAccelerationValue.y, - z: accelerometerValue.z - linearAccelerationValue.z - }; - var differenceMagnitudeSq = difference.x*difference.x + difference.y*difference.y + difference.z*difference.z; - - var sum = { - x: accelerometerValue.x + linearAccelerationValue.x, - y: accelerometerValue.y + linearAccelerationValue.y, - z: accelerometerValue.z + linearAccelerationValue.z - }; - var sumMagnitudeSq = sum.x*sum.x + sum.y*sum.y + sum.z*sum.z; - - return (differenceMagnitudeSq <= sumMagnitudeSq) ? difference : sum; - } - function JS_DeviceMotion_eventHandler(event) { - // The accelerationIncludingGravity property is the amount of acceleration recorded by the device, in meters per second squared (m/s2). - // Its value is the sum of the acceleration of the device as induced by the user and the acceleration caused by gravity. - // Apply the JS_Accelerometer_multiplier to convert to g - var accelerometerValue = { - x: event.accelerationIncludingGravity.x * JS_Accelerometer_multiplier, - y: event.accelerationIncludingGravity.y * JS_Accelerometer_multiplier, - z: event.accelerationIncludingGravity.z * JS_Accelerometer_multiplier - }; - if (JS_Accelerometer_callback != 0) - ((a1, a2, a3) => dynCall_vfff.apply(null, [JS_Accelerometer_callback, a1, a2, a3]))(accelerometerValue.x, accelerometerValue.y, accelerometerValue.z); - - // The acceleration property is the amount of acceleration recorded by the device, in meters per second squared (m/s2), compensated for gravity. - // Apply the JS_Accelerometer_multiplier to convert to g - var linearAccelerationValue = { - x: event.acceleration.x * JS_Accelerometer_multiplier, - y: event.acceleration.y * JS_Accelerometer_multiplier, - z: event.acceleration.z * JS_Accelerometer_multiplier - }; - if (JS_LinearAccelerationSensor_callback != 0) - ((a1, a2, a3) => dynCall_vfff.apply(null, [JS_LinearAccelerationSensor_callback, a1, a2, a3]))(linearAccelerationValue.x, linearAccelerationValue.y, linearAccelerationValue.z); - - // Compute and raise the gravity sensor vector - if (JS_GravitySensor_callback != 0) { - assert(typeof GravitySensor === 'undefined'); - var gravityValue = JS_ComputeGravity(accelerometerValue, linearAccelerationValue); - ((a1, a2, a3) => dynCall_vfff.apply(null, [JS_GravitySensor_callback, a1, a2, a3]))(gravityValue.x, gravityValue.y, gravityValue.z); - } - - // The rotationRate property describes the rotation rates of the device around each of its axes (deg/s), but we want in radians/s so must scale - // Note that the spec here has been updated so x,y,z axes are alpha,beta,gamma. - // Therefore the order of axes at https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/rotationRate is incorrect - // - // There is a bug in Chrome < M66 where rotationRate values are not in deg/s https://bugs.chromium.org/p/chromium/issues/detail?id=541607 - // But that version is too old to include a check here - if (JS_Gyroscope_callback != 0) { - var degToRad = Math.PI / 180; - ((a1, a2, a3) => dynCall_vfff.apply(null, [JS_Gyroscope_callback, a1, a2, a3]))(event.rotationRate.alpha * degToRad, event.rotationRate.beta * degToRad, event.rotationRate.gamma * degToRad); - } - } - - var JS_DeviceSensorPermissions = 0; - function JS_RequestDeviceSensorPermissions(permissions) { - // iOS requires that we request permissions before using device sensor events - if (permissions & 1/*DeviceOrientationEvent permission*/) { - if (typeof DeviceOrientationEvent.requestPermission === 'function') { - DeviceOrientationEvent.requestPermission() - .then(function(permissionState) { - if (permissionState === 'granted') { - JS_DeviceSensorPermissions &= ~1; // Remove DeviceOrientationEvent permission bit - } else { - warnOnce("DeviceOrientationEvent permission not granted"); - } - }) - .catch(function(err) { - // Permissions cannot be requested unless on a user interaction (a touch event) - // So in this case set JS_DeviceSensorPermissions and we will try again on a touch event - warnOnce(err); - JS_DeviceSensorPermissions |= 1/*DeviceOrientationEvent permission*/; - }); - } - } - if (permissions & 2/*DeviceMotionEvent permission*/) { - if (typeof DeviceMotionEvent.requestPermission === 'function') { - DeviceMotionEvent.requestPermission() - .then(function(permissionState) { - if (permissionState === 'granted') { - JS_DeviceSensorPermissions &= ~2; // Remove DeviceMotionEvent permission bit - } else { - warnOnce("DeviceMotionEvent permission not granted"); - } - }) - .catch(function(err) { - // Permissions cannot be requested unless on a user interaction (a touch event) - // So in this case set JS_DeviceSensorPermissions and we will try again on a touch event - warnOnce(err); - JS_DeviceSensorPermissions |= 2/*DeviceMotionEvent permission*/; - }); - } - } - } - - - - - function JS_DeviceMotion_add() { - // Only add the event listener if we don't yet have any of the motion callbacks set - if (JS_Accelerometer_callback == 0 && - JS_LinearAccelerationSensor_callback == 0 && - JS_GravitySensor_callback == 0 && - JS_Gyroscope_callback == 0) { - JS_RequestDeviceSensorPermissions(2/*DeviceMotionEvent permission*/); - window.addEventListener('devicemotion', JS_DeviceMotion_eventHandler); - } - } - - function JS_DefineAccelerometerMultiplier() { - // Earth's gravity in m/s^2, same as ASENSOR_STANDARD_GRAVITY - var g = 9.80665; - - // Multiplier is 1/g to normalize acceleration - // iOS has its direction opposite to Android and Windows (tested Surface Pro tablet) - // We include Macintosh in the test to capture Safari on iOS viewing in Desktop mode (the default now on iPads) - JS_Accelerometer_multiplier = (/(iPhone|iPad|Macintosh)/i.test(navigator.userAgent)) ? 1/g : -1/g; - } - function _JS_Accelerometer_Start(callback, frequency) { - // callback can be zero here when called via JS_GravitySensor_Start - - JS_DefineAccelerometerMultiplier(); - - // If we don't have new sensor API, fallback to old DeviceMotionEvent - if (typeof Accelerometer === 'undefined') { - JS_DeviceMotion_add(); // Must call before we set the callback - if (callback != 0) JS_Accelerometer_callback = callback; - return; - } - - if (callback != 0) JS_Accelerometer_callback = callback; - - function InitializeAccelerometer(frequency) { - // Use device referenceFrame, since New Input System package does its own compensation - JS_Accelerometer = new Accelerometer({ frequency: frequency, referenceFrame: 'device' }); - JS_Accelerometer.addEventListener('reading', JS_Accelerometer_eventHandler); - JS_Accelerometer.addEventListener('error', function(e) { - // e.error could be DOMException: Could not connect to a sensor - warnOnce((e.error) ? e.error : e); - }); - JS_Accelerometer.start(); - JS_Accelerometer_frequency = frequency; - } - - // If the sensor is already created, stop and re-create it with new frequency - if (JS_Accelerometer) { - if (JS_Accelerometer_frequency != frequency) { - JS_Accelerometer.stop(); - JS_Accelerometer.removeEventListener('reading', JS_Accelerometer_eventHandler); - InitializeAccelerometer(frequency); - } - } - else if (JS_Accelerometer_frequencyRequest != 0) { - // If the permissions promise is currently in progress, then note new frequency only - JS_Accelerometer_frequencyRequest = frequency; - } - else { - JS_Accelerometer_frequencyRequest = frequency; - - // Request required permission for the Accelerometer - navigator.permissions.query({name: 'accelerometer'}) - .then(function(result) { - if (result.state === "granted") { - InitializeAccelerometer(JS_Accelerometer_frequencyRequest); - } else { - warnOnce("No permission to use Accelerometer."); - } - JS_Accelerometer_frequencyRequest = 0; - }); - } - } - - - - - - - - - - - function JS_DeviceMotion_remove() { - // If we've removed the last callback, remove the devicemotion event listener - if (JS_Accelerometer_callback == 0 && - JS_LinearAccelerationSensor_callback == 0 && - JS_GravitySensor_callback == 0 && - JS_Gyroscope_callback == 0 ) { - window.removeEventListener('devicemotion', JS_DeviceOrientation_eventHandler); - } - } - function _JS_Accelerometer_Stop() { - if (JS_Accelerometer) { - // Only actually stop the accelerometer if we don't need it to compute gravity values - if (typeof GravitySensor !== 'undefined' || JS_GravitySensor_callback == 0) { - JS_Accelerometer.stop(); - JS_Accelerometer.removeEventListener('reading', JS_Accelerometer_eventHandler); - JS_Accelerometer = null; - } - JS_Accelerometer_callback = 0; - JS_Accelerometer_frequency = 0; - } - else if (JS_Accelerometer_callback != 0) { - JS_Accelerometer_callback = 0; - JS_DeviceMotion_remove(); - } - } - - var ExceptionsSeen = 0; - - function LogErrorWithAdditionalInformation(error) { - // Module.dynCall_* or dynCall_* is directly is used for calling callbacks. - // Use makeDynCall instead for compatibility with WebAssembly.Table. - if ( - ( - error instanceof ReferenceError || - error instanceof TypeError - ) && - error.message.indexOf('dynCall_') != -1 - ) { - error.message = 'Detected use of deprecated "Module.dynCall_*" API. Use "makeDynCall" API instead. Refer to https://docs.unity3d.com/6000.0/Documentation/Manual/web-interacting-browser-deprecated.html#dyncall for more information.\n' + error.message; - } - - console.error(error); - } - function _JS_CallAsLongAsNoExceptionsSeen(cb) { - if (!ExceptionsSeen) { - try { - (() => dynCall_v.call(null, cb))(); - } catch(e) { - ExceptionsSeen = 1; - console.error('Uncaught exception from main loop:'); - LogErrorWithAdditionalInformation(e); - console.error('Halting program.'); - if (Module.errorHandler) Module.errorHandler(e); - throw e; - } - } - } - - function _JS_Cursor_SetImage(ptr, length) { - ptr = ptr; - var binary = ""; - for (var i = 0; i < length; i++) - binary += String.fromCharCode(HEAPU8[ptr + i]); - Module.canvas.style.cursor = "url(data:image/cur;base64," + btoa(binary) + "),default"; - } - - function _JS_Cursor_SetShow(show) { - Module.canvas.style.cursor = show ? "default" : "none"; - } - - function jsDomCssEscapeId(id) { - // Use CSS Object Model to escape ID if feature is present - if (typeof window.CSS !== "undefined" && typeof window.CSS.escape !== "undefined") { - return window.CSS.escape(id); - } - - // Fallback: Escape special characters with RegExp. This handles most cases but not all! - return id.replace(/(#|\.|\+|\[|\]|\(|\)|\{|\})/g, "\\$1"); - } - function jsCanvasSelector() { - // This lookup specifies the target canvas that different DOM - // events are registered to, like keyboard and mouse events. - // This requires that Module['canvas'] must have a CSS ID associated - // with it, it cannot be empty. Override Module['canvas'] to specify - // some other target to use, e.g. if the page contains multiple Unity - // game instances. - if (Module['canvas'] && !Module['canvas'].id) throw 'Module["canvas"] must have a CSS ID associated with it!'; - var canvasId = Module['canvas'] ? Module['canvas'].id : 'unity-canvas'; - return '#' + jsDomCssEscapeId(canvasId); - } - function _JS_DOM_MapViewportCoordinateToElementLocalCoordinate(viewportX, viewportY, targetX, targetY) { - targetX = (targetX >> 2); - targetY = (targetY >> 2); - var canvas = document.querySelector(jsCanvasSelector()); - var rect = canvas && canvas.getBoundingClientRect(); - HEAPU32[targetX] = viewportX - (rect ? rect.left : 0); - HEAPU32[targetY] = viewportY - (rect ? rect.top : 0); - } - - - - function stringToNewUTF8(str) { - var size = lengthBytesUTF8(str) + 1; - var ret = _malloc(size); - if (ret) stringToUTF8(str, ret, size); - return ret; - } - - function _JS_DOM_UnityCanvasSelector() { - var canvasSelector = jsCanvasSelector(); - if (_JS_DOM_UnityCanvasSelector.selector != canvasSelector) { - _free(_JS_DOM_UnityCanvasSelector.ptr); - _JS_DOM_UnityCanvasSelector.ptr = stringToNewUTF8(canvasSelector); - _JS_DOM_UnityCanvasSelector.selector = canvasSelector; - } - return _JS_DOM_UnityCanvasSelector.ptr; - } - - - function _JS_Eval_OpenURL(ptr) - { - var str = UTF8ToString(ptr); - window.open(str, '_blank', ''); - } - - function _JS_FileSystem_Initialize() - { - // no-op - } - - function _JS_FileSystem_Sync() - { - // Kick off a new IDBFS sync on Unity's Application.persistentDataPath directory tree. - // Do it carefully in a fashion that is compatible with the Module.autoSyncPersistentDataPath option - // (to avoid multiple redundant syncs in flight at the same time) - IDBFS.queuePersist(Module.__unityIdbfsMount.mount); - if (!window.warnedAboutManualFilesystemSyncGettingDeprecated) { - window.warnedAboutManualFilesystemSyncGettingDeprecated = true; - if (!Module.autoSyncPersistentDataPath) { - console.warn('Manual synchronization of Unity Application.persistentDataPath via JS_FileSystem_Sync() is deprecated and will be later removed in a future Unity version. The persistent data directory will be automatically synchronized instead on file modification. Pass config.autoSyncPersistentDataPath = true; to configuration in createUnityInstance() to opt in to the new behavior.'); - } - } - } - - function _JS_GetRandomBytes(destBuffer, numBytes) { - // Crypto is widely available in browsers, but if running in - // Node.js or another shell, it might not be present. - // getRandomValues() cannot be called for more than 64K bytes at a time. - if (typeof crypto === 'undefined' || numBytes > 65535) - return 0; - - crypto.getRandomValues(new Uint8Array(HEAPU8.buffer, destBuffer, numBytes)); - return 1; - } - - function _JS_Get_WASM_Size() - { - return Module.wasmFileSize; - } - - var JS_GravitySensor = null; - - function _JS_GravitySensor_IsRunning() { - return (typeof GravitySensor !== 'undefined') ? (JS_GravitySensor && JS_GravitySensor.activated) : JS_GravitySensor_callback != 0; - } - - - - - function JS_GravitySensor_eventHandler() { - if (JS_GravitySensor_callback != 0) - ((a1, a2, a3) => dynCall_vfff.apply(null, [JS_GravitySensor_callback, a1, a2, a3]))( - JS_GravitySensor.x * JS_Accelerometer_multiplier, - JS_GravitySensor.y * JS_Accelerometer_multiplier, - JS_GravitySensor.z * JS_Accelerometer_multiplier); - } - - - var JS_GravitySensor_frequencyRequest = 0; - - - - var JS_LinearAccelerationSensor = null; - - - - - - - function JS_LinearAccelerationSensor_eventHandler() { - var linearAccelerationValue = { - x: JS_LinearAccelerationSensor.x * JS_Accelerometer_multiplier, - y: JS_LinearAccelerationSensor.y * JS_Accelerometer_multiplier, - z: JS_LinearAccelerationSensor.z * JS_Accelerometer_multiplier - }; - if (JS_LinearAccelerationSensor_callback != 0) - ((a1, a2, a3) => dynCall_vfff.apply(null, [JS_LinearAccelerationSensor_callback, a1, a2, a3]))(linearAccelerationValue.x, linearAccelerationValue.y, linearAccelerationValue.z); - - // Calculate and call the Gravity callback if the Gravity Sensor API isn't present - if (JS_GravitySensor_callback != 0 && typeof GravitySensor === 'undefined') { - var gravityValue = JS_ComputeGravity(JS_Accelerometer_lastValue, linearAccelerationValue); - ((a1, a2, a3) => dynCall_vfff.apply(null, [JS_GravitySensor_callback, a1, a2, a3]))(gravityValue.x, gravityValue.y, gravityValue.z); - } - } - - - var JS_LinearAccelerationSensor_frequencyRequest = 0; - - var JS_LinearAccelerationSensor_frequency = 0; - - - function _JS_LinearAccelerationSensor_Start(callback, frequency) { - // callback can be zero here when called via JS_GravitySensor_Start - - JS_DefineAccelerometerMultiplier(); - - // If we don't have new sensor API, fallback to old DeviceMotionEvent - if (typeof LinearAccelerationSensor === 'undefined') { - JS_DeviceMotion_add(); // Must call before we set the callback - if (callback != 0) JS_LinearAccelerationSensor_callback = callback; - return; - } - - if (callback != 0) JS_LinearAccelerationSensor_callback = callback; - - function InitializeLinearAccelerationSensor(frequency) { - // Use device referenceFrame, since New Input System package does its own compensation - JS_LinearAccelerationSensor = new LinearAccelerationSensor({ frequency: frequency, referenceFrame: 'device' }); - JS_LinearAccelerationSensor.addEventListener('reading', JS_LinearAccelerationSensor_eventHandler); - JS_LinearAccelerationSensor.addEventListener('error', function(e) { - // e.error could be DOMException: Could not connect to a sensor - warnOnce((e.error) ? e.error : e); - }); - JS_LinearAccelerationSensor.start(); - JS_LinearAccelerationSensor_frequency = frequency; - } - - // If the sensor is already created, stop and re-create it with new frequency - if (JS_LinearAccelerationSensor) { - if (JS_LinearAccelerationSensor_frequency != frequency) { - JS_LinearAccelerationSensor.stop(); - JS_LinearAccelerationSensor.removeEventListener('reading', JS_LinearAccelerationSensor_eventHandler); - InitializeLinearAccelerationSensor(frequency); - } - } - else if (JS_LinearAccelerationSensor_frequencyRequest != 0) { - // If the permissions promise is currently in progress, then note new frequency only - JS_LinearAccelerationSensor_frequencyRequest = frequency; - } - else { - JS_LinearAccelerationSensor_frequencyRequest = frequency; - - // Request required permission for the LinearAccelerationSensor - navigator.permissions.query({name: 'accelerometer'}) - .then(function(result) { - if (result.state === "granted") { - InitializeLinearAccelerationSensor(JS_LinearAccelerationSensor_frequencyRequest); - } else { - warnOnce("No permission to use LinearAccelerationSensor."); - } - JS_LinearAccelerationSensor_frequencyRequest = 0; - }); - } - } - - - function _JS_GravitySensor_Start(callback, frequency) { - assert(callback != 0, 'Invalid callback passed to JS_GravitySensor_Start'); - - // If we don't have explicit new Gravity Sensor API, start the Accelerometer and LinearAccelerationSensor - // and we will compute the gravity value from those readings - if (typeof GravitySensor === 'undefined') { - // Start both Accelerometer and LinearAccelerationSensor - _JS_Accelerometer_Start(0, Math.max(frequency, JS_Accelerometer_frequency)); - _JS_LinearAccelerationSensor_Start(0, Math.max(frequency, JS_LinearAccelerationSensor_frequency)); - - // Add the gravity sensor callback (must be after Accelerometer and LinearAccelerationSensor start) - JS_GravitySensor_callback = callback; - return; - } - - JS_DefineAccelerometerMultiplier(); - - JS_GravitySensor_callback = callback; - - function InitializeGravitySensor(frequency) { - // Use device referenceFrame, since New Input System package does its own compensation - JS_GravitySensor = new GravitySensor({ frequency: frequency, referenceFrame: 'device' }); - JS_GravitySensor.addEventListener('reading', JS_GravitySensor_eventHandler); - JS_GravitySensor.addEventListener('error', function(e) { - // e.error could be DOMException: Could not connect to a sensor - warnOnce((e.error) ? e.error : e); - }); - JS_GravitySensor.start(); - } - - // If the sensor is already created, stop and re-create it with new frequency - if (JS_GravitySensor) { - JS_GravitySensor.stop(); - JS_GravitySensor.removeEventListener('reading', JS_GravitySensor_eventHandler); - InitializeGravitySensor(frequency); - } - else if (JS_GravitySensor_frequencyRequest != 0) { - // If the permissions promise is currently in progress, then note new frequency only - JS_GravitySensor_frequencyRequest = frequency; - } - else { - JS_GravitySensor_frequencyRequest = frequency; - - // Request required permission for the GravitySensor - navigator.permissions.query({name: 'accelerometer'}) - .then(function(result) { - if (result.state === "granted") { - InitializeGravitySensor(JS_GravitySensor_frequencyRequest); - } else { - warnOnce("No permission to use GravitySensor."); - } - JS_GravitySensor_frequencyRequest = 0; - }); - } - } - - - - - - - - - - - - - function _JS_LinearAccelerationSensor_Stop() { - if (JS_LinearAccelerationSensor) { - // Only actually stop the Linear Acceleration Sensor if we don't need it to compute gravity values - if (typeof GravitySensor !== 'undefined' || JS_GravitySensor_callback == 0) { - JS_LinearAccelerationSensor.stop(); - JS_LinearAccelerationSensor.removeEventListener('reading', JS_LinearAccelerationSensor_eventHandler); - JS_LinearAccelerationSensor = null; - } - JS_LinearAccelerationSensor_callback = 0; - JS_LinearAccelerationSensor_frequency = 0; - } - else if (JS_LinearAccelerationSensor_callback != 0) { - JS_LinearAccelerationSensor_callback = 0; - JS_DeviceMotion_remove(); - } - } - function _JS_GravitySensor_Stop() { - JS_GravitySensor_callback = 0; - - // If we don't have Gravity Sensor API, stop the Accelerometer and LinearAccelerationSensor - if (typeof GravitySensor === 'undefined') { - // Stop the source sensors if they're not used explicitly by Unity - if (JS_Accelerometer_callback == 0) _JS_Accelerometer_Stop(); - if (JS_LinearAccelerationSensor_callback == 0) _JS_LinearAccelerationSensor_Stop(); - return; - } - - if (JS_GravitySensor) { - JS_GravitySensor.stop(); - JS_GravitySensor.removeEventListener('reading', JS_GravitySensor_eventHandler); - JS_GravitySensor = null; - } - } - - var JS_Gyroscope = null; - - function _JS_Gyroscope_IsRunning() { - // Sensor is running if there is an activated new JS_Gyroscope; or the JS_Gyroscope_callback is hooked up - return (JS_Gyroscope && JS_Gyroscope.activated) || (JS_Gyroscope_callback != 0); - } - - - - function JS_Gyroscope_eventHandler() { - // Radians per second - if (JS_Gyroscope_callback != 0) - ((a1, a2, a3) => dynCall_vfff.apply(null, [JS_Gyroscope_callback, a1, a2, a3]))(JS_Gyroscope.x, JS_Gyroscope.y, JS_Gyroscope.z); - } - - - var JS_Gyroscope_frequencyRequest = 0; - - function _JS_Gyroscope_Start(callback, frequency) { - assert(callback != 0, 'Invalid callback passed to JS_Gyroscope_Start'); - - // If we don't have new sensor API, fallback to old DeviceMotionEvent - if (typeof Gyroscope === 'undefined') { - JS_DeviceMotion_add(); // Must call before we set the callback - JS_Gyroscope_callback = callback; - return; - } - - JS_Gyroscope_callback = callback; - - function InitializeGyroscope(frequency) { - // Use device referenceFrame, since New Input System package does its own compensation - JS_Gyroscope = new Gyroscope({ frequency: frequency, referenceFrame: 'device' }); - JS_Gyroscope.addEventListener('reading', JS_Gyroscope_eventHandler); - JS_Gyroscope.addEventListener('error', function(e) { - // e.error could be DOMException: Could not connect to a sensor - warnOnce((e.error) ? e.error : e); - }); - JS_Gyroscope.start(); - } - - // If the sensor is already created, stop and re-create it with new frequency - if (JS_Gyroscope) { - JS_Gyroscope.stop(); - JS_Gyroscope.removeEventListener('reading', JS_Gyroscope_eventHandler); - InitializeGyroscope(frequency); - } - else if (JS_Gyroscope_frequencyRequest != 0) { - // If the permissions promise is currently in progress, then note new frequency only - JS_Gyroscope_frequencyRequest = frequency; - } - else { - JS_Gyroscope_frequencyRequest = frequency; - - // Request required permission for the Gyroscope - navigator.permissions.query({name: 'gyroscope'}) - .then(function(result) { - if (result.state === "granted") { - InitializeGyroscope(JS_Gyroscope_frequencyRequest); - } else { - warnOnce("No permission to use Gyroscope."); - } - JS_Gyroscope_frequencyRequest = 0; - }); - } - } - - - - - function _JS_Gyroscope_Stop() { - if (JS_Gyroscope) { - JS_Gyroscope.stop(); - JS_Gyroscope.removeEventListener('reading', JS_Gyroscope_eventHandler); - JS_Gyroscope = null; - JS_Gyroscope_callback = 0; - } - else if (JS_Gyroscope_callback != 0) { - JS_Gyroscope_callback = 0; - JS_DeviceMotion_remove(); - } - } - - function _JS_Init_ContextMenuHandler() { - const _handleContextMenu = function (event){ - if(event.target.localName !== "canvas") - _ReleaseKeys(); - } - - document.addEventListener("contextmenu", _handleContextMenu); - - Module.deinitializers.push(function() { - document.removeEventListener("contextmenu", _handleContextMenu); - }); - } - - - - - - - - var mobile_input_hide_delay = null; - var mobile_input_text = null; - - var mobile_input = null; - - function _JS_Init_CopyPaste() { - var canvas = document.querySelector(jsCanvasSelector()); - - // UUM-72388 we need to conditionally prevent default so that users can - // copy paste between elements on the page to the unity canvas. Check - // mobile input here otherwise paste data will paste twice. - const _handlePaste = function (event) { - if (document.activeElement == canvas || !!mobile_input) - event.preventDefault(); - const data = event.clipboardData.getData("text"); - - if(!!mobile_input){ - mobile_input.input.value += data; - } else { - var str_wasm = stringToNewUTF8(data); - _SendPasteEvent(str_wasm); - _free(str_wasm); - } - } - - const _handleCopy = function (event) { - if (document.activeElement == canvas) - event.preventDefault(); - const data = !!mobile_input ? - mobile_input.input.value.slice(mobile_input.input.selectionStart, mobile_input.input.selectionEnd) - : UTF8ToString(_GetCopyBufferAsCStr()); - - event.clipboardData.setData("text/plain", data); - } - - // Add the event listener on the window to account for mobile copy paste. - // When copying/pasting elements, the canvas is not the one in focus so - // we cannot prevent default. On desktop canvas does not pick up copy paste events. - window.addEventListener("paste", _handlePaste); - window.addEventListener("copy", _handleCopy); - window.addEventListener("cut", _handleCopy); - - Module.deinitializers.push(function() { - window.removeEventListener("paste", _handlePaste); - window.removeEventListener("copy", _handleCopy); - window.removeEventListener("cut", _handleCopy); - }); - } - - - function _JS_LinearAccelerationSensor_IsRunning() { - // Sensor is running if there is an activated new JS_LinearAccelerationSensor; or the JS_LinearAccelerationSensor_callback is hooked up - return (JS_LinearAccelerationSensor && JS_LinearAccelerationSensor.activated) || (JS_LinearAccelerationSensor_callback != 0); - } - - - - - function _JS_Log_Dump(ptr, type) - { - var str = UTF8ToString(ptr); - if (typeof dump == 'function') - dump (str); - switch (type) - { - case 0: //LogType_Error - case 1: //LogType_Assert - case 4: //LogType_Exception - console.error (str); - return; - - case 2: //LogType_Warning - console.warn (str); - return; - - case 3: //LogType_Log - case 5: //LogType_Debug - console.log (str); - return; - - default: - console.error ("Unknown console message type!") - console.error (str); - } - } - - - function _JS_Log_StackTrace(buffer, bufferSize) - { - var trace = stackTrace(); - if (buffer) - stringToUTF8(trace, buffer, bufferSize); - return lengthBytesUTF8(trace); - } - - - - var mobile_input_ignore_blur_event = false; - - - - function _JS_MobileKeybard_GetIgnoreBlurEvent() { - // On some platforms, such as iOS15, a blur event is sent to the window after the keyboard - // is closed. This causes the game to be paused in the blur event handler in ScreenManagerWebGL. - // It checks this return value to see if it should ignore the blur event. - return mobile_input_ignore_blur_event; - } - - - - function _JS_MobileKeyboard_GetKeyboardStatus() - { - var kKeyboardStatusVisible = 0; - var kKeyboardStatusDone = 1; - //var kKeyboardStatusCanceled = 2; - //var kKeyboardStatusLostFocus = 3; - if (!mobile_input) return kKeyboardStatusDone; - return kKeyboardStatusVisible; - } - - - - - - function _JS_MobileKeyboard_GetText(buffer, bufferSize) - { - // If the keyboard was closed, use the cached version of the input's text so that Unity can - // still ask for it. - var text = mobile_input && mobile_input.input ? mobile_input.input.value : - mobile_input_text ? mobile_input_text : - ""; - if (buffer) stringToUTF8(text, buffer, bufferSize); - return lengthBytesUTF8(text); - } - - - - function _JS_MobileKeyboard_GetTextSelection(outStart, outLength) - { - outStart = (outStart >> 2); - outLength = (outLength >> 2); - - if (!mobile_input) { - HEAP32[outStart] = 0; - HEAP32[outLength] = 0; - return; - } - HEAP32[outStart] = mobile_input.input.selectionStart; - HEAP32[outLength] = mobile_input.input.selectionEnd - mobile_input.input.selectionStart; - } - - - - - function _JS_MobileKeyboard_Hide(delay) - { - if (mobile_input_hide_delay) return; - mobile_input_ignore_blur_event = true; - - function hideMobileKeyboard() { - if (mobile_input && mobile_input.input) { - mobile_input_text = mobile_input.input.value; - mobile_input.input = null; - if (mobile_input.parentNode && mobile_input.parentNode) { - mobile_input.parentNode.removeChild(mobile_input); - } - } - mobile_input = null; - mobile_input_hide_delay = null; - - // mobile_input_ignore_blur_event was set to true so that ScreenManagerWebGL will ignore - // the blur event it might get from the closing of the keyboard. But it might not get that - // blur event, too, depending on the browser. So we want to clear the flag, as soon as we - // can, but some time after the blur event has been potentially fired. - setTimeout(function() { - mobile_input_ignore_blur_event = false; - }, 100); - } - - if (delay) { - // Delaying the hide of the input/keyboard allows a new input to be selected and re-use the - // existing control. This fixes a problem where a quick tap select of a new element would - // cause it to not be displayed because it tried to be focused before the old keyboard finished - // sliding away. - var hideDelay = 200; - mobile_input_hide_delay = setTimeout(hideMobileKeyboard, hideDelay); - } else { - hideMobileKeyboard(); - } - } - - - - function _JS_MobileKeyboard_SetCharacterLimit(limit) - { - if (!mobile_input) return; - mobile_input.input.maxLength = limit; - } - - - - - - function _JS_MobileKeyboard_SetText(text) - { - if (!mobile_input) return; - text = UTF8ToString(text); - mobile_input.input.value = text; - } - - - - function _JS_MobileKeyboard_SetTextSelection(start, length) - { - if (!mobile_input) return; - if(mobile_input.input.type === "number"){ // The type of input field has to be changed to use setSelectionRange - mobile_input.input.type = "text"; - mobile_input.input.setSelectionRange(start, start + length); - mobile_input.input.type = "number"; - } else { - mobile_input.input.setSelectionRange(start, start + length); - } - } - - - - - - - function _JS_MobileKeyboard_Show(text, keyboardType, autocorrection, multiline, secure, alert, - placeholder, characterLimit) - { - if (mobile_input_hide_delay) { - clearTimeout(mobile_input_hide_delay); - mobile_input_hide_delay = null; - } - - text = UTF8ToString(text); - mobile_input_text = text; - - placeholder = UTF8ToString(placeholder); - - var container = document.body; - - var hasExistingMobileInput = !!mobile_input; - - // From KeyboardOnScreen::KeyboardTypes - var input_type; - var KEYBOARD_TYPE_NUMBERS_AND_PUNCTUATION = 2; - var KEYBOARD_TYPE_URL = 3; - var KEYBOARD_TYPE_NUMBER_PAD = 4; - var KEYBOARD_TYPE_PHONE_PAD = 5; - var KEYBOARD_TYPE_EMAIL_ADDRESS = 7; - if (!secure) { - switch (keyboardType) { - case KEYBOARD_TYPE_EMAIL_ADDRESS: - input_type = "email"; - break; - case KEYBOARD_TYPE_URL: - input_type = "url"; - break; - case KEYBOARD_TYPE_NUMBERS_AND_PUNCTUATION: - case KEYBOARD_TYPE_NUMBER_PAD: - case KEYBOARD_TYPE_PHONE_PAD: - input_type = "number"; - break; - default: - input_type = "text"; - break; - } - } else { - input_type = "password"; - } - - if (hasExistingMobileInput) { - if (mobile_input.multiline != multiline) { - _JS_MobileKeyboard_Hide(false); - return; - } - } - - var inputContainer = mobile_input || document.createElement("div"); - if (!hasExistingMobileInput) { - inputContainer.style = "width:100%; position:fixed; bottom:0px; margin:0px; padding:0px; left:0px; border: 1px solid #000; border-radius: 5px; background-color:#fff; font-size:14pt;"; - - container.appendChild(inputContainer); - mobile_input = inputContainer; - } - - var input = hasExistingMobileInput ? - mobile_input.input : - document.createElement(multiline ? "textarea" : "input"); - - mobile_input.multiline = multiline; - mobile_input.secure = secure; - mobile_input.keyboardType = keyboardType; - mobile_input.inputType = input_type; - - input.type = input_type; - input.style = "width:calc(100% - 85px); " + (multiline ? "height:100px;" : "") + "vertical-align:top; border-radius: 5px; outline:none; cursor:default; resize:none; border:0px; padding:10px 0px 10px 10px;"; - - input.spellcheck = autocorrection ? true : false; - input.maxLength = characterLimit > 0 ? characterLimit : 524288; - input.value = text; - input.placeholder = placeholder; - - if (!hasExistingMobileInput) { - inputContainer.appendChild(input); - inputContainer.input = input; - } - - if (!hasExistingMobileInput) { - var okButton = document.createElement("button"); - okButton.innerText = "OK"; - okButton.style = "border:0; position:absolute; left:calc(100% - 75px); top:0px; width:75px; height:100%; margin:0; padding:0; border-radius: 5px; background-color:#fff"; - okButton.addEventListener("touchend", function() { - _JS_MobileKeyboard_Hide(true); - }); - - inputContainer.appendChild(okButton); - inputContainer.okButton = okButton; - - // For single-line text input, enter key will close the keyboard. - input.addEventListener('keyup', function(e) { - if (input.parentNode.multiline) return; - if (e.code == 'Enter' || e.which == 13 || e.keyCode == 13) { - _JS_MobileKeyboard_Hide(true); - } - }); - - // On iOS, the keyboard has a done button that hides the keyboard. The only way to detect - // when this happens seems to be when the HTML input looses focus, so we watch for the blur - // event on the input element and close the element/keybaord when it's gotten. - input.addEventListener("blur", function(e) { - _JS_MobileKeyboard_Hide(true); - e.stopPropagation(); - e.preventDefault(); - }); - - input.select(); - input.focus(); - } else { - input.select(); - } - } - - function _JS_Module_WebGLContextAttributes_PowerPreference() { - return Module.webglContextAttributes.powerPreference; - } - - function _JS_Module_WebGLContextAttributes_PremultipliedAlpha() { - return Module.webglContextAttributes.premultipliedAlpha; - } - - function _JS_Module_WebGLContextAttributes_PreserveDrawingBuffer() { - return Module.webglContextAttributes.preserveDrawingBuffer; - } - - var JS_OrientationSensor = null; - - var JS_OrientationSensor_callback = 0; - function _JS_OrientationSensor_IsRunning() { - // Sensor is running if there is an activated new JS_OrientationSensor; or the DeviceOrientation handler is hooked up - return (JS_OrientationSensor && JS_OrientationSensor.activated) || (JS_OrientationSensor_callback != 0); - } - - - - function JS_OrientationSensor_eventHandler() { - if (JS_OrientationSensor_callback != 0) - ((a1, a2, a3, a4) => dynCall_vffff.apply(null, [JS_OrientationSensor_callback, a1, a2, a3, a4]))(JS_OrientationSensor.quaternion[0], JS_OrientationSensor.quaternion[1], JS_OrientationSensor.quaternion[2], JS_OrientationSensor.quaternion[3]); - } - - - var JS_OrientationSensor_frequencyRequest = 0; - - function JS_DeviceOrientation_eventHandler(event) { - if (JS_OrientationSensor_callback) { - // OBSERVATION: On Android Firefox, absolute = false, webkitCompassHeading = null - // OBSERVATION: On iOS Safari, absolute is undefined, webkitCompassHeading and webkitCompassAccuracy are set - - // Convert alpha, beta, gamma Euler angles to a quaternion - var degToRad = Math.PI / 180; - var x = event.beta * degToRad; - var y = event.gamma * degToRad; - var z = event.alpha * degToRad; - - var cx = Math.cos(x/2); - var sx = Math.sin(x/2); - var cy = Math.cos(y/2); - var sy = Math.sin(y/2); - var cz = Math.cos(z/2); - var sz = Math.sin(z/2); - - var qx = sx * cy * cz - cx * sy * sz; - var qy = cx * sy * cz + sx * cy * sz; - var qz = cx * cy * sz + sx * sy * cz; - var qw = cx * cy * cz - sx * sy * sz; - - ((a1, a2, a3, a4) => dynCall_vffff.apply(null, [JS_OrientationSensor_callback, a1, a2, a3, a4]))(qx, qy, qz, qw); - } - } - - function _JS_OrientationSensor_Start(callback, frequency) { - assert(callback != 0, 'Invalid callback passed to JS_OrientationSensor_Start'); - - // If we don't have new sensor API, fallback to old DeviceOrientationEvent - if (typeof RelativeOrientationSensor === 'undefined') { - if (JS_OrientationSensor_callback == 0) { - JS_OrientationSensor_callback = callback; - JS_RequestDeviceSensorPermissions(1/*DeviceOrientationEvent permission*/); - window.addEventListener('deviceorientation', JS_DeviceOrientation_eventHandler); - } - return; - } - - JS_OrientationSensor_callback = callback; - - function InitializeOrientationSensor(frequency) { - // Use device referenceFrame, since New Input System package does its own compensation - // Use relative orientation to match native players - JS_OrientationSensor = new RelativeOrientationSensor({ frequency: frequency, referenceFrame: 'device' }); - JS_OrientationSensor.addEventListener('reading', JS_OrientationSensor_eventHandler); - JS_OrientationSensor.addEventListener('error', function(e) { - // e.error could be DOMException: Could not connect to a sensor - warnOnce((e.error) ? e.error : e); - }); - JS_OrientationSensor.start(); - } - - // If the sensor is already created, stop and re-create it with new frequency - if (JS_OrientationSensor) { - JS_OrientationSensor.stop(); - JS_OrientationSensor.removeEventListener('reading', JS_OrientationSensor_eventHandler); - InitializeOrientationSensor(frequency); - } - else if (JS_OrientationSensor_frequencyRequest != 0) { - // If the permissions promise is currently in progress, then note new frequency only - JS_OrientationSensor_frequencyRequest = frequency; - } - else { - JS_OrientationSensor_frequencyRequest = frequency; - - // Request required permissions for the RelativeOrientationSensor - Promise.all([navigator.permissions.query({ name: "accelerometer" }), - navigator.permissions.query({ name: "gyroscope" })]) - .then(function(results) { - if (results.every(function(result) {return(result.state === "granted");})) { - InitializeOrientationSensor(JS_OrientationSensor_frequencyRequest); - } else { - warnOnce("No permissions to use RelativeOrientationSensor."); - } - JS_OrientationSensor_frequencyRequest = 0; - }); - } - } - - - - - function _JS_OrientationSensor_Stop() { - if (JS_OrientationSensor) { - JS_OrientationSensor.stop(); - JS_OrientationSensor.removeEventListener('reading', JS_OrientationSensor_eventHandler); - JS_OrientationSensor = null; - } - else if (JS_OrientationSensor_callback != 0) { - window.removeEventListener('deviceorientation', JS_DeviceOrientation_eventHandler); - } - JS_OrientationSensor_callback = 0; - } - - function _JS_Profiler_InjectJobs() - { - for (var jobname in Module["Jobs"]) - { - var job = Module["Jobs"][jobname]; - if (typeof job["endtime"] != "undefined") - Module.ccall("InjectProfilerSample", null, ["string", "number", "number"], [jobname, job.starttime, job.endtime]); - } - } - - - function _JS_RequestDeviceSensorPermissionsOnTouch() { - if (JS_DeviceSensorPermissions == 0) return; - - // Re-request any required device sensor permissions (iOS requires that permissions are requested on a user interaction event) - JS_RequestDeviceSensorPermissions(JS_DeviceSensorPermissions); - } - - function _JS_RunQuitCallbacks() { - Module.QuitCleanup(); - } - - var JS_ScreenOrientation_callback = 0; - function JS_ScreenOrientation_eventHandler() { - if (JS_ScreenOrientation_callback) ((a1, a2, a3) => dynCall_viii.apply(null, [JS_ScreenOrientation_callback, a1, a2, a3]))(window.innerWidth, window.innerHeight, screen.orientation ? screen.orientation.angle : window.orientation); - } - - function _JS_ScreenOrientation_DeInit() { - JS_ScreenOrientation_callback = 0; - window.removeEventListener('resize', JS_ScreenOrientation_eventHandler); - if (screen.orientation) { - screen.orientation.removeEventListener('change', JS_ScreenOrientation_eventHandler); - } - } - - - function _JS_ScreenOrientation_Init(callback) { - // Only register if not yet registered - if (!JS_ScreenOrientation_callback) { - if (screen.orientation) { - // Use Screen Orientation API if available: - // - https://www.w3.org/TR/screen-orientation/ - // - https://caniuse.com/screen-orientation - // - https://developer.mozilla.org/en-US/docs/Web/API/Screen/orientation - // (Firefox, Chrome, Chrome for Android, Firefox for Android) - screen.orientation.addEventListener('change', JS_ScreenOrientation_eventHandler); - } - - // As a fallback, use deprecated DOM window.orientation field if available: - // - https://compat.spec.whatwg.org/#dom-window-orientation - // - https://developer.mozilla.org/en-US/docs/Web/API/Window/orientation - // (Safari for iOS) - // Listening to resize event also helps emulate landscape/portrait transitions on desktop - // browsers when the browser window is scaled to narrow/wide configurations. - window.addEventListener('resize', JS_ScreenOrientation_eventHandler); - - JS_ScreenOrientation_callback = callback; - - // Trigger the event handler immediately after the engine initialization is done to start up - // ScreenManager with the initial state. - setTimeout(JS_ScreenOrientation_eventHandler, 0); - } - } - - var JS_ScreenOrientation_requestedLockType = -1; - - var JS_ScreenOrientation_appliedLockType = -1; - - var JS_ScreenOrientation_timeoutID = -1; - function _JS_ScreenOrientation_Lock(orientationLockType) { - // We will use the Screen Orientation API if available, and silently return if not available - // - https://www.w3.org/TR/screen-orientation/ - // - https://caniuse.com/screen-orientation - // - https://developer.mozilla.org/en-US/docs/Web/API/Screen/orientation - if (!screen.orientation || !screen.orientation.lock) { - // As of writing, this is only not implemented on Safari - return; - } - - // Callback to apply the lock - function applyLock() { - JS_ScreenOrientation_appliedLockType = JS_ScreenOrientation_requestedLockType; - - // Index must match enum class OrientationLockType in ScreenOrientation.h - var screenOrientations = ['any', 0/*natural*/, 'landscape', 'portrait', 'portrait-primary', 'portrait-secondary', 'landscape-primary', 'landscape-secondary' ]; - var type = screenOrientations[JS_ScreenOrientation_appliedLockType]; - - assert(type, 'Invalid orientationLockType passed to JS_ScreenOrientation_Lock'); - - // Apply the lock, which is done asynchronously and returns a Promise - screen.orientation.lock(type).then(function() { - // Upon success, see if the JS_ScreenOrientation_requestedLockType value has changed, in which case, we will now need to queue another applyLock - if (JS_ScreenOrientation_requestedLockType != JS_ScreenOrientation_appliedLockType) { - JS_ScreenOrientation_timeoutID = setTimeout(applyLock, 0); - } - else { - JS_ScreenOrientation_timeoutID = -1; - } - }).catch(function(err) { - // When screen.orientation.lock() is called on a desktop browser, a DOMException is thrown by the promise - warnOnce(err); - JS_ScreenOrientation_timeoutID = -1; - }); - - // Note, there is also an screen.orientation.unlock() which unlocks auto rotate to default orientation. - // On my Google Pixel 5, this allows 'portrait-primary' AND 'landscape', but will differ depending on device. - } - - // Request this orientationLockType be applied on the callback - JS_ScreenOrientation_requestedLockType = orientationLockType; - - // Queue applyLock callback if there is not already a callback or a screen.orientation.lock call in progress - if (JS_ScreenOrientation_timeoutID == -1 && orientationLockType != JS_ScreenOrientation_appliedLockType) { - JS_ScreenOrientation_timeoutID = setTimeout(applyLock, 0); - } - } - - - function handleException(e) { - // Certain exception types we do not treat as errors since they are used for - // internal control flow. - // 1. ExitStatus, which is thrown by exit() - // 2. "unwind", which is thrown by emscripten_unwind_to_js_event_loop() and others - // that wish to return to JS event loop. - if (e instanceof ExitStatus || e == 'unwind') { - return EXITSTATUS; - } - checkStackCookie(); - if (e instanceof WebAssembly.RuntimeError) { - if (_emscripten_stack_get_current() <= 0) { - err('Stack overflow detected. You can try increasing -sSTACK_SIZE (currently set to ' + 524288 + ')'); - } - } - quit_(1, e); - } - - - var PATH = {isAbs:(path) => path.charAt(0) === '/',splitPath:(filename) => { - var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; - return splitPathRe.exec(filename).slice(1); - },normalizeArray:(parts, allowAboveRoot) => { - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = parts.length - 1; i >= 0; i--) { - var last = parts[i]; - if (last === '.') { - parts.splice(i, 1); - } else if (last === '..') { - parts.splice(i, 1); - up++; - } else if (up) { - parts.splice(i, 1); - up--; - } - } - // if the path is allowed to go above the root, restore leading ..s - if (allowAboveRoot) { - for (; up; up--) { - parts.unshift('..'); - } - } - return parts; - },normalize:(path) => { - var isAbsolute = PATH.isAbs(path), - trailingSlash = path.substr(-1) === '/'; - // Normalize the path - path = PATH.normalizeArray(path.split('/').filter((p) => !!p), !isAbsolute).join('/'); - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; - } - return (isAbsolute ? '/' : '') + path; - },dirname:(path) => { - var result = PATH.splitPath(path), - root = result[0], - dir = result[1]; - if (!root && !dir) { - // No dirname whatsoever - return '.'; - } - if (dir) { - // It has a dirname, strip trailing slash - dir = dir.substr(0, dir.length - 1); - } - return root + dir; - },basename:(path) => { - // EMSCRIPTEN return '/'' for '/', not an empty string - if (path === '/') return '/'; - path = PATH.normalize(path); - path = path.replace(/\/$/, ""); - var lastSlash = path.lastIndexOf('/'); - if (lastSlash === -1) return path; - return path.substr(lastSlash+1); - },join:function() { - var paths = Array.prototype.slice.call(arguments); - return PATH.normalize(paths.join('/')); - },join2:(l, r) => { - return PATH.normalize(l + '/' + r); - }}; - - function initRandomFill() { - if (typeof crypto == 'object' && typeof crypto['getRandomValues'] == 'function') { - // for modern web browsers - return (view) => crypto.getRandomValues(view); - } else - // we couldn't find a proper implementation, as Math.random() is not suitable for /dev/random, see emscripten-core/emscripten/pull/7096 - abort("no cryptographic support found for randomDevice. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: function(array) { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };"); - } - function randomFill(view) { - // Lazily init on the first invocation. - return (randomFill = initRandomFill())(view); - } - - - - var PATH_FS = {resolve:function() { - var resolvedPath = '', - resolvedAbsolute = false; - for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) ? arguments[i] : FS.cwd(); - // Skip empty and invalid entries - if (typeof path != 'string') { - throw new TypeError('Arguments to path.resolve must be strings'); - } else if (!path) { - return ''; // an invalid portion invalidates the whole thing - } - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = PATH.isAbs(path); - } - // At this point the path should be resolved to a full absolute path, but - // handle relative paths to be safe (might happen when process.cwd() fails) - resolvedPath = PATH.normalizeArray(resolvedPath.split('/').filter((p) => !!p), !resolvedAbsolute).join('/'); - return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; - },relative:(from, to) => { - from = PATH_FS.resolve(from).substr(1); - to = PATH_FS.resolve(to).substr(1); - function trim(arr) { - var start = 0; - for (; start < arr.length; start++) { - if (arr[start] !== '') break; - } - var end = arr.length - 1; - for (; end >= 0; end--) { - if (arr[end] !== '') break; - } - if (start > end) return []; - return arr.slice(start, end - start + 1); - } - var fromParts = trim(from.split('/')); - var toParts = trim(to.split('/')); - var length = Math.min(fromParts.length, toParts.length); - var samePartsLength = length; - for (var i = 0; i < length; i++) { - if (fromParts[i] !== toParts[i]) { - samePartsLength = i; - break; - } - } - var outputParts = []; - for (var i = samePartsLength; i < fromParts.length; i++) { - outputParts.push('..'); - } - outputParts = outputParts.concat(toParts.slice(samePartsLength)); - return outputParts.join('/'); - }}; - - - - /** @type {function(string, boolean=, number=)} */ - function intArrayFromString(stringy, dontAddNull, length) { - var len = length > 0 ? length : lengthBytesUTF8(stringy)+1; - var u8array = new Array(len); - var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length); - if (dontAddNull) u8array.length = numBytesWritten; - return u8array; - } - - var TTY = {ttys:[],init:function () { - // https://github.com/emscripten-core/emscripten/pull/1555 - // if (ENVIRONMENT_IS_NODE) { - // // currently, FS.init does not distinguish if process.stdin is a file or TTY - // // device, it always assumes it's a TTY device. because of this, we're forcing - // // process.stdin to UTF8 encoding to at least make stdin reading compatible - // // with text files until FS.init can be refactored. - // process.stdin.setEncoding('utf8'); - // } - },shutdown:function() { - // https://github.com/emscripten-core/emscripten/pull/1555 - // if (ENVIRONMENT_IS_NODE) { - // // inolen: any idea as to why node -e 'process.stdin.read()' wouldn't exit immediately (with process.stdin being a tty)? - // // isaacs: because now it's reading from the stream, you've expressed interest in it, so that read() kicks off a _read() which creates a ReadReq operation - // // inolen: I thought read() in that case was a synchronous operation that just grabbed some amount of buffered data if it exists? - // // isaacs: it is. but it also triggers a _read() call, which calls readStart() on the handle - // // isaacs: do process.stdin.pause() and i'd think it'd probably close the pending call - // process.stdin.pause(); - // } - },register:function(dev, ops) { - TTY.ttys[dev] = { input: [], output: [], ops: ops }; - FS.registerDevice(dev, TTY.stream_ops); - },stream_ops:{open:function(stream) { - var tty = TTY.ttys[stream.node.rdev]; - if (!tty) { - throw new FS.ErrnoError(43); - } - stream.tty = tty; - stream.seekable = false; - },close:function(stream) { - // flush any pending line data - stream.tty.ops.fsync(stream.tty); - },fsync:function(stream) { - stream.tty.ops.fsync(stream.tty); - },read:function(stream, buffer, offset, length, pos /* ignored */) { - if (!stream.tty || !stream.tty.ops.get_char) { - throw new FS.ErrnoError(60); - } - var bytesRead = 0; - for (var i = 0; i < length; i++) { - var result; - try { - result = stream.tty.ops.get_char(stream.tty); - } catch (e) { - throw new FS.ErrnoError(29); - } - if (result === undefined && bytesRead === 0) { - throw new FS.ErrnoError(6); - } - if (result === null || result === undefined) break; - bytesRead++; - buffer[offset+i] = result; - } - if (bytesRead) { - stream.node.timestamp = Date.now(); - } - return bytesRead; - },write:function(stream, buffer, offset, length, pos) { - if (!stream.tty || !stream.tty.ops.put_char) { - throw new FS.ErrnoError(60); - } - try { - for (var i = 0; i < length; i++) { - stream.tty.ops.put_char(stream.tty, buffer[offset+i]); - } - } catch (e) { - throw new FS.ErrnoError(29); - } - if (length) { - stream.node.timestamp = Date.now(); - } - return i; - }},default_tty_ops:{get_char:function(tty) { - if (!tty.input.length) { - var result = null; - if (typeof window != 'undefined' && - typeof window.prompt == 'function') { - // Browser. - result = window.prompt('Input: '); // returns null on cancel - if (result !== null) { - result += '\n'; - } - } else if (typeof readline == 'function') { - // Command line. - result = readline(); - if (result !== null) { - result += '\n'; - } - } - if (!result) { - return null; - } - tty.input = intArrayFromString(result, true); - } - return tty.input.shift(); - },put_char:function(tty, val) { - if (val === null || val === 10) { - out(UTF8ArrayToString(tty.output, 0)); - tty.output = []; - } else { - if (val != 0) tty.output.push(val); // val == 0 would cut text output off in the middle. - } - },fsync:function(tty) { - if (tty.output && tty.output.length > 0) { - out(UTF8ArrayToString(tty.output, 0)); - tty.output = []; - } - }},default_tty1_ops:{put_char:function(tty, val) { - if (val === null || val === 10) { - err(UTF8ArrayToString(tty.output, 0)); - tty.output = []; - } else { - if (val != 0) tty.output.push(val); - } - },fsync:function(tty) { - if (tty.output && tty.output.length > 0) { - err(UTF8ArrayToString(tty.output, 0)); - tty.output = []; - } - }}}; - - - function zeroMemory(address, size) { - HEAPU8.fill(0, address, address + size); - return address; - } - - function alignMemory(size, alignment) { - assert(alignment, "alignment argument is required"); - return Math.ceil(size / alignment) * alignment; - } - function mmapAlloc(size) { - size = alignMemory(size, 65536); - var ptr = _emscripten_builtin_memalign(65536, size); - if (!ptr) return 0; - return zeroMemory(ptr, size); - } - var MEMFS = {ops_table:null,mount:function(mount) { - return MEMFS.createNode(null, '/', 16384 | 511 /* 0777 */, 0); - },createNode:function(parent, name, mode, dev) { - if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { - // no supported - throw new FS.ErrnoError(63); - } - if (!MEMFS.ops_table) { - MEMFS.ops_table = { - dir: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr, - lookup: MEMFS.node_ops.lookup, - mknod: MEMFS.node_ops.mknod, - rename: MEMFS.node_ops.rename, - unlink: MEMFS.node_ops.unlink, - rmdir: MEMFS.node_ops.rmdir, - readdir: MEMFS.node_ops.readdir, - symlink: MEMFS.node_ops.symlink - }, - stream: { - llseek: MEMFS.stream_ops.llseek - } - }, - file: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr - }, - stream: { - llseek: MEMFS.stream_ops.llseek, - read: MEMFS.stream_ops.read, - write: MEMFS.stream_ops.write, - allocate: MEMFS.stream_ops.allocate, - mmap: MEMFS.stream_ops.mmap, - msync: MEMFS.stream_ops.msync - } - }, - link: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr, - readlink: MEMFS.node_ops.readlink - }, - stream: {} - }, - chrdev: { - node: { - getattr: MEMFS.node_ops.getattr, - setattr: MEMFS.node_ops.setattr - }, - stream: FS.chrdev_stream_ops - } - }; - } - var node = FS.createNode(parent, name, mode, dev); - if (FS.isDir(node.mode)) { - node.node_ops = MEMFS.ops_table.dir.node; - node.stream_ops = MEMFS.ops_table.dir.stream; - node.contents = {}; - } else if (FS.isFile(node.mode)) { - node.node_ops = MEMFS.ops_table.file.node; - node.stream_ops = MEMFS.ops_table.file.stream; - node.usedBytes = 0; // The actual number of bytes used in the typed array, as opposed to contents.length which gives the whole capacity. - // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred - // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size - // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme. - node.contents = null; - } else if (FS.isLink(node.mode)) { - node.node_ops = MEMFS.ops_table.link.node; - node.stream_ops = MEMFS.ops_table.link.stream; - } else if (FS.isChrdev(node.mode)) { - node.node_ops = MEMFS.ops_table.chrdev.node; - node.stream_ops = MEMFS.ops_table.chrdev.stream; - } - node.timestamp = Date.now(); - // add the new node to the parent - if (parent) { - parent.contents[name] = node; - parent.timestamp = node.timestamp; - } - return node; - },getFileDataAsTypedArray:function(node) { - if (!node.contents) return new Uint8Array(0); - if (node.contents.subarray) return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes. - return new Uint8Array(node.contents); - },expandFileStorage:function(node, newCapacity) { - var prevCapacity = node.contents ? node.contents.length : 0; - if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough. - // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity. - // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to - // avoid overshooting the allocation cap by a very large margin. - var CAPACITY_DOUBLING_MAX = 1024 * 1024; - newCapacity = Math.max(newCapacity, (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) >>> 0); - if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding. - var oldContents = node.contents; - node.contents = new Uint8Array(newCapacity); // Allocate new storage. - if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0); // Copy old data over to the new storage. - },resizeFileStorage:function(node, newSize) { - if (node.usedBytes == newSize) return; - if (newSize == 0) { - node.contents = null; // Fully decommit when requesting a resize to zero. - node.usedBytes = 0; - } else { - var oldContents = node.contents; - node.contents = new Uint8Array(newSize); // Allocate new storage. - if (oldContents) { - node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))); // Copy old data over to the new storage. - } - node.usedBytes = newSize; - } - },node_ops:{getattr:function(node) { - var attr = {}; - // device numbers reuse inode numbers. - attr.dev = FS.isChrdev(node.mode) ? node.id : 1; - attr.ino = node.id; - attr.mode = node.mode; - attr.nlink = 1; - attr.uid = 0; - attr.gid = 0; - attr.rdev = node.rdev; - if (FS.isDir(node.mode)) { - attr.size = 4096; - } else if (FS.isFile(node.mode)) { - attr.size = node.usedBytes; - } else if (FS.isLink(node.mode)) { - attr.size = node.link.length; - } else { - attr.size = 0; - } - attr.atime = new Date(node.timestamp); - attr.mtime = new Date(node.timestamp); - attr.ctime = new Date(node.timestamp); - // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), - // but this is not required by the standard. - attr.blksize = 4096; - attr.blocks = Math.ceil(attr.size / attr.blksize); - return attr; - },setattr:function(node, attr) { - if (attr.mode !== undefined) { - node.mode = attr.mode; - } - if (attr.timestamp !== undefined) { - node.timestamp = attr.timestamp; - } - if (attr.size !== undefined) { - MEMFS.resizeFileStorage(node, attr.size); - } - },lookup:function(parent, name) { - throw FS.genericErrors[44]; - },mknod:function(parent, name, mode, dev) { - return MEMFS.createNode(parent, name, mode, dev); - },rename:function(old_node, new_dir, new_name) { - // if we're overwriting a directory at new_name, make sure it's empty. - if (FS.isDir(old_node.mode)) { - var new_node; - try { - new_node = FS.lookupNode(new_dir, new_name); - } catch (e) { - } - if (new_node) { - for (var i in new_node.contents) { - throw new FS.ErrnoError(55); - } - } - } - // do the internal rewiring - delete old_node.parent.contents[old_node.name]; - old_node.parent.timestamp = Date.now() - old_node.name = new_name; - new_dir.contents[new_name] = old_node; - new_dir.timestamp = old_node.parent.timestamp; - old_node.parent = new_dir; - },unlink:function(parent, name) { - delete parent.contents[name]; - parent.timestamp = Date.now(); - },rmdir:function(parent, name) { - var node = FS.lookupNode(parent, name); - for (var i in node.contents) { - throw new FS.ErrnoError(55); - } - delete parent.contents[name]; - parent.timestamp = Date.now(); - },readdir:function(node) { - var entries = ['.', '..']; - for (var key in node.contents) { - if (!node.contents.hasOwnProperty(key)) { - continue; - } - entries.push(key); - } - return entries; - },symlink:function(parent, newname, oldpath) { - var node = MEMFS.createNode(parent, newname, 511 /* 0777 */ | 40960, 0); - node.link = oldpath; - return node; - },readlink:function(node) { - if (!FS.isLink(node.mode)) { - throw new FS.ErrnoError(28); - } - return node.link; - }},stream_ops:{read:function(stream, buffer, offset, length, position) { - var contents = stream.node.contents; - if (position >= stream.node.usedBytes) return 0; - var size = Math.min(stream.node.usedBytes - position, length); - assert(size >= 0); - if (size > 8 && contents.subarray) { // non-trivial, and typed array - buffer.set(contents.subarray(position, position + size), offset); - } else { - for (var i = 0; i < size; i++) buffer[offset + i] = contents[position + i]; - } - return size; - },write:function(stream, buffer, offset, length, position, canOwn) { - // The data buffer should be a typed array view - assert(!(buffer instanceof ArrayBuffer)); - // If the buffer is located in main memory (HEAP), and if - // memory can grow, we can't hold on to references of the - // memory buffer, as they may get invalidated. That means we - // need to do copy its contents. - if (buffer.buffer === HEAP8.buffer) { - canOwn = false; - } - - if (!length) return 0; - var node = stream.node; - node.timestamp = Date.now(); - - if (buffer.subarray && (!node.contents || node.contents.subarray)) { // This write is from a typed array to a typed array? - if (canOwn) { - assert(position === 0, 'canOwn must imply no weird position inside the file'); - node.contents = buffer.subarray(offset, offset + length); - node.usedBytes = length; - return length; - } else if (node.usedBytes === 0 && position === 0) { // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data. - node.contents = buffer.slice(offset, offset + length); - node.usedBytes = length; - return length; - } else if (position + length <= node.usedBytes) { // Writing to an already allocated and used subrange of the file? - node.contents.set(buffer.subarray(offset, offset + length), position); - return length; - } - } - - // Appending to an existing file and we need to reallocate, or source data did not come as a typed array. - MEMFS.expandFileStorage(node, position+length); - if (node.contents.subarray && buffer.subarray) { - // Use typed array write which is available. - node.contents.set(buffer.subarray(offset, offset + length), position); - } else { - for (var i = 0; i < length; i++) { - node.contents[position + i] = buffer[offset + i]; // Or fall back to manual write if not. - } - } - node.usedBytes = Math.max(node.usedBytes, position + length); - return length; - },llseek:function(stream, offset, whence) { - var position = offset; - if (whence === 1) { - position += stream.position; - } else if (whence === 2) { - if (FS.isFile(stream.node.mode)) { - position += stream.node.usedBytes; - } - } - if (position < 0) { - throw new FS.ErrnoError(28); - } - return position; - },allocate:function(stream, offset, length) { - MEMFS.expandFileStorage(stream.node, offset + length); - stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length); - },mmap:function(stream, length, position, prot, flags) { - if (!FS.isFile(stream.node.mode)) { - throw new FS.ErrnoError(43); - } - var ptr; - var allocated; - var contents = stream.node.contents; - // Only make a new copy when MAP_PRIVATE is specified. - if (!(flags & 2) && contents.buffer === HEAP8.buffer) { - // We can't emulate MAP_SHARED when the file is not backed by the - // buffer we're mapping to (e.g. the HEAP buffer). - allocated = false; - ptr = contents.byteOffset; - } else { - // Try to avoid unnecessary slices. - if (position > 0 || position + length < contents.length) { - if (contents.subarray) { - contents = contents.subarray(position, position + length); - } else { - contents = Array.prototype.slice.call(contents, position, position + length); - } - } - allocated = true; - ptr = mmapAlloc(length); - if (!ptr) { - throw new FS.ErrnoError(48); - } - HEAP8.set(contents, ptr); - } - return { ptr: ptr, allocated: allocated }; - },msync:function(stream, buffer, offset, length, mmapFlags) { - MEMFS.stream_ops.write(stream, buffer, 0, length, offset, false); - // should we check if bytesWritten and length are the same? - return 0; - }}}; - - /** @param {boolean=} noRunDep */ - function asyncLoad(url, onload, onerror, noRunDep) { - var dep = !noRunDep ? getUniqueRunDependency('al ' + url) : ''; - readAsync(url, (arrayBuffer) => { - assert(arrayBuffer, `Loading data file "${url}" failed (no arrayBuffer).`); - onload(new Uint8Array(arrayBuffer)); - if (dep) removeRunDependency(dep); - }, (event) => { - if (onerror) { - onerror(); - } else { - throw `Loading data file "${url}" failed.`; - } - }); - if (dep) addRunDependency(dep); - } - - var preloadPlugins = Module['preloadPlugins'] || []; - function FS_handledByPreloadPlugin(byteArray, fullname, finish, onerror) { - // Ensure plugins are ready. - if (typeof Browser != 'undefined') Browser.init(); - - var handled = false; - preloadPlugins.forEach(function(plugin) { - if (handled) return; - if (plugin['canHandle'](fullname)) { - plugin['handle'](byteArray, fullname, finish, onerror); - handled = true; - } - }); - return handled; - } - function FS_createPreloadedFile(parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) { - // TODO we should allow people to just pass in a complete filename instead - // of parent and name being that we just join them anyways - var fullname = name ? PATH_FS.resolve(PATH.join2(parent, name)) : parent; - var dep = getUniqueRunDependency('cp ' + fullname); // might have several active requests for the same fullname - function processData(byteArray) { - function finish(byteArray) { - if (preFinish) preFinish(); - if (!dontCreateFile) { - FS.createDataFile(parent, name, byteArray, canRead, canWrite, canOwn); - } - if (onload) onload(); - removeRunDependency(dep); - } - if (FS_handledByPreloadPlugin(byteArray, fullname, finish, () => { - if (onerror) onerror(); - removeRunDependency(dep); - })) { - return; - } - finish(byteArray); - } - addRunDependency(dep); - if (typeof url == 'string') { - asyncLoad(url, (byteArray) => processData(byteArray), onerror); - } else { - processData(url); - } - } - - function FS_modeStringToFlags(str) { - var flagModes = { - 'r': 0, - 'r+': 2, - 'w': 512 | 64 | 1, - 'w+': 512 | 64 | 2, - 'a': 1024 | 64 | 1, - 'a+': 1024 | 64 | 2, - }; - var flags = flagModes[str]; - if (typeof flags == 'undefined') { - throw new Error('Unknown file open mode: ' + str); - } - return flags; - } - - function FS_getMode(canRead, canWrite) { - var mode = 0; - if (canRead) mode |= 292 | 73; - if (canWrite) mode |= 146; - return mode; - } - - - - - - - var IDBFS = {dbs:{},indexedDB:() => { - if (typeof indexedDB != 'undefined') return indexedDB; - var ret = null; - if (typeof window == 'object') ret = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; - assert(ret, 'IDBFS used, but indexedDB not supported'); - return ret; - },DB_VERSION:21,DB_STORE_NAME:"FILE_DATA",mount:function(mount) { - // reuse all of the core MEMFS functionality - return MEMFS.mount.apply(null, arguments); - },syncfs:(mount, populate, callback) => { - IDBFS.getLocalSet(mount, (err, local) => { - if (err) return callback(err); - - IDBFS.getRemoteSet(mount, (err, remote) => { - if (err) return callback(err); - - var src = populate ? remote : local; - var dst = populate ? local : remote; - - IDBFS.reconcile(src, dst, callback); - }); - }); - },quit:() => { - Object.values(IDBFS.dbs).forEach((value) => value.close()); - IDBFS.dbs = {}; - },getDB:(name, callback) => { - // check the cache first - var db = IDBFS.dbs[name]; - if (db) { - return callback(null, db); - } - - var req; - try { - req = IDBFS.indexedDB().open(name, IDBFS.DB_VERSION); - } catch (e) { - return callback(e); - } - if (!req) { - return callback("Unable to connect to IndexedDB"); - } - req.onupgradeneeded = (e) => { - var db = /** @type {IDBDatabase} */ (e.target.result); - var transaction = e.target.transaction; - - var fileStore; - - if (db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)) { - fileStore = transaction.objectStore(IDBFS.DB_STORE_NAME); - } else { - fileStore = db.createObjectStore(IDBFS.DB_STORE_NAME); - } - - if (!fileStore.indexNames.contains('timestamp')) { - fileStore.createIndex('timestamp', 'timestamp', { unique: false }); - } - }; - req.onsuccess = () => { - db = /** @type {IDBDatabase} */ (req.result); - - // add to the cache - IDBFS.dbs[name] = db; - callback(null, db); - }; - req.onerror = (e) => { - callback(this.error); - e.preventDefault(); - }; - },getLocalSet:(mount, callback) => { - var entries = {}; - - function isRealDir(p) { - return p !== '.' && p !== '..'; - }; - function toAbsolute(root) { - return (p) => { - return PATH.join2(root, p); - } - }; - - var check = FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint)); - - while (check.length) { - var path = check.pop(); - var stat; - - try { - stat = FS.stat(path); - } catch (e) { - return callback(e); - } - - if (FS.isDir(stat.mode)) { - check.push.apply(check, FS.readdir(path).filter(isRealDir).map(toAbsolute(path))); - } - - entries[path] = { 'timestamp': stat.mtime }; - } - - return callback(null, { type: 'local', entries: entries }); - },getRemoteSet:(mount, callback) => { - var entries = {}; - - IDBFS.getDB(mount.mountpoint, (err, db) => { - if (err) return callback(err); - - try { - var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readonly'); - transaction.onerror = (e) => { - callback(this.error); - e.preventDefault(); - }; - - var store = transaction.objectStore(IDBFS.DB_STORE_NAME); - var index = store.index('timestamp'); - - index.openKeyCursor().onsuccess = (event) => { - var cursor = event.target.result; - - if (!cursor) { - return callback(null, { type: 'remote', db: db, entries: entries }); - } - - entries[cursor.primaryKey] = { 'timestamp': cursor.key }; - - cursor.continue(); - }; - } catch (e) { - return callback(e); - } - }); - },loadLocalEntry:(path, callback) => { - var stat, node; - - try { - var lookup = FS.lookupPath(path); - node = lookup.node; - stat = FS.stat(path); - } catch (e) { - return callback(e); - } - - if (FS.isDir(stat.mode)) { - return callback(null, { 'timestamp': stat.mtime, 'mode': stat.mode }); - } else if (FS.isFile(stat.mode)) { - // Performance consideration: storing a normal JavaScript array to a IndexedDB is much slower than storing a typed array. - // Therefore always convert the file contents to a typed array first before writing the data to IndexedDB. - node.contents = MEMFS.getFileDataAsTypedArray(node); - return callback(null, { 'timestamp': stat.mtime, 'mode': stat.mode, 'contents': node.contents }); - } else { - return callback(new Error('node type not supported')); - } - },storeLocalEntry:(path, entry, callback) => { - try { - if (FS.isDir(entry['mode'])) { - FS.mkdirTree(path, entry['mode']); - } else if (FS.isFile(entry['mode'])) { - FS.writeFile(path, entry['contents'], { canOwn: true }); - } else { - return callback(new Error('node type not supported')); - } - - FS.chmod(path, entry['mode']); - FS.utime(path, entry['timestamp'], entry['timestamp']); - } catch (e) { - return callback(e); - } - - callback(null); - },removeLocalEntry:(path, callback) => { - try { - var stat = FS.stat(path); - - if (FS.isDir(stat.mode)) { - FS.rmdir(path); - } else if (FS.isFile(stat.mode)) { - FS.unlink(path); - } - } catch (e) { - return callback(e); - } - - callback(null); - },loadRemoteEntry:(store, path, callback) => { - var req = store.get(path); - req.onsuccess = (event) => { callback(null, event.target.result); }; - req.onerror = (e) => { - callback(this.error); - e.preventDefault(); - }; - },storeRemoteEntry:(store, path, entry, callback) => { - try { - var req = store.put(entry, path); - } catch (e) { - callback(e); - return; - } - req.onsuccess = () => { callback(null); }; - req.onerror = (e) => { - callback(this.error); - e.preventDefault(); - }; - },removeRemoteEntry:(store, path, callback) => { - var req = store.delete(path); - req.onsuccess = () => { callback(null); }; - req.onerror = (e) => { - callback(this.error); - e.preventDefault(); - }; - },reconcile:(src, dst, callback) => { - var total = 0; - - var create = []; - Object.keys(src.entries).forEach(function (key) { - var e = src.entries[key]; - var e2 = dst.entries[key]; - if (!e2 || e['timestamp'].getTime() != e2['timestamp'].getTime()) { - create.push(key); - total++; - } - }); - - var remove = []; - Object.keys(dst.entries).forEach(function (key) { - if (!src.entries[key]) { - remove.push(key); - total++; - } - }); - - if (!total) { - return callback(null); - } - - var errored = false; - var db = src.type === 'remote' ? src.db : dst.db; - var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readwrite'); - var store = transaction.objectStore(IDBFS.DB_STORE_NAME); - - function done(err) { - if (err && !errored) { - errored = true; - return callback(err); - } - }; - - transaction.onerror = (e) => { - done(this.error); - e.preventDefault(); - }; - - transaction.oncomplete = (e) => { - if (!errored) { - callback(null); - } - }; - - // sort paths in ascending order so directory entries are created - // before the files inside them - create.sort().forEach((path) => { - if (dst.type === 'local') { - IDBFS.loadRemoteEntry(store, path, (err, entry) => { - if (err) return done(err); - IDBFS.storeLocalEntry(path, entry, done); - }); - } else { - IDBFS.loadLocalEntry(path, (err, entry) => { - if (err) return done(err); - IDBFS.storeRemoteEntry(store, path, entry, done); - }); - } - }); - - // sort paths in descending order so files are deleted before their - // parent directories - remove.sort().reverse().forEach((path) => { - if (dst.type === 'local') { - IDBFS.removeLocalEntry(path, done); - } else { - IDBFS.removeRemoteEntry(store, path, done); - } - }); - }}; - - var ERRNO_MESSAGES = {0:"Success",1:"Arg list too long",2:"Permission denied",3:"Address already in use",4:"Address not available",5:"Address family not supported by protocol family",6:"No more processes",7:"Socket already connected",8:"Bad file number",9:"Trying to read unreadable message",10:"Mount device busy",11:"Operation canceled",12:"No children",13:"Connection aborted",14:"Connection refused",15:"Connection reset by peer",16:"File locking deadlock error",17:"Destination address required",18:"Math arg out of domain of func",19:"Quota exceeded",20:"File exists",21:"Bad address",22:"File too large",23:"Host is unreachable",24:"Identifier removed",25:"Illegal byte sequence",26:"Connection already in progress",27:"Interrupted system call",28:"Invalid argument",29:"I/O error",30:"Socket is already connected",31:"Is a directory",32:"Too many symbolic links",33:"Too many open files",34:"Too many links",35:"Message too long",36:"Multihop attempted",37:"File or path name too long",38:"Network interface is not configured",39:"Connection reset by network",40:"Network is unreachable",41:"Too many open files in system",42:"No buffer space available",43:"No such device",44:"No such file or directory",45:"Exec format error",46:"No record locks available",47:"The link has been severed",48:"Not enough core",49:"No message of desired type",50:"Protocol not available",51:"No space left on device",52:"Function not implemented",53:"Socket is not connected",54:"Not a directory",55:"Directory not empty",56:"State not recoverable",57:"Socket operation on non-socket",59:"Not a typewriter",60:"No such device or address",61:"Value too large for defined data type",62:"Previous owner died",63:"Not super-user",64:"Broken pipe",65:"Protocol error",66:"Unknown protocol",67:"Protocol wrong type for socket",68:"Math result not representable",69:"Read only file system",70:"Illegal seek",71:"No such process",72:"Stale file handle",73:"Connection timed out",74:"Text file busy",75:"Cross-device link",100:"Device not a stream",101:"Bad font file fmt",102:"Invalid slot",103:"Invalid request code",104:"No anode",105:"Block device required",106:"Channel number out of range",107:"Level 3 halted",108:"Level 3 reset",109:"Link number out of range",110:"Protocol driver not attached",111:"No CSI structure available",112:"Level 2 halted",113:"Invalid exchange",114:"Invalid request descriptor",115:"Exchange full",116:"No data (for no delay io)",117:"Timer expired",118:"Out of streams resources",119:"Machine is not on the network",120:"Package not installed",121:"The object is remote",122:"Advertise error",123:"Srmount error",124:"Communication error on send",125:"Cross mount point (not really error)",126:"Given log. name not unique",127:"f.d. invalid for this operation",128:"Remote address changed",129:"Can access a needed shared lib",130:"Accessing a corrupted shared lib",131:".lib section in a.out corrupted",132:"Attempting to link in too many libs",133:"Attempting to exec a shared library",135:"Streams pipe error",136:"Too many users",137:"Socket type not supported",138:"Not supported",139:"Protocol family not supported",140:"Can't send after socket shutdown",141:"Too many references",142:"Host is down",148:"No medium (in tape drive)",156:"Level 2 not synchronized"}; - - var ERRNO_CODES = {}; - - var FS = {root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path, opts = {}) => { - path = PATH_FS.resolve(path); - - if (!path) return { path: '', node: null }; - - var defaults = { - follow_mount: true, - recurse_count: 0 - }; - opts = Object.assign(defaults, opts) - - if (opts.recurse_count > 8) { // max recursive lookup of 8 - throw new FS.ErrnoError(32); - } - - // split the absolute path - var parts = path.split('/').filter((p) => !!p); - - // start at the root - var current = FS.root; - var current_path = '/'; - - for (var i = 0; i < parts.length; i++) { - var islast = (i === parts.length-1); - if (islast && opts.parent) { - // stop resolving - break; - } - - current = FS.lookupNode(current, parts[i]); - current_path = PATH.join2(current_path, parts[i]); - - // jump to the mount's root node if this is a mountpoint - if (FS.isMountpoint(current)) { - if (!islast || (islast && opts.follow_mount)) { - current = current.mounted.root; - } - } - - // by default, lookupPath will not follow a symlink if it is the final path component. - // setting opts.follow = true will override this behavior. - if (!islast || opts.follow) { - var count = 0; - while (FS.isLink(current.mode)) { - var link = FS.readlink(current_path); - current_path = PATH_FS.resolve(PATH.dirname(current_path), link); - - var lookup = FS.lookupPath(current_path, { recurse_count: opts.recurse_count + 1 }); - current = lookup.node; - - if (count++ > 40) { // limit max consecutive symlinks to 40 (SYMLOOP_MAX). - throw new FS.ErrnoError(32); - } - } - } - } - - return { path: current_path, node: current }; - },getPath:(node) => { - var path; - while (true) { - if (FS.isRoot(node)) { - var mount = node.mount.mountpoint; - if (!path) return mount; - return mount[mount.length-1] !== '/' ? mount + '/' + path : mount + path; - } - path = path ? node.name + '/' + path : node.name; - node = node.parent; - } - },hashName:(parentid, name) => { - var hash = 0; - - for (var i = 0; i < name.length; i++) { - hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; - } - return ((parentid + hash) >>> 0) % FS.nameTable.length; - },hashAddNode:(node) => { - var hash = FS.hashName(node.parent.id, node.name); - node.name_next = FS.nameTable[hash]; - FS.nameTable[hash] = node; - },hashRemoveNode:(node) => { - var hash = FS.hashName(node.parent.id, node.name); - if (FS.nameTable[hash] === node) { - FS.nameTable[hash] = node.name_next; - } else { - var current = FS.nameTable[hash]; - while (current) { - if (current.name_next === node) { - current.name_next = node.name_next; - break; - } - current = current.name_next; - } - } - },lookupNode:(parent, name) => { - var errCode = FS.mayLookup(parent); - if (errCode) { - throw new FS.ErrnoError(errCode, parent); - } - var hash = FS.hashName(parent.id, name); - for (var node = FS.nameTable[hash]; node; node = node.name_next) { - var nodeName = node.name; - if (node.parent.id === parent.id && nodeName === name) { - return node; - } - } - // if we failed to find it in the cache, call into the VFS - return FS.lookup(parent, name); - },createNode:(parent, name, mode, rdev) => { - assert(typeof parent == 'object') - var node = new FS.FSNode(parent, name, mode, rdev); - - FS.hashAddNode(node); - - return node; - },destroyNode:(node) => { - FS.hashRemoveNode(node); - },isRoot:(node) => { - return node === node.parent; - },isMountpoint:(node) => { - return !!node.mounted; - },isFile:(mode) => { - return (mode & 61440) === 32768; - },isDir:(mode) => { - return (mode & 61440) === 16384; - },isLink:(mode) => { - return (mode & 61440) === 40960; - },isChrdev:(mode) => { - return (mode & 61440) === 8192; - },isBlkdev:(mode) => { - return (mode & 61440) === 24576; - },isFIFO:(mode) => { - return (mode & 61440) === 4096; - },isSocket:(mode) => { - return (mode & 49152) === 49152; - },flagsToPermissionString:(flag) => { - var perms = ['r', 'w', 'rw'][flag & 3]; - if ((flag & 512)) { - perms += 'w'; - } - return perms; - },nodePermissions:(node, perms) => { - if (FS.ignorePermissions) { - return 0; - } - // return 0 if any user, group or owner bits are set. - if (perms.includes('r') && !(node.mode & 292)) { - return 2; - } else if (perms.includes('w') && !(node.mode & 146)) { - return 2; - } else if (perms.includes('x') && !(node.mode & 73)) { - return 2; - } - return 0; - },mayLookup:(dir) => { - var errCode = FS.nodePermissions(dir, 'x'); - if (errCode) return errCode; - if (!dir.node_ops.lookup) return 2; - return 0; - },mayCreate:(dir, name) => { - try { - var node = FS.lookupNode(dir, name); - return 20; - } catch (e) { - } - return FS.nodePermissions(dir, 'wx'); - },mayDelete:(dir, name, isdir) => { - var node; - try { - node = FS.lookupNode(dir, name); - } catch (e) { - return e.errno; - } - var errCode = FS.nodePermissions(dir, 'wx'); - if (errCode) { - return errCode; - } - if (isdir) { - if (!FS.isDir(node.mode)) { - return 54; - } - if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { - return 10; - } - } else { - if (FS.isDir(node.mode)) { - return 31; - } - } - return 0; - },mayOpen:(node, flags) => { - if (!node) { - return 44; - } - if (FS.isLink(node.mode)) { - return 32; - } else if (FS.isDir(node.mode)) { - if (FS.flagsToPermissionString(flags) !== 'r' || // opening for write - (flags & 512)) { // TODO: check for O_SEARCH? (== search for dir only) - return 31; - } - } - return FS.nodePermissions(node, FS.flagsToPermissionString(flags)); - },MAX_OPEN_FDS:4096,nextfd:(fd_start = 0, fd_end = FS.MAX_OPEN_FDS) => { - for (var fd = fd_start; fd <= fd_end; fd++) { - if (!FS.streams[fd]) { - return fd; - } - } - throw new FS.ErrnoError(33); - },getStream:(fd) => FS.streams[fd],createStream:(stream, fd_start, fd_end) => { - if (!FS.FSStream) { - FS.FSStream = /** @constructor */ function() { - this.shared = { }; - }; - FS.FSStream.prototype = {}; - Object.defineProperties(FS.FSStream.prototype, { - object: { - /** @this {FS.FSStream} */ - get: function() { return this.node; }, - /** @this {FS.FSStream} */ - set: function(val) { this.node = val; } - }, - isRead: { - /** @this {FS.FSStream} */ - get: function() { return (this.flags & 2097155) !== 1; } - }, - isWrite: { - /** @this {FS.FSStream} */ - get: function() { return (this.flags & 2097155) !== 0; } - }, - isAppend: { - /** @this {FS.FSStream} */ - get: function() { return (this.flags & 1024); } - }, - flags: { - /** @this {FS.FSStream} */ - get: function() { return this.shared.flags; }, - /** @this {FS.FSStream} */ - set: function(val) { this.shared.flags = val; }, - }, - position : { - /** @this {FS.FSStream} */ - get: function() { return this.shared.position; }, - /** @this {FS.FSStream} */ - set: function(val) { this.shared.position = val; }, - }, - }); - } - // clone it, so we can return an instance of FSStream - stream = Object.assign(new FS.FSStream(), stream); - var fd = FS.nextfd(fd_start, fd_end); - stream.fd = fd; - FS.streams[fd] = stream; - return stream; - },closeStream:(fd) => { - FS.streams[fd] = null; - },chrdev_stream_ops:{open:(stream) => { - var device = FS.getDevice(stream.node.rdev); - // override node's stream ops with the device's - stream.stream_ops = device.stream_ops; - // forward the open call - if (stream.stream_ops.open) { - stream.stream_ops.open(stream); - } - },llseek:() => { - throw new FS.ErrnoError(70); - }},major:(dev) => ((dev) >> 8),minor:(dev) => ((dev) & 0xff),makedev:(ma, mi) => ((ma) << 8 | (mi)),registerDevice:(dev, ops) => { - FS.devices[dev] = { stream_ops: ops }; - },getDevice:(dev) => FS.devices[dev],getMounts:(mount) => { - var mounts = []; - var check = [mount]; - - while (check.length) { - var m = check.pop(); - - mounts.push(m); - - check.push.apply(check, m.mounts); - } - - return mounts; - },syncfs:(populate, callback) => { - if (typeof populate == 'function') { - callback = populate; - populate = false; - } - - FS.syncFSRequests++; - - if (FS.syncFSRequests > 1) { - err('warning: ' + FS.syncFSRequests + ' FS.syncfs operations in flight at once, probably just doing extra work'); - } - - var mounts = FS.getMounts(FS.root.mount); - var completed = 0; - - function doCallback(errCode) { - assert(FS.syncFSRequests > 0); - FS.syncFSRequests--; - return callback(errCode); - } - - function done(errCode) { - if (errCode) { - if (!done.errored) { - done.errored = true; - return doCallback(errCode); - } - return; - } - if (++completed >= mounts.length) { - doCallback(null); - } - }; - - // sync all mounts - mounts.forEach((mount) => { - if (!mount.type.syncfs) { - return done(null); - } - mount.type.syncfs(mount, populate, done); - }); - },mount:(type, opts, mountpoint) => { - if (typeof type == 'string') { - // The filesystem was not included, and instead we have an error - // message stored in the variable. - throw type; - } - var root = mountpoint === '/'; - var pseudo = !mountpoint; - var node; - - if (root && FS.root) { - throw new FS.ErrnoError(10); - } else if (!root && !pseudo) { - var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); - - mountpoint = lookup.path; // use the absolute path - node = lookup.node; - - if (FS.isMountpoint(node)) { - throw new FS.ErrnoError(10); - } - - if (!FS.isDir(node.mode)) { - throw new FS.ErrnoError(54); - } - } - - var mount = { - type: type, - opts: opts, - mountpoint: mountpoint, - mounts: [] - }; - - // create a root node for the fs - var mountRoot = type.mount(mount); - mountRoot.mount = mount; - mount.root = mountRoot; - - if (root) { - FS.root = mountRoot; - } else if (node) { - // set as a mountpoint - node.mounted = mount; - - // add the new mount to the current mount's children - if (node.mount) { - node.mount.mounts.push(mount); - } - } - - return mountRoot; - },unmount:(mountpoint) => { - var lookup = FS.lookupPath(mountpoint, { follow_mount: false }); - - if (!FS.isMountpoint(lookup.node)) { - throw new FS.ErrnoError(28); - } - - // destroy the nodes for this mount, and all its child mounts - var node = lookup.node; - var mount = node.mounted; - var mounts = FS.getMounts(mount); - - Object.keys(FS.nameTable).forEach((hash) => { - var current = FS.nameTable[hash]; - - while (current) { - var next = current.name_next; - - if (mounts.includes(current.mount)) { - FS.destroyNode(current); - } - - current = next; - } - }); - - // no longer a mountpoint - node.mounted = null; - - // remove this mount from the child mounts - var idx = node.mount.mounts.indexOf(mount); - assert(idx !== -1); - node.mount.mounts.splice(idx, 1); - },lookup:(parent, name) => { - return parent.node_ops.lookup(parent, name); - },mknod:(path, mode, dev) => { - var lookup = FS.lookupPath(path, { parent: true }); - var parent = lookup.node; - var name = PATH.basename(path); - if (!name || name === '.' || name === '..') { - throw new FS.ErrnoError(28); - } - var errCode = FS.mayCreate(parent, name); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - if (!parent.node_ops.mknod) { - throw new FS.ErrnoError(63); - } - return parent.node_ops.mknod(parent, name, mode, dev); - },create:(path, mode) => { - mode = mode !== undefined ? mode : 438 /* 0666 */; - mode &= 4095; - mode |= 32768; - return FS.mknod(path, mode, 0); - },mkdir:(path, mode) => { - mode = mode !== undefined ? mode : 511 /* 0777 */; - mode &= 511 | 512; - mode |= 16384; - return FS.mknod(path, mode, 0); - },mkdirTree:(path, mode) => { - var dirs = path.split('/'); - var d = ''; - for (var i = 0; i < dirs.length; ++i) { - if (!dirs[i]) continue; - d += '/' + dirs[i]; - try { - FS.mkdir(d, mode); - } catch(e) { - if (e.errno != 20) throw e; - } - } - },mkdev:(path, mode, dev) => { - if (typeof dev == 'undefined') { - dev = mode; - mode = 438 /* 0666 */; - } - mode |= 8192; - return FS.mknod(path, mode, dev); - },symlink:(oldpath, newpath) => { - if (!PATH_FS.resolve(oldpath)) { - throw new FS.ErrnoError(44); - } - var lookup = FS.lookupPath(newpath, { parent: true }); - var parent = lookup.node; - if (!parent) { - throw new FS.ErrnoError(44); - } - var newname = PATH.basename(newpath); - var errCode = FS.mayCreate(parent, newname); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - if (!parent.node_ops.symlink) { - throw new FS.ErrnoError(63); - } - return parent.node_ops.symlink(parent, newname, oldpath); - },rename:(old_path, new_path) => { - var old_dirname = PATH.dirname(old_path); - var new_dirname = PATH.dirname(new_path); - var old_name = PATH.basename(old_path); - var new_name = PATH.basename(new_path); - // parents must exist - var lookup, old_dir, new_dir; - - // let the errors from non existant directories percolate up - lookup = FS.lookupPath(old_path, { parent: true }); - old_dir = lookup.node; - lookup = FS.lookupPath(new_path, { parent: true }); - new_dir = lookup.node; - - if (!old_dir || !new_dir) throw new FS.ErrnoError(44); - // need to be part of the same mount - if (old_dir.mount !== new_dir.mount) { - throw new FS.ErrnoError(75); - } - // source must exist - var old_node = FS.lookupNode(old_dir, old_name); - // old path should not be an ancestor of the new path - var relative = PATH_FS.relative(old_path, new_dirname); - if (relative.charAt(0) !== '.') { - throw new FS.ErrnoError(28); - } - // new path should not be an ancestor of the old path - relative = PATH_FS.relative(new_path, old_dirname); - if (relative.charAt(0) !== '.') { - throw new FS.ErrnoError(55); - } - // see if the new path already exists - var new_node; - try { - new_node = FS.lookupNode(new_dir, new_name); - } catch (e) { - // not fatal - } - // early out if nothing needs to change - if (old_node === new_node) { - return; - } - // we'll need to delete the old entry - var isdir = FS.isDir(old_node.mode); - var errCode = FS.mayDelete(old_dir, old_name, isdir); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - // need delete permissions if we'll be overwriting. - // need create permissions if new doesn't already exist. - errCode = new_node ? - FS.mayDelete(new_dir, new_name, isdir) : - FS.mayCreate(new_dir, new_name); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - if (!old_dir.node_ops.rename) { - throw new FS.ErrnoError(63); - } - if (FS.isMountpoint(old_node) || (new_node && FS.isMountpoint(new_node))) { - throw new FS.ErrnoError(10); - } - // if we are going to change the parent, check write permissions - if (new_dir !== old_dir) { - errCode = FS.nodePermissions(old_dir, 'w'); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - } - // remove the node from the lookup hash - FS.hashRemoveNode(old_node); - // do the underlying fs rename - try { - old_dir.node_ops.rename(old_node, new_dir, new_name); - } catch (e) { - throw e; - } finally { - // add the node back to the hash (in case node_ops.rename - // changed its name) - FS.hashAddNode(old_node); - } - },rmdir:(path) => { - var lookup = FS.lookupPath(path, { parent: true }); - var parent = lookup.node; - var name = PATH.basename(path); - var node = FS.lookupNode(parent, name); - var errCode = FS.mayDelete(parent, name, true); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - if (!parent.node_ops.rmdir) { - throw new FS.ErrnoError(63); - } - if (FS.isMountpoint(node)) { - throw new FS.ErrnoError(10); - } - parent.node_ops.rmdir(parent, name); - FS.destroyNode(node); - },readdir:(path) => { - var lookup = FS.lookupPath(path, { follow: true }); - var node = lookup.node; - if (!node.node_ops.readdir) { - throw new FS.ErrnoError(54); - } - return node.node_ops.readdir(node); - },unlink:(path) => { - var lookup = FS.lookupPath(path, { parent: true }); - var parent = lookup.node; - if (!parent) { - throw new FS.ErrnoError(44); - } - var name = PATH.basename(path); - var node = FS.lookupNode(parent, name); - var errCode = FS.mayDelete(parent, name, false); - if (errCode) { - // According to POSIX, we should map EISDIR to EPERM, but - // we instead do what Linux does (and we must, as we use - // the musl linux libc). - throw new FS.ErrnoError(errCode); - } - if (!parent.node_ops.unlink) { - throw new FS.ErrnoError(63); - } - if (FS.isMountpoint(node)) { - throw new FS.ErrnoError(10); - } - parent.node_ops.unlink(parent, name); - FS.destroyNode(node); - },readlink:(path) => { - var lookup = FS.lookupPath(path); - var link = lookup.node; - if (!link) { - throw new FS.ErrnoError(44); - } - if (!link.node_ops.readlink) { - throw new FS.ErrnoError(28); - } - return PATH_FS.resolve(FS.getPath(link.parent), link.node_ops.readlink(link)); - },stat:(path, dontFollow) => { - var lookup = FS.lookupPath(path, { follow: !dontFollow }); - var node = lookup.node; - if (!node) { - throw new FS.ErrnoError(44); - } - if (!node.node_ops.getattr) { - throw new FS.ErrnoError(63); - } - return node.node_ops.getattr(node); - },lstat:(path) => { - return FS.stat(path, true); - },chmod:(path, mode, dontFollow) => { - var node; - if (typeof path == 'string') { - var lookup = FS.lookupPath(path, { follow: !dontFollow }); - node = lookup.node; - } else { - node = path; - } - if (!node.node_ops.setattr) { - throw new FS.ErrnoError(63); - } - node.node_ops.setattr(node, { - mode: (mode & 4095) | (node.mode & ~4095), - timestamp: Date.now() - }); - },lchmod:(path, mode) => { - FS.chmod(path, mode, true); - },fchmod:(fd, mode) => { - var stream = FS.getStream(fd); - if (!stream) { - throw new FS.ErrnoError(8); - } - FS.chmod(stream.node, mode); - },chown:(path, uid, gid, dontFollow) => { - var node; - if (typeof path == 'string') { - var lookup = FS.lookupPath(path, { follow: !dontFollow }); - node = lookup.node; - } else { - node = path; - } - if (!node.node_ops.setattr) { - throw new FS.ErrnoError(63); - } - node.node_ops.setattr(node, { - timestamp: Date.now() - // we ignore the uid / gid for now - }); - },lchown:(path, uid, gid) => { - FS.chown(path, uid, gid, true); - },fchown:(fd, uid, gid) => { - var stream = FS.getStream(fd); - if (!stream) { - throw new FS.ErrnoError(8); - } - FS.chown(stream.node, uid, gid); - },truncate:(path, len) => { - if (len < 0) { - throw new FS.ErrnoError(28); - } - var node; - if (typeof path == 'string') { - var lookup = FS.lookupPath(path, { follow: true }); - node = lookup.node; - } else { - node = path; - } - if (!node.node_ops.setattr) { - throw new FS.ErrnoError(63); - } - if (FS.isDir(node.mode)) { - throw new FS.ErrnoError(31); - } - if (!FS.isFile(node.mode)) { - throw new FS.ErrnoError(28); - } - var errCode = FS.nodePermissions(node, 'w'); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - node.node_ops.setattr(node, { - size: len, - timestamp: Date.now() - }); - },ftruncate:(fd, len) => { - var stream = FS.getStream(fd); - if (!stream) { - throw new FS.ErrnoError(8); - } - if ((stream.flags & 2097155) === 0) { - throw new FS.ErrnoError(28); - } - FS.truncate(stream.node, len); - },utime:(path, atime, mtime) => { - var lookup = FS.lookupPath(path, { follow: true }); - var node = lookup.node; - node.node_ops.setattr(node, { - timestamp: Math.max(atime, mtime) - }); - },open:(path, flags, mode) => { - if (path === "") { - throw new FS.ErrnoError(44); - } - flags = typeof flags == 'string' ? FS_modeStringToFlags(flags) : flags; - mode = typeof mode == 'undefined' ? 438 /* 0666 */ : mode; - if ((flags & 64)) { - mode = (mode & 4095) | 32768; - } else { - mode = 0; - } - var node; - if (typeof path == 'object') { - node = path; - } else { - path = PATH.normalize(path); - try { - var lookup = FS.lookupPath(path, { - follow: !(flags & 131072) - }); - node = lookup.node; - } catch (e) { - // ignore - } - } - // perhaps we need to create the node - var created = false; - if ((flags & 64)) { - if (node) { - // if O_CREAT and O_EXCL are set, error out if the node already exists - if ((flags & 128)) { - throw new FS.ErrnoError(20); - } - } else { - // node doesn't exist, try to create it - node = FS.mknod(path, mode, 0); - created = true; - } - } - if (!node) { - throw new FS.ErrnoError(44); - } - // can't truncate a device - if (FS.isChrdev(node.mode)) { - flags &= ~512; - } - // if asked only for a directory, then this must be one - if ((flags & 65536) && !FS.isDir(node.mode)) { - throw new FS.ErrnoError(54); - } - // check permissions, if this is not a file we just created now (it is ok to - // create and write to a file with read-only permissions; it is read-only - // for later use) - if (!created) { - var errCode = FS.mayOpen(node, flags); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - } - // do truncation if necessary - if ((flags & 512) && !created) { - FS.truncate(node, 0); - } - // we've already handled these, don't pass down to the underlying vfs - flags &= ~(128 | 512 | 131072); - - // register the stream with the filesystem - var stream = FS.createStream({ - node: node, - path: FS.getPath(node), // we want the absolute path to the node - flags: flags, - seekable: true, - position: 0, - stream_ops: node.stream_ops, - // used by the file family libc calls (fopen, fwrite, ferror, etc.) - ungotten: [], - error: false - }); - // call the new stream's open function - if (stream.stream_ops.open) { - stream.stream_ops.open(stream); - } - if (Module['logReadFiles'] && !(flags & 1)) { - if (!FS.readFiles) FS.readFiles = {}; - if (!(path in FS.readFiles)) { - FS.readFiles[path] = 1; - } - } - return stream; - },close:(stream) => { - if (FS.isClosed(stream)) { - throw new FS.ErrnoError(8); - } - if (stream.getdents) stream.getdents = null; // free readdir state - try { - if (stream.stream_ops.close) { - stream.stream_ops.close(stream); - } - } catch (e) { - throw e; - } finally { - FS.closeStream(stream.fd); - } - stream.fd = null; - },isClosed:(stream) => { - return stream.fd === null; - },llseek:(stream, offset, whence) => { - if (FS.isClosed(stream)) { - throw new FS.ErrnoError(8); - } - if (!stream.seekable || !stream.stream_ops.llseek) { - throw new FS.ErrnoError(70); - } - if (whence != 0 && whence != 1 && whence != 2) { - throw new FS.ErrnoError(28); - } - stream.position = stream.stream_ops.llseek(stream, offset, whence); - stream.ungotten = []; - return stream.position; - },read:(stream, buffer, offset, length, position) => { - if (length < 0 || position < 0) { - throw new FS.ErrnoError(28); - } - if (FS.isClosed(stream)) { - throw new FS.ErrnoError(8); - } - if ((stream.flags & 2097155) === 1) { - throw new FS.ErrnoError(8); - } - if (FS.isDir(stream.node.mode)) { - throw new FS.ErrnoError(31); - } - if (!stream.stream_ops.read) { - throw new FS.ErrnoError(28); - } - var seeking = typeof position != 'undefined'; - if (!seeking) { - position = stream.position; - } else if (!stream.seekable) { - throw new FS.ErrnoError(70); - } - var bytesRead = stream.stream_ops.read(stream, buffer, offset, length, position); - if (!seeking) stream.position += bytesRead; - return bytesRead; - },write:(stream, buffer, offset, length, position, canOwn) => { - if (length < 0 || position < 0) { - throw new FS.ErrnoError(28); - } - if (FS.isClosed(stream)) { - throw new FS.ErrnoError(8); - } - if ((stream.flags & 2097155) === 0) { - throw new FS.ErrnoError(8); - } - if (FS.isDir(stream.node.mode)) { - throw new FS.ErrnoError(31); - } - if (!stream.stream_ops.write) { - throw new FS.ErrnoError(28); - } - if (stream.seekable && stream.flags & 1024) { - // seek to the end before writing in append mode - FS.llseek(stream, 0, 2); - } - var seeking = typeof position != 'undefined'; - if (!seeking) { - position = stream.position; - } else if (!stream.seekable) { - throw new FS.ErrnoError(70); - } - var bytesWritten = stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); - if (!seeking) stream.position += bytesWritten; - return bytesWritten; - },allocate:(stream, offset, length) => { - if (FS.isClosed(stream)) { - throw new FS.ErrnoError(8); - } - if (offset < 0 || length <= 0) { - throw new FS.ErrnoError(28); - } - if ((stream.flags & 2097155) === 0) { - throw new FS.ErrnoError(8); - } - if (!FS.isFile(stream.node.mode) && !FS.isDir(stream.node.mode)) { - throw new FS.ErrnoError(43); - } - if (!stream.stream_ops.allocate) { - throw new FS.ErrnoError(138); - } - stream.stream_ops.allocate(stream, offset, length); - },mmap:(stream, length, position, prot, flags) => { - // User requests writing to file (prot & PROT_WRITE != 0). - // Checking if we have permissions to write to the file unless - // MAP_PRIVATE flag is set. According to POSIX spec it is possible - // to write to file opened in read-only mode with MAP_PRIVATE flag, - // as all modifications will be visible only in the memory of - // the current process. - if ((prot & 2) !== 0 - && (flags & 2) === 0 - && (stream.flags & 2097155) !== 2) { - throw new FS.ErrnoError(2); - } - if ((stream.flags & 2097155) === 1) { - throw new FS.ErrnoError(2); - } - if (!stream.stream_ops.mmap) { - throw new FS.ErrnoError(43); - } - return stream.stream_ops.mmap(stream, length, position, prot, flags); - },msync:(stream, buffer, offset, length, mmapFlags) => { - if (!stream.stream_ops.msync) { - return 0; - } - return stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags); - },munmap:(stream) => 0,ioctl:(stream, cmd, arg) => { - if (!stream.stream_ops.ioctl) { - throw new FS.ErrnoError(59); - } - return stream.stream_ops.ioctl(stream, cmd, arg); - },readFile:(path, opts = {}) => { - opts.flags = opts.flags || 0; - opts.encoding = opts.encoding || 'binary'; - if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') { - throw new Error('Invalid encoding type "' + opts.encoding + '"'); - } - var ret; - var stream = FS.open(path, opts.flags); - var stat = FS.stat(path); - var length = stat.size; - var buf = new Uint8Array(length); - FS.read(stream, buf, 0, length, 0); - if (opts.encoding === 'utf8') { - ret = UTF8ArrayToString(buf, 0); - } else if (opts.encoding === 'binary') { - ret = buf; - } - FS.close(stream); - return ret; - },writeFile:(path, data, opts = {}) => { - opts.flags = opts.flags || 577; - var stream = FS.open(path, opts.flags, opts.mode); - if (typeof data == 'string') { - var buf = new Uint8Array(lengthBytesUTF8(data)+1); - var actualNumBytes = stringToUTF8Array(data, buf, 0, buf.length); - FS.write(stream, buf, 0, actualNumBytes, undefined, opts.canOwn); - } else if (ArrayBuffer.isView(data)) { - FS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn); - } else { - throw new Error('Unsupported data type'); - } - FS.close(stream); - },cwd:() => FS.currentPath,chdir:(path) => { - var lookup = FS.lookupPath(path, { follow: true }); - if (lookup.node === null) { - throw new FS.ErrnoError(44); - } - if (!FS.isDir(lookup.node.mode)) { - throw new FS.ErrnoError(54); - } - var errCode = FS.nodePermissions(lookup.node, 'x'); - if (errCode) { - throw new FS.ErrnoError(errCode); - } - FS.currentPath = lookup.path; - },createDefaultDirectories:() => { - FS.mkdir('/tmp'); - FS.mkdir('/home'); - FS.mkdir('/home/web_user'); - },createDefaultDevices:() => { - // create /dev - FS.mkdir('/dev'); - // setup /dev/null - FS.registerDevice(FS.makedev(1, 3), { - read: () => 0, - write: (stream, buffer, offset, length, pos) => length, - }); - FS.mkdev('/dev/null', FS.makedev(1, 3)); - // setup /dev/tty and /dev/tty1 - // stderr needs to print output using err() rather than out() - // so we register a second tty just for it. - TTY.register(FS.makedev(5, 0), TTY.default_tty_ops); - TTY.register(FS.makedev(6, 0), TTY.default_tty1_ops); - FS.mkdev('/dev/tty', FS.makedev(5, 0)); - FS.mkdev('/dev/tty1', FS.makedev(6, 0)); - // setup /dev/[u]random - // use a buffer to avoid overhead of individual crypto calls per byte - var randomBuffer = new Uint8Array(1024), randomLeft = 0; - var randomByte = () => { - if (randomLeft === 0) { - randomLeft = randomFill(randomBuffer).byteLength; - } - return randomBuffer[--randomLeft]; - }; - FS.createDevice('/dev', 'random', randomByte); - FS.createDevice('/dev', 'urandom', randomByte); - // we're not going to emulate the actual shm device, - // just create the tmp dirs that reside in it commonly - FS.mkdir('/dev/shm'); - FS.mkdir('/dev/shm/tmp'); - },createSpecialDirectories:() => { - // create /proc/self/fd which allows /proc/self/fd/6 => readlink gives the - // name of the stream for fd 6 (see test_unistd_ttyname) - FS.mkdir('/proc'); - var proc_self = FS.mkdir('/proc/self'); - FS.mkdir('/proc/self/fd'); - FS.mount({ - mount: () => { - var node = FS.createNode(proc_self, 'fd', 16384 | 511 /* 0777 */, 73); - node.node_ops = { - lookup: (parent, name) => { - var fd = +name; - var stream = FS.getStream(fd); - if (!stream) throw new FS.ErrnoError(8); - var ret = { - parent: null, - mount: { mountpoint: 'fake' }, - node_ops: { readlink: () => stream.path }, - }; - ret.parent = ret; // make it look like a simple root node - return ret; - } - }; - return node; - } - }, {}, '/proc/self/fd'); - },createStandardStreams:() => { - // TODO deprecate the old functionality of a single - // input / output callback and that utilizes FS.createDevice - // and instead require a unique set of stream ops - - // by default, we symlink the standard streams to the - // default tty devices. however, if the standard streams - // have been overwritten we create a unique device for - // them instead. - if (Module['stdin']) { - FS.createDevice('/dev', 'stdin', Module['stdin']); - } else { - FS.symlink('/dev/tty', '/dev/stdin'); - } - if (Module['stdout']) { - FS.createDevice('/dev', 'stdout', null, Module['stdout']); - } else { - FS.symlink('/dev/tty', '/dev/stdout'); - } - if (Module['stderr']) { - FS.createDevice('/dev', 'stderr', null, Module['stderr']); - } else { - FS.symlink('/dev/tty1', '/dev/stderr'); - } - - // open default streams for the stdin, stdout and stderr devices - var stdin = FS.open('/dev/stdin', 0); - var stdout = FS.open('/dev/stdout', 1); - var stderr = FS.open('/dev/stderr', 1); - assert(stdin.fd === 0, 'invalid handle for stdin (' + stdin.fd + ')'); - assert(stdout.fd === 1, 'invalid handle for stdout (' + stdout.fd + ')'); - assert(stderr.fd === 2, 'invalid handle for stderr (' + stderr.fd + ')'); - },ensureErrnoError:() => { - if (FS.ErrnoError) return; - FS.ErrnoError = /** @this{Object} */ function ErrnoError(errno, node) { - // We set the `name` property to be able to identify `FS.ErrnoError` - // - the `name` is a standard ECMA-262 property of error objects. Kind of good to have it anyway. - // - when using PROXYFS, an error can come from an underlying FS - // as different FS objects have their own FS.ErrnoError each, - // the test `err instanceof FS.ErrnoError` won't detect an error coming from another filesystem, causing bugs. - // we'll use the reliable test `err.name == "ErrnoError"` instead - this.name = 'ErrnoError'; - this.node = node; - this.setErrno = /** @this{Object} */ function(errno) { - this.errno = errno; - for (var key in ERRNO_CODES) { - if (ERRNO_CODES[key] === errno) { - this.code = key; - break; - } - } - }; - this.setErrno(errno); - this.message = ERRNO_MESSAGES[errno]; - - // Try to get a maximally helpful stack trace. On Node.js, getting Error.stack - // now ensures it shows what we want. - if (this.stack) { - // Define the stack property for Node.js 4, which otherwise errors on the next line. - Object.defineProperty(this, "stack", { value: (new Error).stack, writable: true }); - this.stack = demangleAll(this.stack); - } - }; - FS.ErrnoError.prototype = new Error(); - FS.ErrnoError.prototype.constructor = FS.ErrnoError; - // Some errors may happen quite a bit, to avoid overhead we reuse them (and suffer a lack of stack info) - [44].forEach((code) => { - FS.genericErrors[code] = new FS.ErrnoError(code); - FS.genericErrors[code].stack = ''; - }); - },staticInit:() => { - FS.ensureErrnoError(); - - FS.nameTable = new Array(4096); - - FS.mount(MEMFS, {}, '/'); - - FS.createDefaultDirectories(); - FS.createDefaultDevices(); - FS.createSpecialDirectories(); - - FS.filesystems = { - 'MEMFS': MEMFS, - 'IDBFS': IDBFS, - }; - },init:(input, output, error) => { - assert(!FS.init.initialized, 'FS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); - FS.init.initialized = true; - - FS.ensureErrnoError(); - - // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here - Module['stdin'] = input || Module['stdin']; - Module['stdout'] = output || Module['stdout']; - Module['stderr'] = error || Module['stderr']; - - FS.createStandardStreams(); - },quit:() => { - FS.init.initialized = false; - // force-flush all streams, so we get musl std streams printed out - _fflush(0); - // close all of our streams - for (var i = 0; i < FS.streams.length; i++) { - var stream = FS.streams[i]; - if (!stream) { - continue; - } - FS.close(stream); - } - },findObject:(path, dontResolveLastLink) => { - var ret = FS.analyzePath(path, dontResolveLastLink); - if (!ret.exists) { - return null; - } - return ret.object; - },analyzePath:(path, dontResolveLastLink) => { - // operate from within the context of the symlink's target - try { - var lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); - path = lookup.path; - } catch (e) { - } - var ret = { - isRoot: false, exists: false, error: 0, name: null, path: null, object: null, - parentExists: false, parentPath: null, parentObject: null - }; - try { - var lookup = FS.lookupPath(path, { parent: true }); - ret.parentExists = true; - ret.parentPath = lookup.path; - ret.parentObject = lookup.node; - ret.name = PATH.basename(path); - lookup = FS.lookupPath(path, { follow: !dontResolveLastLink }); - ret.exists = true; - ret.path = lookup.path; - ret.object = lookup.node; - ret.name = lookup.node.name; - ret.isRoot = lookup.path === '/'; - } catch (e) { - ret.error = e.errno; - }; - return ret; - },createPath:(parent, path, canRead, canWrite) => { - parent = typeof parent == 'string' ? parent : FS.getPath(parent); - var parts = path.split('/').reverse(); - while (parts.length) { - var part = parts.pop(); - if (!part) continue; - var current = PATH.join2(parent, part); - try { - FS.mkdir(current); - } catch (e) { - // ignore EEXIST - } - parent = current; - } - return current; - },createFile:(parent, name, properties, canRead, canWrite) => { - var path = PATH.join2(typeof parent == 'string' ? parent : FS.getPath(parent), name); - var mode = FS_getMode(canRead, canWrite); - return FS.create(path, mode); - },createDataFile:(parent, name, data, canRead, canWrite, canOwn) => { - var path = name; - if (parent) { - parent = typeof parent == 'string' ? parent : FS.getPath(parent); - path = name ? PATH.join2(parent, name) : parent; - } - var mode = FS_getMode(canRead, canWrite); - var node = FS.create(path, mode); - if (data) { - if (typeof data == 'string') { - var arr = new Array(data.length); - for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i); - data = arr; - } - // make sure we can write to the file - FS.chmod(node, mode | 146); - var stream = FS.open(node, 577); - FS.write(stream, data, 0, data.length, 0, canOwn); - FS.close(stream); - FS.chmod(node, mode); - } - return node; - },createDevice:(parent, name, input, output) => { - var path = PATH.join2(typeof parent == 'string' ? parent : FS.getPath(parent), name); - var mode = FS_getMode(!!input, !!output); - if (!FS.createDevice.major) FS.createDevice.major = 64; - var dev = FS.makedev(FS.createDevice.major++, 0); - // Create a fake device that a set of stream ops to emulate - // the old behavior. - FS.registerDevice(dev, { - open: (stream) => { - stream.seekable = false; - }, - close: (stream) => { - // flush any pending line data - if (output && output.buffer && output.buffer.length) { - output(10); - } - }, - read: (stream, buffer, offset, length, pos /* ignored */) => { - var bytesRead = 0; - for (var i = 0; i < length; i++) { - var result; - try { - result = input(); - } catch (e) { - throw new FS.ErrnoError(29); - } - if (result === undefined && bytesRead === 0) { - throw new FS.ErrnoError(6); - } - if (result === null || result === undefined) break; - bytesRead++; - buffer[offset+i] = result; - } - if (bytesRead) { - stream.node.timestamp = Date.now(); - } - return bytesRead; - }, - write: (stream, buffer, offset, length, pos) => { - for (var i = 0; i < length; i++) { - try { - output(buffer[offset+i]); - } catch (e) { - throw new FS.ErrnoError(29); - } - } - if (length) { - stream.node.timestamp = Date.now(); - } - return i; - } - }); - return FS.mkdev(path, mode, dev); - },forceLoadFile:(obj) => { - if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; - if (typeof XMLHttpRequest != 'undefined') { - throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); - } else if (read_) { - // Command-line. - try { - // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as - // read() will try to parse UTF8. - obj.contents = intArrayFromString(read_(obj.url), true); - obj.usedBytes = obj.contents.length; - } catch (e) { - throw new FS.ErrnoError(29); - } - } else { - throw new Error('Cannot load without read() or XMLHttpRequest.'); - } - },createLazyFile:(parent, name, url, canRead, canWrite) => { - // Lazy chunked Uint8Array (implements get and length from Uint8Array). Actual getting is abstracted away for eventual reuse. - /** @constructor */ - function LazyUint8Array() { - this.lengthKnown = false; - this.chunks = []; // Loaded chunks. Index is the chunk number - } - LazyUint8Array.prototype.get = /** @this{Object} */ function LazyUint8Array_get(idx) { - if (idx > this.length-1 || idx < 0) { - return undefined; - } - var chunkOffset = idx % this.chunkSize; - var chunkNum = (idx / this.chunkSize)|0; - return this.getter(chunkNum)[chunkOffset]; - }; - LazyUint8Array.prototype.setDataGetter = function LazyUint8Array_setDataGetter(getter) { - this.getter = getter; - }; - LazyUint8Array.prototype.cacheLength = function LazyUint8Array_cacheLength() { - // Find length - var xhr = new XMLHttpRequest(); - xhr.open('HEAD', url, false); - xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); - var datalength = Number(xhr.getResponseHeader("Content-length")); - var header; - var hasByteServing = (header = xhr.getResponseHeader("Accept-Ranges")) && header === "bytes"; - var usesGzip = (header = xhr.getResponseHeader("Content-Encoding")) && header === "gzip"; - - var chunkSize = 1024*1024; // Chunk size in bytes - - if (!hasByteServing) chunkSize = datalength; - - // Function to get a range from the remote URL. - var doXHR = (from, to) => { - if (from > to) throw new Error("invalid range (" + from + ", " + to + ") or no bytes requested!"); - if (to > datalength-1) throw new Error("only " + datalength + " bytes available! programmer error!"); - - // TODO: Use mozResponseArrayBuffer, responseStream, etc. if available. - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, false); - if (datalength !== chunkSize) xhr.setRequestHeader("Range", "bytes=" + from + "-" + to); - - // Some hints to the browser that we want binary data. - xhr.responseType = 'arraybuffer'; - if (xhr.overrideMimeType) { - xhr.overrideMimeType('text/plain; charset=x-user-defined'); - } - - xhr.send(null); - if (!(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)) throw new Error("Couldn't load " + url + ". Status: " + xhr.status); - if (xhr.response !== undefined) { - return new Uint8Array(/** @type{Array} */(xhr.response || [])); - } - return intArrayFromString(xhr.responseText || '', true); - }; - var lazyArray = this; - lazyArray.setDataGetter((chunkNum) => { - var start = chunkNum * chunkSize; - var end = (chunkNum+1) * chunkSize - 1; // including this byte - end = Math.min(end, datalength-1); // if datalength-1 is selected, this is the last block - if (typeof lazyArray.chunks[chunkNum] == 'undefined') { - lazyArray.chunks[chunkNum] = doXHR(start, end); - } - if (typeof lazyArray.chunks[chunkNum] == 'undefined') throw new Error('doXHR failed!'); - return lazyArray.chunks[chunkNum]; - }); - - if (usesGzip || !datalength) { - // if the server uses gzip or doesn't supply the length, we have to download the whole file to get the (uncompressed) length - chunkSize = datalength = 1; // this will force getter(0)/doXHR do download the whole file - datalength = this.getter(0).length; - chunkSize = datalength; - out("LazyFiles on gzip forces download of the whole file when length is accessed"); - } - - this._length = datalength; - this._chunkSize = chunkSize; - this.lengthKnown = true; - }; - if (typeof XMLHttpRequest != 'undefined') { - if (!ENVIRONMENT_IS_WORKER) throw 'Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc'; - var lazyArray = new LazyUint8Array(); - Object.defineProperties(lazyArray, { - length: { - get: /** @this{Object} */ function() { - if (!this.lengthKnown) { - this.cacheLength(); - } - return this._length; - } - }, - chunkSize: { - get: /** @this{Object} */ function() { - if (!this.lengthKnown) { - this.cacheLength(); - } - return this._chunkSize; - } - } - }); - - var properties = { isDevice: false, contents: lazyArray }; - } else { - var properties = { isDevice: false, url: url }; - } - - var node = FS.createFile(parent, name, properties, canRead, canWrite); - // This is a total hack, but I want to get this lazy file code out of the - // core of MEMFS. If we want to keep this lazy file concept I feel it should - // be its own thin LAZYFS proxying calls to MEMFS. - if (properties.contents) { - node.contents = properties.contents; - } else if (properties.url) { - node.contents = null; - node.url = properties.url; - } - // Add a function that defers querying the file size until it is asked the first time. - Object.defineProperties(node, { - usedBytes: { - get: /** @this {FSNode} */ function() { return this.contents.length; } - } - }); - // override each stream op with one that tries to force load the lazy file first - var stream_ops = {}; - var keys = Object.keys(node.stream_ops); - keys.forEach((key) => { - var fn = node.stream_ops[key]; - stream_ops[key] = function forceLoadLazyFile() { - FS.forceLoadFile(node); - return fn.apply(null, arguments); - }; - }); - function writeChunks(stream, buffer, offset, length, position) { - var contents = stream.node.contents; - if (position >= contents.length) - return 0; - var size = Math.min(contents.length - position, length); - assert(size >= 0); - if (contents.slice) { // normal array - for (var i = 0; i < size; i++) { - buffer[offset + i] = contents[position + i]; - } - } else { - for (var i = 0; i < size; i++) { // LazyUint8Array from sync binary XHR - buffer[offset + i] = contents.get(position + i); - } - } - return size; - } - // use a custom read function - stream_ops.read = (stream, buffer, offset, length, position) => { - FS.forceLoadFile(node); - return writeChunks(stream, buffer, offset, length, position) - }; - // use a custom mmap function - stream_ops.mmap = (stream, length, position, prot, flags) => { - FS.forceLoadFile(node); - var ptr = mmapAlloc(length); - if (!ptr) { - throw new FS.ErrnoError(48); - } - writeChunks(stream, HEAP8, ptr, length, position); - return { ptr: ptr, allocated: true }; - }; - node.stream_ops = stream_ops; - return node; - },absolutePath:() => { - abort('FS.absolutePath has been removed; use PATH_FS.resolve instead'); - },createFolder:() => { - abort('FS.createFolder has been removed; use FS.mkdir instead'); - },createLink:() => { - abort('FS.createLink has been removed; use FS.symlink instead'); - },joinPath:() => { - abort('FS.joinPath has been removed; use PATH.join instead'); - },mmapAlloc:() => { - abort('FS.mmapAlloc has been replaced by the top level function mmapAlloc'); - },standardizePath:() => { - abort('FS.standardizePath has been removed; use PATH.normalize instead'); - }}; - - var SYSCALLS = {DEFAULT_POLLMASK:5,calculateAt:function(dirfd, path, allowEmpty) { - if (PATH.isAbs(path)) { - return path; - } - // relative path - var dir; - if (dirfd === -100) { - dir = FS.cwd(); - } else { - var dirstream = SYSCALLS.getStreamFromFD(dirfd); - dir = dirstream.path; - } - if (path.length == 0) { - if (!allowEmpty) { - throw new FS.ErrnoError(44);; - } - return dir; - } - return PATH.join2(dir, path); - },doStat:function(func, path, buf) { - try { - var stat = func(path); - } catch (e) { - if (e && e.node && PATH.normalize(path) !== PATH.normalize(FS.getPath(e.node))) { - // an error occurred while trying to look up the path; we should just report ENOTDIR - return -54; - } - throw e; - } - HEAP32[((buf)>>2)] = stat.dev; - HEAP32[(((buf)+(8))>>2)] = stat.ino; - HEAP32[(((buf)+(12))>>2)] = stat.mode; - HEAPU32[(((buf)+(16))>>2)] = stat.nlink; - HEAP32[(((buf)+(20))>>2)] = stat.uid; - HEAP32[(((buf)+(24))>>2)] = stat.gid; - HEAP32[(((buf)+(28))>>2)] = stat.rdev; - (tempI64 = [stat.size>>>0,(tempDouble=stat.size,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((buf)+(40))>>2)] = tempI64[0],HEAP32[(((buf)+(44))>>2)] = tempI64[1]); - HEAP32[(((buf)+(48))>>2)] = 4096; - HEAP32[(((buf)+(52))>>2)] = stat.blocks; - var atime = stat.atime.getTime(); - var mtime = stat.mtime.getTime(); - var ctime = stat.ctime.getTime(); - (tempI64 = [Math.floor(atime / 1000)>>>0,(tempDouble=Math.floor(atime / 1000),(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((buf)+(56))>>2)] = tempI64[0],HEAP32[(((buf)+(60))>>2)] = tempI64[1]); - HEAPU32[(((buf)+(64))>>2)] = (atime % 1000) * 1000; - (tempI64 = [Math.floor(mtime / 1000)>>>0,(tempDouble=Math.floor(mtime / 1000),(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((buf)+(72))>>2)] = tempI64[0],HEAP32[(((buf)+(76))>>2)] = tempI64[1]); - HEAPU32[(((buf)+(80))>>2)] = (mtime % 1000) * 1000; - (tempI64 = [Math.floor(ctime / 1000)>>>0,(tempDouble=Math.floor(ctime / 1000),(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((buf)+(88))>>2)] = tempI64[0],HEAP32[(((buf)+(92))>>2)] = tempI64[1]); - HEAPU32[(((buf)+(96))>>2)] = (ctime % 1000) * 1000; - (tempI64 = [stat.ino>>>0,(tempDouble=stat.ino,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? (+(Math.floor((tempDouble)/4294967296.0)))>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)], HEAP32[(((buf)+(104))>>2)] = tempI64[0],HEAP32[(((buf)+(108))>>2)] = tempI64[1]); - return 0; - },doMsync:function(addr, stream, len, flags, offset) { - if (!FS.isFile(stream.node.mode)) { - throw new FS.ErrnoError(43); - } - if (flags & 2) { - // MAP_PRIVATE calls need not to be synced back to underlying fs - return 0; - } - var buffer = HEAPU8.slice(addr, addr + len); - FS.msync(stream, buffer, offset, len, flags); - },varargs:undefined,get:function() { - assert(SYSCALLS.varargs != undefined); - SYSCALLS.varargs += 4; - var ret = HEAP32[(((SYSCALLS.varargs)-(4))>>2)]; - return ret; - },getStr:function(ptr) { - var ret = UTF8ToString(ptr); - return ret; - },getStreamFromFD:function(fd) { - var stream = FS.getStream(fd); - if (!stream) throw new FS.ErrnoError(8); - return stream; - }}; - function _proc_exit(code) { - EXITSTATUS = code; - if (!keepRuntimeAlive()) { - if (Module['onExit']) Module['onExit'](code); - ABORT = true; - } - quit_(code, new ExitStatus(code)); - } - /** @suppress {duplicate } */ - /** @param {boolean|number=} implicit */ - function exitJS(status, implicit) { - EXITSTATUS = status; - - checkUnflushedContent(); - - // if exit() was called explicitly, warn the user if the runtime isn't actually being shut down - if (keepRuntimeAlive() && !implicit) { - var msg = `program exited (with status: ${status}), but keepRuntimeAlive() is set (counter=${runtimeKeepaliveCounter}) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)`; - readyPromiseReject(msg); - err(msg); - } - - _proc_exit(status); - } - var _exit = exitJS; - - function maybeExit() { - if (!keepRuntimeAlive()) { - try { - _exit(EXITSTATUS); - } catch (e) { - handleException(e); - } - } - } - function callUserCallback(func) { - if (ABORT) { - err('user callback triggered after runtime exited or application aborted. Ignoring.'); - return; - } - try { - func(); - maybeExit(); - } catch (e) { - handleException(e); - } - } - - /** @param {number=} timeout */ - function safeSetTimeout(func, timeout) { - - return setTimeout(() => { - - callUserCallback(func); - }, timeout); - } - - - - - var Browser = {mainLoop:{running:false,scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:function() { - Browser.mainLoop.scheduler = null; - // Incrementing this signals the previous main loop that it's now become old, and it must return. - Browser.mainLoop.currentlyRunningMainloop++; - },resume:function() { - Browser.mainLoop.currentlyRunningMainloop++; - var timingMode = Browser.mainLoop.timingMode; - var timingValue = Browser.mainLoop.timingValue; - var func = Browser.mainLoop.func; - Browser.mainLoop.func = null; - // do not set timing and call scheduler, we will do it on the next lines - setMainLoop(func, 0, false, Browser.mainLoop.arg, true); - _emscripten_set_main_loop_timing(timingMode, timingValue); - Browser.mainLoop.scheduler(); - },updateStatus:function() { - if (Module['setStatus']) { - var message = Module['statusMessage'] || 'Please wait...'; - var remaining = Browser.mainLoop.remainingBlockers; - var expected = Browser.mainLoop.expectedBlockers; - if (remaining) { - if (remaining < expected) { - Module['setStatus'](message + ' (' + (expected - remaining) + '/' + expected + ')'); - } else { - Module['setStatus'](message); - } - } else { - Module['setStatus'](''); - } - } - },runIter:function(func) { - if (ABORT) return; - if (Module['preMainLoop']) { - var preRet = Module['preMainLoop'](); - if (preRet === false) { - return; // |return false| skips a frame - } - } - callUserCallback(func); - if (Module['postMainLoop']) Module['postMainLoop'](); - }},isFullscreen:false,pointerLock:false,moduleContextCreatedCallbacks:[],workers:[],init:function() { - if (Browser.initted) return; - Browser.initted = true; - - // Support for plugins that can process preloaded files. You can add more of these to - // your app by creating and appending to preloadPlugins. - // - // Each plugin is asked if it can handle a file based on the file's name. If it can, - // it is given the file's raw data. When it is done, it calls a callback with the file's - // (possibly modified) data. For example, a plugin might decompress a file, or it - // might create some side data structure for use later (like an Image element, etc.). - - var imagePlugin = {}; - imagePlugin['canHandle'] = function imagePlugin_canHandle(name) { - return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/i.test(name); - }; - imagePlugin['handle'] = function imagePlugin_handle(byteArray, name, onload, onerror) { - var b = new Blob([byteArray], { type: Browser.getMimetype(name) }); - if (b.size !== byteArray.length) { // Safari bug #118630 - // Safari's Blob can only take an ArrayBuffer - b = new Blob([(new Uint8Array(byteArray)).buffer], { type: Browser.getMimetype(name) }); - } - var url = URL.createObjectURL(b); - assert(typeof url == 'string', 'createObjectURL must return a url as a string'); - var img = new Image(); - img.onload = () => { - assert(img.complete, 'Image ' + name + ' could not be decoded'); - var canvas = /** @type {!HTMLCanvasElement} */ (document.createElement('canvas')); - canvas.width = img.width; - canvas.height = img.height; - var ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0); - preloadedImages[name] = canvas; - URL.revokeObjectURL(url); - if (onload) onload(byteArray); - }; - img.onerror = (event) => { - out('Image ' + url + ' could not be decoded'); - if (onerror) onerror(); - }; - img.src = url; - }; - preloadPlugins.push(imagePlugin); - - var audioPlugin = {}; - audioPlugin['canHandle'] = function audioPlugin_canHandle(name) { - return !Module.noAudioDecoding && name.substr(-4) in { '.ogg': 1, '.wav': 1, '.mp3': 1 }; - }; - audioPlugin['handle'] = function audioPlugin_handle(byteArray, name, onload, onerror) { - var done = false; - function finish(audio) { - if (done) return; - done = true; - preloadedAudios[name] = audio; - if (onload) onload(byteArray); - } - function fail() { - if (done) return; - done = true; - preloadedAudios[name] = new Audio(); // empty shim - if (onerror) onerror(); - } - var b = new Blob([byteArray], { type: Browser.getMimetype(name) }); - var url = URL.createObjectURL(b); // XXX we never revoke this! - assert(typeof url == 'string', 'createObjectURL must return a url as a string'); - var audio = new Audio(); - audio.addEventListener('canplaythrough', () => finish(audio), false); // use addEventListener due to chromium bug 124926 - audio.onerror = function audio_onerror(event) { - if (done) return; - err('warning: browser could not fully decode audio ' + name + ', trying slower base64 approach'); - function encode64(data) { - var BASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - var PAD = '='; - var ret = ''; - var leftchar = 0; - var leftbits = 0; - for (var i = 0; i < data.length; i++) { - leftchar = (leftchar << 8) | data[i]; - leftbits += 8; - while (leftbits >= 6) { - var curr = (leftchar >> (leftbits-6)) & 0x3f; - leftbits -= 6; - ret += BASE[curr]; - } - } - if (leftbits == 2) { - ret += BASE[(leftchar&3) << 4]; - ret += PAD + PAD; - } else if (leftbits == 4) { - ret += BASE[(leftchar&0xf) << 2]; - ret += PAD; - } - return ret; - } - audio.src = 'data:audio/x-' + name.substr(-3) + ';base64,' + encode64(byteArray); - finish(audio); // we don't wait for confirmation this worked - but it's worth trying - }; - audio.src = url; - // workaround for chrome bug 124926 - we do not always get oncanplaythrough or onerror - safeSetTimeout(() => { - finish(audio); // try to use it even though it is not necessarily ready to play - }, 10000); - }; - preloadPlugins.push(audioPlugin); - - // Canvas event setup - - function pointerLockChange() { - Browser.pointerLock = document['pointerLockElement'] === Module['canvas'] || - document['mozPointerLockElement'] === Module['canvas'] || - document['webkitPointerLockElement'] === Module['canvas'] || - document['msPointerLockElement'] === Module['canvas']; - } - var canvas = Module['canvas']; - if (canvas) { - // forced aspect ratio can be enabled by defining 'forcedAspectRatio' on Module - // Module['forcedAspectRatio'] = 4 / 3; - - canvas.requestPointerLock = canvas['requestPointerLock'] || - canvas['mozRequestPointerLock'] || - canvas['webkitRequestPointerLock'] || - canvas['msRequestPointerLock'] || - (() => {}); - canvas.exitPointerLock = document['exitPointerLock'] || - document['mozExitPointerLock'] || - document['webkitExitPointerLock'] || - document['msExitPointerLock'] || - (() => {}); // no-op if function does not exist - canvas.exitPointerLock = canvas.exitPointerLock.bind(document); - - document.addEventListener('pointerlockchange', pointerLockChange, false); - document.addEventListener('mozpointerlockchange', pointerLockChange, false); - document.addEventListener('webkitpointerlockchange', pointerLockChange, false); - document.addEventListener('mspointerlockchange', pointerLockChange, false); - - if (Module['elementPointerLock']) { - canvas.addEventListener("click", (ev) => { - if (!Browser.pointerLock && Module['canvas'].requestPointerLock) { - Module['canvas'].requestPointerLock(); - ev.preventDefault(); - } - }, false); - } - } - },createContext:function(/** @type {HTMLCanvasElement} */ canvas, useWebGL, setInModule, webGLContextAttributes) { - if (useWebGL && Module.ctx && canvas == Module.canvas) return Module.ctx; // no need to recreate GL context if it's already been created for this canvas. - - var ctx; - var contextHandle; - if (useWebGL) { - // For GLES2/desktop GL compatibility, adjust a few defaults to be different to WebGL defaults, so that they align better with the desktop defaults. - var contextAttributes = { - antialias: false, - alpha: false, - majorVersion: (typeof WebGL2RenderingContext != 'undefined') ? 2 : 1, - }; - - if (webGLContextAttributes) { - for (var attribute in webGLContextAttributes) { - contextAttributes[attribute] = webGLContextAttributes[attribute]; - } - } - - // This check of existence of GL is here to satisfy Closure compiler, which yells if variable GL is referenced below but GL object is not - // actually compiled in because application is not doing any GL operations. TODO: Ideally if GL is not being used, this function - // Browser.createContext() should not even be emitted. - if (typeof GL != 'undefined') { - contextHandle = GL.createContext(canvas, contextAttributes); - if (contextHandle) { - ctx = GL.getContext(contextHandle).GLctx; - } - } - } else { - ctx = canvas.getContext('2d'); - } - - if (!ctx) return null; - - if (setInModule) { - if (!useWebGL) assert(typeof GLctx == 'undefined', 'cannot set in module if GLctx is used, but we are a non-GL context that would replace it'); - - Module.ctx = ctx; - if (useWebGL) GL.makeContextCurrent(contextHandle); - Module.useWebGL = useWebGL; - Browser.moduleContextCreatedCallbacks.forEach((callback) => callback()); - Browser.init(); - } - return ctx; - },destroyContext:function(canvas, useWebGL, setInModule) {},fullscreenHandlersInstalled:false,lockPointer:undefined,resizeCanvas:undefined,requestFullscreen:function(lockPointer, resizeCanvas) { - Browser.lockPointer = lockPointer; - Browser.resizeCanvas = resizeCanvas; - if (typeof Browser.lockPointer == 'undefined') Browser.lockPointer = true; - if (typeof Browser.resizeCanvas == 'undefined') Browser.resizeCanvas = false; - - var canvas = Module['canvas']; - function fullscreenChange() { - Browser.isFullscreen = false; - var canvasContainer = canvas.parentNode; - if ((document['fullscreenElement'] || document['mozFullScreenElement'] || - document['msFullscreenElement'] || document['webkitFullscreenElement'] || - document['webkitCurrentFullScreenElement']) === canvasContainer) { - canvas.exitFullscreen = Browser.exitFullscreen; - if (Browser.lockPointer) canvas.requestPointerLock(); - Browser.isFullscreen = true; - if (Browser.resizeCanvas) { - Browser.setFullscreenCanvasSize(); - } else { - Browser.updateCanvasDimensions(canvas); - } - } else { - // remove the full screen specific parent of the canvas again to restore the HTML structure from before going full screen - canvasContainer.parentNode.insertBefore(canvas, canvasContainer); - canvasContainer.parentNode.removeChild(canvasContainer); - - if (Browser.resizeCanvas) { - Browser.setWindowedCanvasSize(); - } else { - Browser.updateCanvasDimensions(canvas); - } - } - if (Module['onFullScreen']) Module['onFullScreen'](Browser.isFullscreen); - if (Module['onFullscreen']) Module['onFullscreen'](Browser.isFullscreen); - } - - if (!Browser.fullscreenHandlersInstalled) { - Browser.fullscreenHandlersInstalled = true; - document.addEventListener('fullscreenchange', fullscreenChange, false); - document.addEventListener('mozfullscreenchange', fullscreenChange, false); - document.addEventListener('webkitfullscreenchange', fullscreenChange, false); - document.addEventListener('MSFullscreenChange', fullscreenChange, false); - } - - // create a new parent to ensure the canvas has no siblings. this allows browsers to optimize full screen performance when its parent is the full screen root - var canvasContainer = document.createElement("div"); - canvas.parentNode.insertBefore(canvasContainer, canvas); - canvasContainer.appendChild(canvas); - - // use parent of canvas as full screen root to allow aspect ratio correction (Firefox stretches the root to screen size) - canvasContainer.requestFullscreen = canvasContainer['requestFullscreen'] || - canvasContainer['mozRequestFullScreen'] || - canvasContainer['msRequestFullscreen'] || - (canvasContainer['webkitRequestFullscreen'] ? () => canvasContainer['webkitRequestFullscreen'](Element['ALLOW_KEYBOARD_INPUT']) : null) || - (canvasContainer['webkitRequestFullScreen'] ? () => canvasContainer['webkitRequestFullScreen'](Element['ALLOW_KEYBOARD_INPUT']) : null); - - canvasContainer.requestFullscreen(); - },requestFullScreen:function() { - abort('Module.requestFullScreen has been replaced by Module.requestFullscreen (without a capital S)'); - },exitFullscreen:function() { - // This is workaround for chrome. Trying to exit from fullscreen - // not in fullscreen state will cause "TypeError: Document not active" - // in chrome. See https://github.com/emscripten-core/emscripten/pull/8236 - if (!Browser.isFullscreen) { - return false; - } - - var CFS = document['exitFullscreen'] || - document['cancelFullScreen'] || - document['mozCancelFullScreen'] || - document['msExitFullscreen'] || - document['webkitCancelFullScreen'] || - (() => {}); - CFS.apply(document, []); - return true; - },nextRAF:0,fakeRequestAnimationFrame:function(func) { - // try to keep 60fps between calls to here - var now = Date.now(); - if (Browser.nextRAF === 0) { - Browser.nextRAF = now + 1000/60; - } else { - while (now + 2 >= Browser.nextRAF) { // fudge a little, to avoid timer jitter causing us to do lots of delay:0 - Browser.nextRAF += 1000/60; - } - } - var delay = Math.max(Browser.nextRAF - now, 0); - setTimeout(func, delay); - },requestAnimationFrame:function(func) { - if (typeof requestAnimationFrame == 'function') { - requestAnimationFrame(func); - return; - } - var RAF = Browser.fakeRequestAnimationFrame; - RAF(func); - },safeSetTimeout:function(func, timeout) { - // Legacy function, this is used by the SDL2 port so we need to keep it - // around at least until that is updated. - // See https://github.com/libsdl-org/SDL/pull/6304 - return safeSetTimeout(func, timeout); - },safeRequestAnimationFrame:function(func) { - - return Browser.requestAnimationFrame(() => { - - callUserCallback(func); - }); - },getMimetype:function(name) { - return { - 'jpg': 'image/jpeg', - 'jpeg': 'image/jpeg', - 'png': 'image/png', - 'bmp': 'image/bmp', - 'ogg': 'audio/ogg', - 'wav': 'audio/wav', - 'mp3': 'audio/mpeg' - }[name.substr(name.lastIndexOf('.')+1)]; - },getUserMedia:function(func) { - if (!window.getUserMedia) { - window.getUserMedia = navigator['getUserMedia'] || - navigator['mozGetUserMedia']; - } - window.getUserMedia(func); - },getMovementX:function(event) { - return event['movementX'] || - event['mozMovementX'] || - event['webkitMovementX'] || - 0; - },getMovementY:function(event) { - return event['movementY'] || - event['mozMovementY'] || - event['webkitMovementY'] || - 0; - },getMouseWheelDelta:function(event) { - var delta = 0; - switch (event.type) { - case 'DOMMouseScroll': - // 3 lines make up a step - delta = event.detail / 3; - break; - case 'mousewheel': - // 120 units make up a step - delta = event.wheelDelta / 120; - break; - case 'wheel': - delta = event.deltaY - switch (event.deltaMode) { - case 0: - // DOM_DELTA_PIXEL: 100 pixels make up a step - delta /= 100; - break; - case 1: - // DOM_DELTA_LINE: 3 lines make up a step - delta /= 3; - break; - case 2: - // DOM_DELTA_PAGE: A page makes up 80 steps - delta *= 80; - break; - default: - throw 'unrecognized mouse wheel delta mode: ' + event.deltaMode; - } - break; - default: - throw 'unrecognized mouse wheel event: ' + event.type; - } - return delta; - },mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:function(event) { // event should be mousemove, mousedown or mouseup - if (Browser.pointerLock) { - // When the pointer is locked, calculate the coordinates - // based on the movement of the mouse. - // Workaround for Firefox bug 764498 - if (event.type != 'mousemove' && - ('mozMovementX' in event)) { - Browser.mouseMovementX = Browser.mouseMovementY = 0; - } else { - Browser.mouseMovementX = Browser.getMovementX(event); - Browser.mouseMovementY = Browser.getMovementY(event); - } - - // check if SDL is available - if (typeof SDL != "undefined") { - Browser.mouseX = SDL.mouseX + Browser.mouseMovementX; - Browser.mouseY = SDL.mouseY + Browser.mouseMovementY; - } else { - // just add the mouse delta to the current absolut mouse position - // FIXME: ideally this should be clamped against the canvas size and zero - Browser.mouseX += Browser.mouseMovementX; - Browser.mouseY += Browser.mouseMovementY; - } - } else { - // Otherwise, calculate the movement based on the changes - // in the coordinates. - var rect = Module["canvas"].getBoundingClientRect(); - var cw = Module["canvas"].width; - var ch = Module["canvas"].height; - - // Neither .scrollX or .pageXOffset are defined in a spec, but - // we prefer .scrollX because it is currently in a spec draft. - // (see: http://www.w3.org/TR/2013/WD-cssom-view-20131217/) - var scrollX = ((typeof window.scrollX != 'undefined') ? window.scrollX : window.pageXOffset); - var scrollY = ((typeof window.scrollY != 'undefined') ? window.scrollY : window.pageYOffset); - // If this assert lands, it's likely because the browser doesn't support scrollX or pageXOffset - // and we have no viable fallback. - assert((typeof scrollX != 'undefined') && (typeof scrollY != 'undefined'), 'Unable to retrieve scroll position, mouse positions likely broken.'); - - if (event.type === 'touchstart' || event.type === 'touchend' || event.type === 'touchmove') { - var touch = event.touch; - if (touch === undefined) { - return; // the "touch" property is only defined in SDL - - } - var adjustedX = touch.pageX - (scrollX + rect.left); - var adjustedY = touch.pageY - (scrollY + rect.top); - - adjustedX = adjustedX * (cw / rect.width); - adjustedY = adjustedY * (ch / rect.height); - - var coords = { x: adjustedX, y: adjustedY }; - - if (event.type === 'touchstart') { - Browser.lastTouches[touch.identifier] = coords; - Browser.touches[touch.identifier] = coords; - } else if (event.type === 'touchend' || event.type === 'touchmove') { - var last = Browser.touches[touch.identifier]; - if (!last) last = coords; - Browser.lastTouches[touch.identifier] = last; - Browser.touches[touch.identifier] = coords; - } - return; - } - - var x = event.pageX - (scrollX + rect.left); - var y = event.pageY - (scrollY + rect.top); - - // the canvas might be CSS-scaled compared to its backbuffer; - // SDL-using content will want mouse coordinates in terms - // of backbuffer units. - x = x * (cw / rect.width); - y = y * (ch / rect.height); - - Browser.mouseMovementX = x - Browser.mouseX; - Browser.mouseMovementY = y - Browser.mouseY; - Browser.mouseX = x; - Browser.mouseY = y; - } - },resizeListeners:[],updateResizeListeners:function() { - var canvas = Module['canvas']; - Browser.resizeListeners.forEach((listener) => listener(canvas.width, canvas.height)); - },setCanvasSize:function(width, height, noUpdates) { - var canvas = Module['canvas']; - Browser.updateCanvasDimensions(canvas, width, height); - if (!noUpdates) Browser.updateResizeListeners(); - },windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize:function() { - // check if SDL is available - if (typeof SDL != "undefined") { - var flags = HEAPU32[((SDL.screen)>>2)]; - flags = flags | 0x00800000; // set SDL_FULLSCREEN flag - HEAP32[((SDL.screen)>>2)] = flags; - } - Browser.updateCanvasDimensions(Module['canvas']); - Browser.updateResizeListeners(); - },setWindowedCanvasSize:function() { - // check if SDL is available - if (typeof SDL != "undefined") { - var flags = HEAPU32[((SDL.screen)>>2)]; - flags = flags & ~0x00800000; // clear SDL_FULLSCREEN flag - HEAP32[((SDL.screen)>>2)] = flags; - } - Browser.updateCanvasDimensions(Module['canvas']); - Browser.updateResizeListeners(); - },updateCanvasDimensions:function(canvas, wNative, hNative) { - if (wNative && hNative) { - canvas.widthNative = wNative; - canvas.heightNative = hNative; - } else { - wNative = canvas.widthNative; - hNative = canvas.heightNative; - } - var w = wNative; - var h = hNative; - if (Module['forcedAspectRatio'] && Module['forcedAspectRatio'] > 0) { - if (w/h < Module['forcedAspectRatio']) { - w = Math.round(h * Module['forcedAspectRatio']); - } else { - h = Math.round(w / Module['forcedAspectRatio']); - } - } - if (((document['fullscreenElement'] || document['mozFullScreenElement'] || - document['msFullscreenElement'] || document['webkitFullscreenElement'] || - document['webkitCurrentFullScreenElement']) === canvas.parentNode) && (typeof screen != 'undefined')) { - var factor = Math.min(screen.width / w, screen.height / h); - w = Math.round(w * factor); - h = Math.round(h * factor); - } - if (Browser.resizeCanvas) { - if (canvas.width != w) canvas.width = w; - if (canvas.height != h) canvas.height = h; - if (typeof canvas.style != 'undefined') { - canvas.style.removeProperty( "width"); - canvas.style.removeProperty("height"); - } - } else { - if (canvas.width != wNative) canvas.width = wNative; - if (canvas.height != hNative) canvas.height = hNative; - if (typeof canvas.style != 'undefined') { - if (w != wNative || h != hNative) { - canvas.style.setProperty( "width", w + "px", "important"); - canvas.style.setProperty("height", h + "px", "important"); - } else { - canvas.style.removeProperty( "width"); - canvas.style.removeProperty("height"); - } - } - } - }}; - function _emscripten_set_main_loop_timing(mode, value) { - Browser.mainLoop.timingMode = mode; - Browser.mainLoop.timingValue = value; - - if (!Browser.mainLoop.func) { - err('emscripten_set_main_loop_timing: Cannot set timing mode for main loop since a main loop does not exist! Call emscripten_set_main_loop first to set one up.'); - return 1; // Return non-zero on failure, can't set timing mode when there is no main loop. - } - - if (!Browser.mainLoop.running) { - - Browser.mainLoop.running = true; - } - if (mode == 0 /*EM_TIMING_SETTIMEOUT*/) { - Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler_setTimeout() { - var timeUntilNextTick = Math.max(0, Browser.mainLoop.tickStartTime + value - _emscripten_get_now())|0; - setTimeout(Browser.mainLoop.runner, timeUntilNextTick); // doing this each time means that on exception, we stop - }; - Browser.mainLoop.method = 'timeout'; - } else if (mode == 1 /*EM_TIMING_RAF*/) { - Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler_rAF() { - Browser.requestAnimationFrame(Browser.mainLoop.runner); - }; - Browser.mainLoop.method = 'rAF'; - } else if (mode == 2 /*EM_TIMING_SETIMMEDIATE*/) { - if (typeof setImmediate == 'undefined') { - // Emulate setImmediate. (note: not a complete polyfill, we don't emulate clearImmediate() to keep code size to minimum, since not needed) - var setImmediates = []; - var emscriptenMainLoopMessageId = 'setimmediate'; - /** @param {Event} event */ - var Browser_setImmediate_messageHandler = (event) => { - // When called in current thread or Worker, the main loop ID is structured slightly different to accommodate for --proxy-to-worker runtime listening to Worker events, - // so check for both cases. - if (event.data === emscriptenMainLoopMessageId || event.data.target === emscriptenMainLoopMessageId) { - event.stopPropagation(); - setImmediates.shift()(); - } - }; - addEventListener("message", Browser_setImmediate_messageHandler, true); - setImmediate = /** @type{function(function(): ?, ...?): number} */(function Browser_emulated_setImmediate(func) { - setImmediates.push(func); - if (ENVIRONMENT_IS_WORKER) { - if (Module['setImmediates'] === undefined) Module['setImmediates'] = []; - Module['setImmediates'].push(func); - postMessage({target: emscriptenMainLoopMessageId}); // In --proxy-to-worker, route the message via proxyClient.js - } else postMessage(emscriptenMainLoopMessageId, "*"); // On the main thread, can just send the message to itself. - }) - } - Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler_setImmediate() { - setImmediate(Browser.mainLoop.runner); - }; - Browser.mainLoop.method = 'immediate'; - } - return 0; - } - - var _emscripten_get_now;_emscripten_get_now = () => performance.now(); - ; - - - /** - * @param {number=} arg - * @param {boolean=} noSetTiming - */ - function setMainLoop(browserIterationFunc, fps, simulateInfiniteLoop, arg, noSetTiming) { - assert(!Browser.mainLoop.func, 'emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.'); - - Browser.mainLoop.func = browserIterationFunc; - Browser.mainLoop.arg = arg; - - var thisMainLoopId = Browser.mainLoop.currentlyRunningMainloop; - function checkIsRunning() { - if (thisMainLoopId < Browser.mainLoop.currentlyRunningMainloop) { - - return false; - } - return true; - } - - // We create the loop runner here but it is not actually running until - // _emscripten_set_main_loop_timing is called (which might happen a - // later time). This member signifies that the current runner has not - // yet been started so that we can call runtimeKeepalivePush when it - // gets it timing set for the first time. - Browser.mainLoop.running = false; - Browser.mainLoop.runner = function Browser_mainLoop_runner() { - if (ABORT) return; - if (Browser.mainLoop.queue.length > 0) { - var start = Date.now(); - var blocker = Browser.mainLoop.queue.shift(); - blocker.func(blocker.arg); - if (Browser.mainLoop.remainingBlockers) { - var remaining = Browser.mainLoop.remainingBlockers; - var next = remaining%1 == 0 ? remaining-1 : Math.floor(remaining); - if (blocker.counted) { - Browser.mainLoop.remainingBlockers = next; - } else { - // not counted, but move the progress along a tiny bit - next = next + 0.5; // do not steal all the next one's progress - Browser.mainLoop.remainingBlockers = (8*remaining + next)/9; - } - } - out('main loop blocker "' + blocker.name + '" took ' + (Date.now() - start) + ' ms'); //, left: ' + Browser.mainLoop.remainingBlockers); - Browser.mainLoop.updateStatus(); - - // catches pause/resume main loop from blocker execution - if (!checkIsRunning()) return; - - setTimeout(Browser.mainLoop.runner, 0); - return; - } - - // catch pauses from non-main loop sources - if (!checkIsRunning()) return; - - // Implement very basic swap interval control - Browser.mainLoop.currentFrameNumber = Browser.mainLoop.currentFrameNumber + 1 | 0; - if (Browser.mainLoop.timingMode == 1/*EM_TIMING_RAF*/ && Browser.mainLoop.timingValue > 1 && Browser.mainLoop.currentFrameNumber % Browser.mainLoop.timingValue != 0) { - // Not the scheduled time to render this frame - skip. - Browser.mainLoop.scheduler(); - return; - } else if (Browser.mainLoop.timingMode == 0/*EM_TIMING_SETTIMEOUT*/) { - Browser.mainLoop.tickStartTime = _emscripten_get_now(); - } - - // Signal GL rendering layer that processing of a new frame is about to start. This helps it optimize - // VBO double-buffering and reduce GPU stalls. - GL.newRenderingFrameStarted(); - - if (Browser.mainLoop.method === 'timeout' && Module.ctx) { - warnOnce('Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!'); - Browser.mainLoop.method = ''; // just warn once per call to set main loop - } - - Browser.mainLoop.runIter(browserIterationFunc); - - checkStackCookie(); - - // catch pauses from the main loop itself - if (!checkIsRunning()) return; - - // Queue new audio data. This is important to be right after the main loop invocation, so that we will immediately be able - // to queue the newest produced audio samples. - // TODO: Consider adding pre- and post- rAF callbacks so that GL.newRenderingFrameStarted() and SDL.audio.queueNewAudioData() - // do not need to be hardcoded into this function, but can be more generic. - if (typeof SDL == 'object' && SDL.audio && SDL.audio.queueNewAudioData) SDL.audio.queueNewAudioData(); - - Browser.mainLoop.scheduler(); - } - - if (!noSetTiming) { - if (fps && fps > 0) _emscripten_set_main_loop_timing(0/*EM_TIMING_SETTIMEOUT*/, 1000.0 / fps); - else _emscripten_set_main_loop_timing(1/*EM_TIMING_RAF*/, 1); // Do rAF by rendering each frame (no decimating) - - Browser.mainLoop.scheduler(); - } - - if (simulateInfiniteLoop) { - throw 'unwind'; - } - } - - function _emscripten_set_main_loop(func, fps, simulateInfiniteLoop) { - var browserIterationFunc = (() => dynCall_v.call(null, func)); - setMainLoop(browserIterationFunc, fps, simulateInfiniteLoop); - } - function _JS_SetMainLoop(func, fps, simulateInfiniteLoop) { - try { - _emscripten_set_main_loop(func, fps, simulateInfiniteLoop); - } catch { - // Discard the thrown 'unwind' exception. - } - } - - var WEBAudio = {audioInstanceIdCounter:0,audioInstances:{},audioContext:null,audioWebEnabled:0,audioCache:[],pendingAudioSources:{},FAKEMOD_SAMPLERATE:44100}; - function jsAudioMixinSetPitch(source) { - // Add a helper to AudioBufferSourceNode which gives the current playback position of the clip in seconds. - source.estimatePlaybackPosition = function () { - var t = (WEBAudio.audioContext.currentTime - source.playbackStartTime) * source.playbackRate.value; - // Collapse extra times that the audio clip has looped through. - if (source.loop && t >= source.loopStart) { - t = (t - source.loopStart) % (source.loopEnd - source.loopStart) + source.loopStart; - } - return t; - } - - // Add a helper to AudioBufferSourceNode to allow adjusting pitch in a way that keeps playback position estimation functioning. - source.setPitch = function (newPitch) { - var curPosition = source.estimatePlaybackPosition(); - if (curPosition >= 0) { // If negative, the clip has not begun to play yet (that delay is not scaled by pitch) - source.playbackStartTime = WEBAudio.audioContext.currentTime - curPosition / newPitch; - } - if (source.playbackRate.value !== newPitch) source.playbackRate.value = newPitch; - } - } - - function jsAudioCreateUncompressedSoundClip(buffer, error) { - var soundClip = { - buffer: buffer, - error: error - }; - - /** - * Release resources of a sound clip - */ - soundClip.release = function () { }; - - /** - * Get length of sound clip in number of samples - * @returns {number} - */ - soundClip.getLength = function () { - if (!this.buffer) { - console.log ("Trying to get length of sound which is not loaded."); - return 0; - } - - return this.buffer.length; - } - - /** - * Gets uncompressed audio data from sound clip. - * If output buffer is smaller than the sound data only the first portion - * of the sound data is read. - * Sound clips with multiple channels will be stored one after the other. - * - * @param {number} ptr Pointer to the output buffer - * @param {number} length Size of output buffer in bytes - * @returns Size of data in bytes written to output buffer - */ - soundClip.getData = function (ptr, length) { - if (!this.buffer) { - console.log ("Trying to get data of sound which is not loaded."); - return 0; - } - - // Get output buffer - var startOutputBuffer = (ptr >> 2); - var output = HEAPF32.subarray(startOutputBuffer, startOutputBuffer + (length >> 2)); - var numMaxSamples = Math.floor((length >> 2) / this.buffer.numberOfChannels); - var numReadSamples = Math.min(this.buffer.length, numMaxSamples); - - // Copy audio data to outputbuffer - for (var i = 0; i < this.buffer.numberOfChannels; i++) { - var channelData = this.buffer.getChannelData(i).subarray(0, numReadSamples); - output.set(channelData, i * numReadSamples); - } - - return numReadSamples * this.buffer.numberOfChannels * 4; - } - - /** - * Gets number of channels of soundclip - * @returns {number} - */ - soundClip.getNumberOfChannels = function () { - if (!this.buffer) { - console.log ("Trying to get metadata of sound which is not loaded."); - return 0; - } - - return this.buffer.numberOfChannels; - } - - /** - * Gets sampling rate in Hz - * @returns {number} - */ - soundClip.getFrequency = function () { - if (!this.buffer) { - console.log ("Trying to get metadata of sound which is not loaded."); - return 0; - } - - return this.buffer.sampleRate; - } - - /** - * Create an audio source node. - * @returns {AudioBufferSourceNode} - */ - soundClip.createSourceNode = function () { - if (!this.buffer) { - console.log ("Trying to play sound which is not loaded."); - } - - var source = WEBAudio.audioContext.createBufferSource(); - source.buffer = this.buffer; - jsAudioMixinSetPitch(source); - - return source; - }; - - return soundClip; - } - - function jsAudioCreateChannel(callback, userData) { - var channel = { - callback: callback, - userData: userData, - source: null, - gain: WEBAudio.audioContext.createGain(), - panner: WEBAudio.audioContext.createPanner(), - spatialBlendDryGain: WEBAudio.audioContext.createGain(), - spatialBlendWetGain: WEBAudio.audioContext.createGain(), - spatialBlendLevel: 0, - loop: false, - loopStart: 0, - loopEnd: 0, - pitch: 1.0 - }; - - channel.panner.rolloffFactor = 0; // We calculate rolloff ourselves. - - /** - * Release internal resources. - */ - channel.release = function () { - // Explicitly disconnect audio nodes related to this audio channel when the channel should be - // GCd to work around Safari audio performance bug that resulted in crackling audio; as suggested - // in https://bugs.webkit.org/show_bug.cgi?id=222098#c23 - this.disconnectSource(); - this.gain.disconnect(); - this.panner.disconnect(); - } - - /** - * Play a sound clip on the channel - * @param {UncompressedSoundClip|CompressedSoundClip} soundClip - * @param {number} startTime Scheduled start time in seconds - * @param {number} startOffset Start offset in seconds - */ - channel.playSoundClip = function (soundClip, startTime, startOffset) { - try { - var self = this; - this.source = soundClip.createSourceNode(); - this.configurePanningNodes(); - this.setSpatialBlendLevel(this.spatialBlendLevel); - - // Setup on ended callback - this.source.onended = function () { - self.source.isStopped = true; - self.disconnectSource(); - if (self.callback) { - ((a1) => dynCall_vi.apply(null, [self.callback, a1]))(self.userData); - } - }; - - this.source.loop = this.loop; - this.source.loopStart = this.loopStart; - this.source.loopEnd = this.loopEnd; - this.source.start(startTime, startOffset); - this.source.playbackStartTime = startTime - startOffset / this.source.playbackRate.value; - this.source.setPitch(this.pitch); - } catch (e) { - // Need to catch exception, otherwise execution will stop on Safari if audio output is missing/broken - console.error("Channel.playSoundClip error. Exception: " + e); - } - }; - - /** - * Stop playback on channel - */ - channel.stop = function (delay) { - if (!this.source) { - return; - } - - // stop source currently playing. - try { - channel.source.stop(WEBAudio.audioContext.currentTime + delay); - } catch (e) { - // when stop() is used more than once for the same source in Safari it causes the following exception: - // InvalidStateError: DOM Exception 11: An attempt was made to use an object that is not, or is no longer, usable. - // Ignore that exception. - } - - if (delay == 0) { - this.disconnectSource(); - } - }; - - /** - * Return wether the channel is currently paused - * @returns {boolean} - */ - channel.isPaused = function () { - if (!this.source) { - return true; - } - - if (this.source.isPausedMockNode) { - return true; - } - - if (this.source.mediaElement) { - return this.source.mediaElement.paused || this.source.pauseRequested; - } - - return false; - }; - - /** - * Pause playback of channel - */ - channel.pause = function () { - if (!this.source || this.source.isPausedMockNode) { - return; - } - - if (this.source.mediaElement) { - this.source._pauseMediaElement(); - return; - } - - // WebAudio does not have support for pausing and resuming AudioBufferSourceNodes (they are a fire-once abstraction) - // When we want to pause a node, create a mocked object in its place that represents the needed state that is required - // for resuming the clip. - var pausedSource = { - isPausedMockNode: true, - buffer: this.source.buffer, - loop: this.source.loop, - loopStart: this.source.loopStart, - loopEnd: this.source.loopEnd, - playbackRate: this.source.playbackRate.value, - scheduledStopTime: undefined, - // Specifies in seconds the time at the clip where the playback was paused at. - // Can be negative if the audio clip has not started yet. - playbackPausedAtPosition: this.source.estimatePlaybackPosition(), - setPitch: function (v) { this.playbackRate = v; }, - stop: function(when) { this.scheduledStopTime = when; } - }; - // Stop and clear the real audio source... - this.stop(0); - this.disconnectSource(); - // .. and replace the source with a paused mock version. - this.source = pausedSource; - }; - - /** - * Resume playback on channel. - */ - channel.resume = function () { - // If the source is a compressed audio MediaElement, it was directly paused so we can - // directly play it again. - if (this.source && this.source.mediaElement) { - this.source.start(undefined, this.source.currentTime); - return; - } - - // N.B. We only resume a source that has been previously paused. That is, resume() cannot be used to start playback if - // channel was not playing an audio clip before, but playSoundClip() is to be used. - if (!this.source || !this.source.isPausedMockNode) { - return; - } - - var pausedSource = this.source; - var soundClip = jsAudioCreateUncompressedSoundClip(pausedSource.buffer, false); - this.playSoundClip(soundClip, WEBAudio.audioContext.currentTime, Math.max(0, pausedSource.playbackPausedAtPosition)); - this.source.loop = pausedSource.loop; - this.source.loopStart = pausedSource.loopStart; - this.source.loopEnd = pausedSource.loopEnd; - this.source.setPitch(pausedSource.playbackRate); - - // Apply scheduled stop of source if present - if (typeof pausedSource.scheduledStopTime !== "undefined") { - var delay = Math.max(pausedSource.scheduledStopTime - WEBAudio.audioContext.currentTime, 0); - this.stop(delay); - } - }; - - /** - * Set loop mode - * @param {boolean} loop If true audio will be looped. - */ - channel.setLoop = function (loop) { - this.loop = loop; - if (!this.source || this.source.loop == loop) { - return; - } - - this.source.loop = loop; - } - - /** - * Set loop start and end - * @param {number} loopStart Start of the loop in seconds. - * @param {number} loopEnd End of the loop in seconds. - */ - channel.setLoopPoints = function (loopStart, loopEnd) { - this.loopStart = loopStart; - this.loopEnd = loopEnd; - if (!this.source) { - return; - } - - if (this.source.loopStart !== loopStart) { - this.source.loopStart = loopStart; - } - - if (this.source.loopEnd !== loopEnd) { - this.source.loopEnd = loopEnd; - } - } - - /** - * Set channel 3D mode - * @param {number} spatialBlendLevel Dry/wet mix for spatial panning - */ - channel.set3D = function (spatialBlendLevel) { - if (this.spatialBlendLevel != spatialBlendLevel) { - this.setSpatialBlendLevel(spatialBlendLevel); - } - } - - /** - * Set the pitch of the channel - * @param {number} pitch Pitch of the channel - */ - channel.setPitch = function (pitch) { - this.pitch = pitch; - - // Only update pitch if source is initialized - if (!this.source) { - return; - } - - this.source.setPitch(pitch); - } - - /** - * Set volume of channel - * @param {number} volume Volume of channel - */ - channel.setVolume = function (volume) { - // Work around WebKit bug https://bugs.webkit.org/show_bug.cgi?id=222098 - // Updating volume only if it changes reduces sound distortion over time. - // See case 1350204, 1348348 and 1352665 - if (this.gain.gain.value == volume) { - return; - } - - this.gain.gain.value = volume; - } - - /** - * Set the 3D position of the audio channel - * @param {number} x - * @param {number} y - * @param {number} z - */ - channel.setPosition = function (x, y, z) { - var p = this.panner; - - // Work around Chrome performance bug https://bugs.chromium.org/p/chromium/issues/detail?id=1133233 - // by only updating the PannerNode position if it has changed. - // See case 1270768. - if (p.positionX) { - // Use new properties if they exist ... - if (p.positionX.value !== x) p.positionX.value = x; - if (p.positionY.value !== y) p.positionY.value = y; - if (p.positionZ.value !== z) p.positionZ.value = z; - } else if (p._x !== x || p._y !== y || p._z !== z) { - // ... or the deprecated set function if they don't (and shadow cache the set values to avoid re-setting later) - p.setPosition(x, y, z); - p._x = x; - p._y = y; - p._z = z; - } - } - - /** - * Disconnect source node from graph - */ - channel.disconnectSource = function () { - if (!this.source || this.source.isPausedMockNode) { - return; - } - - if (this.source.mediaElement) { - // Pause playback of media element - this.source._pauseMediaElement(); - } - - this.source.onended = null; - this.source.disconnect(); - delete this.source; - }; - - /** - * Updates the spatial blend of the channel, reconfigures audio nodes if necessary - */ - channel.setSpatialBlendLevel = function (spatialBlendLevel) { - - var sourceCanBeConfigured = this.source && !this.source.isPausedMockNode; - var spatializationTypeChanged = (this.spatialBlendLevel > 0 && spatialBlendLevel == 0) || (this.spatialBlendLevel == 0 && spatialBlendLevel > 0); - var needToReconfigureNodes = sourceCanBeConfigured && spatializationTypeChanged; - - this.spatialBlendWetGain.gain.value = spatialBlendLevel; - this.spatialBlendDryGain.gain.value = 1 - spatialBlendLevel; - this.spatialBlendLevel = spatialBlendLevel; - - if (needToReconfigureNodes) - this.configurePanningNodes(); - } - - /** - * Configure audio panning options either for 3D or 2D. - */ - channel.configurePanningNodes = function() { - - if (!this.source) - return; - - this.source.disconnect(); - this.spatialBlendDryGain.disconnect(); - this.spatialBlendWetGain.disconnect(); - this.panner.disconnect(); - this.gain.disconnect(); - - if (this.spatialBlendLevel > 0) { - // In 3D: SourceNode -> DryGainNode --------------> GainNode -> AudioContext.destination - // ↘ WetGainNode -> PannerNode ↗ - - // Dry path - this.source.connect(this.spatialBlendDryGain); - this.spatialBlendDryGain.connect(this.gain); - - // Spatialized path - this.source.connect(this.spatialBlendWetGain); - this.spatialBlendWetGain.connect(this.panner); - this.panner.connect(this.gain); - - } else { - // In 2D: SourceNode -> GainNode -> AudioContext.destination - this.source.connect(this.gain); - } - this.gain.connect(WEBAudio.audioContext.destination); - } - - /** - * Returns wether playback on a channel is stopped. - * @returns {boolean} Returns true if playback on channel is stopped. - */ - channel.isStopped = function () { - if (!this.source) { - // Uncompressed audio - // No playback source -> channel is stopped - return true; - } - - if (this.source.mediaElement) { - // Compressed audio - return this.source.isStopped; - } - - return false; - } - - return channel; - } - - function _JS_Sound_Create_Channel(callback, userData) - { - if (WEBAudio.audioWebEnabled == 0) - return; - - WEBAudio.audioInstances[++WEBAudio.audioInstanceIdCounter] = jsAudioCreateChannel(callback, userData); - return WEBAudio.audioInstanceIdCounter; - } - - function _JS_Sound_GetAudioBufferSampleRate(soundInstance) - { - if (WEBAudio.audioWebEnabled == 0) - return WEBAudio.FAKEMOD_SAMPLERATE; - - var audioInstance = WEBAudio.audioInstances[soundInstance]; - if (!audioInstance) - return WEBAudio.FAKEMOD_SAMPLERATE; - - // Handle the case where it's a channel instance rather than a sound instance - var buffer = audioInstance.buffer ? audioInstance.buffer : audioInstance.source ? audioInstance.source.buffer : 0; - if (!buffer) - return WEBAudio.FAKEMOD_SAMPLERATE; - - return buffer.sampleRate; - } - - function _JS_Sound_GetAudioContextSampleRate() - { - if (WEBAudio.audioWebEnabled == 0) - return WEBAudio.FAKEMOD_SAMPLERATE; - return WEBAudio.audioContext.sampleRate; - } - - function _JS_Sound_GetData(bufferInstance, ptr, length) - { - if (WEBAudio.audioWebEnabled == 0) - return 0; - - var soundClip = WEBAudio.audioInstances[bufferInstance]; - - if (!soundClip) - return 0; - - return soundClip.getData(ptr, length); - } - - function _JS_Sound_GetLength(bufferInstance) - { - if (WEBAudio.audioWebEnabled == 0) - return 0; - - var soundClip = WEBAudio.audioInstances[bufferInstance]; - - if (!soundClip) - return 0; - - return soundClip.getLength(); - } - - function _JS_Sound_GetLoadState(bufferInstance) - { - if (WEBAudio.audioWebEnabled == 0) - return 2; - - var sound = WEBAudio.audioInstances[bufferInstance]; - if (sound.error) - return 2; - if (sound.buffer || sound.url) - return 0; - return 1; - } - - function _JS_Sound_GetMetaData(bufferInstance, metaData) - { - metaData = (metaData >> 2); - if (WEBAudio.audioWebEnabled == 0) - { - HEAPU32[metaData] = 0; - HEAPU32[metaData + 1] = 0; - return false; - } - - var soundClip = WEBAudio.audioInstances[bufferInstance]; - - if (!soundClip) - { - - HEAPU32[metaData] = 0; - HEAPU32[metaData + 1] = 0; - return false; - } - - HEAPU32[metaData] = soundClip.getNumberOfChannels(); - HEAPU32[metaData + 1] = soundClip.getFrequency(); - - return true; - } - - function jsAudioPlayPendingBlockedAudio(soundId) { - var pendingAudio = WEBAudio.pendingAudioSources[soundId]; - pendingAudio.sourceNode._startPlayback(pendingAudio.offset); - delete WEBAudio.pendingAudioSources[soundId]; - } - - function jsAudioPlayBlockedAudios() { - Object.keys(WEBAudio.pendingAudioSources).forEach(function (audioId) { - jsAudioPlayPendingBlockedAudio(audioId); - }); - } - - function _JS_Sound_Init() { - try { - window.AudioContext = window.AudioContext || window.webkitAudioContext; - WEBAudio.audioContext = new AudioContext(); - - var tryToResumeAudioContext = function () { - if (WEBAudio.audioContext.state === 'suspended') - WEBAudio.audioContext.resume().catch(function (error) { - console.warn("Could not resume audio context. Exception: " + error); - }); - else - Module.clearInterval(resumeInterval); - }; - var resumeInterval = Module.setInterval(tryToResumeAudioContext, 400); - - WEBAudio.audioWebEnabled = 1; - - // Safari has the restriction where Audio elements need to be created from a direct user event, - // even if the rest of the audio playback requirements is that a user event has happeend - // at some point previously. The AudioContext also needs to be resumed, if paused, from a - // direct user event. Catch user events here and use them to fill a cache of Audio - // elements to be used by the rest of the system. - var _userEventCallback = function () { - try { - // On Safari, resuming the audio context needs to happen from a user event. - // The AudioContext is suspended by default, and on iOS if the user switches tabs - // and comes back, it will be interrupted. Touching the page will resume audio - // playback. - if (WEBAudio.audioContext.state !== "running" && WEBAudio.audioContext.state !== "closed") { - WEBAudio.audioContext.resume().catch(function (error) { - console.warn("Could not resume audio context. Exception: " + error); - }); - } - - // Play blocked audio elements - jsAudioPlayBlockedAudios(); - - // How many audio elements should we cache? How many compressed audio channels might - // be played at a single time? - var audioCacheSize = 20; - while (WEBAudio.audioCache.length < audioCacheSize) { - var audio = new Audio(); - audio.autoplay = false; - WEBAudio.audioCache.push(audio); - } - } catch (e) { - // Audio error, but don't need to notify here, they would have already been - // informed of audio errors. - } - }; - window.addEventListener("mousedown", _userEventCallback); - window.addEventListener("touchstart", _userEventCallback); - - // Make sure we release the event listeners when the app quits to avoid leaking memory. - Module.deinitializers.push(function () { - window.removeEventListener("mousedown", _userEventCallback); - window.removeEventListener("touchstart", _userEventCallback); - }); - } - catch (e) { - alert('Web Audio API is not supported in this browser'); - } - } - - - function jsAudioCreateUncompressedSoundClipFromCompressedAudio(audioData) { - var soundClip = jsAudioCreateUncompressedSoundClip(null, false); - - WEBAudio.audioContext.decodeAudioData( - audioData, - function (_buffer) { - soundClip.buffer = _buffer; - }, - function (_error) { - soundClip.error = true; - console.log("Decode error: " + _error); - } - ); - - return soundClip; - } - - - function jsAudioAddPendingBlockedAudio(sourceNode, offset) { - WEBAudio.pendingAudioSources[sourceNode.mediaElement.src] = { - sourceNode: sourceNode, - offset: offset - }; - } - - function jsAudioGetMimeTypeFromType(fmodSoundType) { - switch(fmodSoundType) - { - case 13: // FMOD_SOUND_TYPE_MPEG - return "audio/mpeg"; - case 20: // FMOD_SOUND_TYPE_WAV - return "audio/wav"; - default: // Fallback to mp4 audio file for other types or if not set (works on most browsers) - return "audio/mp4"; - } - } - - function jsAudioCreateCompressedSoundClip(audioData, fmodSoundType) { - var mimeType = jsAudioGetMimeTypeFromType(fmodSoundType); - var blob = new Blob([audioData], { type: mimeType }); - - var soundClip = { - url: URL.createObjectURL(blob), - error: false, - mediaElement: new Audio() - }; - - // An Audio element is created for the buffer so that we can access properties like duration - // in JS_Sound_GetLength, which knows about the buffer object, but not the channel object. - // This Audio element is used for metadata properties only, not for playback. Trying to play - // back this Audio element would cause an error on Safari because it's not created in a - // direct user event handler. - soundClip.mediaElement.preload = "metadata"; - soundClip.mediaElement.src = soundClip.url; - - /** - * Release resources of a sound clip - */ - soundClip.release = function () { - if (!this.mediaElement) { - return; - } - - this.mediaElement.src = ""; - URL.revokeObjectURL(this.url); - delete this.mediaElement; - delete this.url; - } - - /** - * Get length of sound clip in number of samples - * @returns {number} - */ - soundClip.getLength = function () { - // Convert duration (seconds) to number of samples. - return this.mediaElement.duration * 44100; - } - /** - * Gets uncompressed audio data from sound clip. - * If output buffer is smaller than the sound data only the first portion - * of the sound data is read. - * Sound clips with multiple channels will be stored one after the other. - * - * @param {number} ptr Pointer to the output buffer - * @param {number} length Size of output buffer in bytes - * @returns Size of data in bytes written to output buffer - */ - soundClip.getData = function (ptr, length) { - console.warn("getData() is not supported for compressed sound."); - - return 0; - } - - /** - * Gets number of channels of soundclip - * @returns {number} - */ - soundClip.getNumberOfChannels = function () { - console.warn("getNumberOfChannels() is not supported for compressed sound."); - - return 0; - } - - /** - * Gets sampling rate in Hz - * @returns {number} - */ - soundClip.getFrequency = function () { - console.warn("getFrequency() is not supported for compressed sound."); - - return 0; - } - - /** - * Create an audio source node - * @returns {MediaElementAudioSourceNode} - */ - soundClip.createSourceNode = function () { - var self = this; - var mediaElement = WEBAudio.audioCache.length ? WEBAudio.audioCache.pop() : new Audio();; - mediaElement.preload = "metadata"; - mediaElement.src = this.url; - var source = WEBAudio.audioContext.createMediaElementSource(mediaElement); - - Object.defineProperty(source, "loop", { - get: function () { - return source.mediaElement.loop; - }, - set: function (v) { - if (source.mediaElement.loop !== v) source.mediaElement.loop = v; - } - }); - - source.playbackRate = {}; - Object.defineProperty(source.playbackRate, "value", { - get: function () { - return source.mediaElement.playbackRate; - }, - set: function (v) { - if (source.mediaElement.playbackRate !== v) source.mediaElement.playbackRate = v; - } - }); - Object.defineProperty(source, "currentTime", { - get: function () { - return source.mediaElement.currentTime; - }, - set: function (v) { - if (source.mediaElement.currentTime !== v) source.mediaElement.currentTime = v; - } - }); - Object.defineProperty(source, "mute", { - get: function () { - return source.mediaElement.mute; - }, - set: function (v) { - if (source.mediaElement.mute !== v) source.mediaElement.mute = v; - } - }); - Object.defineProperty(source, "onended", { - get: function () { - return source.mediaElement.onended; - }, - set: function (onended) { - source.mediaElement.onended = onended; - } - }); - - source.playPromise = null; - source.playTimeout = null; - source.pauseRequested = false; - source.isStopped = false; - - source._pauseMediaElement = function () { - // If there is a play request still pending, then pausing now would cause an - // error. Instead, mark that we want the audio paused as soon as it can be, - // which will be when the play promise resolves. - if (source.playPromise || source.playTimeout) { - source.pauseRequested = true; - } else { - // If there is no play request pending, we can pause immediately. - source.mediaElement.pause(); - } - }; - - source._startPlayback = function (offset) { - if (source.playPromise || source.playTimeout) { - source.mediaElement.currentTime = offset; - source.pauseRequested = false; - return; - } - - source.mediaElement.currentTime = offset; - source.playPromise = source.mediaElement.play(); - - if (source.playPromise) { - source.playPromise.then(function () { - // If a pause was requested between play() and the MediaElement actually - // starting, then pause it now. - if (source.pauseRequested) { - source.mediaElement.pause(); - source.pauseRequested = false; - } - source.playPromise = null; - }).catch(function (error) { - source.playPromise = null; - if (error.name !== 'NotAllowedError') - throw error; - - // Playing a media element may fail if there was no previous user interaction - // Retry playback when there was a user interaction - jsAudioAddPendingBlockedAudio(source, offset); - }); - } - }; - - source.start = function (startTime, offset) { - if (typeof startTime === "undefined") { - startTime = WEBAudio.audioContext.currentTime; - } - - if (typeof offset === "undefined") { - offset = 0.0; - } - - // Compare startTime to WEBAudio context currentTime, and if - // startTime is more than about 4 msecs in the future, do a setTimeout() wait - // for the remaining duration, and only then play. 4 msecs boundary because - // setTimeout() is specced to throttle <= 4 msec waits if repeatedly called. - var startDelayThresholdMS = 4; - // Convert startTime and currentTime to milliseconds - var startDelayMS = (startTime - WEBAudio.audioContext.currentTime) * 1000; - if (startDelayMS > startDelayThresholdMS) { - source.playTimeout = setTimeout(function () { - source.playTimeout = null; - source._startPlayback(offset); - }, startDelayMS); - } else { - source._startPlayback(offset); - } - }; - - source.stop = function (stopTime) { - if (typeof stopTime === "undefined") { - stopTime = WEBAudio.audioContext.currentTime; - } - - // Compare stopTime to WEBAudio context currentTime, and if - // stopTime is more than about 4 msecs in the future, do a setTimeout() wait - // for the remaining duration, and only then stop. 4 msecs boundary because - // setTimeout() is specced to throttle <= 4 msec waits if repeatedly called. - var stopDelayThresholdMS = 4; - // Convert startTime and currentTime to milliseconds - var stopDelayMS = (stopTime - WEBAudio.audioContext.currentTime) * 1000; - - if (stopDelayMS > stopDelayThresholdMS) { - setTimeout(function () { - source._pauseMediaElement(); - source.isStopped = true; - }, stopDelayMS); - } else { - source._pauseMediaElement(); - source.isStopped = true; - } - }; - - jsAudioMixinSetPitch(source); - - return source; - } - - return soundClip; - } - - function _JS_Sound_Load(ptr, length, decompress, fmodSoundType) { - if (WEBAudio.audioWebEnabled == 0) - return 0; - - ptr = ptr; - var audioData = HEAPU8.buffer.slice(ptr, ptr + length); - - // We don't ever want to play back really small audio clips as compressed, the compressor has a startup CPU cost, - // and replaying the same audio clip multiple times (either individually or when looping) has an unwanted CPU - // overhead if the same data will be decompressed on demand again and again. Hence we want to play back small - // audio files always as fully uncompressed in memory. - - // However this will be a memory usage tradeoff. - - // Tests with aac audio sizes in a .m4a container shows: - // 2.11MB stereo 44.1kHz .m4a file containing 90 seconds of 196kbps aac audio decompresses to 30.3MB of float32 PCM data. (~14.3x size increase) - // 721KB stereo 44.1kHz .m4a file 29 seconds of 196kbps aac audio decompresses to 10.0MB of float32 PCM data. (~14x size increase) - // 6.07KB mono 44.1kHZ .m4a file containing 1 second of 101kbps aac audio decompresses to 72kB of float32 PCM data. (~11x size increase) - // -> overall AAC compression factor is ~10x-15x. - - // Based on above, take 128KB as a cutoff size: if we have a .m4a clip that is smaller than this, - // we always uncompress it up front, receiving at most ~1.8MB of raw audio data, which can hold about ~10 seconds of mono audio. - // In other words, heuristically all audio clips <= mono ~10 seconds (5 seconds if stereo) in duration will be always fully uncompressed in memory. - if (length < 131072) decompress = 1; - - var sound; - if (decompress) { - sound = jsAudioCreateUncompressedSoundClipFromCompressedAudio(audioData); - } else { - sound = jsAudioCreateCompressedSoundClip(audioData, fmodSoundType); - } - - WEBAudio.audioInstances[++WEBAudio.audioInstanceIdCounter] = sound; - - return WEBAudio.audioInstanceIdCounter; - } - - - function jsAudioCreateUncompressedSoundClipFromPCM(channels, length, sampleRate, ptr) { - var buffer = WEBAudio.audioContext.createBuffer(channels, length, sampleRate); - var idx = (ptr >> 2) - - // Copy audio data to buffer - for (var i = 0; i < channels; i++) { - var offs = idx + length * i; - var copyToChannel = buffer['copyToChannel'] || function (source, channelNumber, startInChannel) { - // Shim for copyToChannel on browsers which don't support it like Safari. - var clipped = source.subarray(0, Math.min(source.length, this.length - (startInChannel | 0))); - this.getChannelData(channelNumber | 0).set(clipped, startInChannel | 0); - }; - copyToChannel.apply(buffer, [HEAPF32.subarray(offs, offs + length), i, 0]); - } - - return jsAudioCreateUncompressedSoundClip(buffer, false); - } - - function _JS_Sound_Load_PCM(channels, length, sampleRate, ptr) { - if (WEBAudio.audioWebEnabled == 0) - return 0; - - var sound = jsAudioCreateUncompressedSoundClipFromPCM(channels, length, sampleRate, ptr); - - WEBAudio.audioInstances[++WEBAudio.audioInstanceIdCounter] = sound; - return WEBAudio.audioInstanceIdCounter; - } - - function _JS_Sound_Play(bufferInstance, channelInstance, offset, delay) - { - if (WEBAudio.audioWebEnabled == 0) - return; - - // stop sound clip which is currently playing in the channel. - _JS_Sound_Stop(channelInstance, 0); - - var soundClip = WEBAudio.audioInstances[bufferInstance]; - var channel = WEBAudio.audioInstances[channelInstance]; - - if (!soundClip) { - console.log("Trying to play sound which is not loaded."); - return; - } - - try { - channel.playSoundClip(soundClip, WEBAudio.audioContext.currentTime + delay, offset); - } catch (error) { - console.error("playSoundClip error. Exception: " + e); - } - } - - function _JS_Sound_ReleaseInstance(instance) { - var object = WEBAudio.audioInstances[instance]; - if (object) { - object.release(); - } - - // Let the GC free up the audio object. - delete WEBAudio.audioInstances[instance]; - } - - function _JS_Sound_ResumeIfNeeded() - { - if (WEBAudio.audioWebEnabled == 0) - return; - - if (WEBAudio.audioContext.state === 'suspended') - WEBAudio.audioContext.resume().catch(function (error) { - console.warn("Could not resume audio context. Exception: " + error); - }); - - } - - function _JS_Sound_Set3D(channelInstance, spatialBlendLevel) - { - var channel = WEBAudio.audioInstances[channelInstance]; - channel.set3D(spatialBlendLevel); - } - - function _JS_Sound_SetListenerOrientation(x, y, z, xUp, yUp, zUp) - { - if (WEBAudio.audioWebEnabled == 0) - return; - - // Web Audio uses a RHS coordinate system, Unity uses LHS, causing orientations to be flipped. - // So we pass a negative direction here to compensate, otherwise channels will be flipped. - x = -x; - y = -y; - z = -z; - - var l = WEBAudio.audioContext.listener; - - // Do not re-set same values here if the orientation has not changed. This avoid unpredictable performance issues in Chrome - // and Safari Web Audio implementations. - if (l.forwardX) { - // Use new properties if they exist ... - if (l.forwardX.value !== x) l.forwardX.value = x; - if (l.forwardY.value !== y) l.forwardY.value = y; - if (l.forwardZ.value !== z) l.forwardZ.value = z; - - if (l.upX.value !== xUp) l.upX.value = xUp; - if (l.upY.value !== yUp) l.upY.value = yUp; - if (l.upZ.value !== zUp) l.upZ.value = zUp; - } else if (l._forwardX !== x || l._forwardY !== y || l._forwardZ !== z || l._upX !== xUp || l._upY !== yUp || l._upZ !== zUp) { - // ... and old deprecated setOrientation if new properties are not supported. - l.setOrientation(x, y, z, xUp, yUp, zUp); - l._forwardX = x; - l._forwardY = y; - l._forwardZ = z; - l._upX = xUp; - l._upY = yUp; - l._upZ = zUp; - } - } - - function _JS_Sound_SetListenerPosition(x, y, z) - { - if (WEBAudio.audioWebEnabled == 0) - return; - - var l = WEBAudio.audioContext.listener; - - // Do not re-set same values here if the orientation has not changed. This avoid unpredictable performance issues in Chrome - // and Safari Web Audio implementations. - if (l.positionX) { - // Use new properties if they exist ... - if (l.positionX.value !== x) l.positionX.value = x; - if (l.positionY.value !== y) l.positionY.value = y; - if (l.positionZ.value !== z) l.positionZ.value = z; - } else if (l._positionX !== x || l._positionY !== y || l._positionZ !== z) { - // ... and old deprecated setPosition if new properties are not supported. - l.setPosition(x, y, z); - l._positionX = x; - l._positionY = y; - l._positionZ = z; - } - } - - function _JS_Sound_SetLoop(channelInstance, loop) - { - if (WEBAudio.audioWebEnabled == 0) - return; - - var channel = WEBAudio.audioInstances[channelInstance]; - channel.setLoop(loop); - } - - function _JS_Sound_SetLoopPoints(channelInstance, loopStart, loopEnd) - { - if (WEBAudio.audioWebEnabled == 0) - return; - var channel = WEBAudio.audioInstances[channelInstance]; - channel.setLoopPoints(loopStart, loopEnd); - } - - function _JS_Sound_SetPaused(channelInstance, paused) - { - if (WEBAudio.audioWebEnabled == 0) - return; - var channel = WEBAudio.audioInstances[channelInstance]; - if (paused != channel.isPaused()) { - if (paused) channel.pause(); - else channel.resume(); - } - } - - function _JS_Sound_SetPitch(channelInstance, v) - { - if (WEBAudio.audioWebEnabled == 0) - return; - - try { - var channel = WEBAudio.audioInstances[channelInstance]; - channel.setPitch(v); - } catch (e) { - console.error('JS_Sound_SetPitch(channel=' + channelInstance + ', pitch=' + v + ') threw an exception: ' + e); - } - } - - function _JS_Sound_SetPosition(channelInstance, x, y, z) - { - if (WEBAudio.audioWebEnabled == 0) - return; - - var channel = WEBAudio.audioInstances[channelInstance]; - channel.setPosition(x, y, z); - } - - function _JS_Sound_SetVolume(channelInstance, v) - { - if (WEBAudio.audioWebEnabled == 0) - return; - - try { - var channel = WEBAudio.audioInstances[channelInstance]; - channel.setVolume(v); - } catch (e) { - console.error('JS_Sound_SetVolume(channel=' + channelInstance + ', volume=' + v + ') threw an exception: ' + e); - } - } - - function _JS_Sound_Stop(channelInstance, delay) - { - if (WEBAudio.audioWebEnabled == 0) - return; - - var channel = WEBAudio.audioInstances[channelInstance]; - channel.stop(delay); - } - - - function _JS_SystemInfo_GetBrowserName(buffer, bufferSize) - { - var browser = Module.SystemInfo.browser; - if (buffer) - stringToUTF8(browser, buffer, bufferSize); - return lengthBytesUTF8(browser); - } - - - function _JS_SystemInfo_GetBrowserVersionString(buffer, bufferSize) - { - var browserVer = Module.SystemInfo.browserVersion; - if (buffer) - stringToUTF8(browserVer, buffer, bufferSize); - return lengthBytesUTF8(browserVer); - } - - - function _JS_SystemInfo_GetCanvasClientSize(domElementSelector, outWidth, outHeight) - { - var selector = UTF8ToString(domElementSelector); - var canvas = (selector == '#canvas') ? Module['canvas'] : document.querySelector(selector); - var w = 0, h = 0; - if (canvas) { - var size = canvas.getBoundingClientRect(); - w = size.width; - h = size.height; - } - outWidth = (outWidth >> 3); - outHeight = (outHeight >> 3); - HEAPF64[outWidth] = w; - HEAPF64[outHeight] = h; - } - - - function _JS_SystemInfo_GetDocumentURL(buffer, bufferSize) - { - if (buffer) - stringToUTF8(document.URL, buffer, bufferSize); - return lengthBytesUTF8(document.URL); - } - - - function _JS_SystemInfo_GetGPUInfo(buffer, bufferSize) - { - var gpuinfo = Module.SystemInfo.gpu; - if (buffer) - stringToUTF8(gpuinfo, buffer, bufferSize); - return lengthBytesUTF8(gpuinfo); - } - - - function _JS_SystemInfo_GetLanguage(buffer, bufferSize) - { - var language = Module.SystemInfo.language; - if (buffer) - stringToUTF8(language, buffer, bufferSize); - return lengthBytesUTF8(language); - } - - function _JS_SystemInfo_GetMatchWebGLToCanvasSize() - { - // If matchWebGLToCanvasSize is not present, it is - // same as true, to keep backwards compatibility with user page templates - // that are not setting this field. - return Module.matchWebGLToCanvasSize || Module.matchWebGLToCanvasSize === undefined; - } - - - function _JS_SystemInfo_GetOS(buffer, bufferSize) - { - var browser = Module.SystemInfo.os + " " + Module.SystemInfo.osVersion; - if (buffer) - stringToUTF8(browser, buffer, bufferSize); - return lengthBytesUTF8(browser); - } - - function _JS_SystemInfo_GetPreferredDevicePixelRatio() - { - return Module.matchWebGLToCanvasSize == false ? 1 : Module.devicePixelRatio || window.devicePixelRatio || 1; - } - - function _JS_SystemInfo_GetScreenSize(outWidth, outHeight) - { - outWidth = (outWidth >> 3); - outHeight = (outHeight >> 3); - HEAPF64[outWidth] = Module.SystemInfo.width; - HEAPF64[outHeight] = Module.SystemInfo.height; - } - - - function _JS_SystemInfo_GetStreamingAssetsURL(buffer, bufferSize) - { - if (buffer) - stringToUTF8(Module.streamingAssetsUrl, buffer, bufferSize); - return lengthBytesUTF8(Module.streamingAssetsUrl); - } - - function _JS_SystemInfo_HasAstcHdr() - { - var ext = GLctx.getExtension('WEBGL_compressed_texture_astc'); - if (ext && ext.getSupportedProfiles) { - return ext.getSupportedProfiles().includes("hdr"); - } - return false; - } - - function _JS_SystemInfo_HasCursorLock() - { - return Module.SystemInfo.hasCursorLock; - } - - function _JS_SystemInfo_HasFullscreen() - { - return Module.SystemInfo.hasFullscreen; - } - - function _JS_SystemInfo_HasWebGL() - { - return Module.SystemInfo.hasWebGL; - } - - function _JS_SystemInfo_HasWebGPU() - { - return Module.SystemInfo.hasWebGPU; - } - - function _JS_UnityEngineShouldQuit() { - return !!Module.shouldQuit; - } - - var videoInstances = {}; - var jsSupportedVideoFormats = []; - - var jsUnsupportedVideoFormats = []; - - - - function _JS_Video_CanPlayFormat(format) - { - format = UTF8ToString(format); - if (jsSupportedVideoFormats.indexOf(format) != -1) return true; - if (jsUnsupportedVideoFormats.indexOf(format) != -1) return false; - var video = document.createElement('video'); - var canPlay = video.canPlayType(format); - if (canPlay) jsSupportedVideoFormats.push(format); - else jsUnsupportedVideoFormats.push(format); - return !!canPlay; - } - - - var videoInstanceIdCounter = 0; - - function jsVideoEnded() { - var cb = this.onendedCallback; - if (cb) ((a1) => dynCall_vi.apply(null, [cb, a1]))(this.onendedRef); - } - - var hasSRGBATextures = null; - - - - function _JS_Video_Create(url) - { - var str = UTF8ToString(url); - var video = document.createElement('video'); - video.style.display = 'none'; - video.src = str; - video.muted = true; - // Fix for iOS: Set muted and playsinline attribute to disable fullscreen playback - video.setAttribute("muted", ""); - video.setAttribute("playsinline", ""); - - // Enable CORS on the request fetching the video so the browser accepts - // playing it. This is needed since the data is fetched and used - // programmatically - rendering into a canvas - and not displayed normally. - video.crossOrigin = "anonymous"; - - videoInstances[++videoInstanceIdCounter] = video; - - // Firefox and Webkit have a bug that makes GLctx.SRGB8_ALPHA8 not work consistently. - // This means linearized video textures will not have an alpha channel until we can get - // that format working consistently. - // https://bugzilla.mozilla.org/show_bug.cgi?id=1696693 - // https://bugs.webkit.org/show_bug.cgi?id=222822 - if (hasSRGBATextures == null) - hasSRGBATextures = Module.SystemInfo.browser == "Chrome" || Module.SystemInfo.browser == "Edge"; - - return videoInstanceIdCounter; - } - - var jsVideoPendingBlockedVideos = {}; - - - - - - function jsVideoPlayPendingBlockedVideo(video) { - jsVideoPendingBlockedVideos[video].play().then(function() { - var v = jsVideoPendingBlockedVideos[video]; - jsVideoRemovePendingBlockedVideo(video); - // WebGPU can't import the video frame until it has been completely loaded, as notified by - // requestVideoFrameCallback - if (v.requestVideoFrameCallback) - v.requestVideoFrameCallback(function() { - v.isLoaded = true; - }); - }); - } - - - function jsVideoAttemptToPlayBlockedVideos() { - for (var i in jsVideoPendingBlockedVideos) { - if (jsVideoPendingBlockedVideos.hasOwnProperty(i)) jsVideoPlayPendingBlockedVideo(i); - } - } - - function jsVideoRemovePendingBlockedVideo(video) { - delete jsVideoPendingBlockedVideos[video]; - if (Object.keys(jsVideoPendingBlockedVideos).length == 0) { - window.removeEventListener('mousedown', jsVideoAttemptToPlayBlockedVideos); - window.removeEventListener('touchstart', jsVideoAttemptToPlayBlockedVideos); - } - } - - function _JS_Video_Destroy(video) - { - var v = videoInstances[video]; - if (v.loopEndPollInterval) { - clearInterval(v.loopEndPollInterval); - } - jsVideoRemovePendingBlockedVideo(video); - // Reset video source to cancel download of video file - v.src = ""; - // Clear the registered event handlers so that we won't get any events from phantom videos. - delete v.onendedCallback; - v.onended = v.onerror = v.oncanplay = v.onseeked = null; - // And let browser GC the video object itself. - delete videoInstances[video]; - } - - function _JS_Video_Duration(video) - { - return videoInstances[video].duration; - } - - function _JS_Video_EnableAudioTrack(video, trackIndex, enabled) - { - var v = videoInstances[video]; - - // Keep a manual track of enabled audio tracks for browsers that - // do not support the