UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

1,646 lines (1,632 loc) 19.2 MB
/** * @license * PlayCanvas Engine v2.19.1 revision 9069deb (DEBUG) * Copyright 2011-2026 PlayCanvas Ltd. All rights reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __require = /* @__PURE__ */ ((x2) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x2, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x2)(function(x2) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x2 + '" is not supported'); }); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // src/core/constants.js var TRACEID_RENDER_FRAME = "RenderFrame"; var TRACEID_RENDER_FRAME_TIME = "RenderFrameTime"; var TRACEID_RENDER_PASS = "RenderPass"; var TRACEID_RENDER_PASS_DETAIL = "RenderPassDetail"; var TRACEID_RENDER_ACTION = "RenderAction"; var TRACEID_RENDER_TARGET_ALLOC = "RenderTargetAlloc"; var TRACEID_TEXTURE_ALLOC = "TextureAlloc"; var TRACEID_SHADER_ALLOC = "ShaderAlloc"; var TRACEID_SHADER_COMPILE = "ShaderCompile"; var TRACEID_VRAM_TEXTURE = "VRAM.Texture"; var TRACEID_VRAM_VB = "VRAM.Vb"; var TRACEID_VRAM_IB = "VRAM.Ib"; var TRACEID_VRAM_SB = "VRAM.Sb"; var TRACEID_BINDGROUP_ALLOC = "BindGroupAlloc"; var TRACEID_BINDGROUPFORMAT_ALLOC = "BindGroupFormatAlloc"; var TRACEID_RENDERPIPELINE_ALLOC = "RenderPipelineAlloc"; var TRACEID_COMPUTEPIPELINE_ALLOC = "ComputePipelineAlloc"; var TRACEID_PIPELINELAYOUT_ALLOC = "PipelineLayoutAlloc"; var TRACEID_ELEMENT = "Element"; var TRACEID_TEXTURES = "Textures"; var TRACEID_BUFFERS = "Buffers"; var TRACEID_ASSETS = "Assets"; var TRACEID_RENDER_QUEUE = "RenderQueue"; var TRACEID_OCTREE_RESOURCES = "OctreeResources"; var TRACEID_GPU_TIMINGS = "GpuTimings"; // src/core/core.js var version = "2.19.1"; var revision = "9069deb"; function extend(target2, ex) { for (const prop in ex) { const copy = ex[prop]; if (Array.isArray(copy)) { target2[prop] = extend([], copy); } else if (copy && typeof copy === "object") { target2[prop] = extend({}, copy); } else { target2[prop] = copy; } } return target2; } // src/core/guid.js var guid = { /** * Create an RFC4122 version 4 compliant GUID. * * @returns {string} A new GUID. */ create() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c2) => { const r = Math.random() * 16 | 0; const v6 = c2 === "x" ? r : r & 3 | 8; return v6.toString(16); }); } }; // src/core/path.js var path = { /** * The character that separates path segments. * * @type {string} */ delimiter: "/", /** * Join two or more sections of file path together, inserting a delimiter if needed. * * @param {...string} sections - Sections of the path to join. * @returns {string} The joined file path. * @example * const path = pc.path.join('foo', 'bar'); * console.log(path); // Prints 'foo/bar' * @example * const path = pc.path.join('alpha', 'beta', 'gamma'); * console.log(path); // Prints 'alpha/beta/gamma' */ join(...sections) { let result = sections[0]; for (let i = 0; i < sections.length - 1; i++) { const one = sections[i]; const two = sections[i + 1]; if (two[0] === path.delimiter) { result = two; continue; } if (one && two && one[one.length - 1] !== path.delimiter && two[0] !== path.delimiter) { result += path.delimiter + two; } else { result += two; } } return result; }, /** * Normalize the path by removing '.' and '..' instances. * * @param {string} pathname - The path to normalize. * @returns {string} The normalized path. */ normalize(pathname) { const lead = pathname.startsWith(path.delimiter); const trail = pathname.endsWith(path.delimiter); const parts = pathname.split("/"); let result = ""; let cleaned = []; for (let i = 0; i < parts.length; i++) { if (parts[i] === "") continue; if (parts[i] === ".") continue; if (parts[i] === ".." && cleaned.length > 0) { cleaned = cleaned.slice(0, cleaned.length - 2); continue; } if (i > 0) cleaned.push(path.delimiter); cleaned.push(parts[i]); } result = cleaned.join(""); if (!lead && result[0] === path.delimiter) { result = result.slice(1); } if (trail && result[result.length - 1] !== path.delimiter) { result += path.delimiter; } return result; }, /** * Split the pathname path into a pair [head, tail] where tail is the final part of the path * after the last delimiter and head is everything leading up to that. tail will never contain * a slash. * * @param {string} pathname - The path to split. * @returns {string[]} The split path which is an array of two strings, the path and the * filename. */ split(pathname) { const lastDelimiterIndex = pathname.lastIndexOf(path.delimiter); if (lastDelimiterIndex !== -1) { return [pathname.substring(0, lastDelimiterIndex), pathname.substring(lastDelimiterIndex + 1)]; } return ["", pathname]; }, /** * Return the basename of the path. That is the second element of the pair returned by passing * path into {@link path.split}. * * @param {string} pathname - The path to process. * @returns {string} The basename. * @example * pc.path.getBasename("/path/to/file.txt"); // returns "file.txt" * pc.path.getBasename("/path/to/dir"); // returns "dir" */ getBasename(pathname) { return path.split(pathname)[1]; }, /** * Get the directory name from the path. This is everything up to the final instance of * {@link path.delimiter}. * * @param {string} pathname - The path to get the directory from. * @returns {string} The directory part of the path. */ getDirectory(pathname) { return path.split(pathname)[0]; }, /** * Return the extension of the path. Pop the last value of a list after path is split by * question mark and comma. * * @param {string} pathname - The path to process. * @returns {string} The extension. * @example * pc.path.getExtension("/path/to/file.txt"); // returns ".txt" * pc.path.getExtension("/path/to/file.jpg"); // returns ".jpg" * pc.path.getExtension("/path/to/file.txt?function=getExtension"); // returns ".txt" */ getExtension(pathname) { const ext = pathname.split("?")[0].split(".").pop(); if (ext !== pathname) { return `.${ext}`; } return ""; }, /** * Check if a string s is relative path. * * @param {string} pathname - The path to process. * @returns {boolean} True if s doesn't start with slash and doesn't include colon and double * slash. * * @example * pc.path.isRelativePath("file.txt"); // returns true * pc.path.isRelativePath("path/to/file.txt"); // returns true * pc.path.isRelativePath("./path/to/file.txt"); // returns true * pc.path.isRelativePath("../path/to/file.jpg"); // returns true * pc.path.isRelativePath("/path/to/file.jpg"); // returns false * pc.path.isRelativePath("http://path/to/file.jpg"); // returns false */ isRelativePath(pathname) { return pathname.charAt(0) !== "/" && pathname.match(/:\/\//) === null; }, /** * Return the path without file name. If path is relative path, start with period. * * @param {string} pathname - The full path to process. * @returns {string} The path without a last element from list split by slash. * @example * pc.path.extractPath("path/to/file.txt"); // returns "./path/to" * pc.path.extractPath("./path/to/file.txt"); // returns "./path/to" * pc.path.extractPath("../path/to/file.txt"); // returns "../path/to" * pc.path.extractPath("/path/to/file.txt"); // returns "/path/to" */ extractPath(pathname) { let result = ""; const parts = pathname.split("/"); let i = 0; if (parts.length > 1) { if (path.isRelativePath(pathname)) { if (parts[0] === ".") { for (i = 0; i < parts.length - 1; ++i) { result += i === 0 ? parts[i] : `/${parts[i]}`; } } else if (parts[0] === "..") { for (i = 0; i < parts.length - 1; ++i) { result += i === 0 ? parts[i] : `/${parts[i]}`; } } else { result = "."; for (i = 0; i < parts.length - 1; ++i) { result += `/${parts[i]}`; } } } else { for (i = 0; i < parts.length - 1; ++i) { result += i === 0 ? parts[i] : `/${parts[i]}`; } } } return result; } }; // src/core/platform.js var detectPassiveEvents = () => { let result = false; try { const opts = Object.defineProperty({}, "passive", { get: function() { result = true; return false; } }); window.addEventListener("testpassive", null, opts); window.removeEventListener("testpassive", null, opts); } catch (e) { } return result; }; var ua = typeof navigator !== "undefined" ? navigator.userAgent : ""; var environment = typeof window !== "undefined" ? "browser" : typeof global !== "undefined" ? "node" : "worker"; var platformName = /android/i.test(ua) ? "android" : /ip(?:[ao]d|hone)/i.test(ua) ? "ios" : /windows/i.test(ua) ? "windows" : /mac os/i.test(ua) ? "osx" : /linux/i.test(ua) ? "linux" : /cros/i.test(ua) ? "cros" : null; var browserName = environment !== "browser" ? null : /Chrome\/|Chromium\/|Edg.*\//.test(ua) ? "chrome" : ( // chrome, chromium, edge /Safari\//.test(ua) ? "safari" : ( // safari, ios chrome/firefox /Firefox\//.test(ua) ? "firefox" : "other" ) ); var xbox = /xbox/i.test(ua); var visionos = /Macintosh/i.test(ua) && typeof navigator !== "undefined" && navigator.maxTouchPoints > 0 && !/iPhone|iPad|iPod/i.test(ua); var touch = environment === "browser" && ("ontouchstart" in window || "maxTouchPoints" in navigator && navigator.maxTouchPoints > 0); var gamepads = environment === "browser" && (!!navigator.getGamepads || !!navigator.webkitGetGamepads); var workers = typeof Worker !== "undefined"; var passiveEvents = detectPassiveEvents(); var platform = { /** * String identifying the current platform. Can be one of: android, ios, windows, osx, linux, * cros or null. * * @type {'android' | 'ios' | 'windows' | 'osx' | 'linux' | 'cros' | null} * @ignore */ name: platformName, /** * String identifying the current runtime environment. Either 'browser', 'node' or 'worker'. * * @type {'browser' | 'node' | 'worker'} */ environment, /** * The global object. This will be the window object when running in a browser and the global * object when running in nodejs and self when running in a worker. * * @type {object} */ global: (typeof globalThis !== "undefined" && globalThis) ?? (environment === "browser" && window) ?? (environment === "node" && global) ?? (environment === "worker" && self), /** * Convenience boolean indicating whether we're running in the browser. * * @type {boolean} */ browser: environment === "browser", /** * True if running in a Web Worker. * * @type {boolean} * @ignore */ worker: environment === "worker", /** * True if running on a desktop or laptop device. * * @type {boolean} */ desktop: ["windows", "osx", "linux", "cros"].includes(platformName), /** * True if running on a mobile or tablet device. * * @type {boolean} */ mobile: ["android", "ios"].includes(platformName), /** * True if running on an iOS device. * * @type {boolean} */ ios: platformName === "ios", /** * True if running on an Android device. * * @type {boolean} */ android: platformName === "android", /** * True if running on Apple Vision Pro (visionOS). * * @type {boolean} * @ignore */ visionos, /** * True if running on an Xbox device. * * @type {boolean} */ xbox, /** * True if the platform supports gamepads. * * @type {boolean} */ gamepads, /** * True if the platform supports touch input. * * @type {boolean} */ touch, /** * True if the platform supports Web Workers. * * @type {boolean} */ workers, /** * True if the platform supports an options object as the third parameter to * `EventTarget.addEventListener()` and the passive property is supported. * * @type {boolean} * @ignore */ passiveEvents, /** * Get the browser name. * * @type {'chrome' | 'safari' | 'firefox' | 'other' | null} * @ignore */ browserName }; // src/core/string.js var ASCII_LOWERCASE = "abcdefghijklmnopqrstuvwxyz"; var ASCII_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; var ASCII_LETTERS = ASCII_LOWERCASE + ASCII_UPPERCASE; var HIGH_SURROGATE_BEGIN = 55296; var HIGH_SURROGATE_END = 56319; var LOW_SURROGATE_BEGIN = 56320; var LOW_SURROGATE_END = 57343; var ZERO_WIDTH_JOINER = 8205; var REGIONAL_INDICATOR_BEGIN = 127462; var REGIONAL_INDICATOR_END = 127487; var FITZPATRICK_MODIFIER_BEGIN = 127995; var FITZPATRICK_MODIFIER_END = 127999; var DIACRITICAL_MARKS_BEGIN = 8400; var DIACRITICAL_MARKS_END = 8447; var VARIATION_MODIFIER_BEGIN = 65024; var VARIATION_MODIFIER_END = 65039; function getCodePointData(string2, i = 0) { const size = string2.length; if (i < 0 || i >= size) { return null; } const first = string2.charCodeAt(i); if (size > 1 && first >= HIGH_SURROGATE_BEGIN && first <= HIGH_SURROGATE_END) { const second = string2.charCodeAt(i + 1); if (second >= LOW_SURROGATE_BEGIN && second <= LOW_SURROGATE_END) { return { code: (first - HIGH_SURROGATE_BEGIN) * 1024 + second - LOW_SURROGATE_BEGIN + 65536, long: true }; } } return { code: first, long: false }; } function isCodeBetween(string2, begin, end) { if (!string2) { return false; } const codeData = getCodePointData(string2); if (codeData) { const code = codeData.code; return code >= begin && code <= end; } return false; } function numCharsToTakeForNextSymbol(string2, index) { if (index === string2.length - 1) { return 1; } if (isCodeBetween(string2[index], HIGH_SURROGATE_BEGIN, HIGH_SURROGATE_END)) { const first = string2.substring(index, index + 2); const second = string2.substring(index + 2, index + 4); if (isCodeBetween(second, FITZPATRICK_MODIFIER_BEGIN, FITZPATRICK_MODIFIER_END) || isCodeBetween(first, REGIONAL_INDICATOR_BEGIN, REGIONAL_INDICATOR_END) && isCodeBetween(second, REGIONAL_INDICATOR_BEGIN, REGIONAL_INDICATOR_END)) { return 4; } if (isCodeBetween(second, VARIATION_MODIFIER_BEGIN, VARIATION_MODIFIER_END)) { return 3; } return 2; } if (isCodeBetween(string2[index + 1], VARIATION_MODIFIER_BEGIN, VARIATION_MODIFIER_END)) { return 2; } return 1; } var string = { /** * All lowercase letters. * * @type {string} */ ASCII_LOWERCASE, /** * All uppercase letters. * * @type {string} */ ASCII_UPPERCASE, /** * All ASCII letters. * * @type {string} */ ASCII_LETTERS, /** * Return a string with \{n\} replaced with the n-th argument. * * @param {string} s - The string to format. * @param {...*} args - All other arguments are substituted into the string. * @returns {string} The formatted string. * @example * const s = pc.string.format("Hello {0}", "world"); * console.log(s); // Prints "Hello world" */ format(s2, ...args) { for (let i = 0; i < args.length; i++) { s2 = s2.replace(`{${i}}`, args[i]); } return s2; }, /** * Get the code point number for a character in a string. Polyfill for * [`codePointAt`]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt}. * * @param {string} string - The string to get the code point from. * @param {number} [i] - The index in the string. * @returns {number} The code point value for the character in the string. */ getCodePoint(string2, i) { const codePointData = getCodePointData(string2, i); return codePointData && codePointData.code; }, /** * Gets an array of all code points in a string. * * @param {string} string - The string to get code points from. * @returns {number[]} The code points in the string. */ getCodePoints(string2) { if (typeof string2 !== "string") { throw new TypeError("Not a string"); } let i = 0; const arr = []; let codePoint; while (!!(codePoint = getCodePointData(string2, i))) { arr.push(codePoint.code); i += codePoint.long ? 2 : 1; } return arr; }, /** * Gets an array of all grapheme clusters (visible symbols) in a string. This is needed because * some symbols (such as emoji or accented characters) are actually made up of multiple * character codes. See {@link https://mathiasbynens.be/notes/javascript-unicode here} for more * info. * * @param {string} string - The string to break into symbols. * @returns {string[]} The symbols in the string. */ getSymbols(string2) { if (typeof string2 !== "string") { throw new TypeError("Not a string"); } let index = 0; const length = string2.length; const output = []; let take = 0; let ch; while (index < length) { take += numCharsToTakeForNextSymbol(string2, index + take); ch = string2[index + take]; if (isCodeBetween(ch, DIACRITICAL_MARKS_BEGIN, DIACRITICAL_MARKS_END)) { ch = string2[index + take++]; } if (isCodeBetween(ch, VARIATION_MODIFIER_BEGIN, VARIATION_MODIFIER_END)) { ch = string2[index + take++]; } if (ch && ch.charCodeAt(0) === ZERO_WIDTH_JOINER) { ch = string2[index + take++]; continue; } const char = string2.substring(index, index + take); output.push(char); index += take; take = 0; } return output; }, /** * Get the string for a given code point or set of code points. Polyfill for * [`fromCodePoint`]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint}. * * @param {...number} args - The code points to convert to a string. * @returns {string} The converted string. * @ignore */ fromCodePoint(...args) { return args.map((codePoint) => { if (codePoint > 65535) { codePoint -= 65536; return String.fromCharCode( (codePoint >> 10) + 55296, codePoint % 1024 + 56320 ); } return String.fromCharCode(codePoint); }).join(""); } }; // src/core/tracing.js var _Tracing = class _Tracing { /** * Enable or disable a trace channel. * * @param {string} channel - Name of the trace channel. Can be: * * - {@link TRACEID_RENDER_FRAME} * - {@link TRACEID_RENDER_FRAME_TIME} * - {@link TRACEID_RENDER_PASS} * - {@link TRACEID_RENDER_PASS_DETAIL} * - {@link TRACEID_RENDER_ACTION} * - {@link TRACEID_RENDER_TARGET_ALLOC} * - {@link TRACEID_TEXTURE_ALLOC} * - {@link TRACEID_SHADER_ALLOC} * - {@link TRACEID_SHADER_COMPILE} * - {@link TRACEID_VRAM_TEXTURE} * - {@link TRACEID_VRAM_VB} * - {@link TRACEID_VRAM_IB} * - {@link TRACEID_RENDERPIPELINE_ALLOC} * - {@link TRACEID_COMPUTEPIPELINE_ALLOC} * - {@link TRACEID_PIPELINELAYOUT_ALLOC} * - {@link TRACEID_TEXTURES} * - {@link TRACEID_BUFFERS} * - {@link TRACEID_ASSETS} * - {@link TRACEID_GPU_TIMINGS} * * @param {boolean} enabled - New enabled state for the channel. */ static set(channel, enabled = true) { if (enabled) { _Tracing._traceChannels.add(channel); } else { _Tracing._traceChannels.delete(channel); } } /** * Test if the trace channel is enabled. * * @param {string} channel - Name of the trace channel. * @returns {boolean} - True if the trace channel is enabled. */ static get(channel) { return _Tracing._traceChannels.has(channel); } }; /** * Set storing the names of enabled trace channels. * * @type {Set<string>} * @private */ __publicField(_Tracing, "_traceChannels", /* @__PURE__ */ new Set()); /** * Enable call stack logging for trace calls. Defaults to false. * * @type {boolean} */ __publicField(_Tracing, "stack", false); var Tracing = _Tracing; // src/core/debug.js var _Debug = class _Debug { /** * Deprecated warning message. * * @param {string} message - The message to log. */ static deprecated(message) { if (!_Debug._loggedMessages.has(message)) { _Debug._loggedMessages.add(message); console.warn(`DEPRECATED: ${message}`); } } /** * Removed warning message. * * @param {string} message - The message to log. */ static removed(message) { if (!_Debug._loggedMessages.has(message)) { _Debug._loggedMessages.add(message); console.error(`REMOVED: ${message}`); } } /** * Assertion deprecated message. If the assertion is false, the deprecated message is written to the log. * * @param {boolean|object} assertion - The assertion to check. * @param {string} message - The message to log. */ static assertDeprecated(assertion, message) { if (!assertion) { _Debug.deprecated(message); } } /** * Assertion error message. If the assertion is false, the error message is written to the log. * * @param {boolean|object} assertion - The assertion to check. * @param {...*} args - The values to be written to the log. */ static assert(assertion, ...args) { if (!assertion) { console.error("ASSERT FAILED: ", ...args); } } /** * Assertion error message that writes an error message to the log if the object has already * been destroyed. To be used along setDestroyed. * * @param {object} object - The object to check. */ static assertDestroyed(object) { if (object?.__alreadyDestroyed) { const message = `[${object.constructor?.name}] with name [${object.name}] has already been destroyed, and cannot be used.`; if (!_Debug._loggedMessages.has(message)) { _Debug._loggedMessages.add(message); console.error("ASSERT FAILED: ", message, object); } } } /** * Executes a function in debug mode only. * * @param {Function} func - Function to call. */ static call(func) { func(); } /** * Info message. * * @param {...*} args - The values to be written to the log. */ static log(...args) { console.log(...args); } /** * Info message logged no more than once. * * @param {string} message - The message to log. * @param {...*} args - The values to be written to the log. */ static logOnce(message, ...args) { if (!_Debug._loggedMessages.has(message)) { _Debug._loggedMessages.add(message); console.log(message, ...args); } } /** * Warning message. * * @param {...*} args - The values to be written to the log. */ static warn(...args) { console.warn(...args); } /** * Warning message logged no more than once. * * @param {string} message - The message to log. * @param {...*} args - The values to be written to the log. */ static warnOnce(message, ...args) { if (!_Debug._loggedMessages.has(message)) { _Debug._loggedMessages.add(message); console.warn(message, ...args); } } /** * Error message. * * @param {...*} args - The values to be written to the log. */ static error(...args) { console.error(...args); } /** * Error message logged no more than once. * * @param {string} message - The message to log. * @param {...*} args - The values to be written to the log. */ static errorOnce(message, ...args) { if (!_Debug._loggedMessages.has(message)) { _Debug._loggedMessages.add(message); console.error(message, ...args); } } /** * Trace message, which is logged to the console if the tracing for the channel is enabled * * @param {string} channel - The trace channel * @param {...*} args - The values to be written to the log. */ static trace(channel, ...args) { if (Tracing.get(channel)) { console.groupCollapsed(`${channel.padEnd(20, " ")}|`, ...args); if (Tracing.stack) { console.trace(); } console.groupEnd(); } } }; /** * Set storing already logged messages, to only print each unique message one time. * * @type {Set<string>} * @private */ __publicField(_Debug, "_loggedMessages", /* @__PURE__ */ new Set()); var Debug = _Debug; var DebugHelper = class { /** * Set a name to the name property of the object. Executes only in the debug build. * * @param {object} object - The object to assign the name to. * @param {string} name - The name to assign. */ static setName(object, name) { if (object) { object.name = name; } } /** * Set a label to the label property of the object. Executes only in the debug build. * * @param {object} object - The object to assign the name to. * @param {string} label - The label to assign. */ static setLabel(object, label) { if (object) { object.label = label; } } /** * Marks object as destroyed. Executes only in the debug build. To be used along assertDestroyed. * * @param {object} object - The object to mark as destroyed. */ static setDestroyed(object) { if (object) { object.__alreadyDestroyed = true; } } }; // src/core/event-handle.js var EventHandle = class { /** * @param {EventHandler} handler - source object of the event. * @param {string} name - Name of the event. * @param {HandleEventCallback} callback - Function that is called when event is fired. * @param {object} scope - Object that is used as `this` when event is fired. * @param {boolean} [once] - If this is a single event and will be removed after event is fired. */ constructor(handler, name, callback, scope, once = false) { /** * @type {EventHandler} * @private */ __publicField(this, "handler"); /** * @type {string} * @ignore */ __publicField(this, "name"); /** * @type {HandleEventCallback} * @ignore */ __publicField(this, "callback"); /** * @type {object} * @ignore */ __publicField(this, "scope"); /** * @type {boolean} * @ignore */ __publicField(this, "_once"); /** * True if event has been removed. * * @private */ __publicField(this, "_removed", false); this.handler = handler; this.name = name; this.callback = callback; this.scope = scope; this._once = once; } /** * Remove this event from its handler. */ off() { if (this._removed) return; this.handler.offByHandle(this); } on(name, callback, scope = this) { Debug.deprecated("Using chaining with EventHandler.on is deprecated, subscribe to an event from EventHandler directly instead."); return this.handler._addCallback(name, callback, scope, false); } once(name, callback, scope = this) { Debug.deprecated("Using chaining with EventHandler.once is deprecated, subscribe to an event from EventHandler directly instead."); return this.handler._addCallback(name, callback, scope, true); } /** * Mark if event has been removed. * * @type {boolean} * @ignore */ set removed(value) { if (!value) return; this._removed = true; } /** * True if event has been removed. * * @type {boolean} * @ignore */ get removed() { return this._removed; } // don't stringify EventHandle to JSON by JSON.stringify toJSON(key) { return void 0; } }; // src/core/event-handler.js var EventHandler = class { constructor() { /** * @type {Map<string,Array<EventHandle>>} * @private */ __publicField(this, "_callbacks", /* @__PURE__ */ new Map()); /** * @type {Map<string,Array<EventHandle>>} * @private */ __publicField(this, "_callbackActive", /* @__PURE__ */ new Map()); } /** * Reinitialize the event handler. * @ignore */ initEventHandler() { this._callbacks = /* @__PURE__ */ new Map(); this._callbackActive = /* @__PURE__ */ new Map(); } /** * Registers a new event handler. * * @param {string} name - Name of the event to bind the callback to. * @param {HandleEventCallback} callback - Function that is called when event is fired. Note * the callback is limited to 8 arguments. * @param {object} scope - Object to use as 'this' when the event is fired, defaults to * current this. * @param {boolean} once - If true, the callback will be unbound after being fired once. * @returns {EventHandle} Created {@link EventHandle}. * @ignore */ _addCallback(name, callback, scope, once) { if (!name || typeof name !== "string" || !callback) { console.warn(`EventHandler: subscribing to an event (${name}) with missing arguments`, callback); } if (!this._callbacks.has(name)) { this._callbacks.set(name, []); } if (this._callbackActive.has(name)) { const callbackActive = this._callbackActive.get(name); if (callbackActive && callbackActive === this._callbacks.get(name)) { this._callbackActive.set(name, callbackActive.slice()); } } const evt = new EventHandle(this, name, callback, scope, once); this._callbacks.get(name).push(evt); return evt; } /** * Attach an event handler to an event. * * @param {string} name - Name of the event to bind the callback to. * @param {HandleEventCallback} callback - Function that is called when event is fired. Note * the callback is limited to 8 arguments. * @param {object} [scope] - Object to use as 'this' when the event is fired, defaults to * current this. * @returns {EventHandle} Can be used for removing event in the future. * @example * obj.on('test', (a, b) => { * console.log(a + b); * }); * obj.fire('test', 1, 2); // prints 3 to the console * @example * const evt = obj.on('test', (a, b) => { * console.log(a + b); * }); * // some time later * evt.off(); */ on(name, callback, scope = this) { return this._addCallback(name, callback, scope, false); } /** * Attach an event handler to an event. This handler will be removed after being fired once. * * @param {string} name - Name of the event to bind the callback to. * @param {HandleEventCallback} callback - Function that is called when event is fired. Note * the callback is limited to 8 arguments. * @param {object} [scope] - Object to use as 'this' when the event is fired, defaults to * current this. * @returns {EventHandle} Can be used for removing event in the future. * @example * obj.once('test', (a, b) => { * console.log(a + b); * }); * obj.fire('test', 1, 2); // prints 3 to the console * obj.fire('test', 1, 2); // not going to get handled */ once(name, callback, scope = this) { return this._addCallback(name, callback, scope, true); } /** * Detach an event handler from an event. If callback is not provided then all callbacks are * unbound from the event, if scope is not provided then all events with the callback will be * unbound. * * @param {string} [name] - Name of the event to unbind. * @param {HandleEventCallback} [callback] - Function to be unbound. * @param {object} [scope] - Scope that was used as the this when the event is fired. * @returns {EventHandler} Self for chaining. * @example * const handler = () => {}; * obj.on('test', handler); * * obj.off(); // Removes all events * obj.off('test'); // Removes all events called 'test' * obj.off('test', handler); // Removes all handler functions, called 'test' * obj.off('test', handler, this); // Removes all handler functions, called 'test' with scope this */ off(name, callback, scope) { if (name) { if (this._callbackActive.has(name) && this._callbackActive.get(name) === this._callbacks.get(name)) { this._callbackActive.set(name, this._callbackActive.get(name).slice()); } } else { for (const [key, callbacks] of this._callbackActive) { if (!this._callbacks.has(key)) { continue; } if (this._callbacks.get(key) !== callbacks) { continue; } this._callbackActive.set(key, callbacks.slice()); } } if (!name) { for (const callbacks of this._callbacks.values()) { for (let i = 0; i < callbacks.length; i++) { callbacks[i].removed = true; } } this._callbacks.clear(); } else if (!callback) { const callbacks = this._callbacks.get(name); if (callbacks) { for (let i = 0; i < callbacks.length; i++) { callbacks[i].removed = true; } this._callbacks.delete(name); } } else { const callbacks = this._callbacks.get(name); if (!callbacks) { return this; } for (let i = 0; i < callbacks.length; i++) { if (callbacks[i].callback !== callback) { continue; } if (scope && callbacks[i].scope !== scope) { continue; } callbacks[i].removed = true; callbacks.splice(i, 1); i--; } if (callbacks.length === 0) { this._callbacks.delete(name); } } return this; } /** * Detach an event handler from an event using EventHandle instance. More optimal remove * as it does not have to scan callbacks array. * * @param {EventHandle} handle - Handle of event. * @ignore */ offByHandle(handle) { const name = handle.name; handle.removed = true; if (this._callbackActive.has(name) && this._callbackActive.get(name) === this._callbacks.get(name)) { this._callbackActive.set(name, this._callbackActive.get(name).slice()); } const callbacks = this._callbacks.get(name); if (!callbacks) { return this; } const ind = callbacks.indexOf(handle); if (ind !== -1) { callbacks.splice(ind, 1); if (callbacks.length === 0) { this._callbacks.delete(name); } } return this; } /** * Fire an event, all additional arguments are passed on to the event listener. * * @param {string} name - Name of event to fire. * @param {any} [arg1] - First argument that is passed to the event handler. * @param {any} [arg2] - Second argument that is passed to the event handler. * @param {any} [arg3] - Third argument that is passed to the event handler. * @param {any} [arg4] - Fourth argument that is passed to the event handler. * @param {any} [arg5] - Fifth argument that is passed to the event handler. * @param {any} [arg6] - Sixth argument that is passed to the event handler. * @param {any} [arg7] - Seventh argument that is passed to the event handler. * @param {any} [arg8] - Eighth argument that is passed to the event handler. * @returns {EventHandler} Self for chaining. * @example * obj.fire('test', 'This is the message'); */ fire(name, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { if (!name) { return this; } const callbacksInitial = this._callbacks.get(name); if (!callbacksInitial) { return this; } let callbacks; if (!this._callbackActive.has(name)) { this._callbackActive.set(name, callbacksInitial); } else if (this._callbackActive.get(name) !== callbacksInitial) { callbacks = callbacksInitial.slice(); } for (let i = 0; (callbacks || this._callbackActive.get(name)) && i < (callbacks || this._callbackActive.get(name)).length; i++) { const evt = (callbacks || this._callbackActive.get(name))[i]; if (!evt.callback) continue; evt.callback.call(evt.scope, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); if (evt._once) { const existingCallback = this._callbacks.get(name); const ind = existingCallback ? existingCallback.indexOf(evt) : -1; if (ind !== -1) { if (this._callbackActive.get(name) === existingCallback) { this._callbackActive.set(name, this._callbackActive.get(name).slice()); } const callbacks2 = this._callbacks.get(name); if (!callbacks2) continue; callbacks2[ind].removed = true; callbacks2.splice(ind, 1); if (callbacks2.length === 0) { this._callbacks.delete(name); } } } } if (!callbacks) { this._callbackActive.delete(name); } return this; } /** * Test if there are any handlers bound to an event name. * * @param {string} name - The name of the event to test. * @returns {boolean} True if the object has handlers bound to the specified event name. * @example * obj.on('test', () => {}); // bind an event to 'test' * obj.hasEvent('test'); // returns true * obj.hasEvent('hello'); // returns false */ hasEvent(name) { return !!this._callbacks.get(name)?.length; } }; // src/core/indexed-list.js var IndexedList = class { constructor() { /** * @type {object[]} * @private */ __publicField(this, "_list", []); /** * @type {Object<string, number>} * @private */ __publicField(this, "_index", {}); } /** * Add a new item into the list with an index key. * * @param {string} key - Key used to look up item in index. * @param {object} item - Item to be stored. */ push(key, item) { if (this._index[key]) { throw Error(`Key already in index ${key}`); } const location = this._list.push(item) - 1; this._index[key] = location; } /** * Test whether a key has been added to the index. * * @param {string} key - The key to test. * @returns {boolean} Returns true if key is in the index, false if not. */ has(key) { return this._index[key] !== void 0; } /** * Return the item indexed by a key. * * @param {string} key - The key of the item to retrieve. * @returns {object|null} The item stored at key. Returns null if key is not in the index. */ get(key) { const location = this._index[key]; if (location !== void 0) { return this._list[location]; } return null; } /** * Remove the item indexed by key from the list. * * @param {string} key - The key at which to remove the item. * @returns {boolean} Returns true if the key exists and an item was removed, returns false if * no item was removed. */ remove(key) { const location = this._index[key]; if (location !== void 0) { this._list.splice(location, 1); delete this._index[key]; for (key in this._index) { const idx = this._index[key]; if (idx > location) { this._index[key] = idx - 1; } } return true; } return false; } /** * Returns the list of items. * * @returns {object[]} The list of items. */ list() { return this._list; } /** * Remove all items from the list. */ clear() { this._list.length = 0; for (const prop in this._index) { delete this._index[prop]; } } }; // src/core/wasm-module.js var cachedResult = (func) => { const uninitToken = {}; let result = uninitToken; return () => { if (result === uninitToken) { result = func(); } return result; }; }; var _Impl = class _Impl { // load a script static loadScript(url, callback) { const s2 = document.createElement("script"); s2.setAttribute("src", url); s2.onload = () => { callback(null); }; s2.onerror = () => { callback(`Failed to load script='${url}'`); }; document.body.appendChild(s2); } // load a wasm module static loadWasm(moduleName, config, callback) { const loadUrl = _Impl.wasmSupported() && config.glueUrl && config.wasmUrl ? config.glueUrl : config.fallbackUrl; if (loadUrl) { _Impl.loadScript(loadUrl, (err2) => { if (err2) { callback(err2, null); } else { const module = window[moduleName]; window[moduleName] = void 0; module({ locateFile: () => config.wasmUrl, onAbort: () => { callback("wasm module aborted."); } }).then((instance) => { callback(null, instance); }); } }); } else { callback("No supported wasm modules found.", null); } } // get state object for the named module static getModule(name) { if (!_Impl.modules.hasOwnProperty(name)) { _Impl.modules[name] = { config: null, initializing: false, instance: null, callbacks: [] }; } return _Impl.modules[name]; } static initialize(moduleName, module) { if (module.initializing) { return; } const config = module.config; if (config.glueUrl || config.wasmUrl || config.fallbackUrl) { module.initializing = true; _Impl.loadWasm(moduleName, config, (err2, instance) => { if (err2) { if (config.errorHandler) { config.errorHandler(err2); } else { console.error(`failed to initialize module=${moduleName} error=${err2}`); } } else { module.instance = instance; module.callbacks.forEach((callback) => { callback(instance); }); } }); } } }; __publicField(_Impl, "modules", {}); // returns true if the running host supports wasm modules (all browsers except IE) __publicField(_Impl, "wasmSupported", cachedResult(() => { try { if (typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function") { const module = new WebAssembly.Module(Uint8Array.of(0, 97, 115, 109, 1, 0, 0, 0)); if (module instanceof WebAssembly.Module) { return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; } } } catch (e) { } return false; })); var Impl = _Impl; var WasmModule = class { /** * Set a wasm module's configuration. * * @param {string} moduleName - Name of the module. * @param {object} [config] - The configuration object. * @param {string} [config.glueUrl] - URL of glue script. * @param {string} [config.wasmUrl] - URL of the wasm script. * @param {string} [config.fallbackUrl] - URL of the fallback script to use when wasm modules * aren't supported. * @param {number} [config.numWorkers] - For modules running on worker threads, the number of * threads to use. Default value is based on module implementation. * @param {ModuleErrorCallback} [config.errorHandler] - Function to be called if the module fails * to download. */ static setConfig(moduleName, config) { const module = Impl.getModule(moduleName); module.config = config; if (module.callbacks.length > 0) { Impl.initialize(moduleName, module); } } /** * Get a wasm module's configuration. * * @param {string} moduleName - Name of the module. * @returns {object | undefined} The previously set configuration. */ static getConfig(moduleName) { return Impl.modules?.[moduleName]?.config; } /** * Get a wasm module instance. The instance will be created if necessary and returned * in the second parameter to callback. * * @param {string} moduleName - Name of the module. * @param {ModuleInstanceCallback} callback - The function called when the instance is * available. */ static getInstance(moduleName, callback) { const module = Impl.getModule(moduleName); if (module.instance) { callback(module.instance); } else { module.callbacks.push(callback); if (module.config) { Impl.initialize(moduleName, module); } } } }; // src/core/read-stream.js var ReadStream = class { /** * @param {ArrayBuffer} arraybuffer - The buffer to read from. */ constructor(arraybuffer) { /** @type {ArrayBuffer} */ __publicField(this, "arraybuffer"); /** @type {DataView} */ __publicField(this, "dataView"); /** @type {number} */ __publicField(this, "offset", 0); this.arraybuffer = arraybuffer; this.dataView = new DataView(arraybuffer); } /** * The number of bytes remaining to be read. * * @type {number} */ get remainingBytes() { return this.dataView.byteLength - this.offset; } /** * Resets the offset to a given value. If no value is given, the offset is reset to 0. * * @param {number} offset - The new offset. */ reset(offset3 = 0) { this.offset = offset3; } /** * Skips a number of bytes. * * @param {number} bytes - The number of bytes to skip. */ skip(bytes) { this.offset += bytes; } /** * Aligns the offset to a multiple of a number of bytes. * * @param {number} bytes - The number of bytes to align to. */ align(bytes) { this.offset = this.offset + bytes - 1 & ~(bytes - 1); } /** * Increments the offset by the specified number of bytes and returns the previous offset. * * @param {number} amount - The number of bytes to increment by. * @returns {number} The previous offset. * @private */ _inc(amount) { this.offset += amount; return this.offset - amount; } /** * Reads a single character. * * @returns {string} The character. */ readChar() { return String.fromCharCode(this.dataView.getUint8(this.offset++)); } /** * Reads a string of a given length. * * @param {number} numChars - The number of characters to read. * @returns {string} The string. */ readChars(numChars) { let result = ""; for (let i = 0; i < numChars; ++i) { result += this.readChar(); } return result; } /** * Read an unsigned 8-bit integer. * * @returns {number} The integer. */ readU8() { return this.dataView.getUint8(this.offset++); } /** * Read an unsigned 16-bit integer. * * @returns {number} The integer. */ readU16() { return this.dataView.getUint16(this._inc(2), true); } /** * Read an unsigned 32-bit integer. * * @returns {number} The integer. */ readU32() { return this.dataView.getUint32(this._inc(4), true); } /** * Read an unsigned 64-bit integer. * * @returns {number} The integer. */ readU64() { return this.readU32() + 2 ** 32 * this.readU32(); } /** * Read a big endian unsigned 32-bit integer. * * @returns {number} The integer. */ readU32be() { return this.dataView.getUint32(this._inc(4), false); } /** * Read unsigned 8-bit integers into an array. * * @param {number[]} result - The array to read into. */ readArray(result) { for (let i = 0; i < result.length; ++i) { result[i] = this.readU8(); } } /** * Read a line of text from the stream. * * @returns {string} The line of text. */ readLine() { const view = this.dataView; let result = ""; while (true) { if (this.offset >= view.byteLength) { break; } const c2 = String.fromCharCode(this.readU8()); if (c2 === "\n") { break; } result += c2; } return result; } }; // src/core/sorted-loop-array.js var SortedLoopArray = class { /** * Create a new SortedLoopArray instance. * * @param {object} args - Arguments. * @param {string} args.sortBy - The name of the field that each element in the array is going * to be sorted by. * @example * const array = new pc.SortedLoopArray({ sortBy: 'priority' }); * array.insert(item); // adds item to the right slot based on item.priority * array.append(item); // adds item to the end of the array * array.remove(item); // removes item from array * for (array.loopIndex = 0; array.loopIndex < array.length; array.loopIndex++) { * // do things with array elements * // safe to remove and add elements into the array while looping * } */ constructor(args) { /** * The internal array that holds the actual array elements. * * @type {object[]} */ __publicField(this, "items", []); /** * The number of elements in the array. */ __publicField(this, "length", 0); /** * The current index used to loop through the array. This gets modified if we add or remove * elements from the array while looping. See the example to see how to loop through this * array. */ __publicField(this, "loopIndex", -1); /** @private */ __publicField(this, "_sortBy"); /** @private */ __publicField(this, "_sortHandler"); this._sortBy = args.sortBy; this._sortHandler = this._doSort.bind(this); } /** * Searches for the right spot to insert the specified item. * * @param {object} item - The item. * @returns {number} The index where to insert the item. * @private */ _binarySearch(item) { let left = 0; let right2 = this.items.length - 1; const search = item[this._sortBy]; let middle; let current; while (left <= right2) { middle = Math.floor((left + right2) / 2); current = this.items[middle][this._sortBy]; if (current <= search) { left = middle + 1; } else if (current >